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 fff275140 fix(topic): discard a comparison that resolves after the
instance pair changed (#4586)
fff275140 is described below
commit fff27514035d2a9df29c7d0753bb1a4ad7a3e8f8
Author: 烤化の初雪 <[email protected]>
AuthorDate: Thu Sep 24 17:45:51 2026 +0800
fix(topic): discard a comparison that resolves after the instance pair
changed (#4586)
test(topic): cover the in-flight indicator when the comparison pair changes
The invalidation helper also clears the loading flag: without it the
superseded response's finally block no longer runs and the compare control
keeps spinning. Assert that the control is loading while the comparison runs
and no longer loading once the pair changes. The node is captured before the
click because the loading spinner contributes to the accessible name.
fix(topic): discard a comparison that resolves after the instance pair
changed
The drawer guarded its comparison with requestIdRef, but only runComparison
bumped it. Changing the source or target instance (or swapping them) cleared
the result without invalidating the in-flight request, so a response for the
previous pair was rendered under the new selection: summary cards, the field
table headers and the exported file name all named the new pair while the
rows came from the old one.
Invalidate the in-flight comparison from every change of the compared pair.
---
web/src/components/TopicConfigComparisonDrawer.tsx | 14 +++++++--
.../__tests__/TopicConfigComparisonDrawer.test.tsx | 35 +++++++++++++++++++++-
2 files changed, 45 insertions(+), 4 deletions(-)
diff --git a/web/src/components/TopicConfigComparisonDrawer.tsx
b/web/src/components/TopicConfigComparisonDrawer.tsx
index 72e1c5de3..3204efa2c 100644
--- a/web/src/components/TopicConfigComparisonDrawer.tsx
+++ b/web/src/components/TopicConfigComparisonDrawer.tsx
@@ -144,10 +144,18 @@ const TopicConfigComparisonDrawer = ({
}
};
+ // 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();
};
const exportComparison = () => {
@@ -205,7 +213,7 @@ const TopicConfigComparisonDrawer = ({
style={{ width: '100%', marginTop: 4 }}
onChange={(value) => {
setSourceInstanceId(value);
- setResult(null);
+ invalidateComparison();
}}
/>
</label>
@@ -223,7 +231,7 @@ const TopicConfigComparisonDrawer = ({
style={{ width: '100%', marginTop: 4 }}
onChange={(value) => {
setTargetInstanceId(value);
- setResult(null);
+ invalidateComparison();
}}
/>
</label>
diff --git a/web/src/components/__tests__/TopicConfigComparisonDrawer.test.tsx
b/web/src/components/__tests__/TopicConfigComparisonDrawer.test.tsx
index 93ecbe2f5..cbe29e551 100644
--- a/web/src/components/__tests__/TopicConfigComparisonDrawer.test.tsx
+++ b/web/src/components/__tests__/TopicConfigComparisonDrawer.test.tsx
@@ -16,7 +16,7 @@
*/
import { beforeAll, beforeEach, describe, expect, it, vi } from 'vitest';
-import { render, screen, waitFor } from '@testing-library/react';
+import { act, render, screen, waitFor } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import { App } from 'antd';
import type { Instance } from '../../api/instance';
@@ -216,6 +216,39 @@ describe('TopicConfigComparisonDrawer', () => {
expect(screen.queryByText('配置一致')).not.toBeInTheDocument();
});
+ it('discards a comparison that resolves after the instance pair changed',
async () => {
+ const archiveInstance: Instance = { ...instances[1], id: 3, name:
'archive' };
+ const pending = new Map<string, (value: Topic[]) => void>();
+ topicServiceMocks.listAllTopics.mockImplementation(
+ ({ instanceId }: { instanceId: string }) =>
+ new Promise<Topic[]>((resolve) => {
+ pending.set(instanceId, resolve);
+ }),
+ );
+ const user = userEvent.setup();
+ renderDrawer({ instances: [...instances, archiveInstance] });
+ // Hold the node: while it is loading, the spinner contributes to its
accessible name.
+ const compareButton = screen.getByRole('button', { name: '开始对比' });
+
+ await user.click(compareButton);
+ await waitFor(() => expect(pending.size).toBe(2));
+ expect(compareButton).toHaveClass('ant-btn-loading');
+
+ // Change the source instance while the production/staging comparison is
still in flight.
+ await user.click(screen.getByRole('combobox', { name: '源实例' }));
+ await user.click(await screen.findByTitle('archive'));
+
+ await act(async () => {
+ pending.get('production')?.(productionTopics);
+ pending.get('staging')?.(stagingTopics);
+ });
+
+ expect(screen.queryByText('配置一致')).not.toBeInTheDocument();
+ expect(screen.queryByText('source-only-topic')).not.toBeInTheDocument();
+ // Changing the pair must also end the in-flight indicator, so the control
cannot stay spinning.
+ await waitFor(() =>
expect(compareButton).not.toHaveClass('ant-btn-loading'));
+ });
+
it('calls onClose from the drawer close control', async () => {
const onClose = vi.fn();
const user = userEvent.setup();