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 a562601d0 fix(web): ignore stale maintenance-window pagination
responses (#4997)
a562601d0 is described below
commit a562601d0971001c1cd3b888a9f2664b6ef77811
Author: Yexi Xiang <[email protected]>
AuthorDate: Thu Sep 24 18:22:32 2026 +0800
fix(web): ignore stale maintenance-window pagination responses (#4997)
fix(studio): ignore stale maintenance window responses
---
.../pages/ops/__tests__/SystemAlertsPage.test.tsx | 84 +++++++++++++++++++++-
web/src/pages/ops/systemAlerts.tsx | 9 ++-
2 files changed, 89 insertions(+), 4 deletions(-)
diff --git a/web/src/pages/ops/__tests__/SystemAlertsPage.test.tsx
b/web/src/pages/ops/__tests__/SystemAlertsPage.test.tsx
index 11a566d7e..edfa95227 100644
--- a/web/src/pages/ops/__tests__/SystemAlertsPage.test.tsx
+++ b/web/src/pages/ops/__tests__/SystemAlertsPage.test.tsx
@@ -6,7 +6,7 @@
*/
import { App } from 'antd';
-import { fireEvent, render, screen, waitFor } from '@testing-library/react';
+import { act, fireEvent, render, screen, waitFor } from
'@testing-library/react';
import userEvent from '@testing-library/user-event';
import { beforeAll, beforeEach, describe, expect, it, vi } from 'vitest';
import { LangProvider } from '../../../i18n/LangContext';
@@ -71,6 +71,14 @@ const renderPage = () =>
</App>,
);
+const deferred = <T,>() => {
+ let resolve!: (value: T) => void;
+ const promise = new Promise<T>((resolvePromise) => {
+ resolve = resolvePromise;
+ });
+ return { promise, resolve };
+};
+
describe('SystemAlertsPage', () => {
beforeEach(() => {
vi.clearAllMocks();
@@ -707,4 +715,78 @@ describe('SystemAlertsPage', () => {
expect(listAlertSilencesPage).toHaveBeenLastCalledWith({ page: 1,
pageSize: 10 });
});
});
+
+ it('keeps the newest maintenance-window page when an older request resolves
later', async () => {
+ const pageTwo = deferred<Awaited<ReturnType<typeof
listAlertSilencesPage>>>();
+ const pageThree = deferred<Awaited<ReturnType<typeof
listAlertSilencesPage>>>();
+ vi.mocked(listAlertSilencesPage)
+ .mockResolvedValueOnce({
+ items: [
+ {
+ id: 1,
+ domain: 'CLUSTER',
+ instanceId: 'page-one',
+ startsAt: '2026-08-10T01:00',
+ endsAt: '2026-08-10T02:00',
+ createdBy: 'admin',
+ },
+ ],
+ total: 30,
+ page: 1,
+ size: 10,
+ })
+ .mockImplementationOnce(() => pageTwo.promise)
+ .mockImplementationOnce(() => pageThree.promise);
+ const user = userEvent.setup();
+ renderPage();
+
+ await user.click(await screen.findByRole('button', { name: /维护窗口|缁存姢绐楀彛/
}));
+ await screen.findByText(/CLUSTER.*page-one/);
+ fireEvent.click(screen.getByRole('listitem', { name: '2' }));
+ fireEvent.click(screen.getByRole('listitem', { name: '3' }));
+ expect(listAlertSilencesPage).toHaveBeenNthCalledWith(2, { page: 2,
pageSize: 10 });
+ expect(listAlertSilencesPage).toHaveBeenNthCalledWith(3, { page: 3,
pageSize: 10 });
+
+ await act(async () => {
+ pageThree.resolve({
+ items: [
+ {
+ id: 3,
+ domain: 'BUSINESS',
+ instanceId: 'page-three',
+ startsAt: '2026-08-12T01:00',
+ endsAt: '2026-08-12T02:00',
+ createdBy: 'admin',
+ },
+ ],
+ total: 30,
+ page: 3,
+ size: 10,
+ });
+ await pageThree.promise;
+ });
+ expect(await
screen.findByText(/BUSINESS.*page-three/)).toBeInTheDocument();
+
+ await act(async () => {
+ pageTwo.resolve({
+ items: [
+ {
+ id: 2,
+ domain: 'BUSINESS',
+ instanceId: 'page-two',
+ startsAt: '2026-08-11T01:00',
+ endsAt: '2026-08-11T02:00',
+ createdBy: 'admin',
+ },
+ ],
+ total: 30,
+ page: 2,
+ size: 10,
+ });
+ await pageTwo.promise;
+ });
+
+ expect(screen.getByText(/BUSINESS.*page-three/)).toBeInTheDocument();
+ expect(screen.queryByText(/page-two/)).not.toBeInTheDocument();
+ });
});
diff --git a/web/src/pages/ops/systemAlerts.tsx
b/web/src/pages/ops/systemAlerts.tsx
index b9cdd7c81..453f99053 100644
--- a/web/src/pages/ops/systemAlerts.tsx
+++ b/web/src/pages/ops/systemAlerts.tsx
@@ -15,7 +15,7 @@
* limitations under the License.
*/
-import { useEffect, useState } from 'react';
+import { useEffect, useRef, useState } from 'react';
import {
Card,
Tag,
@@ -173,6 +173,7 @@ const SystemAlertsPage = () => {
const [silenceTotal, setSilenceTotal] = useState(0);
const [savingSilence, setSavingSilence] = useState(false);
const [deletingSilenceId, setDeletingSilenceId] = useState<number |
null>(null);
+ const silenceRequestId = useRef(0);
const silencePageSize = 10;
const [silenceForm] = Form.useForm();
const silenceRecurrence = Form.useWatch('recurrence', silenceForm) ?? 'ONCE';
@@ -360,16 +361,18 @@ const SystemAlertsPage = () => {
};
const loadSilences = async (nextPage = silencePage) => {
+ const requestId = ++silenceRequestId.current;
setLoadingSilences(true);
try {
const result = await listAlertSilencesPage({ page: nextPage, pageSize:
silencePageSize });
+ if (requestId !== silenceRequestId.current) return;
setSilences(result.items);
setSilenceTotal(result.total);
setSilencePage(result.page);
} catch {
- message.error(t('sysAlerts.silenceLoadFailed'));
+ if (requestId === silenceRequestId.current)
message.error(t('sysAlerts.silenceLoadFailed'));
} finally {
- setLoadingSilences(false);
+ if (requestId === silenceRequestId.current) setLoadingSilences(false);
}
};