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]