unbridled-41 opened a new pull request, #4588:
URL: https://github.com/apache/rocketmq-dashboard/pull/4588

   Fixes #4587.
   
   ## Problem / Evidence
   
   `GeneralSettingsTab` loads the settings record once and fills the 
notification and security forms from the response:
   
   ```tsx
     useEffect(() => {
       let cancelled = false;
       void getGeneralSettings()
         .then((loaded) => {
           if (cancelled) return;
           setSettings(loaded);
           securityForm.setFieldsValue({ sessionTimeout: loaded.sessionTimeout 
});
           notifyForm.setFieldsValue({ dingtalkWebhook: …, emailRecipients: …, 
smsWebhook: … });
         })
         …
     }, [message, notifyForm, securityForm, t]);      // :100
   ```
   
   `message` (`App.useApp()`), `securityForm`/`notifyForm` (`Form.useForm()`) 
are stable; `t` is recreated on every display-language change 
(`web/src/i18n/LangContext.tsx`). So switching the language re-runs the effect 
and overwrites both forms from the server snapshot — the operator's unsaved 
webhook / recipient / session-timeout edits silently disappear. Reproduced by 
the regression test added here 
(`web/src/pages/settings/__tests__/GeneralSettingsFormResidue.test.tsx`), which 
switches the language through the real `LangProvider.setLang` and then asserts 
the field value.
   
   ## Root cause / Fix
   
   A load-once effect whose dependency list contains the translation function: 
the effect's job (fetch on mount, seed the forms) is unrelated to rendering 
translated text, but the dependency made the language toggle a re-seed trigger. 
The fix follows the pattern this repository already uses for the same problem 
(`web/src/pages/studio/LiteTopic.tsx:126-161`, `messageRef`/`translationRef`):
   
   ```diff
      const securityInFlightRef = useRef(false);
      const notifyInFlightRef = useRef(false);
   +  const translationRef = useRef(t);
      const [securityForm] = Form.useForm();
      const [notifyForm] = Form.useForm();
   
   +  // The load effect below fills both forms from the server snapshot and 
must therefore run only
   +  // once per mount, while the translation function changes identity with 
the display language.
   +  useEffect(() => {
   +    translationRef.current = t;
   +  }, [t]);
   +
      useEffect(() => {
        …
   -        if (!cancelled) message.error(t('settings.loadFailed'));
   +        if (!cancelled) 
message.error(translationRef.current('settings.loadFailed'));
        …
   -  }, [message, notifyForm, securityForm, t]);
   +  }, [message, notifyForm, securityForm]);
   ```
   
   Refs are written in an effect (not in render), so the repository's 
`react-hooks/set-state-in-effect` rule stays satisfied; the error message still 
follows the current language.
   
   ## Priority & scoring
   
   - PRIORITY **70** = impact 28 + blast radius 8 + reproducibility 20 + 
maintenance value 14
     - impact 28 — silent loss of user input on a settings form (webhook URL, 
signing secret, recipient list, session timeout); the operator only notices 
when the saved value turns out to be the old one.
     - blast radius 8 — one tab, but it is the tab where notification channel 
configuration lives, and the trigger (top-bar language toggle) is one click 
away.
     - reproducibility 20 — deterministic; the regression test drives the real 
language switch.
     - maintenance value 14 — removes a trap that recurs across this codebase 
(the same dependency shape was already fixed in `LiteTopic`/`#2200`); the ref 
pattern is the established remedy.
   - FIX_CONFIDENCE **92** — the intent is unambiguous (only `t` is unstable, 
and the effect's whole purpose is a one-time seed), the fix mirrors an existing 
in-repo solution, and the full suite stays green.
   
   ## Tests
   
   Environment: Node 24.20.0, `web/` at the PR head.
   
   Red — base source (`d50ffecc`) with the new test:
   
   ```
   $ git checkout origin/master -- web/src/pages/settings/GeneralSettingsTab.tsx
   $ npx vitest run 
src/pages/settings/__tests__/GeneralSettingsFormResidue.test.tsx
    FAIL  src/pages/settings/__tests__/GeneralSettingsFormResidue.test.tsx > 
GeneralSettingsTab unsaved input > keeps unsaved notification edits when the 
display language changes
   Error: expect(element).toHaveValue(…)
   
   Expected the element to have value:
     https://oapi.dingtalk.com/robot/send?access_token=edited
   Received:
     https://oapi.dingtalk.com/robot/send?access_token=server
   
    ❯ src/pages/settings/__tests__/GeneralSettingsFormResidue.test.tsx:108:21
   
    Test Files  1 failed (1)
         Tests  1 failed (1)
   ```
   
   Green — with the fix, including the tab's existing suites:
   
   ```
   $ npx vitest run 
src/pages/settings/__tests__/GeneralSettingsFormResidue.test.tsx \
                    src/pages/settings/__tests__/GeneralSettingsTab.test.tsx \
                    src/pages/settings/__tests__/SettingsPage.test.tsx
    Test Files  3 passed (3)
         Tests  9 passed (9)
   ```
   
   Full suite:
   
   ```
   $ npx vitest run --maxWorkers=4
    Test Files  123 passed (123)
         Tests  1035 passed (1035)
   ```
   
   1035 = 1034 (`origin/master`) + 1 new test. (The default-parallel `npx 
vitest run` on this repository intermittently reports failures in the 
load-fragile files — MetricsExplorer, ConsumerPage, ClusterPage, TopicPage, 
InstancePage, AlertsPage, AuditPage, ClientsPage, 
MessagePage/MessagePageAsyncState — on pristine `master` as well; the 
limited-parallelism run above is the contention-free result, and the touched 
files pass in both.)
   
   Static checks:
   
   ```
   $ npx tsc -b             # clean, exit 0
   $ npx eslint src/pages/settings/GeneralSettingsTab.tsx 
src/pages/settings/__tests__/GeneralSettingsFormResidue.test.tsx
                            # no output: 0 errors, 0 warnings
   $ npm run build          # ✓ built in 11.82s
   ```
   
   Diff: `web/src/pages/settings/GeneralSettingsTab.tsx` +9/−2, 
`web/src/pages/settings/__tests__/GeneralSettingsFormResidue.test.tsx` +110/−0 
(`git show --numstat`).
   
   ## Risk
   
   - The load effect no longer re-fetches on a language change. That request 
only re-seeded the forms and the `settings` snapshot; translated labels are 
re-rendered from `t` as before, and the tab still re-reads the record before 
every save (`loadFreshSettings`), so a save cannot write a stale snapshot.
   - If the record changes elsewhere while this tab is open, the previous 
behaviour would not have refreshed it either (the same effect only ran on mount 
and on language changes).
   - No API, contract or translation-key changes.
   


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: [email protected]

For queries about this service, please contact Infrastructure at:
[email protected]

Reply via email to