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

   Fixes #4585.
   
   ## Problem / Evidence
   
   `TopicConfigComparisonDrawer` starts a comparison with a request token and 
only renders the response when the token is still current:
   
   ```tsx
   const requestId = ++requestIdRef.current;            // :128
   if (requestId === requestIdRef.current) { … }        // :135 / :141 / :143
   ```
   
   But the three paths that change the compared pair clear the result without 
invalidating the in-flight request — 源实例 `onChange` (`:211`), 目标实例 `onChange` 
(`:224`) and `swapInstances` (`:147`, used by the swap button). A response that 
is already in flight therefore repopulates the result after the user switched 
the pair.
   
   The rendered output is then mislabelled, because the labels come from live 
state: the expanded field table columns are titled with 
`sourceInstanceId`/`targetInstanceId` (`:313`), and 导出结果 writes 
`rocketmq-topic-config-<source>-vs-<target>.csv` (`:164`) — both reflect the 
*new* pair, while `result.rows` belong to the old one. The user can click 开始对比, 
switch instances while it loads, and read/export a drift report for a pair they 
did not select.
   
   Regression test added in this PR: the `discards a comparison that resolves 
after the instance pair changed` case in 
`web/src/components/__tests__/TopicConfigComparisonDrawer.test.tsx` (two 
`listAllTopics` promises are held open, the 源实例 is switched, then both promises 
resolve).
   
   ## Root cause / Fix
   
   Request ownership was attached to the *action* (starting a comparison) 
instead of the *pair being compared*: the guard is only as good as the set of 
paths that bump it, and every path that changes the pair was left out. The fix 
introduces one helper for "the pair changed" and calls it from all three paths:
   
   ```diff
   +  // Any change of the compared pair invalidates a comparison that is still 
loading, so a
   +  // response for the previous pair can never be rendered under the new 
selection.
   +  const invalidateComparison = () => {
   +    requestIdRef.current += 1;
   +    setResult(null);
   +    setLoading(false);
   +  };
   +
      const swapInstances = () => {
        setSourceInstanceId(targetInstanceId);
        setTargetInstanceId(sourceInstanceId);
   -    setResult(null);
   +    invalidateComparison();
      };
   ```
   
   …and the same replacement in both `onChange` handlers. Clearing `loading` in 
the helper matters: without it the button would keep spinning, because the 
superseded response's `finally` block no longer resets it.
   
   ## Priority & scoring
   
   - PRIORITY **75** = impact 28 + blast radius 12 + reproducibility 20 + 
maintenance value 15
     - impact 28 — a wrong cross-instance drift report is attributed to the 
wrong instance pair, including in the CSV export; the user acts on data from a 
different pair than the one displayed.
     - blast radius 12 — reachable for every instance pair from the topic page, 
and the wrong labels travel off-page through the export.
     - reproducibility 20 — deterministic; captured by one regression test with 
two held-open requests.
     - maintenance value 15 — restores the request-ownership contract this repo 
already standardised in #3154/#4166 (merged) and #4331 (merged refactor); one 
shared helper prevents the omission from recurring at the next pair-changing 
path.
   - FIX_CONFIDENCE **92** — the guard already exists and is used by 
`runComparison`; the fix only adds the missing invalidation paths, and the 
sibling pages define the intended semantics.
   
   ## Tests
   
   Environment: Node 24.20.0, `web/` at the PR head.
   
   Red — with the base source and the new test:
   
   ```
   $ git checkout origin/master -- 
web/src/components/TopicConfigComparisonDrawer.tsx
   $ npx vitest run 
src/components/__tests__/TopicConfigComparisonDrawer.test.tsx
    FAIL  src/components/__tests__/TopicConfigComparisonDrawer.test.tsx > 
TopicConfigComparisonDrawer > discards a comparison that resolves after the 
instance pair changed
   Error: expect(element).not.toBeInTheDocument()
   expected document not to contain element, found <div 
class="ant-statistic-title">配置一致</div> instead
   
    ❯ src/components/__tests__/TopicConfigComparisonDrawer.test.tsx:243:44
   
    Test Files  1 failed (1)
         Tests  1 failed | 7 passed (8)
   ```
   
   Green — with the fix:
   
   ```
   $ npx vitest run 
src/components/__tests__/TopicConfigComparisonDrawer.test.tsx
    Test Files  1 passed (1)
         Tests  8 passed (8)
   
   $ npx vitest run src/pages/instance/__tests__/TopicPage.test.tsx   # the 
page that mounts the drawer
    Test Files  1 passed (1)
         Tests  26 passed (26)
   ```
   
   Full suite:
   
   ```
   $ npx vitest run --maxWorkers=4
    Test Files  122 passed (122)
         Tests  1035 passed (1035)
   ```
   
   1035 = 1034 (`origin/master`) + 1 new test. The default-parallel `npx vitest 
run` on this same head reported 23 failures across 10 files, all of them files 
this PR does not touch (MetricsExplorer, ClientsPage, ClusterPage, 
ConsumerPage, InstancePage, MessagePage, MessagePageAsyncState, TopicPage, 
AlertsPage, AuditPage) — the load-fragile set that also fails intermittently on 
pristine `master` when the whole suite runs in parallel. Isolating exactly 
those 10 files passes:
   
   ```
   $ npx vitest run <those 10 files>
    Test Files  10 passed (10)
         Tests  226 passed (226)
   ```
   
   Static checks:
   
   ```
   $ npx tsc -b             # clean, exit 0
   $ npx eslint src/components/TopicConfigComparisonDrawer.tsx 
src/components/__tests__/TopicConfigComparisonDrawer.test.tsx
                            # no problems (0 errors, 0 warnings)
   $ npm run build          # ✓ built in 14.80s
   ```
   
   Diff: `web/src/components/TopicConfigComparisonDrawer.tsx` +11/−3, 
`web/src/components/__tests__/TopicConfigComparisonDrawer.test.tsx` +29/−1 
(`git show --numstat`).
   
   ## Risk
   
   - The helper only ever discards a comparison the user has already moved away 
from; the flow for a stable pair (start → rows → expand → export) is unchanged, 
and the existing 7 tests of the file, including export and search/filter tests, 
still pass.
   - `setLoading(false)` in the helper is what keeps the 开始对比 button usable 
after a pair change; without it the superseded response's `finally` would no 
longer clear it.
   - No API, contract or translation 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