diff --git a/packages/app/cypress/e2e/collectivex.cy.ts b/packages/app/cypress/e2e/collectivex.cy.ts index ebdc623f7..939da5fa7 100644 --- a/packages/app/cypress/e2e/collectivex.cy.ts +++ b/packages/app/cypress/e2e/collectivex.cy.ts @@ -523,12 +523,49 @@ describe('CollectiveX kv-transfer card', () => { 'contain.text', `${kvDataset.run.measured_cases}/${kvDataset.run.requested_cases}`, ); + // The runs table distinguishes the run's suites: this run carries both. + cy.get(`[data-testid="collectivex-run-suite-ep-${kvDataset.run.run_id}"]`).should('be.visible'); + cy.get(`[data-testid="collectivex-run-suite-kv-${kvDataset.run.run_id}"]`).should('be.visible'); }); - it('renders no kv card for an EP-only run', () => { + it('plots the kv chart and switches metric, axis, and page size', () => { + installRuns([kvDataset]); + installRun(kvDataset); + openCollectiveX(); + // Default view: aggregate GB/s vs batch at the largest ISL, page 64, pull. + // The measured fixture case carries paged-64 rows at batch 1 and 16. + cy.get('[data-testid="collectivex-kv-chart"]').should('be.visible'); + cy.get('[data-testid="collectivex-kv-chart"] circle').should('have.length', 2); + cy.get('[data-testid="collectivex-kv-chart"]').should( + 'contain.text', + 'Aggregate pull bandwidth at p50 (GB/s)', + ); + // Metric toggle swaps the y axis to burst latency. + cy.get('[data-testid="collectivex-kv-metric-toggle"]').contains('button', 'ms').click(); + cy.get('[data-testid="collectivex-kv-chart"]').should( + 'contain.text', + 'Burst completion latency p50 (ms)', + ); + // ISL on the x axis pins batch 1: one paged-64 row in the fixture. + cy.get('[data-testid="collectivex-kv-xaxis-toggle"]').contains('button', 'ISL').click(); + cy.get('[data-testid="collectivex-kv-chart"] circle').should('have.length', 1); + // Page 16 keeps a single batch-1 row. + cy.get('[data-testid="collectivex-kv-page-toggle"]').contains('button', '16').click(); + cy.get('[data-testid="collectivex-kv-chart"] circle').should('have.length', 1); + // The kv section renders above the EP explorer chart. + cy.get('[data-testid="collectivex-kv-table"]').then(($kv) => { + cy.get('[data-testid="collectivex-main-chart"]').then(($chart) => { + expect($kv[0].compareDocumentPosition($chart[0]) & 4).to.equal(4); + }); + }); + }); + + it('renders no kv card and no KV suite badge for an EP-only run', () => { installRuns(); installRun(); openCollectiveX(); cy.get('[data-testid="collectivex-kv-table"]').should('not.exist'); + cy.get(`[data-testid="collectivex-run-suite-ep-${runId}"]`).should('be.visible'); + cy.get(`[data-testid="collectivex-run-suite-kv-${runId}"]`).should('not.exist'); }); }); diff --git a/packages/app/src/components/collectivex/CollectiveXDisplay.tsx b/packages/app/src/components/collectivex/CollectiveXDisplay.tsx index 6c3c1c519..a448a6371 100644 --- a/packages/app/src/components/collectivex/CollectiveXDisplay.tsx +++ b/packages/app/src/components/collectivex/CollectiveXDisplay.tsx @@ -26,7 +26,7 @@ import { useLocale } from '@/lib/use-locale'; import { CollectiveXChart } from './CollectiveXChart'; import { CollectiveXInventory } from './CollectiveXInventory'; -import { CollectiveXKvTable } from './CollectiveXKvTable'; +import { CollectiveXKvSection } from './CollectiveXKvSection'; import { CollectiveXRunsTable } from './CollectiveXRunsTable'; import { collectiveXColorKey, @@ -862,6 +862,9 @@ export default function CollectiveXDisplay() {
{t.noSeries}
)} + {/* KV-transfer cases lead for kv-only runs: the EP chart below is + legitimately empty for them and must not bury the selected data. */} ++ No measured kv rows match the selected page size and direction. +
+{row.detail ?? row.reason}
+ )} ++ {rows.length} cases · {measured} measured · {strings.description} +
+ {measuredCases.length > 0 && ( + <> +{row.detail ?? row.reason}
- )} -- {rows.length} cases · {measured} measured · {strings.description} -
-| {t.shown} | {t.run} | {t.result} | +{t.suites} | {t.cases} | {t.points} | {t.skus} | @@ -112,6 +120,11 @@ export function CollectiveXRunsTable({ const deleting = deletingRunIds.has(run.run_id); const conclusion = run.conclusion ?? t.pending; const selectedRunIndex = selectedRunIndexById.get(run.run_id); + // Summaries stored before the kv suite carry no kv_cases: EP-only. + const kvRequested = run.kv_cases?.requested ?? 0; + const kvMeasured = run.kv_cases?.measured ?? 0; + const epRequested = run.requested_cases - kvRequested; + const epMeasured = run.measured_cases - kvMeasured; const lineDasharray = selectedRunIndex === undefined ? null : collectiveXRunDasharray(selectedRunIndex); return ( @@ -191,6 +204,34 @@ export function CollectiveXRunsTable({ {conclusion} +
+
+ {epRequested > 0 && (
+
+ EP
+
+ )}
+ {kvRequested > 0 && (
+
+ KV
+
+ )}
+
+ |
{run.measured_cases}/{run.requested_cases} | diff --git a/packages/app/src/components/collectivex/data.test.ts b/packages/app/src/components/collectivex/data.test.ts index 4777bdee3..9741c9cf8 100644 --- a/packages/app/src/components/collectivex/data.test.ts +++ b/packages/app/src/components/collectivex/data.test.ts @@ -12,6 +12,8 @@ import { metricValue, seriesMatchesSelection, type CollectiveXSeriesSelection, + collectiveXKvChartPoints, + type CollectiveXKvRunCase, collectiveXKvCell, } from './data'; import type { CollectiveXKvRow, CollectiveXPercentiles, CollectiveXSeries } from './types'; @@ -282,7 +284,10 @@ describe('chartPoints', () => { }); }); -const kvRow = (overrides: Partial
|---|