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 e03cc3f1 fix(web): align mock exports and dashboard availability
states (#1946)
e03cc3f1 is described below
commit e03cc3f132e5b6dc37499d7c70554dab11699c50
Author: youngkermit8-coder <[email protected]>
AuthorDate: Thu Aug 13 19:53:10 2026 +0800
fix(web): align mock exports and dashboard availability states (#1946)
Consolidates #1717, #1759, #1946: label mock Grafana bulk exports as JSON
with filenames, refresh the stale Topic consumer metrics test mock, and
mark dashboard group counts unavailable instead of zero when the broker
returns null.
---
.../provider/apache/RocketMQDashboardProvider.java | 5 +-
.../apache/RocketMQDashboardProviderTest.java | 55 ++++++++++++++++++++++
web/src/components/GrafanaDashboardList.tsx | 4 +-
.../__tests__/GrafanaDashboardList.test.tsx | 40 ++++++++++++++--
.../pages/instance/__tests__/TopicPage.test.tsx | 6 +++
web/src/services/grafanaService.test.ts | 14 +++---
web/src/services/grafanaService.ts | 17 +++++--
7 files changed, 126 insertions(+), 15 deletions(-)
diff --git
a/server/src/main/java/org/apache/rocketmq/studio/provider/apache/RocketMQDashboardProvider.java
b/server/src/main/java/org/apache/rocketmq/studio/provider/apache/RocketMQDashboardProvider.java
index 92c6c9d3..4cafa763 100644
---
a/server/src/main/java/org/apache/rocketmq/studio/provider/apache/RocketMQDashboardProvider.java
+++
b/server/src/main/java/org/apache/rocketmq/studio/provider/apache/RocketMQDashboardProvider.java
@@ -179,7 +179,10 @@ public class RocketMQDashboardProvider implements
DashboardProvider {
try {
SubscriptionGroupWrapper subscriptionGroupWrapper =
admin.getAllSubscriptionGroup(brokerAddr, 5000);
- if (subscriptionGroupWrapper != null &&
subscriptionGroupWrapper.getSubscriptionGroupTable() != null) {
+ if (subscriptionGroupWrapper == null
+ ||
subscriptionGroupWrapper.getSubscriptionGroupTable() == null) {
+ markCountUnavailable(groupCountsUnavailableClusters,
clusterName);
+ } else {
for (Map.Entry<String, SubscriptionGroupConfig> entry :
subscriptionGroupWrapper.getSubscriptionGroupTable().entrySet()) {
String groupName = entry.getKey();
diff --git
a/server/src/test/java/org/apache/rocketmq/studio/provider/apache/RocketMQDashboardProviderTest.java
b/server/src/test/java/org/apache/rocketmq/studio/provider/apache/RocketMQDashboardProviderTest.java
index 2e40d50a..76e56824 100644
---
a/server/src/test/java/org/apache/rocketmq/studio/provider/apache/RocketMQDashboardProviderTest.java
+++
b/server/src/test/java/org/apache/rocketmq/studio/provider/apache/RocketMQDashboardProviderTest.java
@@ -60,6 +60,7 @@ class RocketMQDashboardProviderTest {
when(adminExt.examineBrokerClusterInfo()).thenReturn(clusterInfo());
when(adminExt.fetchAllTopicList()).thenReturn(topicList());
when(adminExt.getAllTopicConfig("10.0.0.11:10911",
5000)).thenReturn(topicConfig("order-topic"));
+ when(adminExt.getAllSubscriptionGroup("10.0.0.11:10911",
5000)).thenReturn(subscriptionGroups());
when(adminExt.fetchBrokerRuntimeStats("10.0.0.11:10911")).thenReturn(runtimeStats());
RocketMQDashboardProvider provider = newProvider(adminExt);
@@ -79,6 +80,7 @@ class RocketMQDashboardProviderTest {
when(adminExt.examineBrokerClusterInfo()).thenReturn(clusterInfo());
when(adminExt.getAllTopicConfig("10.0.0.11:10911", 5000))
.thenReturn(topicConfig("order-topic", "payments",
"SCHEDULE_TOPIC_XXXX"));
+ when(adminExt.getAllSubscriptionGroup("10.0.0.11:10911",
5000)).thenReturn(subscriptionGroups());
when(adminExt.fetchBrokerRuntimeStats("10.0.0.11:10911")).thenReturn(runtimeStats());
DashboardDataVO dashboard = newProvider(adminExt).getDashboardData();
@@ -164,6 +166,59 @@ class RocketMQDashboardProviderTest {
assertThat(dashboard.getStats().getHealthyClusters()).isZero();
}
+ @Test
+ void dashboardShouldMarkClusterWarningWhenGroupResponseIsNull() throws
Exception {
+ DefaultMQAdminExt adminExt = mock(DefaultMQAdminExt.class);
+ when(adminExt.examineBrokerClusterInfo()).thenReturn(clusterInfo());
+ when(adminExt.getAllTopicConfig("10.0.0.11:10911",
5000)).thenReturn(topicConfig("orders"));
+ when(adminExt.getAllSubscriptionGroup("10.0.0.11:10911",
5000)).thenReturn(null);
+
when(adminExt.fetchBrokerRuntimeStats("10.0.0.11:10911")).thenReturn(runtimeStats());
+
+ DashboardDataVO dashboard = newProvider(adminExt).getDashboardData();
+
+ assertThat(dashboard.getStats().getTotalConsumerGroups()).isZero();
+ assertThat(dashboard.getStats().getHealthyClusters()).isZero();
+ assertThat(dashboard.getClusters()).singleElement().satisfies(cluster
-> {
+ assertThat(cluster.getGroups()).isZero();
+ assertThat(cluster.getStatus()).isEqualTo(ClusterStatus.warning);
+ });
+ }
+
+ @Test
+ void dashboardShouldMarkClusterWarningWhenGroupTableIsNull() throws
Exception {
+ DefaultMQAdminExt adminExt = mock(DefaultMQAdminExt.class);
+ SubscriptionGroupWrapper nullTable = new SubscriptionGroupWrapper();
+ nullTable.setSubscriptionGroupTable(null);
+ when(adminExt.examineBrokerClusterInfo()).thenReturn(clusterInfo());
+ when(adminExt.getAllTopicConfig("10.0.0.11:10911",
5000)).thenReturn(topicConfig("orders"));
+ when(adminExt.getAllSubscriptionGroup("10.0.0.11:10911",
5000)).thenReturn(nullTable);
+
when(adminExt.fetchBrokerRuntimeStats("10.0.0.11:10911")).thenReturn(runtimeStats());
+
+ DashboardDataVO dashboard = newProvider(adminExt).getDashboardData();
+
+ assertThat(dashboard.getClusters()).singleElement()
+ .extracting(cluster -> cluster.getStatus())
+ .isEqualTo(ClusterStatus.warning);
+ assertThat(dashboard.getStats().getHealthyClusters()).isZero();
+ }
+
+ @Test
+ void dashboardShouldKeepClusterHealthyForValidEmptyGroupTable() throws
Exception {
+ DefaultMQAdminExt adminExt = mock(DefaultMQAdminExt.class);
+ when(adminExt.examineBrokerClusterInfo()).thenReturn(clusterInfo());
+ when(adminExt.getAllTopicConfig("10.0.0.11:10911",
5000)).thenReturn(topicConfig("orders"));
+ when(adminExt.getAllSubscriptionGroup("10.0.0.11:10911",
5000)).thenReturn(subscriptionGroups());
+
when(adminExt.fetchBrokerRuntimeStats("10.0.0.11:10911")).thenReturn(runtimeStats());
+
+ DashboardDataVO dashboard = newProvider(adminExt).getDashboardData();
+
+ assertThat(dashboard.getClusters()).singleElement().satisfies(cluster
-> {
+ assertThat(cluster.getGroups()).isZero();
+ assertThat(cluster.getStatus()).isEqualTo(ClusterStatus.healthy);
+ });
+ assertThat(dashboard.getStats().getHealthyClusters()).isEqualTo(1);
+ }
+
@Test
void dashboardShouldSurviveNullTopologyTables() throws Exception {
DefaultMQAdminExt adminExt = mock(DefaultMQAdminExt.class);
diff --git a/web/src/components/GrafanaDashboardList.tsx
b/web/src/components/GrafanaDashboardList.tsx
index 2a08d542..65f6acfd 100644
--- a/web/src/components/GrafanaDashboardList.tsx
+++ b/web/src/components/GrafanaDashboardList.tsx
@@ -120,8 +120,8 @@ export const GrafanaDashboardList: React.FC = () => {
const handleExportAll = async () => {
setExportingAll(true);
try {
- const blob = await exportGrafanaDashboards();
- triggerDownload('rocketmq-grafana-dashboards.zip', blob);
+ const download = await exportGrafanaDashboards();
+ triggerDownload(download.filename, download.blob);
message.success(t('grafana.exportAllDone'));
} catch {
message.error(t('grafana.exportAllFailed'));
diff --git a/web/src/components/__tests__/GrafanaDashboardList.test.tsx
b/web/src/components/__tests__/GrafanaDashboardList.test.tsx
index 5717e3a7..84014700 100644
--- a/web/src/components/__tests__/GrafanaDashboardList.test.tsx
+++ b/web/src/components/__tests__/GrafanaDashboardList.test.tsx
@@ -76,9 +76,10 @@ beforeEach(() => {
vi.mocked(exportGrafanaDashboard).mockResolvedValue(
new Blob([JSON.stringify(dashboardModel, null, 2)], { type:
'application/json' }),
);
- vi.mocked(exportGrafanaDashboards).mockResolvedValue(
- new Blob(['zip-content'], { type: 'application/zip' }),
- );
+ vi.mocked(exportGrafanaDashboards).mockResolvedValue({
+ blob: new Blob(['zip-content'], { type: 'application/zip' }),
+ filename: 'rocketmq-grafana-dashboards.zip',
+ });
});
afterEach(() => {
@@ -236,4 +237,37 @@ describe('GrafanaDashboardList', () => {
clickSpy.mockRestore();
});
+
+ it('uses the JSON filename returned by mock bulk export', async () => {
+ vi.mocked(exportGrafanaDashboards).mockResolvedValue({
+ blob: new Blob(['json-content'], { type: 'application/json' }),
+ filename: 'rocketmq-grafana-dashboards.json',
+ });
+ const user = userEvent.setup();
+ let downloadedFilename = '';
+ vi.spyOn(HTMLAnchorElement.prototype, 'click').mockImplementation(function
(
+ this: HTMLAnchorElement,
+ ) {
+ downloadedFilename = this.download;
+ });
+ Object.defineProperty(URL, 'createObjectURL', {
+ writable: true,
+ value: vi.fn().mockReturnValue('blob:grafana-all-json'),
+ });
+ Object.defineProperty(URL, 'revokeObjectURL', { writable: true, value:
vi.fn() });
+
+ render(
+ <App>
+ <LangProvider>
+ <GrafanaDashboardList />
+ </LangProvider>
+ </App>,
+ );
+
+ await screen.findByText('RocketMQ Cluster Overview');
+ await user.click(screen.getByRole('button', { name: /Export all|导出全部/ }));
+
+ await waitFor(() =>
expect(exportGrafanaDashboards).toHaveBeenCalledTimes(1));
+ expect(downloadedFilename).toBe('rocketmq-grafana-dashboards.json');
+ });
});
diff --git a/web/src/pages/instance/__tests__/TopicPage.test.tsx
b/web/src/pages/instance/__tests__/TopicPage.test.tsx
index 4adc1375..8fb070ff 100644
--- a/web/src/pages/instance/__tests__/TopicPage.test.tsx
+++ b/web/src/pages/instance/__tests__/TopicPage.test.tsx
@@ -480,6 +480,12 @@ describe('TopicPage', () => {
await user.click(await screen.findByRole('button', { name: /详情/ }));
+ expect(topicServiceMocks.getTopicConsumerPage).toHaveBeenCalledWith(
+ 'topic-01',
+ 'instance-proxy-1',
+ 1,
+ 20,
+ );
expect(await screen.findAllByText('不可用')).not.toHaveLength(0);
});
});
diff --git a/web/src/services/grafanaService.test.ts
b/web/src/services/grafanaService.test.ts
index 40e835c2..8bf35b7d 100644
--- a/web/src/services/grafanaService.test.ts
+++ b/web/src/services/grafanaService.test.ts
@@ -45,11 +45,12 @@ describe('grafanaService', () => {
await expect(blob.text()).resolves.toContain('rocketmq-overview');
});
- it('exports all dashboards as a blob in mock mode', async () => {
- const blob = await exportGrafanaDashboards();
- expect(blob).toBeInstanceOf(Blob);
- expect(blob.type).toBe('application/zip');
- await expect(blob.text()).resolves.toContain('rocketmq-overview.json');
+ it('exports all mock dashboards as a JSON download', async () => {
+ const download = await exportGrafanaDashboards();
+ expect(download.filename).toBe('rocketmq-grafana-dashboards.json');
+ expect(download.blob).toBeInstanceOf(Blob);
+ expect(download.blob.type).toBe('application/json');
+ await
expect(download.blob.text()).resolves.toContain('rocketmq-overview.json');
});
it('delegates to the api in real mode', async () => {
@@ -73,6 +74,7 @@ describe('grafanaService', () => {
const result = await exportGrafanaDashboards();
expect(exportSpy).toHaveBeenCalledTimes(1);
- expect(result.type).toBe('application/zip');
+ expect(result.filename).toBe('rocketmq-grafana-dashboards.zip');
+ expect(result.blob.type).toBe('application/zip');
});
});
diff --git a/web/src/services/grafanaService.ts
b/web/src/services/grafanaService.ts
index 1568df8a..43120def 100644
--- a/web/src/services/grafanaService.ts
+++ b/web/src/services/grafanaService.ts
@@ -38,13 +38,24 @@ export async function exportGrafanaDashboard(uid: string):
Promise<Blob> {
return metricsApi.exportGrafanaDashboard(uid);
}
-export async function exportGrafanaDashboards(): Promise<Blob> {
+export interface GrafanaDashboardDownload {
+ blob: Blob;
+ filename: string;
+}
+
+export async function exportGrafanaDashboards():
Promise<GrafanaDashboardDownload> {
if (isMockMode()) {
const bundle = mockGrafanaDashboards.map(({ uid, model }) => ({
filename: `${uid}.json`,
model,
}));
- return new Blob([JSON.stringify(bundle, null, 2)], { type:
'application/zip' });
+ return {
+ blob: new Blob([JSON.stringify(bundle, null, 2)], { type:
'application/json' }),
+ filename: 'rocketmq-grafana-dashboards.json',
+ };
}
- return metricsApi.exportGrafanaDashboards();
+ return {
+ blob: await metricsApi.exportGrafanaDashboards(),
+ filename: 'rocketmq-grafana-dashboards.zip',
+ };
}