This is an automated email from the ASF dual-hosted git repository.
lizhimins pushed a commit to branch rocketmq-studio
in repository https://gitbox.apache.org/repos/asf/rocketmq-dashboard.git
The following commit(s) were added to refs/heads/rocketmq-studio by this push:
new 506f74a7d fix(settings): abort saves when the fresh settings read
fails (#5212)
506f74a7d is described below
commit 506f74a7d8b2c7504fc681298984cd465ba642db
Author: 健康宝 <[email protected]>
AuthorDate: Fri Oct 9 11:24:56 2026 +0800
fix(settings): abort saves when the fresh settings read fails (#5212)
`loadFreshSettings` swallowed a failed read and returned the mount-time
snapshot. Since
`buildPayload` sends the whole 14-field record, one read hiccup wrote stale
values over whatever
another tab had just saved - contradicting the comment right above it. Let
the error propagate to
the existing toast and drop the now-unused fallback parameter; both call
sites and all four
`mergeAndSave` consumers stay consistent.
---
web/src/pages/settings/GeneralSettingsTab.tsx | 19 +++++++++----------
.../settings/__tests__/GeneralSettingsTab.test.tsx | 14 ++++++++++++++
2 files changed, 23 insertions(+), 10 deletions(-)
diff --git a/web/src/pages/settings/GeneralSettingsTab.tsx
b/web/src/pages/settings/GeneralSettingsTab.tsx
index b2656dba0..dc9f16113 100644
--- a/web/src/pages/settings/GeneralSettingsTab.tsx
+++ b/web/src/pages/settings/GeneralSettingsTab.tsx
@@ -108,14 +108,13 @@ export const GeneralSettingsTab = () => {
// Other tabs (AI assistant settings) write the same settings record while
this tab stays
// mounted, so every save must be built from a fresh read instead of the
mount-time snapshot.
- const loadFreshSettings = async (fallback: GeneralSettings):
Promise<GeneralSettings> => {
- try {
- const fresh = await getGeneralSettings();
- setSettings(fresh);
- return fresh;
- } catch {
- return fallback;
- }
+ // When that fresh read fails the save aborts here and the callers report
it: falling back to
+ // the mount-time snapshot would write a whole record of stale values over
what another tab
+ // just saved.
+ const loadFreshSettings = async (): Promise<GeneralSettings> => {
+ const fresh = await getGeneralSettings();
+ setSettings(fresh);
+ return fresh;
};
const persistPreference = async (patch: Partial<GeneralSettings>) => {
@@ -124,7 +123,7 @@ export const GeneralSettingsTab = () => {
setSettings(next);
setSavingPreference(true);
try {
- const base = await loadFreshSettings(settings);
+ const base = await loadFreshSettings();
await saveGeneralSettings(buildPayload({ ...base, ...patch }));
setSettings({ ...base, ...patch });
message.success(t('settings.saveSuccess'));
@@ -138,7 +137,7 @@ export const GeneralSettingsTab = () => {
const mergeAndSave = async (patch: Partial<GeneralSettings>) => {
if (!settings) return false;
try {
- const base = await loadFreshSettings(settings);
+ const base = await loadFreshSettings();
await saveGeneralSettings(buildPayload({ ...base, ...patch }));
const statePatch = { ...patch };
delete statePatch.clearDingtalkSigningSecret;
diff --git a/web/src/pages/settings/__tests__/GeneralSettingsTab.test.tsx
b/web/src/pages/settings/__tests__/GeneralSettingsTab.test.tsx
index 6faa0e9ef..7eb329042 100644
--- a/web/src/pages/settings/__tests__/GeneralSettingsTab.test.tsx
+++ b/web/src/pages/settings/__tests__/GeneralSettingsTab.test.tsx
@@ -200,6 +200,20 @@ describe('GeneralSettingsTab', () => {
);
});
+ it('aborts the save when the fresh settings read fails', async () => {
+ renderTab();
+
+ const saveButtons = await screen.findAllByRole('button', { name: '保存设置' });
+ await waitFor(() => expect(saveButtons[0]).toBeEnabled());
+ // The fresh read that every save is built from now fails: writing the
patch on top of the
+ // mount-time snapshot would clobber fields another tab saved in the
meantime.
+ vi.mocked(getGeneralSettings).mockRejectedValueOnce(new Error('boom'));
+ fireEvent.submit(saveButtons[0].closest('form')!);
+
+ await waitFor(() =>
expect(screen.getByText('设置保存失败,请稍后重试')).toBeInTheDocument());
+ expect(saveGeneralSettings).not.toHaveBeenCalled();
+ });
+
it('can explicitly clear a configured DingTalk signing secret', async () => {
vi.mocked(getGeneralSettings).mockResolvedValue({
theme: 'system',