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 "" }}