unbridled-41 opened a new pull request, #4951:
URL: https://github.com/apache/rocketmq-dashboard/pull/4951

   ## Problem
   
   The consumer-group health card reports **心跳状态正常** ("heartbeat is fine") from 
data that never arrives, and the online-instance table renders a protocol tag 
with no text in it.
   
   The Apache provider builds each online instance from the broker connection, 
which carries a client id and an address only, so `protocol` and 
`lastHeartbeat` are always absent from the response. The card read that absence 
as a healthy heartbeat, and the protocol column built its translation key out 
of the missing value.
   
   ## Evidence
   
   Base commit `1ef5d860`.
   
   **Nothing populates the two fields**
   
   * `server/.../provider/apache/ConsumerConnections.java:33-43` (base) is the 
only builder of `ConsumerInstanceVO`:
     ```java
     .map(conn -> ConsumerInstanceVO.builder()
             .clientId(conn.getClientId())
             .address(conn.getClientAddr())
             .build())
     ```
     `grep -rn "setLastHeartbeat\|setProtocol(" server/src/main/java` finds no 
writer for this VO; the two `protocol(...)` hits are 
`RocketMQClientProvider.java:331,413`, which build the *clients-page* VO, not 
this one. The broker's `Connection` carries 
`clientId`/`clientAddr`/`language`/`version` — there is no heartbeat timestamp 
to send.
   * `server/.../instance/group/ConsumerInstanceVO.java:33-40` (base) declares 
both fields, so they serialise as `null`.
   
   **The health card grades that absence as healthy**
   
   * `web/src/pages/instance/consumer.tsx:2008-2013` (base):
     ```tsx
     {selectedGroupHealth.summary.onlineInstances < 0
       ? '客户端连接信息不可用'
       : selectedGroupHealth.summary.staleClientCount > 0
         ? `${selectedGroupHealth.summary.staleClientCount} 个心跳过期`
         : '心跳状态正常'}
     ```
   * `staleClientCount` can only count `STALE_HEARTBEAT` issues, and those 
require a non-null age: `consumerGroupDiagnostics.ts:135-138` 
(`heartbeatAgeSeconds` → `parseTimestamp(undefined)` → `null`), `:324` (`if 
(age !== null && age > options.staleHeartbeatSeconds)`), `:431-432` 
(`maxHeartbeatAgeSeconds` is `null`, `staleClientCount` is `0`). So the third 
branch is the only reachable one, and it asserts a verdict the page cannot know.
   
   **The protocol cell renders an empty tag**
   
   * `consumer.tsx:1181-1184` (base):
     ```tsx
     render: (protocol: string) => {
       const config = PROTOCOL_MAP[protocol] || { labelKey: protocol, color: 
'default' };
       return <Tag color={config.color}>{t(config.labelKey)}</Tag>;
     },
     ```
     `PROTOCOL_MAP` (`constants/theme.ts:66-69`) has only `Remoting`/`gRPC`, so 
a missing value falls through with `labelKey = null`, and `t` returns the key 
itself (`LangContext.tsx:38` `translations[key]?.[lang] ?? key`) — a coloured 
`<Tag>` whose content is nothing.
   
   **Why the client type hid it**: `web/src/api/metadata.ts:115-122` (base) 
declared `protocol: string` and `lastHeartbeat: string`, non-optional, so every 
consumer compiled as if the values were always there.
   
   The mock and the existing tests supply the fields by hand 
(`web/src/mock/consumers.ts:86-90`, `ConsumerPage.test.tsx:713-722`), which is 
why no test caught the gap.
   
   ## Root cause
   
   The instance table and the heartbeat judgement were written against a mock 
that provided protocol and heartbeat. Nothing marks the two fields as "not 
supplied by this provider", so the UI presents a missing value as a value and 
derives a positive verdict from it.
   
   ## Fix
   
   Front-end only — no protocol can be *invented* server-side, and no heartbeat 
timestamp exists to send:
   
   * the protocol cell renders the page's existing unavailable label when the 
value has no mapping, instead of an empty tag;
   * the health card gains a third state: clients are online but no heartbeat 
time was reported;
   * `protocol` and `lastHeartbeat` are optional in `ConsumerInstance`, 
matching what the endpoint sends, so the next consumer of either field has to 
handle its absence.
   
   The 最后心跳 column is left alone: it already degrades to `-` via 
`formatDateTime`, which is this application's marker for "no value" and does 
not assert anything.
   
   ## Scoring (AGENTS.md)
   
   * PRIORITY **72** = impact 24 (a false "heartbeat is fine" verdict plus two 
misleading cells on the only surface where clients can be inspected) + reach 14 
(every Apache instance with an online consumer group) + reproducibility 20 
(deterministic, one click, no failure needed) + maintenance value 14 (it 
records that `ConsumerInstanceVO.protocol`/`lastHeartbeat` have no writer on 
the broker path).
   * FIX_CONFIDENCE **80** — the honest fix is client-side; it is below 90 
because filling `protocol` from the connection's `language`/path is a 
defensible alternative for that half, while the heartbeat half has no source at 
all.
   
   ## Tests
   
   `web/src/pages/instance/__tests__/ConsumerPage.test.tsx` › `does not call a 
group healthy when the API reports no client heartbeat` — a group with two 
online instances and no protocol/heartbeat, i.e. exactly what the provider 
sends.
   
   Pre-fix, with the test kept and only `consumer.tsx` restored to `1ef5d860`:
   
   ```
   TestingLibraryElementError: Unable to find an element with the text: 不可用.
           (the protocol cell asserted empty)
   
   TestingLibraryElementError: Unable to find an element with the text: 
客户端未上报心跳时间.
           (with the protocol assertion removed, the health card asserted 
instead)
   ```
   
   Post-fix, from the branch:
   
   ```
   npx vitest run src/pages/instance/__tests__/ConsumerPage.test.tsx   → 37/37
   npx vitest run src/pages/instance src/utils src/services           → 40 
files, 396/396
   npx tsc --noEmit                                                   → clean
   npx eslint consumer.tsx ConsumerPage.test.tsx api/metadata.ts       → clean
   ```
   
   ## Risk
   
   Low and cosmetic in the narrow sense: the protocol column now says 不可用 
instead of showing a blank pill, and the health card says 客户端未上报心跳时间 instead of 
claiming a healthy heartbeat. No behaviour depends on `protocol` or 
`lastHeartbeat` elsewhere (`consumerGroupDiagnostics` already treats a missing 
heartbeat as "no age").
   


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: [email protected]

For queries about this service, please contact Infrastructure at:
[email protected]

Reply via email to