Skip to content
Merged
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
4 changes: 4 additions & 0 deletions src/reporters/github/__snapshots__/github.test.ts.snap
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ exports[`CI-signal metadata parity (analyzer#141) > linked repo: renders rollup
"### Query Doctor Analysis

28 queries analyzed

2 regressed · 1 improved · 3 new · 0 removed

#### This PR improves queries
Expand All @@ -17,6 +18,7 @@ exports[`CI-signal metadata parity (analyzer#141) > linked repo: renders rollup




<sub>Using assumed statistics (10000 rows/table). For better results, <a href="https://docs.querydoctor.com/reference/statistics">sync production stats</a>.</sub>

<sub>More detail → get_ci_run({ runId: "9f3a1c20" }) · <a href="https://app.querydoctor.com/alice/proj/ci/9f3a1c20">view run</a> · <a href="https://docs.querydoctor.com">docs</a></sub>
Expand All @@ -27,6 +29,7 @@ exports[`CI-signal metadata parity (analyzer#141) > unlinked repo: rollup + foot
"### Query Doctor Analysis

28 queries analyzed

2 regressed · 1 improved · 3 new · 0 removed

#### This PR improves queries
Expand All @@ -40,6 +43,7 @@ exports[`CI-signal metadata parity (analyzer#141) > unlinked repo: rollup + foot




<sub>Using assumed statistics (10000 rows/table). For better results, <a href="https://docs.querydoctor.com/reference/statistics">sync production stats</a>.</sub>

<sub>More detail → get_ci_run({ runId: "9f3a1c20" }) · <a href="https://docs.querydoctor.com">docs</a></sub>
Expand Down
63 changes: 63 additions & 0 deletions src/reporters/github/github.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -184,6 +184,69 @@ describe("buildViewModel", () => {
expect(vm.newQueryCount).toBe(1);
});

test("new query without a recommendation is still listed (Site#3287 follow-up)", () => {
const ctx = makeContext({
comparison: makeComparison({
newQueries: [
{
hash: "new-covered",
query: 'SELECT "id" FROM "matches"',
formattedQuery: 'SELECT "id" FROM "matches"',
nudges: [], tags: [], tableReferences: [],
optimization: { state: "no_improvement_found", cost: 42, indexesUsed: ["matches_pkey"] },
},
],
}),
recommendations: [],
});
const vm = buildViewModel(ctx);
expect(vm.displayNewQueries).toHaveLength(1);
expect(vm.displayNewQueries[0].queryPreview).toBe('SELECT "id" FROM "matches"');
expect(vm.displayNewQueries[0].costLabel).toBe("cost 42");
});

test("a new query with a recommendation is not double-listed in displayNewQueries", () => {
const ctx = makeContext({
comparison: makeComparison({
newQueries: [
{
hash: "new-with-rec",
query: "SELECT 1",
formattedQuery: "SELECT 1",
nudges: [], tags: [], tableReferences: [],
optimization: { state: "no_improvement_found", cost: 10, indexesUsed: [] },
},
],
}),
recommendations: [makeRecommendation({ fingerprint: "new-with-rec" })],
});
const vm = buildViewModel(ctx);
expect(vm.displayRecommendations.map((r) => r.fingerprint)).toContain(
"new-with-rec",
);
expect(vm.displayNewQueries).toHaveLength(0);
});

test("template lists a new query that has no index suggestion", () => {
const ctx = makeContext({
comparison: makeComparison({
newQueries: [
{
hash: "new-covered",
query: 'SELECT "id" FROM "matches"',
formattedQuery: 'SELECT "id" FROM "matches"',
nudges: [], tags: [], tableReferences: [],
optimization: { state: "no_improvement_found", cost: 42, indexesUsed: ["matches_pkey"] },
},
],
}),
});
const output = renderTemplate(ctx);
expect(output).toContain("This PR introduces new queries");
expect(output).toContain('SELECT "id" FROM "matches"');
expect(output).toContain("cost 42 · no index suggestion");
});

test("regressions surface in displayRegressed", () => {
const ctx = makeContext({
comparison: makeComparison({
Expand Down
39 changes: 38 additions & 1 deletion src/reporters/github/github.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ import {
Reporter,
} from "../reporter.ts";

import type { ImprovedQuery, RegressedQuery } from "../site-api.ts";
import type { CiQueryPayload, ImprovedQuery, RegressedQuery } from "../site-api.ts";

n.configure({ autoescape: false, trimBlocks: true, lstripBlocks: true });

Expand All @@ -39,6 +39,13 @@ interface DisplayImprovement extends ImprovedQuery {
indexesChanged: boolean;
}

interface DisplayNewQuery {
hash: string;
queryPreview: string;
/** Pre-rendered "cost N" label, or "" when the query has no extractable cost. */
costLabel: string;
}

export function formatCost(cost: number): string {
return Math.round(cost).toLocaleString("en-US");
}
Expand Down Expand Up @@ -90,6 +97,23 @@ function addImprovementPreviews(
}));
}

function newQueryCost(q: CiQueryPayload): number | null {
if (q.optimization.state === "improvements_available") return q.optimization.cost;
if (q.optimization.state === "no_improvement_found") return q.optimization.cost;
return null;
}

function addNewQueryPreviews(newQueries: CiQueryPayload[]): DisplayNewQuery[] {
return newQueries.map((q) => {
const cost = newQueryCost(q);
return {
hash: q.hash,
queryPreview: queryPreview(q.formattedQuery),
costLabel: cost === null ? "" : `cost ${formatCost(cost)}`,
};
});
}

/** Per-query detail links keyed by query hash, sourced from the run metadata. */
function buildQueryLinks(ctx: ReportContext): Record<string, string> {
const links: Record<string, string> = {};
Expand All @@ -109,6 +133,7 @@ export function buildViewModel(ctx: ReportContext) {
displayRegressed: [] as DisplayRegression[],
displayAcknowledgedRegressed: [] as DisplayRegression[],
displayImproved: [] as DisplayImprovement[],
displayNewQueries: [] as DisplayNewQuery[],
preExistingRecommendations: [] as DisplayRecommendation[],
newQueryCount: 0,
hasComparison: false,
Expand All @@ -119,6 +144,9 @@ export function buildViewModel(ctx: ReportContext) {
const newQueryHashes = new Set(
ctx.comparison!.newQueries.map((q) => q.hash),
);
const recommendedHashes = new Set(
ctx.recommendations.map((r) => r.fingerprint),
);

const displayRecommendations = addPreviews(
ctx.recommendations.filter((r) => newQueryHashes.has(r.fingerprint)),
Expand All @@ -127,6 +155,14 @@ export function buildViewModel(ctx: ReportContext) {
ctx.recommendations.filter((r) => !newQueryHashes.has(r.fingerprint)),
);

// New queries that carry no index recommendation are otherwise invisible:
// counted in the "N new" tally but listed nowhere (Site#3287 follow-up). The
// ones that DO have a recommendation already render under "introduces queries
// with recommendations", so exclude them here to avoid double-listing.
const displayNewQueries = addNewQueryPreviews(
ctx.comparison!.newQueries.filter((q) => !recommendedHashes.has(q.hash)),
);

const displayRegressed = addRegressionPreviews(ctx.comparison!.regressed);
const displayAcknowledgedRegressed = addRegressionPreviews(ctx.comparison!.acknowledgedRegressed);
const displayImproved = addImprovementPreviews(ctx.comparison!.improved);
Expand All @@ -136,6 +172,7 @@ export function buildViewModel(ctx: ReportContext) {
displayRegressed,
displayAcknowledgedRegressed,
displayImproved,
displayNewQueries,
preExistingRecommendations,
newQueryCount: ctx.comparison!.newQueries.length,
hasComparison: true,
Expand Down
10 changes: 9 additions & 1 deletion src/reporters/github/success.md.j2
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@

{% if hasComparison %}
{{ queryStats.analyzed | default('?') }} queries analyzed
{%- if newQueryCount > 0 %} | {{ newQueryCount }} new quer{{ "ies" if newQueryCount != 1 else "y" }}{% endif %}
{% elif comparisonUnavailable %}
{{ queryStats.analyzed | default('?') }} queries analyzed — comparison temporarily unavailable.

Expand Down Expand Up @@ -59,6 +58,15 @@
{% endfor %}
{% endif %}

{% if displayNewQueries.length > 0 %}
#### This PR introduces new queries

{% for q in displayNewQueries %}
{% set link = queryLinks[q.hash] %}
- {% if link %}[<code>{{ q.queryPreview }}</code>]({{ link }}){% else %}<code>{{ q.queryPreview }}</code>{% endif %}{% if q.costLabel %}<br>{{ q.costLabel }} · no index suggestion{% endif %}
{% endfor %}
{% endif %}

{% if hasComparison and preExistingRecommendations.length > 0 %}
<details>
<summary>{{ preExistingRecommendations.length }} pre-existing issue{{ "s" if preExistingRecommendations.length != 1 else "" }}</summary>
Expand Down
Loading