Skip to content

Commit 6f14c66

Browse files
Render run-wait-age on the Waterline dashboard
The operator-metrics rollout-safety contract now freezes operator_metrics.runs.waiting (integer), operator_metrics.runs.oldest_wait_started_at (ISO-8601 or null), and operator_metrics.runs.max_wait_age_ms (integer milliseconds), plus the matching waiting_runs / oldest_wait_started_at / max_wait_age_ms forward on the durable_resume_paths health check data. The signal counts running runs parked at a durable resume point — signal, update, timer, or compatibility-blocked — so operators can answer "how long has the worst-case run been waiting for its next durable resume?" from the metric alone. Render the trio as a Waiting runs tile on the Operator metrics panel with "oldest N parked (since ISO)" meta line, sibling to the existing Repair needed runs, Claim failed runs, and Compatibility blocked tiles. Gated by operatorRunWaitAvailable() / operatorRunWaitAgeAvailable() so dashboards backed by pre-contract workflow packages still render cleanly without flickering null-only meta lines. Adds V2DashboardStatsControllerTest:: testIndexExposesRunWaitAge which pins the contract against the v2 stats endpoint and self-skips when the vendored workflow package predates the contract, mirroring the existing stuck-lease, ready-due, and dispatch-overdue self-skips. Assets: public/app.js + public/mix-manifest.json rebuilt with npm run development (Laravel Mix).
1 parent 24f80a1 commit 6f14c66

4 files changed

Lines changed: 29805 additions & 29704 deletions

File tree

public/app.js

Lines changed: 29739 additions & 29703 deletions
Large diffs are not rendered by default.

public/mix-manifest.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
{
2-
"/app.js": "/app.js?id=2da3db07039ecaab3c6709cc7c78e192",
2+
"/app.js": "/app.js?id=3597e7e86873ed4d45ca663ba65b46c9",
33
"/app-dark.css": "/app-dark.css?id=14bdc4fb1d6331a94030dc13821344fb",
44
"/app.css": "/app.css?id=3d56d0e6a90f99083d111a8474a3c271",
55
"/img/favicon.png": "/img/favicon.png?id=7c006241b093796d6abfa3049df93a59",

resources/js/screens/dashboard.vue

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -342,6 +342,16 @@
342342
</template>
343343
</div>
344344
</div>
345+
<div class="wl-operator-metric" v-if="operatorRunWaitAvailable()">
346+
<div class="wl-operator-metric__label">Waiting runs</div>
347+
<div class="wl-operator-metric__value">{{ operatorMetricLabel('runs', 'waiting') }}</div>
348+
<div v-if="operatorRunWaitAgeAvailable()" class="wl-operator-metric__meta">
349+
oldest {{ operatorDurationMetricLabel('runs', 'max_wait_age_ms') }} parked
350+
<template v-if="operatorRunWaitOldestStartedAt()">
351+
(since {{ operatorRunWaitOldestStartedAt() }})
352+
</template>
353+
</div>
354+
</div>
345355
<div class="wl-operator-metric">
346356
<div class="wl-operator-metric__label">Active workers</div>
347357
<div class="wl-operator-metric__value">{{ operatorMetricLabel('workers', 'active_workers') }}</div>
@@ -1188,6 +1198,29 @@ export default {
11881198
return tasks.oldest_dispatch_overdue_since || null;
11891199
},
11901200
1201+
operatorRunWaitAvailable() {
1202+
const runs = (this.operatorMetrics && this.operatorMetrics.runs) || {};
1203+
1204+
return runs.waiting !== undefined && runs.waiting !== null;
1205+
},
1206+
1207+
operatorRunWaitAgeAvailable() {
1208+
const runs = (this.operatorMetrics && this.operatorMetrics.runs) || {};
1209+
1210+
if (runs.max_wait_age_ms === undefined || runs.max_wait_age_ms === null) {
1211+
return false;
1212+
}
1213+
1214+
return Number(runs.waiting || 0) > 0
1215+
|| Number(runs.max_wait_age_ms || 0) > 0;
1216+
},
1217+
1218+
operatorRunWaitOldestStartedAt() {
1219+
const runs = (this.operatorMetrics && this.operatorMetrics.runs) || {};
1220+
1221+
return runs.oldest_wait_started_at || null;
1222+
},
1223+
11911224
operatorSchedulesAvailable() {
11921225
const schedules = this.operatorMetrics && this.operatorMetrics.schedules;
11931226

tests/Feature/V2DashboardStatsControllerTest.php

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -822,4 +822,36 @@ public function testIndexExposesDispatchOverdueAge(): void
822822
'operator_metrics.tasks.max_dispatch_overdue_age_ms must be null or integer milliseconds',
823823
);
824824
}
825+
826+
public function testIndexExposesRunWaitAge(): void
827+
{
828+
config()->set('waterline.engine_source', 'v2');
829+
830+
$response = $this->get('/waterline/api/stats')->assertStatus(200);
831+
832+
$runs = $response->json('operator_metrics.runs');
833+
834+
$this->assertIsArray($runs);
835+
836+
if (! array_key_exists('waiting', $runs)
837+
|| ! array_key_exists('oldest_wait_started_at', $runs)
838+
|| ! array_key_exists('max_wait_age_ms', $runs)) {
839+
$this->markTestSkipped(
840+
'Vendored workflow package predates the run-wait-age rollout-safety contract '
841+
. '(operator_metrics.runs.waiting / oldest_wait_started_at / max_wait_age_ms).',
842+
);
843+
}
844+
845+
$this->assertIsInt($runs['waiting']);
846+
$this->assertTrue(
847+
$runs['oldest_wait_started_at'] === null
848+
|| is_string($runs['oldest_wait_started_at']),
849+
'operator_metrics.runs.oldest_wait_started_at must be null or ISO-8601 string',
850+
);
851+
$this->assertTrue(
852+
$runs['max_wait_age_ms'] === null
853+
|| is_int($runs['max_wait_age_ms']),
854+
'operator_metrics.runs.max_wait_age_ms must be null or integer milliseconds',
855+
);
856+
}
825857
}

0 commit comments

Comments
 (0)