|
16 | 16 | const config = $derived(diagnostics?.config); |
17 | 17 | const metric = $derived(diagnostics?.metric); |
18 | 18 | const metricArchive = $derived(diagnostics?.metric_archive); |
| 19 | + const lastRun = $derived(diagnostics?.last_run); |
| 20 | + const lastSuccessfulRun = $derived(diagnostics?.last_successful_run); |
19 | 21 | const checks = $derived(diagnostics?.checks ?? []); |
20 | 22 | const everythingAsExpected = $derived( |
21 | 23 | diagnostics?.everything_as_expected ?? false, |
|
38 | 40 | return `${days.toLocaleString()} day${days === 1 ? "" : "s"}`; |
39 | 41 | } |
40 | 42 |
|
41 | | - // Check names are descriptive slugs, not OBP props — drop the underscores so |
42 | | - // they don't masquerade as prop names. Actual props are printed verbatim. |
43 | | - function humanize(name: string): string { |
44 | | - const s = name.replace(/_/g, " "); |
45 | | - return s.charAt(0).toUpperCase() + s.slice(1); |
| 43 | + // Each field of an archive run, paired with its prop path under the given |
| 44 | + // prefix (last_run / last_successful_run) so every value stays traceable. |
| 45 | + function runFields(run: any, prefix: string) { |
| 46 | + return [ |
| 47 | + { prop: `${prefix}.run_id`, value: run.run_id }, |
| 48 | + { prop: `${prefix}.api_instance_id`, value: run.api_instance_id }, |
| 49 | + { prop: `${prefix}.started_at`, value: formatDate(run.started_at) }, |
| 50 | + { prop: `${prefix}.ended_at`, value: formatDate(run.ended_at) }, |
| 51 | + { prop: `${prefix}.duration_ms`, value: formatCount(run.duration_ms) }, |
| 52 | + { prop: `${prefix}.rows_moved_to_archive`, value: formatCount(run.rows_moved_to_archive) }, |
| 53 | + { prop: `${prefix}.rows_deleted_from_archive`, value: formatCount(run.rows_deleted_from_archive) }, |
| 54 | + { prop: `${prefix}.success`, value: String(run.success) }, |
| 55 | + { prop: `${prefix}.remark`, value: run.remark === "" ? "—" : run.remark }, |
| 56 | + ]; |
46 | 57 | } |
47 | 58 |
|
| 59 | + // Explicit display label per check id — no string munging. An unmapped id |
| 60 | + // falls back to its literal value rather than being reworded. |
| 61 | + const checkLabels: Record<string, string> = { |
| 62 | + check_metrics_are_being_written: "Metrics are being written", |
| 63 | + check_archive_scheduler_is_enabled: "Archive scheduler is enabled", |
| 64 | + check_metric_retention_policy_is_respected: "Metric retention policy is respected", |
| 65 | + check_all_old_metrics_can_be_archived: "All old metrics can be archived", |
| 66 | + check_archive_retention_policy_is_respected: "Archive retention policy is respected", |
| 67 | + check_archive_metrics_is_fresh_enough: "Archive metrics is fresh enough", |
| 68 | + check_last_archive_run_succeeded: "Last archive run succeeded", |
| 69 | + }; |
| 70 | +
|
48 | 71 | // The props each derived integrity check is computed from, so every statement |
49 | 72 | // on the page is traceable to a real field in the API response. Keyed by the |
50 | | - // backend check.name; "+7d grace" in the messages is a hardcoded constant. |
| 73 | + // backend check id; "+7d grace" in the messages is a hardcoded constant. |
| 74 | + // check_all_old_metrics_can_be_archived has no matching prop (the blocked-row |
| 75 | + // count is computed server-side and not in the response), so it is omitted. |
51 | 76 | const checkProps: Record<string, string[]> = { |
52 | | - write_metrics_enabled: ["config.write_metrics"], |
53 | | - metrics_scheduler_enabled: ["config.enable_metrics_scheduler"], |
54 | | - metric_oldest_within_retention: [ |
| 77 | + check_metrics_are_being_written: ["config.write_metrics"], |
| 78 | + check_archive_scheduler_is_enabled: ["config.enable_metrics_scheduler"], |
| 79 | + check_metric_retention_policy_is_respected: [ |
55 | 80 | "metric.oldest_record_age_days", |
56 | 81 | "config.retain_metrics_days_effective", |
57 | 82 | ], |
58 | | - archive_oldest_within_retention: [ |
| 83 | + check_archive_retention_policy_is_respected: [ |
59 | 84 | "metric_archive.oldest_record_age_days", |
60 | 85 | "config.retain_archive_metrics_days_effective", |
61 | 86 | ], |
62 | | - archive_recently_updated: [ |
| 87 | + check_archive_metrics_is_fresh_enough: [ |
63 | 88 | "metric.oldest_record_age_days", |
64 | 89 | "metric_archive.newest_record_age_days", |
65 | 90 | "config.retain_metrics_days_effective", |
66 | 91 | ], |
| 92 | + check_last_archive_run_succeeded: [ |
| 93 | + "last_run.success", |
| 94 | + "last_run.started_at", |
| 95 | + ], |
67 | 96 | }; |
68 | 97 |
|
69 | 98 | function checkBadgeColor(status: string): string { |
|
190 | 219 | <div class="flex items-center gap-2 border-b border-gray-200 p-6 dark:border-gray-700"> |
191 | 220 | <Settings class="h-5 w-5 text-gray-500" /> |
192 | 221 | <h2 class="text-lg font-semibold text-gray-900 dark:text-gray-100"> |
193 | | - Archiving Configuration |
| 222 | + Metrics Configuration |
194 | 223 | </h2> |
195 | 224 | </div> |
196 | 225 | <div class="grid grid-cols-1 gap-px overflow-hidden rounded-b-lg bg-gray-200 dark:bg-gray-700 sm:grid-cols-2 lg:grid-cols-3"> |
|
211 | 240 | </div> |
212 | 241 | </div> |
213 | 242 |
|
| 243 | + <!-- Last Run / Last Successful Run --> |
| 244 | + {#each [{ title: "Last Run", prefix: "last_run", run: lastRun }, { title: "Last Successful Run", prefix: "last_successful_run", run: lastSuccessfulRun }] as section (section.prefix)} |
| 245 | + <div |
| 246 | + class="mb-6 rounded-lg border border-gray-200 bg-white shadow-sm dark:border-gray-700 dark:bg-gray-800" |
| 247 | + data-testid="archive-run" |
| 248 | + data-prefix={section.prefix} |
| 249 | + > |
| 250 | + <div class="border-b border-gray-200 p-6 dark:border-gray-700"> |
| 251 | + <h2 class="text-lg font-semibold text-gray-900 dark:text-gray-100"> |
| 252 | + {section.title} |
| 253 | + </h2> |
| 254 | + <code class="text-xs font-mono text-gray-500 dark:text-gray-400">{section.prefix}</code> |
| 255 | + </div> |
| 256 | + {#if section.run} |
| 257 | + <div class="grid grid-cols-1 gap-px overflow-hidden rounded-b-lg bg-gray-200 dark:bg-gray-700 sm:grid-cols-2 lg:grid-cols-3"> |
| 258 | + {#each runFields(section.run, section.prefix) as field (field.prop)} |
| 259 | + <div class="bg-white p-4 dark:bg-gray-800" data-testid="run-field" data-prop={field.prop}> |
| 260 | + <code class="text-sm font-mono text-gray-600 dark:text-gray-400">{field.prop}</code> |
| 261 | + <p class="mt-1 text-lg font-semibold break-words text-gray-900 dark:text-gray-100"> |
| 262 | + {field.value} |
| 263 | + </p> |
| 264 | + </div> |
| 265 | + {/each} |
| 266 | + </div> |
| 267 | + {:else} |
| 268 | + <p class="p-6 text-sm text-gray-600 dark:text-gray-400"> |
| 269 | + No archive run recorded. |
| 270 | + </p> |
| 271 | + {/if} |
| 272 | + </div> |
| 273 | + {/each} |
| 274 | + |
214 | 275 | <!-- Integrity Checks --> |
215 | 276 | <div |
216 | 277 | class="mb-6 rounded-lg border border-gray-200 bg-white shadow-sm dark:border-gray-700 dark:bg-gray-800" |
|
235 | 296 | <div class="flex-1"> |
236 | 297 | <div class="flex items-center gap-2"> |
237 | 298 | <span class="text-sm font-semibold text-gray-900 dark:text-gray-100"> |
238 | | - {humanize(check.name)} |
| 299 | + {checkLabels[check.name] ?? check.name} |
239 | 300 | </span> |
240 | 301 | <span class="rounded-full px-2.5 py-0.5 text-xs font-medium {checkBadgeColor(check.status)}"> |
241 | 302 | {check.status} |
242 | 303 | </span> |
243 | 304 | </div> |
| 305 | + <code class="text-xs font-mono text-gray-500 dark:text-gray-400">{check.name}</code> |
244 | 306 | <p class="mt-1 text-sm text-gray-600 dark:text-gray-400"> |
245 | 307 | {check.message} |
246 | 308 | </p> |
|
0 commit comments