From 7704dc8d1e53f6111964634aeb7d0ab8d7c559b2 Mon Sep 17 00:00:00 2001 From: Wei Hu Date: Tue, 18 Aug 2026 05:56:51 +0000 Subject: [PATCH 1/2] Fix overview grouping for management C# emitter --- packages/spec-dashboard/src/apis.test.ts | 52 +++++++++++++++++++ .../src/components/coverage-overview.tsx | 31 ++++++++--- 2 files changed, 76 insertions(+), 7 deletions(-) diff --git a/packages/spec-dashboard/src/apis.test.ts b/packages/spec-dashboard/src/apis.test.ts index f46702a047e..5f027ef1efe 100644 --- a/packages/spec-dashboard/src/apis.test.ts +++ b/packages/spec-dashboard/src/apis.test.ts @@ -1,7 +1,10 @@ import type { ScenarioManifest } from "@typespec/spec-coverage-sdk"; +import { createElement } from "react"; +import { renderToStaticMarkup } from "react-dom/server"; import { expect, it } from "vitest"; import type { TableDefinition } from "./apis.js"; import { splitManifestByTables } from "./apis.js"; +import { CoverageOverview } from "./components/coverage-overview.js"; const createManifest = ( packageName: string, @@ -225,6 +228,55 @@ it("should not duplicate scenarios across tables", () => { expect(defaultTable!.manifest.scenarios[0].name).toBe("unique_scenario"); }); +it("should group overview coverage by logical display name across emitter packages", () => { + const coverageSummaries = [ + { + manifest: { + packageName: "azure-test", + displayName: "Azure Test", + commit: "abc123", + version: "1.0.0", + scenarios: [ + { + name: "scenario_1", + scenarioDoc: "Doc", + location: { + path: "x", + start: { line: 1, character: 1 }, + end: { line: 2, character: 1 }, + }, + }, + ], + }, + tableName: "Azure Test", + generatorReports: { + "@azure-typespec/http-client-csharp": { + generatorMetadata: { name: "C#", version: "1.0.0" }, + results: { scenario_1: "pass" }, + }, + "@azure-typespec/http-client-csharp-mgmt": { + generatorMetadata: { name: "C#", version: "1.0.0" }, + results: { scenario_1: "pass" }, + }, + }, + }, + ] as any; + + const html = renderToStaticMarkup( + createElement(CoverageOverview, { + coverageSummaries, + emitterDisplayNames: { + "@azure-typespec/http-client-csharp": "C#", + "@azure-typespec/http-client-csharp-mgmt": "C#", + }, + }), + ); + + const cSharpMatches = html.match(/C#/g) ?? []; + expect(cSharpMatches.length).toBeGreaterThanOrEqual(1); + expect(html).not.toContain("@azure-typespec/http-client-csharp-mgmt"); +}); + it("should include emitterNames from table definition", () => { const manifest = createManifest("test-package", "Display Name", ["scenario1"]); const tables: TableDefinition[] = [ diff --git a/packages/spec-dashboard/src/components/coverage-overview.tsx b/packages/spec-dashboard/src/components/coverage-overview.tsx index 7e8d8d54506..5bf9ce2668a 100644 --- a/packages/spec-dashboard/src/components/coverage-overview.tsx +++ b/packages/spec-dashboard/src/components/coverage-overview.tsx @@ -39,6 +39,14 @@ function getEmitterDisplayName( return emitterName; } +function getEmitterOverviewKey( + emitterName: string, + report: CoverageSummary["generatorReports"][string], + emitterDisplayNames?: Record, +): string { + return getEmitterDisplayName(emitterName, report, emitterDisplayNames); +} + /** * Gets the accent color for a coverage ratio using the same thresholds as the coverage tables. */ @@ -59,22 +67,31 @@ export const CoverageOverview: FunctionComponent = ({ emitterDisplayNames, }) => { const emitterOverviews = useMemo(() => { - // Aggregate scenarios per emitter across all summaries + // Aggregate scenarios per logical emitter language across all summaries. + // This keeps emitters that share the same display name (for example C# data-plane + // and management-plane emitters) grouped into a single overview card. const emitterMap = new Map< string, { totalScenarios: number; coveredScenarios: number; report: CoverageSummary["generatorReports"][string]; + displayName: string; } >(); for (const summary of coverageSummaries) { for (const [emitterName, report] of Object.entries(summary.generatorReports)) { - if (!emitterMap.has(emitterName)) { - emitterMap.set(emitterName, { totalScenarios: 0, coveredScenarios: 0, report }); + const groupKey = getEmitterOverviewKey(emitterName, report, emitterDisplayNames); + if (!emitterMap.has(groupKey)) { + emitterMap.set(groupKey, { + totalScenarios: 0, + coveredScenarios: 0, + report, + displayName: getEmitterDisplayName(emitterName, report, emitterDisplayNames), + }); } - const entry = emitterMap.get(emitterName)!; + const entry = emitterMap.get(groupKey)!; const scenarios = summary.manifest.scenarios; entry.totalScenarios += scenarios.length; if (report) { @@ -89,10 +106,10 @@ export const CoverageOverview: FunctionComponent = ({ } const overviews: EmitterOverview[] = []; - for (const [emitterName, data] of emitterMap) { + for (const [groupKey, data] of emitterMap) { overviews.push({ - name: emitterName, - displayName: getEmitterDisplayName(emitterName, data.report, emitterDisplayNames), + name: groupKey, + displayName: data.displayName, coverageRatio: data.totalScenarios > 0 ? data.coveredScenarios / data.totalScenarios : 0, }); } From 5102e2af2b031df80fe19cfe459cea2223439596 Mon Sep 17 00:00:00 2001 From: Wei Hu Date: Tue, 18 Aug 2026 06:05:55 +0000 Subject: [PATCH 2/2] Fix overview aggregation and tighten C# regression test --- packages/spec-dashboard/src/apis.test.ts | 2 +- .../src/components/coverage-overview.tsx | 58 +++++++++++++++---- 2 files changed, 47 insertions(+), 13 deletions(-) diff --git a/packages/spec-dashboard/src/apis.test.ts b/packages/spec-dashboard/src/apis.test.ts index 5f027ef1efe..97d2ed9185d 100644 --- a/packages/spec-dashboard/src/apis.test.ts +++ b/packages/spec-dashboard/src/apis.test.ts @@ -273,7 +273,7 @@ it("should group overview coverage by logical display name across emitter packag ); const cSharpMatches = html.match(/C#/g) ?? []; - expect(cSharpMatches.length).toBeGreaterThanOrEqual(1); + expect(cSharpMatches).toHaveLength(1); expect(html).not.toContain("@azure-typespec/http-client-csharp-mgmt"); }); diff --git a/packages/spec-dashboard/src/components/coverage-overview.tsx b/packages/spec-dashboard/src/components/coverage-overview.tsx index 5bf9ce2668a..59565081538 100644 --- a/packages/spec-dashboard/src/components/coverage-overview.tsx +++ b/packages/spec-dashboard/src/components/coverage-overview.tsx @@ -81,27 +81,61 @@ export const CoverageOverview: FunctionComponent = ({ >(); for (const summary of coverageSummaries) { + const summaryGroupMap = new Map< + string, + { + scenarioNames: Set; + coveredScenarioNames: Set; + } + >(); + for (const [emitterName, report] of Object.entries(summary.generatorReports)) { const groupKey = getEmitterOverviewKey(emitterName, report, emitterDisplayNames); + if (!summaryGroupMap.has(groupKey)) { + summaryGroupMap.set(groupKey, { + scenarioNames: new Set(), + coveredScenarioNames: new Set(), + }); + } + + const entry = summaryGroupMap.get(groupKey)!; + for (const scenario of summary.manifest.scenarios) { + entry.scenarioNames.add(scenario.name); + const status = report?.results[scenario.name]; + if ( + report && + (status === "pass" || status === "not-applicable" || status === "not-supported") + ) { + entry.coveredScenarioNames.add(scenario.name); + } + } + } + + for (const [groupKey, data] of summaryGroupMap) { if (!emitterMap.has(groupKey)) { + const firstReport = Object.entries(summary.generatorReports).find( + ([emitterName, report]) => + getEmitterOverviewKey(emitterName, report, emitterDisplayNames) === groupKey, + )?.[1]; + emitterMap.set(groupKey, { totalScenarios: 0, coveredScenarios: 0, - report, - displayName: getEmitterDisplayName(emitterName, report, emitterDisplayNames), + report: firstReport, + displayName: getEmitterDisplayName( + Object.entries(summary.generatorReports).find( + ([emitterName, report]) => + getEmitterOverviewKey(emitterName, report, emitterDisplayNames) === groupKey, + )?.[0] ?? groupKey, + firstReport, + emitterDisplayNames, + ), }); } + const entry = emitterMap.get(groupKey)!; - const scenarios = summary.manifest.scenarios; - entry.totalScenarios += scenarios.length; - if (report) { - for (const scenario of scenarios) { - const status = report.results[scenario.name]; - if (status === "pass" || status === "not-applicable" || status === "not-supported") { - entry.coveredScenarios++; - } - } - } + entry.totalScenarios += data.scenarioNames.size; + entry.coveredScenarios += data.coveredScenarioNames.size; } }