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);
     }
   };
 

Reply via email to