From 2d249753f037179feab8c457c609094adc0755a1 Mon Sep 17 00:00:00 2001 From: Jean-Philippe Sirois Date: Tue, 16 Jun 2026 15:58:29 -0400 Subject: [PATCH 1/2] fix: list new queries in the PR comment, not just count them MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit A new query with no index recommendation was counted in the "N new" tally but listed nowhere in the comment body, so "1 new query" was unactionable from the PR — the reported index-covered query was new but invisible (Query-Doctor/Site bug report). compareRuns already yields the new queries with their SQL; surface the ones that carry no recommendation (those with a recommendation already render under "introduces queries with recommendations") in a new "This PR introduces new queries" section. Co-Authored-By: Claude Opus 4.8 (1M context) --- .../github/__snapshots__/github.test.ts.snap | 2 + src/reporters/github/github.test.ts | 63 +++++++++++++++++++ src/reporters/github/github.ts | 39 +++++++++++- src/reporters/github/success.md.j2 | 9 +++ 4 files changed, 112 insertions(+), 1 deletion(-) diff --git a/src/reporters/github/__snapshots__/github.test.ts.snap b/src/reporters/github/__snapshots__/github.test.ts.snap index 9dc6860..c6f8ec4 100644 --- a/src/reporters/github/__snapshots__/github.test.ts.snap +++ b/src/reporters/github/__snapshots__/github.test.ts.snap @@ -17,6 +17,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 @@ -40,6 +41,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..88fa764 100644 --- a/src/reporters/github/success.md.j2 +++ b/src/reporters/github/success.md.j2 @@ -59,6 +59,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 "" }} From a9f81a56d4fe9ee039365d2ad46352070072e99b Mon Sep 17 00:00:00 2001 From: Jean-Philippe Sirois Date: Tue, 16 Jun 2026 16:06:41 -0400 Subject: [PATCH 2/2] fix: drop the redundant new-query count from the comment header MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The header said "N queries analyzed | N new query" while the roll-up line right below already states "· N new ·", so the new count appeared twice on adjacent lines. With new queries now listed in their own section, the header callout is pure redundancy — drop it and let the roll-up carry the tally. Also lets the header sit a blank line clear of the roll-up instead of jammed against it. Co-Authored-By: Claude Opus 4.8 (1M context) --- src/reporters/github/__snapshots__/github.test.ts.snap | 2 ++ src/reporters/github/success.md.j2 | 1 - 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/src/reporters/github/__snapshots__/github.test.ts.snap b/src/reporters/github/__snapshots__/github.test.ts.snap index c6f8ec4..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 @@ -28,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 diff --git a/src/reporters/github/success.md.j2 b/src/reporters/github/success.md.j2 index 88fa764..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.