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>
+        ),
     },
   ];
 

Reply via email to