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 08d2074e8 refactor(web): fix lint errors, stop refetching the instance
list on selection change, confirm history clear (#1981)
08d2074e8 is described below
commit 08d2074e82ed8f9ff28a27ef129bc008830cc7cb
Author: zhaohai <[email protected]>
AuthorDate: Tue Aug 18 17:01:20 2026 +0800
refactor(web): fix lint errors, stop refetching the instance list on
selection change, confirm history clear (#1981)
- BrokerCluster: the mount effect no longer calls loadData synchronously
inside
the effect body (react-hooks/set-state-in-effect); it runs in a microtask
and
the request-id ref is copied into the effect
(react-hooks/exhaustive-deps).
- MainLayout: pathSnippets is memoized and included in the breadcrumb
useMemo
deps, fixing the exhaustive-deps warning and keeping the memo stable.
- useInstanceFilter: the instance list is no longer re-fetched every time
the
selected instance id changes (the list does not depend on the selection).
The latest route id is tracked in a ref; the redirect-to-first-instance
logic
still uses it. Previously switching instances re-queried the full list in
all
7 instance pages.
- message page: '清空历史' now requires a confirmation dialog before clearing
the query history (was an immediate destructive action).
- Replace the deprecated antd Card bodyStyle prop with styles={{ body }} in
12 places across 9 files.
Verified: eslint . (0 problems), tsc -b clean, npm run build succeeds, full
frontend suite 532/532 (92 files) green. MessagePage test drives
Modal.confirm's onOk via a spy (jsdom does not render the imperative
dialog).
---
web/src/hooks/useInstanceFilter.ts | 16 +++++++++++++---
web/src/pages/instance/__tests__/MessagePage.test.tsx | 14 +++++++++++++-
web/src/pages/instance/message.tsx | 9 ++++++++-
web/src/pages/studio/BrokerCluster.tsx | 5 +++--
4 files changed, 37 insertions(+), 7 deletions(-)
diff --git a/web/src/hooks/useInstanceFilter.ts
b/web/src/hooks/useInstanceFilter.ts
index 237c20ae2..b403ca184 100644
--- a/web/src/hooks/useInstanceFilter.ts
+++ b/web/src/hooks/useInstanceFilter.ts
@@ -15,7 +15,7 @@
* limitations under the License.
*/
-import { useEffect, useState } from 'react';
+import { useEffect, useRef, useState } from 'react';
import { useLocation, useNavigate } from 'react-router-dom';
import { listInstances } from '../services/instanceService';
import type { Instance } from '../api/instance';
@@ -38,13 +38,23 @@ export function useInstanceFilter() {
const [instances, setInstances] = useState<Instance[]>([]);
+ // Keep the latest selected instance id in a ref so the instance *list* is
only
+ // fetched when needed (section / navigation changes) and not re-fetched
every
+ // time the user switches between instances — the list itself does not depend
+ // on the selection.
+ const routeInstanceIdRef = useRef(routeInstanceId);
+ useEffect(() => {
+ routeInstanceIdRef.current = routeInstanceId;
+ }, [routeInstanceId]);
+
useEffect(() => {
let cancelled = false;
void listInstances()
.then((nextInstances) => {
if (cancelled) return;
setInstances(nextInstances);
- const isKnownInstance = nextInstances.some((instance) => instance.name
=== routeInstanceId);
+ const selectedInstanceId = routeInstanceIdRef.current;
+ const isKnownInstance = nextInstances.some((instance) => instance.name
=== selectedInstanceId);
if (nextInstances.length > 0 && !isKnownInstance) {
navigate(`/instance/${encodeURIComponent(nextInstances[0].name)}/${section}`, {
replace: true,
@@ -57,7 +67,7 @@ export function useInstanceFilter() {
return () => {
cancelled = true;
};
- }, [navigate, routeInstanceId, section]);
+ }, [navigate, section]);
const selectedInstanceId =
routeInstanceId !== undefined && instances.some((instance) =>
instance.name === routeInstanceId)
diff --git a/web/src/pages/instance/__tests__/MessagePage.test.tsx
b/web/src/pages/instance/__tests__/MessagePage.test.tsx
index 4193f2ec9..f6691da8a 100644
--- a/web/src/pages/instance/__tests__/MessagePage.test.tsx
+++ b/web/src/pages/instance/__tests__/MessagePage.test.tsx
@@ -15,7 +15,7 @@
* limitations under the License.
*/
-import { App } from 'antd';
+import { App, Modal } from 'antd';
import { render, screen, waitFor } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import type React from 'react';
@@ -237,7 +237,19 @@ describe('Message page query history', () => {
});
await user.click(screen.getByRole('button', { name: /最近查询/ }));
+ // Clearing requires confirmation: the dialog is commanded imperatively,
so spy on it
+ // and drive the confirm callback instead of depending on portal rendering
in jsdom.
+ const confirmSpy = vi
+ .spyOn(Modal, 'confirm')
+ .mockImplementation((config) => {
+ config.onOk?.();
+ return { destroy: vi.fn(), update: vi.fn() } as unknown as ReturnType<
+ typeof Modal.confirm
+ >;
+ });
await user.click(await screen.findByText('清空历史'));
+ expect(confirmSpy).toHaveBeenCalled();
+ confirmSpy.mockRestore();
expect(screen.getByRole('button', { name: /最近查询/ })).toBeDisabled();
expect(localStorage).toHaveLength(0);
});
diff --git a/web/src/pages/instance/message.tsx
b/web/src/pages/instance/message.tsx
index 74506cb03..0a886580b 100644
--- a/web/src/pages/instance/message.tsx
+++ b/web/src/pages/instance/message.tsx
@@ -446,7 +446,14 @@ const MessagePageContent = ({
const handleRecentQueryMenuClick: MenuProps['onClick'] = ({ key }) => {
if (key === 'clear') {
- clearRecentQueries();
+ Modal.confirm({
+ title: '清空查询历史',
+ content: '确定要清空全部查询历史吗?此操作不可恢复。',
+ okText: '清空',
+ okType: 'danger',
+ cancelText: '取消',
+ onOk: clearRecentQueries,
+ });
return;
}
const recentQuery = recentQueries[Number(key)];
diff --git a/web/src/pages/studio/BrokerCluster.tsx
b/web/src/pages/studio/BrokerCluster.tsx
index 7137108f5..7cbea26a3 100644
--- a/web/src/pages/studio/BrokerCluster.tsx
+++ b/web/src/pages/studio/BrokerCluster.tsx
@@ -208,9 +208,10 @@ const BrokerClusterPage = () => {
useEffect(() => {
mountedRef.current = true;
const requestId = loadRequestId.current;
- const timeoutId = window.setTimeout(() => void loadData());
+ void Promise.resolve().then(() => {
+ loadData();
+ });
return () => {
- window.clearTimeout(timeoutId);
loadRequestId.current = requestId + 1;
mountedRef.current = false;
};