Skip to content
Original file line number Diff line number Diff line change
Expand Up @@ -328,8 +328,8 @@ describe("decodeStatusesJson", () => {
);

expect(decoded.items.map((check) => [check.name, check.status])).toEqual([
["build / Pipeline", "success"],
["deploy / Pipeline", "failure"],
["build / Pipeline", "success"],
]);
});
});
Expand Down
2 changes: 1 addition & 1 deletion apps/server/src/pullRequest/gitHubPullRequestJson.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -234,8 +234,8 @@ describe("pull request detail decoding", () => {
it("maps check-run status and commit-status state onto one vocabulary", () => {
const detail = expectSuccess(decodePullRequestDetailJson(detailJson));
expect(detail.checks.map((check) => [check.name, check.status])).toEqual([
["build", "pending"],
["test", "failure"],
["build", "pending"],
["ci/legacy", "success"],
]);
});
Expand Down
22 changes: 20 additions & 2 deletions apps/server/src/pullRequest/pullRequestChecks.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,24 @@ describe("dedupeChecks", () => {
]);
});

it("puts failed and cancelled checks first without reshuffling either group", () => {
const checks = dedupeChecks([
entry("lint", "success"),
entry("unit", "failure"),
entry("types", "pending"),
entry("integration", "cancelled"),
entry("format", "success"),
]);

expect(checks.map((check) => [check.name, check.status])).toEqual([
["unit", "failure"],
["integration", "cancelled"],
["lint", "success"],
["types", "pending"],
["format", "success"],
]);
});

it("loses a run that never said when it happened to one that did, whichever came first", () => {
const undated = dedupeChecks([
entry("build", "success", { at: "2026-08-11T16:00:00Z" }),
Expand All @@ -76,8 +94,8 @@ describe("dedupeChecks", () => {
]);

expect(checks.map((check) => [check.name, check.status])).toEqual([
["CI / build", "success"],
["Release / build", "failure"],
["CI / build", "success"],
]);
});

Expand All @@ -88,6 +106,6 @@ describe("dedupeChecks", () => {
entry("build", "failure", { workflowName: "CI", at: "2026-08-11T16:00:00Z" }),
]);

expect(checks.map((check) => check.name)).toEqual(["build", "CI / build"]);
expect(checks.map((check) => check.name)).toEqual(["CI / build", "build"]);
});
});
14 changes: 11 additions & 3 deletions apps/server/src/pullRequest/pullRequestChecks.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,9 @@ function isAtLeastAsNew(candidate: string | null, kept: string | null): boolean
* one before it, and a run that never said when it happened loses to one that did. A tie goes to
* whichever came last, because a host lists a re-run after the run it repeats.
*
* The order is the host's own, held at the place each check first appeared, so a re-run landing
* mid-read replaces a row where it stands instead of reshuffling the list under the reader.
* Failed and cancelled checks come first so the reason for a failing rollup is immediately visible.
* Within that group and the remaining checks, the order is the host's own, held at the place each
* check first appeared. A re-run landing mid-read replaces a row where it stands.
*
* Two checks that survive under the same name are then genuinely different ones, since they came
* from different workflows — each is shown as `workflow / name`, the way GitHub writes it itself.
Expand All @@ -46,10 +47,17 @@ export function dedupeChecks(
for (const entry of survivors) {
countsByName.set(entry.check.name, (countsByName.get(entry.check.name) ?? 0) + 1);
}
return survivors.map((entry) => {
const checks = survivors.map((entry) => {
const workflowName = entry.workflowName ?? "";
return workflowName.length > 0 && (countsByName.get(entry.check.name) ?? 0) > 1
? { ...entry.check, name: `${workflowName} / ${entry.check.name}` }
: entry.check;
});
const failing = checks.filter(
(check) => check.status === "failure" || check.status === "cancelled",
);
return [
...failing,
...checks.filter((check) => check.status !== "failure" && check.status !== "cancelled"),
];
}
Loading