Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 13 additions & 0 deletions src/reporters/github/github.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1110,6 +1110,19 @@ describe("heading wording", () => {
expect(output).not.toContain("0 failing");
});

// A run carrying a warning announced fewer checks than it printed (Site #4201).
test("counts a warning beside the rest", () => {
const ctx = makeContext({
gates: [
{ condition: "new-query-index", label: "New query with index recommendation", fired: true, conclusion: "failure", found: "2 new queries ship a high-impact index recommendation" },
{ condition: "new-query", label: "New query", fired: true, conclusion: "neutral", found: "2 new queries, none with a prior baseline" },
{ condition: "schema-drift", label: "Schema drift", fired: false, conclusion: "success", found: "No schema changes" },
],
});

expect(renderTemplate(ctx)).toContain("Query Doctor — 1 failing, 1 warning and 1 successful check");
});

test("keeps both halves when something failed", () => {
const ctx = makeContext({
gates: [
Expand Down
38 changes: 37 additions & 1 deletion src/reporters/github/github.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ const __filename = fileURLToPath(import.meta.url);
const __dirname = dirname(__filename);
const success = readFileSync(join(__dirname, "success.md.j2"), "utf-8");
import n from "nunjucks";
import { summarizeGates } from "../../gate/evaluate.ts";
import { summarizeGates, type GateSummary } from "../../gate/evaluate.ts";
import {
deriveIndexStatistics,
isQueryLong,
Expand Down Expand Up @@ -214,6 +214,39 @@ export function callSite(
return funcName ? { name: funcName, file } : { name: file!, file: undefined };
}

/**
* The sentence above the roster: what the conditions concluded.
*
* Every outcome is named. Assembled in the template, it read "N failing and M
* successful checks", which had no room for a warning: a run with 1 FAIL, 1 WARN
* and 1 PASS announced two of its three rows (Site #4201). An outcome nothing
* concluded is left out rather than counted as a zero.
*
* DUPLICATED from `heading` in Site's `apps/api/src/ci/capture/capture-comment.ts`,
* which renders this same comment for repositories on the platform's ingest path.
* Both post onto one pull request, so a change to one is a change to both.
*
* Site's version also appends "· N not run". This one cannot: the
* `summarizeGates` beside it has no `unevaluated` count. Collapsing the two
* summaries is what would let the sentence be shared rather than copied.
*/
function formatGateHeading(summary: GateSummary): string {
const counted = [
{ count: summary.failing, word: "failing" },
{ count: summary.neutral, word: "warning" },
{ count: summary.successful, word: "successful" },
].filter((outcome) => outcome.count > 0);

const last = counted.at(-1);
if (!last) return "no checks ran";

// Everything before the last outcome is a comma list. The last one carries
// the "and" and the noun, so the noun agrees with the count beside it.
const lead = counted.slice(0, -1).map(({ count, word }) => `${count} ${word}`);
const tail = `${last.count} ${last.word} check${last.count === 1 ? "" : "s"}`;
return lead.length > 0 ? `${lead.join(", ")} and ${tail}` : tail;
}

export function buildViewModel(ctx: ReportContext) {
const hasComparison = !!ctx.comparison;
const queryLinks = buildQueryLinks(ctx);
Expand All @@ -222,6 +255,7 @@ export function buildViewModel(ctx: ReportContext) {
// The roster the comment leads with. Derived from the same array the check
// annotations read, so the heading cannot contradict the check (ADR-0009).
const gateSummary = ctx.gates ? summarizeGates(ctx.gates) : undefined;
const gateHeading = gateSummary ? formatGateHeading(gateSummary) : undefined;
// Failing first: the reader's question is what is blocking them, and the
// roster otherwise reads in catalogue order regardless of what happened.
const RANK = { failure: 0, neutral: 1, success: 2 } as const;
Expand Down Expand Up @@ -251,6 +285,7 @@ export function buildViewModel(ctx: ReportContext) {
schemaChangeLabel,
modeledTablesNotice,
gateSummary,
gateHeading,
gateIconBase,
brandMark,
gates,
Expand Down Expand Up @@ -318,6 +353,7 @@ export function buildViewModel(ctx: ReportContext) {
schemaChangeLabel,
modeledTablesNotice,
gateSummary,
gateHeading,
gateIconBase,
brandMark,
gates,
Expand Down
2 changes: 1 addition & 1 deletion src/reporters/github/success.md.j2
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
{% if gateSummary %}
### <picture><img src="{{ brandMark }}" width="18" height="20" align="absmiddle" alt=""></picture> Query Doctor — {% if gateSummary.failing > 0 %}{{ gateSummary.failing }} failing and {% endif %}{{ gateSummary.successful }} successful check{{ "" if gateSummary.successful == 1 else "s" }}
### <picture><img src="{{ brandMark }}" width="18" height="20" align="absmiddle" alt=""></picture> Query Doctor — {{ gateHeading }}

{% for g in gates %}
<picture><img src="{{ gateIconBase }}/{{ "fail" if g.conclusion == "failure" else ("warn" if g.conclusion == "neutral" else "pass") }}.svg" width="14" height="16" align="absmiddle" alt=""></picture> &nbsp;**{{ g.label }}** — {{ g.found }}
Expand Down
Loading