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 af47c9697 fix(consumer): refresh the subscription verdict on the 2s
modal auto-refresh (#4593)
af47c9697 is described below
commit af47c96977da261a8ff580ea7a49340f14c6774b
Author: Zhao Jianing <[email protected]>
AuthorDate: Mon Sep 21 21:03:51 2026 +0800
fix(consumer): refresh the subscription verdict on the 2s modal
auto-refresh (#4593)
The consumer group detail modal advertises a 2s auto-refresh in its header
and repeats it next to the diagnostics verdict, but the interval tick only
awaited `refreshConsumerGroup` and `loadProgress`. `loadSubscriptions` returns
early on its per-group cache unless `force` is set, and the call made when the
modal opens does not force it, so the subscription-consistency verdict — the
part of the diagnostics the copy actually refers to — stayed frozen for the
modal's whole lifetime and o [...]
`loadSubscriptions` gains a `silent` parameter mirroring the one
`loadProgress` already had: it skips the loading flag and the error toast but
still records `subscriptionErrorByGroup`, so the existing subscription-failure
alert does not regress. The tick now awaits a forced, silent subscription
refresh after the progress refresh, and the callback joined the effect's
dependency list. The `inFlight` guard still prevents overlapping ticks.
Because `loadSubscriptions` depends on `subscriptionsByGroup`, each tick
rebuilds the interval, so the cadence becomes "2s after the previous tick
finished" — the same shape `loadProgress` already had.
---
.../pages/instance/__tests__/ConsumerPage.test.tsx | 49 ++++++++++++++++++++++
web/src/pages/instance/consumer.tsx | 20 ++++++---
2 files changed, 64 insertions(+), 5 deletions(-)
diff --git a/web/src/pages/instance/__tests__/ConsumerPage.test.tsx
b/web/src/pages/instance/__tests__/ConsumerPage.test.tsx
index eee564664..7ffd8d61d 100644
--- a/web/src/pages/instance/__tests__/ConsumerPage.test.tsx
+++ b/web/src/pages/instance/__tests__/ConsumerPage.test.tsx
@@ -1204,6 +1204,55 @@ describe('Consumer page', () => {
expect(consumerService.getConsumerSubscriptions).toHaveBeenCalledTimes(2);
});
+ it('refreshes the subscription verdict on the 2s modal auto-refresh', async
() => {
+ vi.mocked(consumerService.getConsumerSubscriptions)
+ .mockResolvedValueOnce([
+ {
+ topic: 'remote-topic',
+ expression: '*',
+ type: 'NORMAL',
+ filterMode: '全量',
+ consistency: 'consistent',
+ },
+ {
+ topic: 'stale-topic',
+ expression: 'important',
+ type: 'NORMAL',
+ filterMode: 'Tag 过滤',
+ consistency: 'inconsistent',
+ },
+ ])
+ .mockResolvedValueOnce([
+ {
+ topic: 'remote-topic',
+ expression: '*',
+ type: 'NORMAL',
+ filterMode: '全量',
+ consistency: 'consistent',
+ },
+ {
+ topic: 'stale-topic',
+ expression: 'important',
+ type: 'NORMAL',
+ filterMode: 'Tag 过滤',
+ consistency: 'consistent',
+ },
+ ]);
+
+ const user = userEvent.setup();
+ renderWithProviders(<ConsumerPage />);
+
+ await user.click(await screen.findByRole('button', { name: /详情/ }));
+
+ // The modal advertises that the diagnostic result refreshes every 2
seconds. Without any
+ // further user action the subscription consistency verdict must be
re-checked alongside
+ // the progress table, not stay frozen at whatever the first load returned.
+ await waitFor(() =>
expect(consumerService.getConsumerSubscriptions).toHaveBeenCalledTimes(2), {
+ timeout: 10000,
+ });
+ expect(await screen.findByText('全部 2 个订阅配置一致')).toBeInTheDocument();
+ });
+
it('keeps unknown consistency values separate from mismatches', async () => {
vi.mocked(consumerService.getConsumerSubscriptions).mockResolvedValue([
{
diff --git a/web/src/pages/instance/consumer.tsx
b/web/src/pages/instance/consumer.tsx
index d09286875..2927d3c01 100644
--- a/web/src/pages/instance/consumer.tsx
+++ b/web/src/pages/instance/consumer.tsx
@@ -408,10 +408,12 @@ const ConsumerPageContent = ({
}, [autoRefresh, selectedInstanceId, triggerRefresh]);
const loadSubscriptions = useCallback(
- async (groupName: string, force = false) => {
+ async (groupName: string, force = false, silent = false) => {
const cacheKey = diagnosticCacheKey(selectedInstanceId, groupName);
if (!force && subscriptionsByGroup[cacheKey]) return;
- setSubscriptionLoadingByGroup((prev) => ({ ...prev, [cacheKey]: true }));
+ if (!silent) {
+ setSubscriptionLoadingByGroup((prev) => ({ ...prev, [cacheKey]: true
}));
+ }
setSubscriptionErrorByGroup((prev) => ({ ...prev, [cacheKey]: false }));
try {
const subscriptions = await getConsumerSubscriptions(
@@ -421,9 +423,13 @@ const ConsumerPageContent = ({
setSubscriptionsByGroup((prev) => ({ ...prev, [cacheKey]:
subscriptions }));
} catch {
setSubscriptionErrorByGroup((prev) => ({ ...prev, [cacheKey]: true }));
- message.error(t('consumer.fetchSubscriptionsFailed', { name: groupName
}));
+ if (!silent) {
+ message.error(t('consumer.fetchSubscriptionsFailed', { name:
groupName }));
+ }
} finally {
- setSubscriptionLoadingByGroup((prev) => ({ ...prev, [cacheKey]: false
}));
+ if (!silent) {
+ setSubscriptionLoadingByGroup((prev) => ({ ...prev, [cacheKey]:
false }));
+ }
}
},
[subscriptionsByGroup, t, selectedInstanceId],
@@ -463,6 +469,10 @@ const ConsumerPageContent = ({
setSelectedGroup((prev) => (prev && prev.name === groupName ?
refreshed : prev));
}
await loadProgress(groupName, true, true);
+ // The modal advertises "每 2s 自动刷新" for the whole diagnostic result,
which includes
+ // the subscription consistency verdict, not only the progress table.
Refresh it silently
+ // so a failing backend does not toast every 2s and the check spinner
does not flicker.
+ await loadSubscriptions(groupName, true, true);
} catch {
// 自动刷新失败静默处理,避免每 2s 弹错
} finally {
@@ -471,7 +481,7 @@ const ConsumerPageContent = ({
};
const interval = window.setInterval(() => void tick(), 2000);
return () => window.clearInterval(interval);
- }, [modalOpen, selectedGroupName, selectedInstanceId, loadProgress]);
+ }, [modalOpen, selectedGroupName, selectedInstanceId, loadProgress,
loadSubscriptions]);
/* ─── Filtered & sorted data ─── */
const filtered = useMemo(() => {