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 5b98f944a fix(message): paginate against the committed query (#4005)
5b98f944a is described below

commit 5b98f944a3b1395639c3eaddc8760fa3b5e1d74a
Author: 烤化の初雪 <[email protected]>
AuthorDate: Mon Sep 7 18:39:12 2026 +0800

    fix(message): paginate against the committed query (#4005)
    
    The results table rebuilt the query from the live form inputs on every
    pagination click, so editing the topic/date range/keys after running a
    query and then clicking page 2 silently executed a different query:
    page 1 showed the committed results while page 2 was fetched with the
    uncommitted inputs, producing a mixed result list.
    
    Snapshot the executed query (mode and normalized params) whenever a
    query actually runs — including history replays — and paginate against
    that snapshot instead of the live inputs.
    
    Co-authored-by: unbridled-41 
<[email protected]>
---
 .../pages/instance/__tests__/MessagePage.test.tsx  | 40 ++++++++++++-
 web/src/pages/instance/message.tsx                 | 66 +++++++++-------------
 2 files changed, 66 insertions(+), 40 deletions(-)

diff --git a/web/src/pages/instance/__tests__/MessagePage.test.tsx 
b/web/src/pages/instance/__tests__/MessagePage.test.tsx
index c669279d7..f2dadaad5 100644
--- a/web/src/pages/instance/__tests__/MessagePage.test.tsx
+++ b/web/src/pages/instance/__tests__/MessagePage.test.tsx
@@ -16,7 +16,7 @@
  */
 
 import { App } from 'antd';
-import { render, screen, waitFor } from '@testing-library/react';
+import { fireEvent, render, screen, waitFor } from '@testing-library/react';
 import userEvent from '@testing-library/user-event';
 import type React from 'react';
 import { MemoryRouter } from 'react-router-dom';
@@ -318,4 +318,42 @@ describe('Message page query history', () => {
 
     expect(screen.queryByText('order-create')).not.toBeInTheDocument();
   });
+
+  it('paginates against the committed query instead of live form inputs', 
async () => {
+    messageServiceMocks.queryMessages.mockResolvedValue(
+      Array.from({ length: 60 }, (_, index) => createMessage(`m-${index}`)),
+    );
+    const user = userEvent.setup();
+    renderWithProviders(<MessagePage />);
+
+    await user.click(lastElement(screen.getAllByRole('combobox')));
+    await user.click(lastElement(await screen.findAllByText('order-create')));
+    await user.click(screen.getByRole('button', { name: /^search查询$/ }));
+    await waitFor(() => {
+      expect(messageServiceMocks.queryMessages).toHaveBeenLastCalledWith(
+        expect.objectContaining({ topic: 'order-create' }),
+      );
+    });
+
+    // Edit the form without re-running the query.
+    await user.click(screen.getAllByRole('combobox')[1]);
+    const visibleOption = (await screen.findAllByText('payment-callback'))
+      .map((element) => element.closest('.ant-select-item-option'))
+      .find(
+        (element): element is HTMLElement =>
+          element instanceof HTMLElement &&
+          
element.closest('.ant-select-dropdown:not(.ant-select-dropdown-hidden)') !== 
null,
+      );
+    if (!visibleOption) throw new Error('Visible topic option not found');
+    fireEvent.click(visibleOption);
+
+    const secondPage = document.querySelector('.ant-pagination-item-2') as 
HTMLElement | null;
+    if (!secondPage) throw new Error('Pagination page 2 not found');
+    await user.click(secondPage);
+    await waitFor(() => {
+      expect(messageServiceMocks.queryMessages).toHaveBeenLastCalledWith(
+        expect.objectContaining({ topic: 'order-create' }),
+      );
+    });
+  });
 });
diff --git a/web/src/pages/instance/message.tsx 
b/web/src/pages/instance/message.tsx
index c2028652c..45fab7e49 100644
--- a/web/src/pages/instance/message.tsx
+++ b/web/src/pages/instance/message.tsx
@@ -59,7 +59,6 @@ import {
   QueueBrowserResults,
 } from '../../components/QueueBrowser';
 import type { MessageQueryHistory, TraceQueryHistory } from 
'../../api/messageHistory';
-import { getMessageQueryResults } from '../../api/messageHistory';
 import { useLang } from '../../i18n/LangContext';
 import type { MessageQuery, MessageRecord, TraceRecord } from 
'../../api/message';
 import {
@@ -410,6 +409,9 @@ const MessagePageContent = ({
   const [directConsumeClientId, setDirectConsumeClientId] = useState('');
   const [directConsumeSubmitting, setDirectConsumeSubmitting] = 
useState(false);
   const queryGenerationRef = useRef(0);
+  // The query whose results the table currently shows. Pagination must re-run 
this
+  // committed query, not whatever the form inputs hold at the moment a page 
is clicked.
+  const committedQueryRef = useRef<{ mode: QueryMode; params: MessageQuery } | 
null>(null);
   const traceGenerationRef = useRef(0);
   const traceCacheRef = useRef(new Map<string, Promise<TraceRecord | null>>());
   const traceDiagnostics = useMemo(() => analyzeMessageTrace(traceData), 
[traceData]);
@@ -449,6 +451,7 @@ const MessagePageContent = ({
     setResultMayBeTruncated(false);
     setQueryError(null);
     setQueryLoading(false);
+    committedQueryRef.current = null;
   };
 
   const handleReset = () => {
@@ -497,6 +500,9 @@ const MessagePageContent = ({
         pageSize,
       });
       if (queryGenerationRef.current !== requestGeneration) return;
+      // Commit only the query whose results are actually on screen: a failed 
or superseded
+      // request must not become the query that pagination re-runs.
+      committedQueryRef.current = { mode, params: normalizedParams };
       setMessages(result.items);
       setMessageTotal(result.total);
       setMessagePage(result.page);
@@ -530,42 +536,21 @@ const MessagePageContent = ({
       setDateRange([dayjs(record.startTime), dayjs(record.endTime)]);
     }
     setHistoryDrawerOpen(false);
-    const requestGeneration = queryGenerationRef.current + 1;
-    queryGenerationRef.current = requestGeneration;
-    setQueryLoading(true);
-    setQueryError(null);
-    try {
-      const results = await getMessageQueryResults(record.id);
-      if (queryGenerationRef.current !== requestGeneration) return;
-      const mapped: MessageRecord[] = results.map((r) => ({
-        msgId: r.msgId,
-        topic: r.topic,
-        tag: r.tag || null,
-        key: r.key || null,
-        brokerName: r.brokerName || null,
-        queueId: r.queueId,
-        queueOffset: r.queueOffset,
-        body: '',
-        storeTime: r.storeTime,
-        bornHost: r.bornHost,
-        storeHost: r.storeHost,
-        properties: {},
-        size: r.size,
-      }));
-      setMessages(mapped);
-      setMessageTotal(mapped.length);
-      setMessagePage(1);
-      setResultMayBeTruncated(false);
-      message.success(`已加载历史查询结果,共 ${mapped.length} 条`);
-    } catch (error) {
-      if (queryGenerationRef.current === requestGeneration) {
-        setQueryError(getErrorMessage(error, '加载历史结果失败'));
-      }
-    } finally {
-      if (queryGenerationRef.current === requestGeneration) {
-        setQueryLoading(false);
-      }
-    }
+    // Re-run the historical query through the same live path so it is 
normalized and committed
+    // exactly like a normal search: the displayed page and any later 
pagination then share one
+    // query. Loading the archived snapshot here instead would splice snapshot 
page 1 with a live
+    // page 2 on the next pagination click — the very mix this fix removes.
+    const params: MessageQuery =
+      mode === 'topic'
+        ? {
+            topic: record.topic,
+            ...(record.startTime !== undefined ? { startTime: record.startTime 
} : {}),
+            ...(record.endTime !== undefined ? { endTime: record.endTime } : 
{}),
+          }
+        : mode === 'key'
+          ? { topic: record.topic, key: record.messageKey || undefined }
+          : { topic: record.topic, msgId: record.msgId || undefined };
+    await executeQuery(mode, params);
   };
 
   const replayTraceRecord = (record: TraceQueryHistory) => {
@@ -1213,8 +1198,11 @@ const MessagePageContent = ({
               total: messageTotal,
               showSizeChanger: true,
               showTotal: (total) => `共 ${total} 条消息`,
-              onChange: (page, pageSize) =>
-                void executeQuery(queryMode, currentQueryParams, page, 
pageSize),
+              onChange: (page, pageSize) => {
+                const committed = committedQueryRef.current;
+                if (!committed) return;
+                void executeQuery(committed.mode, committed.params, page, 
pageSize);
+              },
             }}
             size="small"
             scroll={{ x: tableScrollX(columns) }}

Reply via email to