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 65d77f417 fix(web): drop the client heartbeat column the API can never
fill (#7138)
65d77f417 is described below
commit 65d77f41738286560e80e22a161e13aebbeb3f93
Author: btlqql <[email protected]>
AuthorDate: Mon Oct 26 20:38:00 2026 +0800
fix(web): drop the client heartbeat column the API can never fill (#7138)
* fix(web): drop the client heartbeat column the API can never fill (#6086)
The clients page advertised a connection time in three places -- the table's
`Heartbeat` column, the CSV export's `Connected At` column and the
connection detail
dialog -- all bound to `ClientConnectionVO.connectedAt`, a field no
provider ever
assigns. `RocketMQClientProvider.toConnectionVO(ProducerInfo, ...)`
(`:337`) and
`toConnectionVO(Connection, ...)` (`:469`) build the VO from `ProducerInfo`
/
`Connection` payloads that carry no timestamp, and the proxy branch passes
none
either, so every cell rendered `-`. That reads as "this client never sent a
heartbeat" rather than "this build does not collect it".
Drop the three surfaces instead of inventing a timestamp the brokers do not
report --
the same resolution #5600 took for the fabricated NameServer `Connections`
column.
The regression test asserts that the column header, the dialog row and the
CSV column
are all gone, and the existing CSV/dialog expectations are trimmed
accordingly.
* fix(web): drop the i18n key the removed heartbeat column owned
cluster.heartbeat labelled the connection-time column this PR removes;
nothing
references it afterwards, and an orphan key in the bilingual directory is
exactly
the drift the completeness guard cannot see.
---
web/src/i18n/translations.ts | 1 -
.../pages/cluster/__tests__/ClientsPage.test.tsx | 39 ++++++++++++++++++++--
web/src/pages/cluster/clients.tsx | 17 ----------
3 files changed, 36 insertions(+), 21 deletions(-)
diff --git a/web/src/i18n/translations.ts b/web/src/i18n/translations.ts
index 4fe25240c..f244ec420 100644
--- a/web/src/i18n/translations.ts
+++ b/web/src/i18n/translations.ts
@@ -286,7 +286,6 @@ const translations: Record<string, Record<Lang, string>> = {
'cluster.warning': { zh: '告警', en: 'Warning' },
'cluster.error': { zh: '异常', en: 'Error' },
'cluster.offline': { zh: '离线', en: 'Offline' },
- 'cluster.heartbeat': { zh: '心跳时间', en: 'Heartbeat' },
'cluster.restartNotSupported': {
zh: '暂不支持在线重启,请通过运维平台操作',
en: 'Online restart not supported, please use ops platform',
diff --git a/web/src/pages/cluster/__tests__/ClientsPage.test.tsx
b/web/src/pages/cluster/__tests__/ClientsPage.test.tsx
index 3aaa8e717..acee44de1 100644
--- a/web/src/pages/cluster/__tests__/ClientsPage.test.tsx
+++ b/web/src/pages/cluster/__tests__/ClientsPage.test.tsx
@@ -592,8 +592,8 @@ describe('Clients page', () => {
const blob = createObjectURL.mock.calls[0][0] as Blob;
await expect(blob.text()).resolves.toBe(
[
- '"Cluster","Client
ID","Type","Group/Topic","Protocol","Address","Language","Version","Connected
At","Partial"',
-
'"ns-prod","\'=risky-client","Producer","order-create","gRPC","10.0.1.12:49152","Java","5.0.7","2026-07-01
08:30:00","false"',
+ '"Cluster","Client
ID","Type","Group/Topic","Protocol","Address","Language","Version","Partial"',
+
'"ns-prod","\'=risky-client","Producer","order-create","gRPC","10.0.1.12:49152","Java","5.0.7","false"',
].join('\n'),
);
expect(
@@ -888,6 +888,39 @@ describe('Clients page', () => {
expect(within(dialog).getByText('10.0.1.12:49152')).toBeInTheDocument();
expect(within(dialog).getByText('Java')).toBeInTheDocument();
expect(within(dialog).getByText('5.0.7')).toBeInTheDocument();
- expect(within(dialog).getByText('2026-07-01
08:30:00')).toBeInTheDocument();
+ });
+
+ // #6086: no provider (Apache remoting, proxy, or stub) can supply a
connection time, so the page
+ // must not advertise one in the table, in the detail dialog or in the CSV
export.
+ it('omits every connection-time control the API can never fill', async () =>
{
+ const createObjectURL = vi.fn((blob: Blob | MediaSource) => {
+ expect(blob).toBeInstanceOf(Blob);
+ return 'blob:connections-without-a-connection-time';
+ });
+ Object.defineProperty(URL, 'createObjectURL', {
+ writable: true,
+ value: createObjectURL,
+ });
+ Object.defineProperty(URL, 'revokeObjectURL', {
+ writable: true,
+ value: vi.fn(),
+ });
+ vi.spyOn(HTMLAnchorElement.prototype, 'click').mockImplementation(() =>
{});
+ const user = userEvent.setup({ pointerEventsCheck: 0 });
+ renderWithProviders(<ClientsPage />);
+
+ const row = await screen.findByRole('row', { name:
/order-svc-0@10\.0\.1\.12:49152/ });
+ expect(screen.queryAllByText('心跳时间')).toHaveLength(0);
+
+ await user.click(screen.getByRole('button', { name: /导出/ }));
+ const csv = await (createObjectURL.mock.calls[0][0] as Blob).text();
+ expect(csv.split('\n')[0]).toBe(
+ '"Cluster","Client
ID","Type","Group/Topic","Protocol","Address","Language","Version","Partial"',
+ );
+
+ await expectInteractive(within(row).getByRole('button', { name: /详情/ }));
+ await user.click(within(row).getByRole('button', { name: /详情/ }));
+ const dialog = await screen.findByRole('dialog', { name: /客户端详情/ });
+ expect(within(dialog).queryAllByText('心跳时间')).toHaveLength(0);
});
});
diff --git a/web/src/pages/cluster/clients.tsx
b/web/src/pages/cluster/clients.tsx
index 65d0e261f..73aa5139f 100644
--- a/web/src/pages/cluster/clients.tsx
+++ b/web/src/pages/cluster/clients.tsx
@@ -44,7 +44,6 @@ import type { ClientConnection } from '../../api/connections';
import { listConnections } from '../../services/connectionsService';
import { listRegistryClusters } from '../../services/clusterService';
import type { ClusterInfo } from '../../api/cluster';
-import { formatDateTime } from '../../utils/format';
import { buildCsv, downloadCsv, localExportDateStamp, type CsvColumn } from
'../../utils/download';
import { describeThrownMessage } from '../../utils/apiError';
import { tableScrollX } from '../../utils/table';
@@ -128,7 +127,6 @@ const CLIENT_CONNECTION_EXPORT_COLUMNS:
CsvColumn<ClientConnection>[] = [
{ header: 'Address', value: (connection) => connection.address },
{ header: 'Language', value: (connection) => connection.language },
{ header: 'Version', value: (connection) => connection.version },
- { header: 'Connected At', value: (connection) => connection.connectedAt },
{ header: 'Partial', value: (connection) => (connection.partial ? 'true' :
'false') },
];
@@ -510,18 +508,6 @@ const ClientsPage = () => {
width: 90,
render: displayMetadata,
},
- {
- title: t('cluster.heartbeat'),
- dataIndex: 'connectedAt',
- key: 'connectedAt',
- width: 170,
- sorter: (a, b) => (a.connectedAt ?? '').localeCompare(b.connectedAt ??
''),
- render: (d?: string | null) => (
- <Text type="secondary" style={{ fontSize: 14 }}>
- {d ? formatDateTime(d) : '-'}
- </Text>
- ),
- },
{
title: t('common.actions'),
key: 'actions',
@@ -1039,9 +1025,6 @@ const ClientsPage = () => {
<Descriptions.Item label={t('common.version')}>
{displayMetadata(selectedConnection.version)}
</Descriptions.Item>
- <Descriptions.Item label={t('cluster.heartbeat')}>
- {selectedConnection.connectedAt ?? '-'}
- </Descriptions.Item>
</Descriptions>
)}
</Modal>