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 32345ec18 fix(web): render a placeholder for an unknown instance 
protocol (#5729)
32345ec18 is described below

commit 32345ec18e66e05d35c3b9a99e8768b1b229c712
Author: 0 <[email protected]>
AuthorDate: Mon Oct 19 19:53:54 2026 +0800

    fix(web): render a placeholder for an unknown instance protocol (#5729)
    
    Verified against `rocketmq-studio` at fbdc5f22b before merging: the defect 
is still present on the
    current trunk and the change merges cleanly.
    
    Maintainer follow-ups on top of the contributor commit:
    - test(consumer): align the unknown-protocol placeholder with the page 
convention
    
    Each follow-up commit message records the review finding it addresses.
    
    Locally verified: targeted suites green on the integration branch; a full 
backend and frontend
    build plus test pass runs on the trunk once this batch lands.
---
 web/src/api/metadata.ts                            | 10 ++++---
 .../pages/instance/__tests__/ConsumerPage.test.tsx | 32 ++++++++++++++++++++++
 web/src/pages/instance/consumer.tsx                |  8 +++++-
 web/src/services/consumerService.test.ts           |  6 ++--
 web/src/services/consumerService.ts                |  4 +--
 5 files changed, 50 insertions(+), 10 deletions(-)

diff --git a/web/src/api/metadata.ts b/web/src/api/metadata.ts
index b11ca24ec..92b1d9309 100644
--- a/web/src/api/metadata.ts
+++ b/web/src/api/metadata.ts
@@ -114,11 +114,13 @@ export interface ConsumerGroup {
 
 export interface ConsumerInstance {
   clientId: string;
-  protocol: string;
+  /** Absent when the backend cannot tell (Apache connections carry no 
protocol marker). */
+  protocol?: string;
   address: string;
-  subscribedTopics: string[];
-  lastHeartbeat: string;
-  topicLag: Record<string, number>;
+  /** Populated only when the provider reports per-topic lag for the instance. 
*/
+  subscribedTopics?: string[];
+  lastHeartbeat?: string;
+  topicLag?: Record<string, number>;
 }
 
 export interface ConsumerThreadStack {
diff --git a/web/src/pages/instance/__tests__/ConsumerPage.test.tsx 
b/web/src/pages/instance/__tests__/ConsumerPage.test.tsx
index f4ca5db97..2c8c51b2b 100644
--- a/web/src/pages/instance/__tests__/ConsumerPage.test.tsx
+++ b/web/src/pages/instance/__tests__/ConsumerPage.test.tsx
@@ -1971,6 +1971,38 @@ describe('Consumer page', () => {
     expect(await within(row).findByText('42')).toBeInTheDocument();
   });
 
+  it('renders a placeholder for an online instance whose protocol is unknown', 
async () => {
+    // The Apache provider derives online instances from a broker connection 
set, which
+    // carries no protocol marker — the API legitimately returns the field 
absent.
+    vi.mocked(consumerService.listConsumerGroupPage).mockResolvedValue(
+      groupPage([
+        {
+          ...group,
+          instances: [{ clientId: 'client-no-proto', address: '10.0.0.9:1234' 
}],
+        },
+      ]),
+    );
+    const user = userEvent.setup({ pointerEventsCheck: 0 });
+    renderWithProviders(<ConsumerPage />);
+
+    await user.click(await screen.findByRole('button', { name: /详情/ }));
+    const dialog = await screen.findByRole('dialog', { name: /remote-cg/ });
+    const instanceRow = await within(dialog).findByText('client-no-proto');
+    const row = instanceRow.closest('tr') as HTMLElement;
+
+    // Locate the protocol cell through its header instead of a hardcoded 
column index, so
+    // the assertion keeps working when the instance columns are reordered.
+    const headers = Array.from(row.closest('table')!.querySelectorAll('thead 
th')).map((th) =>
+      th.textContent?.trim(),
+    );
+    const protocolIndex = headers.indexOf('协议');
+    expect(protocolIndex).toBeGreaterThan(-1);
+
+    const cells = within(row).getAllByRole('cell');
+    expect(cells[protocolIndex].textContent?.trim()).toBe('-');
+    expect(cells[protocolIndex].querySelector('.ant-tag')).toBeNull();
+  });
+
   it('edits retry settings from the detail modal settings tab', async () => {
     vi.mocked(consumerService.getConsumerGroupSettings).mockResolvedValue({
       groupName: 'remote-cg',
diff --git a/web/src/pages/instance/consumer.tsx 
b/web/src/pages/instance/consumer.tsx
index 572e25c3b..0c0170c67 100644
--- a/web/src/pages/instance/consumer.tsx
+++ b/web/src/pages/instance/consumer.tsx
@@ -1229,7 +1229,13 @@ const ConsumerPageContent = ({
       dataIndex: 'protocol',
       key: 'protocol',
       width: 80,
-      render: (protocol: string) => {
+      render: (protocol?: string) => {
+        // The Apache provider cannot tell the protocol from a broker 
connection set,
+        // so the field is legitimately absent — render a placeholder, not an 
empty tag.
+        // Plain '-' matches the other optional columns of this page.
+        if (!protocol) {
+          return '-';
+        }
         const config = PROTOCOL_MAP[protocol] || { labelKey: protocol, color: 
'default' };
         return <Tag color={config.color}>{t(config.labelKey)}</Tag>;
       },
diff --git a/web/src/services/consumerService.test.ts 
b/web/src/services/consumerService.test.ts
index e42efe3a0..ae9eff14c 100644
--- a/web/src/services/consumerService.test.ts
+++ b/web/src/services/consumerService.test.ts
@@ -214,19 +214,19 @@ describe('consumer service mock data', () => {
 
     first[0].name = 'mutated-group';
     first[0].subscribedTopics.push('mutated-topic');
-    first[0].instances[0].topicLag['order-create'] = 999999;
+    first[0].instances[0].topicLag!['order-create'] = 999999;
 
     const second = await listConsumerGroups({ search: 'cg-order-notify' });
     expect(second[0].name).toBe('cg-order-notify');
     expect(second[0].subscribedTopics).not.toContain('mutated-topic');
-    expect(second[0].instances[0].topicLag['order-create']).toBe(180);
+    expect(second[0].instances[0].topicLag!['order-create']).toBe(180);
     expect(second[0]).not.toBe(first[0]);
     expect(second[0].instances[0]).not.toBe(first[0].instances[0]);
   });
 
   it('returns copied consumer group details', async () => {
     const first = await getConsumerGroup('cg-order-notify');
-    first.instances[0].subscribedTopics.push('mutated-topic');
+    first.instances[0].subscribedTopics!.push('mutated-topic');
 
     const second = await getConsumerGroup('cg-order-notify');
     
expect(second.instances[0].subscribedTopics).not.toContain('mutated-topic');
diff --git a/web/src/services/consumerService.ts 
b/web/src/services/consumerService.ts
index 9663345fd..53f57abdb 100644
--- a/web/src/services/consumerService.ts
+++ b/web/src/services/consumerService.ts
@@ -57,8 +57,8 @@ function copyConsumerInstance(
 ): ConsumerGroup['instances'][number] {
   return {
     ...instance,
-    subscribedTopics: [...instance.subscribedTopics],
-    topicLag: { ...instance.topicLag },
+    ...(instance.subscribedTopics ? { subscribedTopics: 
[...instance.subscribedTopics] } : {}),
+    ...(instance.topicLag ? { topicLag: { ...instance.topicLag } } : {}),
   };
 }
 

Reply via email to