diff --git a/src/reporters/github/__snapshots__/github.test.ts.snap b/src/reporters/github/__snapshots__/github.test.ts.snap index 9dc6860..bd63053 100644 --- a/src/reporters/github/__snapshots__/github.test.ts.snap +++ b/src/reporters/github/__snapshots__/github.test.ts.snap @@ -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 @@ -17,6 +18,7 @@ exports[`CI-signal metadata parity (analyzer#141) > linked repo: renders rollup + Using assumed statistics (10000 rows/table). For better results, sync production stats. More detail → get_ci_run({ runId: "9f3a1c20" }) · view run · docs @@ -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 @@ -40,6 +43,7 @@ exports[`CI-signal metadata parity (analyzer#141) > unlinked repo: rollup + foot + Using assumed statistics (10000 rows/table). For better results, sync production stats. More detail → get_ci_run({ runId: "9f3a1c20" }) · docs diff --git a/src/reporters/github/github.test.ts b/src/reporters/github/github.test.ts index e9e6686..22f49c6 100644 --- a/src/reporters/github/github.test.ts +++ b/src/reporters/github/github.test.ts @@ -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({ diff --git a/src/reporters/github/github.ts b/src/reporters/github/github.ts index 6504dc0..ce410c4 100644 --- a/src/reporters/github/github.ts +++ b/src/reporters/github/github.ts @@ -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 }); @@ -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"); } @@ -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 { const links: Record = {}; @@ -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, @@ -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)), @@ -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); @@ -136,6 +172,7 @@ export function buildViewModel(ctx: ReportContext) { displayRegressed, displayAcknowledgedRegressed, displayImproved, + displayNewQueries, preExistingRecommendations, newQueryCount: ctx.comparison!.newQueries.length, hasComparison: true, diff --git a/src/reporters/github/success.md.j2 b/src/reporters/github/success.md.j2 index f84584c..ccd5cb6 100644 --- a/src/reporters/github/success.md.j2 +++ b/src/reporters/github/success.md.j2 @@ -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. @@ -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 %}[{{ q.queryPreview }}]({{ link }}){% else %}{{ q.queryPreview }}{% endif %}{% if q.costLabel %}
{{ q.costLabel }} · no index suggestion{% endif %} +{% endfor %} +{% endif %} + {% if hasComparison and preExistingRecommendations.length > 0 %}
{{ preExistingRecommendations.length }} pre-existing issue{{ "s" if preExistingRecommendations.length != 1 else "" }}