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 04aab1a51 fix(message): keep the history drawer search visible after
reopening (#4564)
04aab1a51 is described below
commit 04aab1a5105c3f08d6295046fa8674011c1cc4a6
Author: 烤化の初雪 <[email protected]>
AuthorDate: Thu Sep 24 17:44:44 2026 +0800
fix(message): keep the history drawer search visible after reopening (#4564)
fix(message): keep the history drawer search visible after reopening
The drawer's search input was uncontrolled while the applied keyword
lived in component state, so the destroyed-on-close content came back
with an empty field that contradicted the still-filtered tables. Drive
the input from a draft that is restored from the applied search when
the drawer opens, and commit it on submit.
Fixes #4563.
---
web/src/components/MessageQueryHistoryDrawer.tsx | 18 +++++
.../__tests__/MessageQueryHistoryDrawer.test.tsx | 79 ++++++++++++++++++++++
2 files changed, 97 insertions(+)
diff --git a/web/src/components/MessageQueryHistoryDrawer.tsx
b/web/src/components/MessageQueryHistoryDrawer.tsx
index 8d83f0052..f96e70a77 100644
--- a/web/src/components/MessageQueryHistoryDrawer.tsx
+++ b/web/src/components/MessageQueryHistoryDrawer.tsx
@@ -41,6 +41,10 @@ const MessageQueryHistoryDrawer = ({
const { t } = useLang();
const [tab, setTab] = useState<'messages' | 'traces'>('messages');
const [search, setSearch] = useState('');
+ // Draft value for the controlled search input. The drawer unmounts its
content when
+ // closed (destroyOnHidden), so an uncontrolled input would come back empty
while the
+ // applied search state keeps filtering the tables; the draft stays in sync
instead.
+ const [searchDraft, setSearchDraft] = useState('');
const [page, setPage] = useState(1);
const [loading, setLoading] = useState(false);
const [error, setError] = useState('');
@@ -99,6 +103,17 @@ const MessageQueryHistoryDrawer = ({
};
}, [load]);
+ // The drawer content is destroyed while closed; restore the visible search
draft from
+ // the applied filter when the drawer opens so the input matches the
filtered tables.
+ // Render-time adjustment (same pattern as the alerts domain switch), not an
effect.
+ const [renderedOpen, setRenderedOpen] = useState(open);
+ if (open !== renderedOpen) {
+ setRenderedOpen(open);
+ if (open) {
+ setSearchDraft(search);
+ }
+ }
+
const messageColumns: ColumnsType<MessageQueryHistory> = [
{
title: t('common.type'),
@@ -159,9 +174,12 @@ const MessageQueryHistoryDrawer = ({
<Input.Search
allowClear
placeholder={t('messageHistory.searchPlaceholder')}
+ value={searchDraft}
+ onChange={(event) => setSearchDraft(event.target.value)}
onSearch={(value) => {
setPage(1);
setSearch(value.trim());
+ setSearchDraft(value.trim());
}}
style={{ marginBottom: 12, width: 420 }}
/>
diff --git a/web/src/components/__tests__/MessageQueryHistoryDrawer.test.tsx
b/web/src/components/__tests__/MessageQueryHistoryDrawer.test.tsx
index 22bd40759..098ba3c95 100644
--- a/web/src/components/__tests__/MessageQueryHistoryDrawer.test.tsx
+++ b/web/src/components/__tests__/MessageQueryHistoryDrawer.test.tsx
@@ -169,4 +169,83 @@ describe('MessageQueryHistoryDrawer', () => {
).toBeInTheDocument();
expect(screen.queryByText('服务端查询历史')).not.toBeInTheDocument();
});
+
+ it('keeps the applied search visible in the input after the drawer is closed
and reopened', async () => {
+ localStorage.setItem(LANGUAGE_STORAGE_KEY, 'en');
+ const user = userEvent.setup();
+ const view = render(
+ <App>
+ <LangProvider>
+ <MessageQueryHistoryDrawer open clusterId="instance-a"
onClose={vi.fn()} />
+ </LangProvider>
+ </App>,
+ );
+
+ expect(await screen.findByText('order-1')).toBeInTheDocument();
+ const searchInput = screen.getByPlaceholderText(
+ 'Search Topic, trace Topic, Message ID, Key or operator',
+ );
+ await user.type(searchInput, 'order-1');
+ await user.keyboard('{Enter}');
+
+ await waitFor(() =>
+ expect(listMessageQueryHistory).toHaveBeenCalledWith(
+ expect.objectContaining({ search: 'order-1' }),
+ ),
+ );
+
+ view.rerender(
+ <App>
+ <LangProvider>
+ <MessageQueryHistoryDrawer open={false} clusterId="instance-a"
onClose={vi.fn()} />
+ </LangProvider>
+ </App>,
+ );
+ view.rerender(
+ <App>
+ <LangProvider>
+ <MessageQueryHistoryDrawer open clusterId="instance-a"
onClose={vi.fn()} />
+ </LangProvider>
+ </App>,
+ );
+
+ // The applied filter is still active after reopening, so the visible
input must
+ // show it instead of an empty field that contradicts the filtered table
below.
+ expect(await screen.findByText('order-1')).toBeInTheDocument();
+ expect(
+ await screen.findByPlaceholderText('Search Topic, trace Topic, Message
ID, Key or operator'),
+ ).toHaveValue('order-1');
+ });
+
+ it('applies a cleared search field as an empty filter', async () => {
+ localStorage.setItem(LANGUAGE_STORAGE_KEY, 'en');
+ const user = userEvent.setup();
+ render(
+ <App>
+ <LangProvider>
+ <MessageQueryHistoryDrawer open clusterId="instance-a"
onClose={vi.fn()} />
+ </LangProvider>
+ </App>,
+ );
+
+ expect(await screen.findByText('order-1')).toBeInTheDocument();
+ const searchInput = screen.getByPlaceholderText(
+ 'Search Topic, trace Topic, Message ID, Key or operator',
+ );
+ await user.type(searchInput, 'order-1');
+ await user.keyboard('{Enter}');
+ await waitFor(() =>
+ expect(listMessageQueryHistory).toHaveBeenCalledWith(
+ expect.objectContaining({ search: 'order-1' }),
+ ),
+ );
+
+ await user.clear(searchInput);
+ await user.keyboard('{Enter}');
+ await waitFor(() =>
+ expect(listMessageQueryHistory).toHaveBeenLastCalledWith(
+ expect.objectContaining({ search: undefined }),
+ ),
+ );
+ });
});