fix(webapp): say why a report's numbers can't be trusted

Absent telemetry and an unmeasured flow were both labelled stale data, so every
snapshot-based report claimed staleness it could not have measured. Choose the
badge and caveat from the reason instead.
This commit is contained in:
Katia Bulatova
2026-08-08 08:25:25 +00:00
parent 1ee6704d53
commit 0db1cf0d23
2 changed files with 125 additions and 10 deletions
@@ -50,12 +50,35 @@ export const REPORT_LABELS = {
read: "read:",
/** The footer heading. */
nextSteps: "Next steps",
/** Shown beside the report's name when the data can't be trusted. */
staleBadge: "stale data",
staleNote:
"The telemetry behind this report is stale, so the numbers below are informational only.",
} as const;
/** The flag beside the report's name, and the caveat under its headline. */
export type LayoutTrust = { badge: string; note: string };
/**
* Why a report's numbers can't be trusted, in its own words. Stale, absent and unmeasured are three
* different states, and a snapshot with no telemetry feed must not be called stale.
*/
const TRUST_CAVEATS: Record<string, LayoutTrust> = {
telemetry_stale: {
badge: "stale data",
note: "The telemetry behind this report is stale, so the numbers below are informational only.",
},
telemetry_absent: {
badge: "no telemetry",
note: "No telemetry reached this report, so the numbers below are a point-in-time snapshot rather than a measured window.",
},
flow_unmeasured: {
badge: "unmeasured",
note: "Throughput could not be measured over this window, so the numbers below are informational only.",
},
};
const TRUST_CAVEAT_FALLBACK: LayoutTrust = {
badge: "unverified data",
note: "The data behind this report could not be verified, so the numbers below are informational only.",
};
/**
* The report's sections, top to bottom. A renderer walks this order; a new section has to be added
* here first, which is what keeps the surfaces aligned. `trust` spans two places: a flag beside the
@@ -87,13 +110,20 @@ const UNASSESSABLE_REASONS = new Set(["unknown", "flow_unmeasured"]);
const NEUTRAL_REASONS = new Set(["freshness_unknown", "flow_unmeasured"]);
/**
* `facts.trustworthy === false` means the telemetry behind the verdict is stale, so the numbers are
* informational only. Absent = trustworthy (the common case, and what pre-`facts` snapshots imply).
* `facts.trustworthy === false` means the numbers behind the verdict are informational only. Absent
* = trustworthy (the common case, and what pre-`facts` snapshots imply).
*/
export function reportIsTrustworthy(vm: { facts?: Record<string, unknown> }): boolean {
return vm.facts?.trustworthy !== false;
}
/** The caveat for an untrustworthy report, chosen by `facts.untrustworthyReason`. */
export function reportTrust(vm: { facts?: Record<string, unknown> }): LayoutTrust | undefined {
if (reportIsTrustworthy(vm)) return undefined;
const reason = vm.facts?.untrustworthyReason;
return (typeof reason === "string" ? TRUST_CAVEATS[reason] : undefined) ?? TRUST_CAVEAT_FALLBACK;
}
export function reportTone(severity: Severity, reason?: string): ReportTone {
return reason !== undefined && NEUTRAL_REASONS.has(reason) ? "neutral" : severity;
}
@@ -283,7 +313,7 @@ export type LayoutFooterEntry = {
export type ReportLayout = {
header: { name: string; meta: string };
/** Present only when the data can't be trusted. */
trust?: { badge: string; note: string };
trust?: LayoutTrust;
headline: { tone: ReportTone; glyph: string; severity: Severity; phrase: string; text?: string };
/** The finding the headline speaks for, always expanded. */
hero?: LayoutFinding;
@@ -343,14 +373,14 @@ export function buildReportLayout(vm: LayoutViewModel, messages: ReportMessages)
.filter((finding) => finding.read !== undefined && !UNASSESSABLE_REASONS.has(finding.reason))
.map((finding) => fillTokens(messages.readMessage(finding.read!), tokens));
const trust = reportTrust(vm);
return {
header: {
name: vm.title,
meta: [vm.scope, vm.period, vm.baselineLabel].filter(Boolean).join(" · "),
},
...(reportIsTrustworthy(vm)
? {}
: { trust: { badge: REPORT_LABELS.staleBadge, note: REPORT_LABELS.staleNote } }),
...(trust === undefined ? {} : { trust }),
headline: {
severity: vm.summary.severity,
tone: reportTone(vm.summary.severity, heroStatement?.reason),
+85
View File
@@ -0,0 +1,85 @@
import { describe, expect, it } from "vitest";
import {
buildReportLayout,
type LayoutViewModel,
reportTrust,
} from "~/presenters/v3/reports/report-layout";
import { reportMessages } from "~/presenters/v3/reports/report-messages";
const messages = reportMessages("health");
function viewModel(facts?: Record<string, unknown>): LayoutViewModel {
return {
title: "health",
scope: "prod",
period: "last 60m",
windowMinutes: 60,
summary: { severity: "warn", statements: [{ findingType: "flow", severity: "warn" }] },
findings: [{ type: "flow", severity: "warn", reason: "backlog_growing", metricIds: [] }],
metrics: [],
footer: [],
...(facts === undefined ? {} : { facts }),
};
}
function trustFor(untrustworthyReason?: string) {
return reportTrust({
facts: { trustworthy: false, ...(untrustworthyReason ? { untrustworthyReason } : {}) },
});
}
describe("report layout — why the numbers can't be trusted", () => {
it("calls stale telemetry stale", () => {
expect(trustFor("telemetry_stale")?.badge).toBe("stale data");
expect(trustFor("telemetry_stale")?.note).toContain("stale");
});
it("does not call a report with no telemetry stale", () => {
const trust = trustFor("telemetry_absent");
expect(trust?.badge).toBe("no telemetry");
expect(trust?.note).toContain("No telemetry");
expect(trust?.note).not.toContain("stale");
});
it("does not call an unmeasured flow stale", () => {
const trust = trustFor("flow_unmeasured");
expect(trust?.badge).toBe("unmeasured");
expect(trust?.note).not.toContain("stale");
});
it("gives the three untrustworthy states three different badges", () => {
const badges = ["telemetry_stale", "telemetry_absent", "flow_unmeasured"].map(
(reason) => trustFor(reason)?.badge
);
expect(new Set(badges).size).toBe(3);
});
it("falls back without claiming staleness when the reason is missing", () => {
expect(trustFor()).toBeDefined();
expect(trustFor()?.note).not.toContain("stale");
});
it("says nothing when the report is trustworthy", () => {
expect(reportTrust({ facts: { trustworthy: true } })).toBeUndefined();
expect(reportTrust({})).toBeUndefined();
});
it("carries the chosen caveat into the layout", () => {
const layout = buildReportLayout(
viewModel({ trustworthy: false, untrustworthyReason: "telemetry_absent" }),
messages
);
expect(layout.trust).toEqual({
badge: "no telemetry",
note: expect.stringContaining("No telemetry"),
});
});
it("leaves a trustworthy report with no caveat at all", () => {
expect(buildReportLayout(viewModel(), messages).trust).toBeUndefined();
});
});