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 971a4d17d fix(consumer): keep a failed progress read out of the empty 
and healthy states (#4604)
971a4d17d is described below

commit 971a4d17d3973efba0be96d25bbcd5d7f121185f
Author: 烤化の初雪 <[email protected]>
AuthorDate: Mon Sep 21 21:04:56 2026 +0800

    fix(consumer): keep a failed progress read out of the empty and healthy 
states (#4604)
    
    `loadProgress` in `web/src/pages/instance/consumer.tsx` toasted on failure 
but never recorded it, and the silent 2s refresh did not even toast. The empty 
array it left in `progressByGroup` then fed two renderers: the progress table, 
whose `locale.emptyText` states that the group is offline and shows no queue 
progress, with every total at zero; and `analyzeConsumerGroupHealth`, which 
with no queue lags produces no progress issues and reduces to a healthy verdict 
with no deductions. One [...]
    
    A new `progressErrorByGroup` map, symmetric with the existing 
`subscriptionErrorByGroup`, is set on failure and cleared on success. The 
progress table's empty text becomes "queue progress unavailable" and gains a 
warning Alert, and the diagnostics tab warns that its verdict was computed 
without queue progress. Health scoring itself is unchanged, which is how a 
failed subscription read is treated today; downgrading the verdict would mean 
adding an issue code to `consumerGroupDiagnostics`.
    
    Maintainer edits on top of the contribution: merged after #4546 added a 
request-ownership guard to `loadProgress`. The conflict was resolved so the new 
`progressErrorByGroup` flag is written only by the request that still owns the 
diagnostic context, and a stale failed read can no longer mark a group as 
failed after a newer read succeeded.
    
    Fixes #4600
---
 .../pages/instance/__tests__/ConsumerPage.test.tsx | 39 ++++++++++++++++++++++
 web/src/pages/instance/consumer.tsx                | 39 ++++++++++++++++++++--
 2 files changed, 75 insertions(+), 3 deletions(-)

diff --git a/web/src/pages/instance/__tests__/ConsumerPage.test.tsx 
b/web/src/pages/instance/__tests__/ConsumerPage.test.tsx
index 7ffd8d61d..f77e4477c 100644
--- a/web/src/pages/instance/__tests__/ConsumerPage.test.tsx
+++ b/web/src/pages/instance/__tests__/ConsumerPage.test.tsx
@@ -662,6 +662,45 @@ describe('Consumer page', () => {
     await waitFor(() => 
expect(screen.getAllByText('remote-topic').length).toBeGreaterThan(0));
   });
 
+  it('does not report a failed progress load as an offline consumer group', 
async () => {
+    vi.mocked(consumerService.getConsumerSubscriptions).mockResolvedValue([
+      {
+        topic: 'remote-topic',
+        expression: '*',
+        type: 'TAG',
+        filterMode: 'TAG',
+        consistency: 'consistent',
+      },
+    ]);
+    vi.mocked(consumerService.getConsumerProgress).mockRejectedValue(
+      new Error('broker unreachable'),
+    );
+    const user = userEvent.setup();
+    renderWithProviders(<ConsumerPage />);
+
+    await user.click(await screen.findByRole('button', { name: /详情/ }));
+    await waitFor(() =>
+      
expect(consumerService.getConsumerProgress).toHaveBeenCalledWith('remote-cg', 
'instance-1'),
+    );
+
+    // The empty-state text claims the group is offline; the read simply 
failed.
+    await user.click(await screen.findByRole('tab', { name: /消费进度/ }));
+    const progressPanel = await screen.findByRole('tabpanel', { name: /消费进度/ 
});
+    await waitFor(() =>
+      expect(
+        within(progressPanel).queryByText('消费组不在线,暂无队列进度数据'),
+      ).not.toBeInTheDocument(),
+    );
+    expect(within(progressPanel).getByText('队列进度暂不可用')).toBeInTheDocument();
+    expect(
+      within(progressPanel).getByText('消费进度加载失败,无法判断消费组是否在线'),
+    ).toBeInTheDocument();
+
+    await user.click(await screen.findByRole('tab', { name: /健康诊断/ }));
+    const panel = await screen.findByRole('tabpanel', { name: /健康诊断/ });
+    await waitFor(() => 
expect(within(panel).queryByText(/消费进度加载失败/)).toBeInTheDocument());
+  });
+
   it('shows group health diagnostics from subscriptions, progress and 
clients', async () => {
     const riskyGroup: ConsumerGroup = {
       ...group,
diff --git a/web/src/pages/instance/consumer.tsx 
b/web/src/pages/instance/consumer.tsx
index 2927d3c01..02d1ade30 100644
--- a/web/src/pages/instance/consumer.tsx
+++ b/web/src/pages/instance/consumer.tsx
@@ -304,6 +304,7 @@ const ConsumerPageContent = ({
   );
   const [showOnlyInconsistent, setShowOnlyInconsistent] = useState(false);
   const [progressByGroup, setProgressByGroup] = useState<Record<string, 
QueueProgress[]>>({});
+  const [progressErrorByGroup, setProgressErrorByGroup] = 
useState<Record<string, boolean>>({});
   const [stackModalOpen, setStackModalOpen] = useState(false);
   const [stackLoading, setStackLoading] = useState(false);
   const [selectedStack, setSelectedStack] = useState<ConsumerStackTrace | 
null>(null);
@@ -445,10 +446,16 @@ const ConsumerPageContent = ({
         const progress = await getConsumerProgress(groupName, 
selectedInstanceId || undefined);
         if (progressRequestIdRef.current[cacheKey] === requestId) {
           setProgressByGroup((prev) => ({ ...prev, [cacheKey]: progress }));
+          setProgressErrorByGroup((prev) => ({ ...prev, [cacheKey]: false }));
         }
       } catch {
-        if (progressRequestIdRef.current[cacheKey] === requestId && !silent) {
-          message.error(t('consumer.fetchProgressFailed', { name: groupName 
}));
+        // A failed read is not an empty result: the progress tab and the 
health
+        // diagnosis must not present it as "the group is offline". A stale 
request
+        // must not write the flag either, or it could mark a group as failed 
after
+        // a newer read already succeeded.
+        if (progressRequestIdRef.current[cacheKey] === requestId) {
+          setProgressErrorByGroup((prev) => ({ ...prev, [cacheKey]: true }));
+          if (!silent) message.error(t('consumer.fetchProgressFailed', { name: 
groupName }));
         }
       }
     },
@@ -650,6 +657,7 @@ const ConsumerPageContent = ({
     () => (selectedGroupName ? (progressByGroup[selectedDiagnosticKey] ?? []) 
: []),
     [progressByGroup, selectedDiagnosticKey, selectedGroupName],
   );
+  const selectedProgressFailed = 
Boolean(progressErrorByGroup[selectedDiagnosticKey]);
   const progressTopicOptions = useMemo(
     () => Array.from(new Set(selectedProgress.map((q) => 
q.topic).filter(Boolean))).sort(),
     [selectedProgress],
@@ -679,6 +687,9 @@ const ConsumerPageContent = ({
         : null,
     [selectedGroup, selectedProgress, selectedSubscriptions],
   );
+  // A failed progress read leaves the queues unknown; the diagnosis stays 
useful
+  // for the loaded data but must never read as "everything is healthy".
+  const selectedGroupHealthIsPartial = selectedProgressFailed && 
selectedSubscriptions.length > 0;
 
   const handlePreviewResetOffset = async () => {
     if (!resetGroup || !resetTopic) {
@@ -1917,6 +1928,14 @@ const ConsumerPageContent = ({
                       />
                     )}
 
+                    {selectedGroupHealthIsPartial && (
+                      <Alert
+                        type="warning"
+                        showIcon
+                        message="消费进度加载失败,诊断未包含队列进度。"
+                      />
+                    )}
+
                     <Row gutter={16}>
                       <Col span={6}>
                         <Card size="small" style={{ borderRadius: 8 }}>
@@ -2039,6 +2058,15 @@ const ConsumerPageContent = ({
                 ),
                 children: (
                   <div>
+                    {selectedProgressFailed && (
+                      <Alert
+                        type="warning"
+                        showIcon
+                        style={{ marginBottom: 12 }}
+                        message="消费进度加载失败,无法判断消费组是否在线"
+                        description="队列进度与堆积统计暂不可用,请稍后重试。"
+                      />
+                    )}
                     {progressTopicOptions.length > 0 && (
                       <Flex align="center" gap={8} style={{ marginBottom: 12 
}}>
                         <Text type="secondary">Topic 筛选:</Text>
@@ -2106,7 +2134,12 @@ const ConsumerPageContent = ({
                       size="small"
                       tableLayout="fixed"
                       scroll={{ x: tableScrollX(queueColumns), y: 380 }}
-                      locale={{ emptyText: '消费组不在线,暂无队列进度数据' }}
+                      locale={{
+                        emptyText: selectedProgressFailed
+                          ? // A failed read must not assert that the group is 
offline.
+                            '队列进度暂不可用'
+                          : '消费组不在线,暂无队列进度数据',
+                      }}
                     />
                   </div>
                 ),

Reply via email to