Skip to content

Commit d61e522

Browse files
Render unhealthy-age rollup on the Waterline dashboard
Surfaces `operator_metrics.tasks.oldest_unhealthy_at` and `operator_metrics.tasks.max_unhealthy_age_ms` on the Unhealthy tasks card so operators can read the worst-case duplicate-risk task age (the earliest of `oldest_dispatch_failed_at`, `oldest_claim_failed_at`, `oldest_dispatch_overdue_since`, `oldest_lease_expired_at`) from a single meta line above the existing per-path detail rows instead of taking a max over four separate age fields themselves. `dashboard.vue` adds `operatorUnhealthyAgeAvailable()` / `operatorUnhealthyOldestAt()` helpers and a v-if-guarded summary line: worst <duration> unhealthy (since <ISO>) The line degrades gracefully when the snapshot predates the rollout- safety contract: the helper returns false when `max_unhealthy_age_ms` is missing or null and the existing per-path age detail rows still surface individual lease, dispatch-overdue, and dispatch-failed ages. `testIndexExposesUnhealthyAgeRollup` pins the rollup keys on the Waterline stats payload so a downstream regression that drops the keys trips the test instead of silently disappearing from the dashboard. The test self-skips when the vendored workflow package predates the contract, mirroring `testIndexExposesBackendSeverityRollup`.
1 parent 1bea749 commit d61e522

2 files changed

Lines changed: 54 additions & 0 deletions

File tree

resources/js/screens/dashboard.vue

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -314,6 +314,12 @@
314314
{{ operatorMetricLabel('tasks', 'dispatch_overdue') }} dispatch overdue,
315315
{{ operatorMetricLabel('tasks', 'lease_expired') }} lease expired
316316
</div>
317+
<div v-if="operatorUnhealthyAgeAvailable()" class="wl-operator-metric__meta">
318+
worst {{ operatorDurationMetricLabel('tasks', 'max_unhealthy_age_ms') }} unhealthy
319+
<template v-if="operatorUnhealthyOldestAt()">
320+
(since {{ operatorUnhealthyOldestAt() }})
321+
</template>
322+
</div>
317323
<div v-if="operatorStuckLeaseAgeAvailable()" class="wl-operator-metric__meta">
318324
oldest lease {{ operatorDurationMetricLabel('tasks', 'max_lease_expired_age_ms') }} expired
319325
<template v-if="operatorStuckLeaseOldestExpiredAt()">
@@ -1351,6 +1357,24 @@ export default {
13511357
return tasks.oldest_dispatch_failed_at || null;
13521358
},
13531359
1360+
operatorUnhealthyAgeAvailable() {
1361+
const tasks = (this.operatorMetrics && this.operatorMetrics.tasks) || {};
1362+
1363+
if (tasks.max_unhealthy_age_ms === undefined
1364+
|| tasks.max_unhealthy_age_ms === null) {
1365+
return false;
1366+
}
1367+
1368+
return Number(tasks.unhealthy || 0) > 0
1369+
|| Number(tasks.max_unhealthy_age_ms || 0) > 0;
1370+
},
1371+
1372+
operatorUnhealthyOldestAt() {
1373+
const tasks = (this.operatorMetrics && this.operatorMetrics.tasks) || {};
1374+
1375+
return tasks.oldest_unhealthy_at || null;
1376+
},
1377+
13541378
operatorSchedulesAvailable() {
13551379
const schedules = this.operatorMetrics && this.operatorMetrics.schedules;
13561380

tests/Feature/V2DashboardStatsControllerTest.php

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -959,6 +959,36 @@ public function testIndexExposesMatchingRole(): void
959959
);
960960
}
961961

962+
public function testIndexExposesUnhealthyAgeRollup(): void
963+
{
964+
config()->set('waterline.engine_source', 'v2');
965+
966+
$response = $this->get('/waterline/api/stats')->assertStatus(200);
967+
968+
$tasks = $response->json('operator_metrics.tasks');
969+
970+
$this->assertIsArray($tasks);
971+
972+
if (! array_key_exists('oldest_unhealthy_at', $tasks)
973+
|| ! array_key_exists('max_unhealthy_age_ms', $tasks)) {
974+
$this->markTestSkipped(
975+
'Vendored workflow package predates the unhealthy-age rollup '
976+
. '(operator_metrics.tasks.{oldest_unhealthy_at,max_unhealthy_age_ms}).',
977+
);
978+
}
979+
980+
$this->assertTrue(
981+
$tasks['oldest_unhealthy_at'] === null
982+
|| is_string($tasks['oldest_unhealthy_at']),
983+
'operator_metrics.tasks.oldest_unhealthy_at must be null or ISO-8601 string',
984+
);
985+
$this->assertTrue(
986+
$tasks['max_unhealthy_age_ms'] === null
987+
|| is_int($tasks['max_unhealthy_age_ms']),
988+
'operator_metrics.tasks.max_unhealthy_age_ms must be null or integer milliseconds',
989+
);
990+
}
991+
962992
public function testIndexExposesBackendSeverityRollup(): void
963993
{
964994
config()->set('waterline.engine_source', 'v2');

0 commit comments

Comments
 (0)