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 895498a7b fix(client): use backend connection filters (#3294)
895498a7b is described below

commit 895498a7bce6b3cda4975a1c3ebbbd48c8c9ceee
Author: xdz997 <[email protected]>
AuthorDate: Tue Sep 8 15:23:42 2026 +0800

    fix(client): use backend connection filters (#3294)
---
 .../pages/cluster/__tests__/ClientsPage.test.tsx   | 66 ++++++++++++++++++++--
 web/src/pages/cluster/clients.tsx                  | 38 +++++++++----
 2 files changed, 87 insertions(+), 17 deletions(-)

diff --git a/web/src/pages/cluster/__tests__/ClientsPage.test.tsx 
b/web/src/pages/cluster/__tests__/ClientsPage.test.tsx
index ad7cb4042..e338a4f14 100644
--- a/web/src/pages/cluster/__tests__/ClientsPage.test.tsx
+++ b/web/src/pages/cluster/__tests__/ClientsPage.test.tsx
@@ -16,7 +16,7 @@
  */
 
 import { App } from 'antd';
-import { act, render, screen, waitFor, within } from '@testing-library/react';
+import { act, fireEvent, render, screen, waitFor, within } from 
'@testing-library/react';
 import userEvent from '@testing-library/user-event';
 import type React from 'react';
 import { afterEach, beforeAll, beforeEach, describe, expect, it, vi } from 
'vitest';
@@ -139,6 +139,17 @@ const renderWithProviders = (ui: React.ReactElement) =>
     </App>,
   );
 
+const selectOption = async (user: ReturnType<typeof userEvent.setup>, label: 
string) => {
+  const option = await waitFor(() => {
+    const match = [
+      
...document.querySelectorAll<HTMLElement>('.ant-select-item-option-content'),
+    ].find((element) => element.textContent === label);
+    if (!match) throw new Error(`Select option not found: ${label}`);
+    return match;
+  });
+  await user.click(option);
+};
+
 const deferred = <T,>() => {
   let resolve!: (value: T) => void;
   let reject!: (reason?: unknown) => void;
@@ -178,6 +189,34 @@ describe('Clients page', () => {
     });
   });
 
+  it('sends cluster and type filters to the backend', async () => {
+    const mixedConnections = [
+      { ...connection, clusterName: 'ns-prod', type: 'Producer' },
+      { ...connection, clusterName: 'ns-prod', type: 'Consumer' },
+    ];
+    
vi.mocked(connectionsService.listConnections).mockResolvedValue(mixedConnections);
+    const user = userEvent.setup();
+    renderWithProviders(<ClientsPage />);
+
+    await screen.findAllByText('[email protected]:49152');
+
+    const clusterSelect = screen.getAllByLabelText('所属集群')[0];
+    fireEvent.mouseDown(clusterSelect.querySelector('.ant-select-selector')!);
+    await selectOption(user, 'ns-prod');
+
+    const typeSelect = screen.getAllByLabelText('类型')[0];
+    fireEvent.mouseDown(typeSelect.querySelector('.ant-select-selector')!);
+    await selectOption(user, 'Consumer');
+
+    await waitFor(() => {
+      expect(connectionsService.listConnections).toHaveBeenLastCalledWith({
+        namesrvAddr: 'namesrv-1:9876',
+        clusterId: 'ns-prod',
+        type: 'Consumer',
+      });
+    });
+  });
+
   it('summarizes connection types, protocols, and language versions', async () 
=> {
     
vi.mocked(connectionsService.listConnections).mockResolvedValue(connections);
     renderWithProviders(<ClientsPage />);
@@ -247,14 +286,29 @@ describe('Clients page', () => {
 
   it('updates statistics when the selected cluster filter changes', async () 
=> {
     const user = userEvent.setup();
-    
vi.mocked(connectionsService.listConnections).mockResolvedValue(connections);
+    vi.mocked(connectionsService.listConnections).mockImplementation((query) =>
+      Promise.resolve(
+        query?.clusterId === 'ns-prod'
+          ? connections.filter((item) => item.clusterName === 'ns-prod')
+          : connections,
+      ),
+    );
     renderWithProviders(<ClientsPage />);
 
-    await screen.findByText('[email protected]:49154');
-    await user.click(screen.getByRole('combobox', { name: '所属集群' }));
-    await user.click(
-      await screen.findByText('ns-prod', { selector: 
'.ant-select-item-option-content' }),
+    await screen.findAllByText('[email protected]:49154');
+    const clusterSelect = screen.getAllByLabelText('所属集群')[0];
+    fireEvent.mouseDown(clusterSelect.querySelector('.ant-select-selector')!);
+    await selectOption(user, 'ns-prod');
+
+    await waitFor(() =>
+      expect(connectionsService.listConnections).toHaveBeenLastCalledWith({
+        namesrvAddr: 'namesrv-1:9876',
+        clusterId: 'ns-prod',
+      }),
     );
+    await act(async () => {
+      await Promise.resolve();
+    });
 
     await waitFor(() => {
       
expect(within(screen.getByTestId('connection-total')).getByText('2')).toBeInTheDocument();
diff --git a/web/src/pages/cluster/clients.tsx 
b/web/src/pages/cluster/clients.tsx
index 74efbd225..6dcd69ae9 100644
--- a/web/src/pages/cluster/clients.tsx
+++ b/web/src/pages/cluster/clients.tsx
@@ -163,6 +163,7 @@ const ClientsPage = () => {
   const [loading, setLoading] = useState(true);
   const [search, setSearch] = useState('');
   const [clusterFilter, setClusterFilter] = useState<string>('ALL');
+  const [typeFilter, setTypeFilter] = useState<string>('ALL');
   const [selectedConnection, setSelectedConnection] = 
useState<ClientConnection | null>(null);
   const [loadError, setLoadError] = useState<string | null>(null);
   const [registryLoadKey, setRegistryLoadKey] = useState(0);
@@ -231,7 +232,11 @@ const ClientsPage = () => {
       if (connectionRequestRef.current === requestId) setLoading(true);
     });
 
-    void listConnections({ namesrvAddr: selectedEndpoint })
+    void listConnections({
+      namesrvAddr: selectedEndpoint,
+      clusterId: clusterFilter === 'ALL' ? undefined : clusterFilter,
+      type: typeFilter === 'ALL' ? undefined : typeFilter,
+    })
       .then((nextConnections) => {
         if (connectionRequestRef.current === requestId) {
           setConnections(nextConnections);
@@ -249,7 +254,7 @@ const ClientsPage = () => {
       .finally(() => {
         if (connectionRequestRef.current === requestId) setLoading(false);
       });
-  }, [connectionLoadKey, selectedEndpoint, selectedCluster]);
+  }, [connectionLoadKey, clusterFilter, selectedEndpoint, selectedCluster, 
typeFilter]);
 
   useEffect(
     () => () => {
@@ -262,21 +267,18 @@ const ClientsPage = () => {
   /* ─── Cluster options using nsClusterName ─── */
   const clusterOptions = useMemo(() => {
     const clusterNames = [
-      ...new Set(connections.map((connection) => connection.clusterName)),
+      ...new Set([
+        ...registryClusters.map((cluster) => 
cluster.nsClusterName).filter(Boolean),
+        ...connections.map((connection) => 
connection.clusterName).filter(Boolean),
+      ]),
     ].sort();
     return [
       { value: 'ALL', label: t('clients.allClusters') },
       ...clusterNames.map((name) => ({ value: name, label: name })),
     ];
-  }, [connections, t]);
+  }, [connections, registryClusters, t]);
 
-  const clusterConnections = useMemo(
-    () =>
-      clusterFilter === 'ALL'
-        ? connections
-        : connections.filter((connection) => connection.clusterName === 
clusterFilter),
-    [connections, clusterFilter],
-  );
+  const clusterConnections = useMemo(() => connections, [connections]);
 
   const connectionStats = useMemo(() => {
     const instances = Array.from(
@@ -707,6 +709,20 @@ const ClientsPage = () => {
             style={{ width: 180 }}
             options={clusterOptions}
           />
+          <Select
+            aria-label={t('common.type')}
+            value={typeFilter}
+            onChange={(value) => {
+              setTypeFilter(value);
+              setCurrentPage(1);
+            }}
+            style={{ width: 140 }}
+            options={[
+              { value: 'ALL', label: t('common.all') },
+              { value: 'Producer', label: typeConfig.Producer?.label ?? 
'Producer' },
+              { value: 'Consumer', label: typeConfig.Consumer?.label ?? 
'Consumer' },
+            ]}
+          />
           <Input.Search
             placeholder={t('clients.searchPlaceholder')}
             allowClear

Reply via email to