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 889f75f5 fix(consumer): surface unavailable metrics instead of zeros
(#1649)
889f75f5 is described below
commit 889f75f5d4e086429ff60875a1e47af5ccfe3c30
Author: aias00 <[email protected]>
AuthorDate: Tue Aug 11 20:44:22 2026 +0800
fix(consumer): surface unavailable metrics instead of zeros (#1649)
---
.../studio/instance/topic/TopicConsumerVO.java | 2 ++
.../provider/apache/RocketMQMetadataProvider.java | 1 +
.../apache/RocketMQMetadataProviderTest.java | 19 +++++++++++++++++++
web/src/api/metadata.ts | 1 +
web/src/pages/instance/__tests__/TopicPage.test.tsx | 20 ++++++++++++++++++++
web/src/pages/instance/topic.tsx | 10 ++++++++--
6 files changed, 51 insertions(+), 2 deletions(-)
diff --git
a/server/src/main/java/org/apache/rocketmq/studio/instance/topic/TopicConsumerVO.java
b/server/src/main/java/org/apache/rocketmq/studio/instance/topic/TopicConsumerVO.java
index 09d5c742..1cb52faa 100644
---
a/server/src/main/java/org/apache/rocketmq/studio/instance/topic/TopicConsumerVO.java
+++
b/server/src/main/java/org/apache/rocketmq/studio/instance/topic/TopicConsumerVO.java
@@ -32,4 +32,6 @@ public class TopicConsumerVO {
private String messageModel;
private double consumeTps;
private long diffTotal;
+ @Builder.Default
+ private boolean metricsAvailable = true;
}
diff --git
a/server/src/main/java/org/apache/rocketmq/studio/provider/apache/RocketMQMetadataProvider.java
b/server/src/main/java/org/apache/rocketmq/studio/provider/apache/RocketMQMetadataProvider.java
index 8f2836f8..5ce9b517 100644
---
a/server/src/main/java/org/apache/rocketmq/studio/provider/apache/RocketMQMetadataProvider.java
+++
b/server/src/main/java/org/apache/rocketmq/studio/provider/apache/RocketMQMetadataProvider.java
@@ -329,6 +329,7 @@ public class RocketMQMetadataProvider implements
MetadataProvider {
.messageModel("CLUSTERING")
.consumeTps(0)
.diffTotal(0)
+ .metricsAvailable(false)
.build());
}
}
diff --git
a/server/src/test/java/org/apache/rocketmq/studio/provider/apache/RocketMQMetadataProviderTest.java
b/server/src/test/java/org/apache/rocketmq/studio/provider/apache/RocketMQMetadataProviderTest.java
index 3259cd90..ef521f19 100644
---
a/server/src/test/java/org/apache/rocketmq/studio/provider/apache/RocketMQMetadataProviderTest.java
+++
b/server/src/test/java/org/apache/rocketmq/studio/provider/apache/RocketMQMetadataProviderTest.java
@@ -30,11 +30,13 @@ import
org.apache.rocketmq.studio.instance.group.ConsumerGroupVO;
import org.apache.rocketmq.studio.persistence.entity.RmqGroup;
import org.apache.rocketmq.studio.persistence.mapper.RmqGroupMapper;
import org.apache.rocketmq.studio.persistence.mapper.RmqTopicMapper;
+import org.apache.rocketmq.remoting.protocol.body.GroupList;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;
+import java.util.HashSet;
import java.util.List;
import static org.assertj.core.api.Assertions.assertThat;
@@ -158,6 +160,23 @@ class RocketMQMetadataProviderTest {
.satisfies(error -> assertThat(((BusinessException)
error).getCode()).isEqualTo(502));
}
+ @Test
+ void getTopicConsumersMarksMetricsUnavailableWhenGroupStatsCannotBeRead()
throws Exception {
+ DefaultMQAdminExt admin =
org.mockito.Mockito.mock(DefaultMQAdminExt.class);
+ GroupList groupList = new GroupList();
+ groupList.setGroupList(new HashSet<>(List.of("cg-orders")));
+ when(admin.queryTopicConsumeByWho("TopicA")).thenReturn(groupList);
+ when(admin.examineConsumeStats("cg-orders", "TopicA"))
+ .thenThrow(new IllegalStateException("broker unavailable"));
+
+ List<TopicConsumerVO> consumers =
newLiveProvider(admin).getTopicConsumers(null, "TopicA");
+
+ assertThat(consumers).singleElement().satisfies(consumer -> {
+ assertThat(consumer.getGroup()).isEqualTo("cg-orders");
+ assertThat(consumer.isMetricsAvailable()).isFalse();
+ });
+ }
+
@Test
void getGroupProgressSurfacesAdminFailure() throws Exception {
DefaultMQAdminExt admin =
org.mockito.Mockito.mock(DefaultMQAdminExt.class);
diff --git a/web/src/api/metadata.ts b/web/src/api/metadata.ts
index 00f7fe0d..a780a695 100644
--- a/web/src/api/metadata.ts
+++ b/web/src/api/metadata.ts
@@ -44,6 +44,7 @@ export interface ConsumerGroupInfo {
messageModel: string;
consumeTps: number;
diffTotal: number;
+ metricsAvailable?: boolean;
}
// ─── Consumer Group (matches mock/consumers.ts) ─────────────────
diff --git a/web/src/pages/instance/__tests__/TopicPage.test.tsx
b/web/src/pages/instance/__tests__/TopicPage.test.tsx
index 9144c18f..48dbc90c 100644
--- a/web/src/pages/instance/__tests__/TopicPage.test.tsx
+++ b/web/src/pages/instance/__tests__/TopicPage.test.tsx
@@ -444,4 +444,24 @@ describe('TopicPage', () => {
expect(screen.getByRole('button', { name: /导入/ })).toBeDisabled();
expect(screen.getByRole('button', { name: /创建 Topic/ })).toBeDisabled();
});
+
+ it('renders unavailable Topic consumer metrics distinctly from zero', async
() => {
+ const user = userEvent.setup();
+ topicServiceMocks.listTopics.mockResolvedValue([buildTopics(1)[0]]);
+ topicServiceMocks.getTopicConsumers.mockResolvedValue([
+ {
+ group: 'cg-orders',
+ consumeType: 'CLUSTERING',
+ messageModel: 'CLUSTERING',
+ consumeTps: 0,
+ diffTotal: 0,
+ metricsAvailable: false,
+ },
+ ]);
+ renderWithProviders();
+
+ await user.click(await screen.findByRole('button', { name: /详情/ }));
+
+ expect(await screen.findAllByText('不可用')).not.toHaveLength(0);
+ });
});
diff --git a/web/src/pages/instance/topic.tsx b/web/src/pages/instance/topic.tsx
index 45c28a74..b7a23eeb 100644
--- a/web/src/pages/instance/topic.tsx
+++ b/web/src/pages/instance/topic.tsx
@@ -565,13 +565,19 @@ const TopicPage = () => {
title: '消费 TPS',
dataIndex: 'consumeTps',
key: 'consumeTps',
- render: (n: number) => formatNumber(n),
+ render: (n: number, record) =>
+ record.metricsAvailable === false ? <Text type="secondary">不可用</Text>
: formatNumber(n),
},
{
title: '堆积量',
dataIndex: 'diffTotal',
key: 'diffTotal',
- render: (n: number) => <Text type={n > 100 ? 'warning' :
undefined}>{formatNumber(n)}</Text>,
+ render: (n: number, record) =>
+ record.metricsAvailable === false ? (
+ <Text type="secondary">不可用</Text>
+ ) : (
+ <Text type={n > 100 ? 'warning' : undefined}>{formatNumber(n)}</Text>
+ ),
},
];