From 45c5b8b5dd2fd7eb35c966bb72721a4c597d32a8 Mon Sep 17 00:00:00 2001 From: Aleksandr Barkhatov Date: Tue, 21 Jul 2026 16:13:53 +0200 Subject: [PATCH 1/2] refactor(ic-dashboard): remove vestigial legacy metric fetches With every metric family on the unified /v1/metric-results path, the /metrics/queries and /catalog/get_metrics fetches were dead over-fetch. Signed-off-by: Aleksandr Barkhatov --- src/lib/insight/attention.test.ts | 82 +---- src/lib/insight/attention.ts | 63 +--- src/lib/insight/kpi-row.test.ts | 38 +- src/lib/insight/kpi-row.ts | 109 +----- .../ic-dashboard/engineering-dashboard-v2.tsx | 342 ++++-------------- 5 files changed, 96 insertions(+), 538 deletions(-) diff --git a/src/lib/insight/attention.test.ts b/src/lib/insight/attention.test.ts index 99bdaf35..0afdffe1 100644 --- a/src/lib/insight/attention.test.ts +++ b/src/lib/insight/attention.test.ts @@ -1,29 +1,9 @@ import { describe, expect, it } from "vitest"; -import type { CatalogMetric } from "@/api/catalog-client"; import type { MetricResult } from "@/api/metric-results-client"; -import { - legacyAttentionItems, - metricAttentionItems, -} from "@/lib/insight/attention"; +import { metricAttentionItems } from "@/lib/insight/attention"; import type { MetricGroup } from "@/lib/insight/groups"; import { normalizeMetricResults } from "@/lib/metrics/collection"; -import type { BulletMetric } from "@/types/insight"; - -function bullet(overrides: Partial = {}): BulletMetric { - return { - metric_key: "meeting_hours", - label: "Meeting hours", - value: "22", - unit: "h", - peer: { p25: 4, p50: 8, p75: 12, min: 1, max: 30, n: 9 }, - ...overrides, - } as BulletMetric; -} - -const CATALOG_ROW = { - higher_is_better: false, -} as unknown as CatalogMetric; function aiMetric(value: number | null): MetricResult { return { @@ -67,41 +47,6 @@ const AI_DEF: MetricGroup = { drilldown: [], }; -describe("legacyAttentionItems", () => { - it("surfaces bottom-quartile rows with a display-ready shape", () => { - const items = legacyAttentionItems( - [{ id: "collaboration", rows: [bullet()] }], - () => CATALOG_ROW, - ); - expect(items).toHaveLength(1); - expect(items[0]).toMatchObject({ - group: "collaboration", - label: "Meeting hours", - valueText: "22 h", - medianText: "8 h", - gapText: "2.8×", - }); - expect(items[0]?.relGap).toBeGreaterThan(0); - }); - - it("skips schema errors, non-numeric values, and in-pack rows", () => { - const items = legacyAttentionItems( - [ - { - id: "collaboration", - rows: [ - bullet({ schema_error: true }), - bullet({ value: "—" }), - bullet({ value: "8" }), - ], - }, - ], - () => CATALOG_ROW, - ); - expect(items).toHaveLength(0); - }); -}); - describe("metricAttentionItems", () => { it("surfaces bottom-quartile metrics with the same item shape", () => { const byKey = normalizeMetricResults([aiMetric(2)]); @@ -126,8 +71,8 @@ describe("metricAttentionItems", () => { metricAttentionItems( AI_DEF, normalizeMetricResults([unmeasured]), - "me@x.com", - ), + "me@x.com" + ) ).toHaveLength(0); }); @@ -136,28 +81,15 @@ describe("metricAttentionItems", () => { metricAttentionItems( AI_DEF, normalizeMetricResults([aiMetric(10)]), - "me@x.com", - ), + "me@x.com" + ) ).toHaveLength(0); expect( metricAttentionItems( AI_DEF, normalizeMetricResults([aiMetric(null)]), - "me@x.com", - ), + "me@x.com" + ) ).toHaveLength(0); }); - - it("produces the identical intermediate shape as the legacy selector", () => { - const legacy = legacyAttentionItems( - [{ id: "collaboration", rows: [bullet()] }], - () => CATALOG_ROW, - )[0]!; - const metric = metricAttentionItems( - AI_DEF, - normalizeMetricResults([aiMetric(2)]), - "me@x.com", - )[0]!; - expect(Object.keys(legacy).sort()).toEqual(Object.keys(metric).sort()); - }); }); diff --git a/src/lib/insight/attention.ts b/src/lib/insight/attention.ts index 1e1ebe72..5da9ae59 100644 --- a/src/lib/insight/attention.ts +++ b/src/lib/insight/attention.ts @@ -1,22 +1,17 @@ import { formatMetricValue } from "@/lib/format"; import { formatGapMagnitude } from "@/lib/metrics/gap"; import type { MetricGroup, GroupId } from "@/lib/insight/groups"; -import { - bulletCatalogKey, - type CatalogByKey, -} from "@/lib/insight/v2/peer-status"; import { forEntity, type NormalizedMetricResult, } from "@/lib/metrics/collection"; import { toPeerStats } from "@/lib/metrics/peer-standing"; import { peerStatusVsQuartiles } from "@/lib/peers"; -import type { BulletMetric } from "@/types/insight"; /** * One "needs attention" row: a metric sitting in the bottom quartile of its - * cohort, display-ready. Selectors below feed the shared surface from both - * data paths; ranking (`relGap` descending) happens in the component. + * cohort, display-ready. Ranking (`relGap` descending) happens in the + * component. */ export interface AttentionItem { key: string; @@ -30,60 +25,6 @@ export interface AttentionItem { relGap: number; } -export interface LegacyAttentionGroup { - id: GroupId; - rows: BulletMetric[]; -} - -/** Legacy bullet rows + catalog direction → attention items. */ -export function legacyAttentionItems( - groups: LegacyAttentionGroup[], - byMetricKey: CatalogByKey -): AttentionItem[] { - const items: AttentionItem[] = []; - for (const group of groups) { - for (const row of group.rows) { - // schema_status='error' rows never trigger the attention surface — we - // can't compare a broken metric to peers. Missing-id rows likewise - // collapse out (no catalog row → no higher_is_better signal). - if (row.schema_error) continue; - const value = Number(row.value); - if (!Number.isFinite(value)) continue; - const stats = row.peer; - if (!stats) continue; - const catalogRow = byMetricKey(bulletCatalogKey(row)); - if (!catalogRow) continue; - const higherIsBetter = catalogRow.higher_is_better; - if (peerStatusVsQuartiles(value, stats, higherIsBetter) !== "bottom") { - continue; - } - const median = stats.p50; - const denom = Math.abs(median) > 1e-9 ? Math.abs(median) : 1; - const relGap = higherIsBetter - ? (median - value) / denom - : (value - median) / denom; - const gapDelta = value - median; - items.push({ - key: row.metric_key, - group: group.id, - label: row.label, - valueText: `${row.value}${row.unit ? ` ${row.unit}` : ""}`, - medianText: `${Math.round(median * 10) / 10}${row.unit ? ` ${row.unit}` : ""}`, - gapText: formatGapMagnitude({ - value, - median, - gapPct: Math.abs(median) > 1e-9 ? gapDelta / Math.abs(median) : null, - gapDelta, - format: "decimal", - unit: row.unit ?? null, - }), - relGap, - }); - } - } - return items; -} - /** Metric-collection results → attention items; direction rides the wire. */ export function metricAttentionItems( def: MetricGroup, diff --git a/src/lib/insight/kpi-row.test.ts b/src/lib/insight/kpi-row.test.ts index b90e8188..1e361ca5 100644 --- a/src/lib/insight/kpi-row.test.ts +++ b/src/lib/insight/kpi-row.test.ts @@ -1,14 +1,13 @@ import { describe, expect, it } from "vitest"; import type { MetricResult } from "@/api/metric-results-client"; -import { kpiRowTiles, metricKpiTiles } from "@/lib/insight/kpi-row"; -import { KPI_ROW } from "@/lib/insight/groups"; +import { metricKpiTiles } from "@/lib/insight/kpi-row"; import { normalizeMetricResults } from "@/lib/metrics/collection"; function metricResult( key: string, value: number | null, - overrides: Partial = {}, + overrides: Partial = {} ): MetricResult { return { metric_key: key, @@ -87,7 +86,7 @@ describe("metricKpiTiles", () => { normalizeMetricResults([result]), null, "me@x.com", - "all", + "all" ); expect(tiles[0]?.valueStatus).toBe("neutral"); expect(tiles[0]?.medianLabel).toBeNull(); @@ -103,7 +102,7 @@ describe("metricKpiTiles", () => { normalizeMetricResults([result]), null, "me@x.com", - "all", + "all" ); expect(tiles[0]?.valueStatus).toBe("neutral"); }); @@ -124,35 +123,8 @@ describe("metricKpiTiles", () => { normalizeMetricResults([current]), normalizeMetricResults([previous]), "me@x.com", - "all", + "all" ); expect(tiles[0]?.delta?.text).toBe("+5 pp"); }); }); - -describe("kpiRowTiles", () => { - it("orders tiles by KPI_ROW display order", () => { - const metric = metricKpiTiles( - normalizeMetricResults([ - metricResult("tasks.closed", 12), - metricResult("git.prs_merged", 9), - metricResult("ai.active_days", 14), - ]), - null, - "me@x.com", - "all", - ); - const ordered = kpiRowTiles([], metric).map((t) => t.key); - const expected = KPI_ROW.map((s) => - s.kind === "legacy" ? s.key : s.metricKey, - ).filter((k) => - ["tasks.closed", "git.prs_merged", "ai.active_days"].includes(k), - ); - expect(ordered).toEqual(expected); - expect(ordered).toEqual([ - "tasks.closed", - "git.prs_merged", - "ai.active_days", - ]); - }); -}); diff --git a/src/lib/insight/kpi-row.ts b/src/lib/insight/kpi-row.ts index b532a37a..412812d6 100644 --- a/src/lib/insight/kpi-row.ts +++ b/src/lib/insight/kpi-row.ts @@ -1,15 +1,9 @@ -import { formatKpiValue } from "@/api/transforms"; -import { - formatMetricNumber, - formatMetricValue, - formatPp, -} from "@/lib/format"; +import { formatMetricNumber, formatMetricValue, formatPp } from "@/lib/format"; import { KPI_ROW, groupIdForMetricKey, type GroupId, } from "@/lib/insight/groups"; -import type { CatalogByKey } from "@/lib/insight/v2/peer-status"; import { forEntity, type NormalizedMetricResult, @@ -19,13 +13,11 @@ import { formatGapMagnitude } from "@/lib/metrics/gap"; import { derivePeerStanding } from "@/lib/metrics/peer-standing"; import { computeDelta, type MetricDelta } from "@/lib/metrics/delta"; import type { FocusMode } from "@/lib/peers"; -import { applyFocusStatus, statusVsMedian, type Status } from "@/lib/status"; -import type { IcKpi } from "@/types/insight"; +import { applyFocusStatus, type Status } from "@/lib/status"; /** * Display-ready KPI tile input: selectors own all formatting and scoring, so - * the tile renders both legacy-batch and metric-collection KPIs without - * knowing which is which. + * the tile renders a value without knowing how it was computed. */ export interface KpiTileData { key: string; @@ -46,79 +38,9 @@ export interface KpiTileData { groupId: GroupId | null; } -const IC_KPI_PREFIX = "ic_kpis."; - -/** Legacy KPI batch rows → tiles (logic lifted verbatim from the old tile). */ -export function legacyKpiTiles( - kpis: IcKpi[], - byMetricKey: CatalogByKey, - focusMode: FocusMode, -): KpiTileData[] { - const byKey = new Map(kpis.map((kpi) => [kpi.metric_key, kpi])); - return KPI_ROW.flatMap((source) => { - if (source.kind !== "legacy") return []; - const kpi = byKey.get(source.key); - if (!kpi) return []; - - const catalogRow = byMetricKey(`${IC_KPI_PREFIX}${kpi.metric_key}`); - const isSchemaError = catalogRow?.schema_status === "error"; - const fmt = catalogRow?.format; - const isCountMetric = - catalogRow !== undefined && fmt !== "percent" && fmt !== "hours"; - const rawValue = kpi.raw_value ?? (isCountMetric ? 0 : null); - const peerMedian = kpi.peer_median ?? null; - const hasMedian = - peerMedian != null && Number.isFinite(peerMedian) && peerMedian > 0; - const isPercent = kpi.unit === "%"; - const value = - kpi.value ?? (isCountMetric ? formatKpiValue(0, fmt) : "—"); - - const valueStatus = applyFocusStatus( - !isSchemaError && catalogRow && rawValue !== null && hasMedian - ? statusVsMedian(rawValue, peerMedian, catalogRow.higher_is_better) - : "neutral", - focusMode, - ); - - const showDelta = kpi.delta !== "" && kpi.delta_type !== "neutral"; - const delta = showDelta - ? { - // Percent-valued metrics deltas are percentage points; drop the `%` - // so "+5" doesn't read as "5% of 86%". - text: isPercent ? kpi.delta.replace(/%$/, "") : kpi.delta, - status: applyFocusStatus(kpi.delta_type, focusMode), - down: kpi.delta.trim().startsWith("-"), - } - : null; - - const medianLabel = - hasMedian && catalogRow !== undefined && !isSchemaError - ? `median ${formatKpiValue(peerMedian, catalogRow.format)}${isPercent ? "%" : ""}` - : null; - - return [ - { - key: kpi.metric_key, - label: kpi.label, - value: `${value}${isPercent && value !== "—" ? "%" : ""}`, - valueStatus, - delta, - medianLabel, - // The legacy batch reconstructs no arithmetic gap; median alone shows. - gapText: null, - gapStatus: "neutral", - context: catalogRow?.source_tags.length - ? catalogRow.source_tags.join(", ") - : null, - groupId: source.groupId, - }, - ]; - }); -} - function deltaStatus( delta: MetricDelta, - direction: NormalizedMetricResult["direction"], + direction: NormalizedMetricResult["direction"] ): Status { if (direction === "neutral" || delta.value === 0) return "neutral"; const favorable = @@ -143,7 +65,7 @@ export function metricKpiTiles( byKey: Map, previousByKey: Map | null, entityId: string, - focusMode: FocusMode, + focusMode: FocusMode ): KpiTileData[] { return KPI_ROW.flatMap((source) => { if (source.kind !== "metric") return []; @@ -160,7 +82,7 @@ export function metricKpiTiles( const standing = derivePeerStanding(metric.direction, data); const valueStatus = applyFocusStatus( peerStatusToStatus(standing.rank), - focusMode, + focusMode ); const previousMetric = previousByKey?.get(source.metricKey) ?? null; @@ -171,7 +93,7 @@ export function metricKpiTiles( value, previousValue, metric.computation, - metric.format, + metric.format ); const deltaText = rawDelta ? formatTileDelta(rawDelta) : null; const delta = @@ -180,7 +102,7 @@ export function metricKpiTiles( text: deltaText, status: applyFocusStatus( deltaStatus(rawDelta, metric.direction), - focusMode, + focusMode ), down: rawDelta.value < 0, } @@ -228,18 +150,3 @@ export function metricKpiTiles( ]; }); } - -/** All KPI tiles in `KPI_ROW` display order. */ -export function kpiRowTiles( - legacy: KpiTileData[], - metric: KpiTileData[], -): KpiTileData[] { - const byKey = new Map( - [...legacy, ...metric].map((tile) => [tile.key, tile]), - ); - return KPI_ROW.flatMap((source) => { - const key = source.kind === "legacy" ? source.key : source.metricKey; - const tile = byKey.get(key); - return tile ? [tile] : []; - }); -} diff --git a/src/screens/ic-dashboard/engineering-dashboard-v2.tsx b/src/screens/ic-dashboard/engineering-dashboard-v2.tsx index 17c556a4..ef5fd544 100644 --- a/src/screens/ic-dashboard/engineering-dashboard-v2.tsx +++ b/src/screens/ic-dashboard/engineering-dashboard-v2.tsx @@ -1,8 +1,6 @@ -import { useMemo, useState } from "react"; +import { useState } from "react"; -import { useCatalog } from "@/api/use-catalog"; import { ComingSoon } from "@/components/widgets/coming-soon"; -import { DashboardEmptyState } from "@/components/widgets/v2/dashboard-empty-state"; import { DashboardHeader } from "@/components/widgets/v2/dashboard-header"; import { IcNeedsAttention } from "@/components/widgets/v2/ic-needs-attention"; import { @@ -11,48 +9,27 @@ import { KpiTilePlaceholder, } from "@/components/widgets/v2/kpi-tile"; import { MetricGroupCard } from "@/components/widgets/metric-views/metric-group-card"; -import { SectionCard } from "@/components/widgets/v2/section-card"; import { GroupDrilldownSheet } from "@/components/widgets/v2/group-drilldown-sheet"; -import { Spinner } from "@/components/ui/spinner"; import { usePeriod } from "@/hooks/use-period"; import { useSettings } from "@/hooks/use-settings"; +import { metricAttentionItems } from "@/lib/insight/attention"; +import { metricKpiTiles, type KpiTileData } from "@/lib/insight/kpi-row"; import { - legacyAttentionItems, - metricAttentionItems, -} from "@/lib/insight/attention"; -import { - kpiRowTiles, - legacyKpiTiles, - metricKpiTiles, - type KpiTileData, -} from "@/lib/insight/kpi-row"; -import { - GROUPS, KPI_ROW, KPI_ROW_COLLECTION, metricGroups, type GroupId, } from "@/lib/insight/groups"; -import { orderRowsForSection } from "@/lib/insight/v2/metric-order"; -import { hasBulletValue } from "@/lib/insight/v2/peer-status"; import { - entityObserved, projectViews, type MetricCollectionConfig, } from "@/lib/metrics/collection"; import { normalizePersonId } from "@/lib/metrics/entity"; -import { cn } from "@/lib/utils"; -import { - useIcDashboardData, - type IcDashboardData, -} from "@/queries/ic-dashboard"; import { useMetricCollection, useMetricCollectionSet, } from "@/queries/metric-results"; -import type { BulletMetric, IdentityPerson } from "@/types/insight"; - -const IC_KPI_PREFIX = "ic_kpis."; +import type { IdentityPerson } from "@/types/insight"; // Stable references so the disabled drilldown query keeps a constant key. const EMPTY_COLLECTION: MetricCollectionConfig = { metrics: [] }; @@ -67,19 +44,6 @@ const CLOSED_DRILLDOWN_DATA = { refetch: () => {}, } as const; -// The one per-key seam that survives coexistence: which legacy batch fields -// feed each legacy group's rows and error flag. Empty while no group is -// `kind: "legacy"`; dies with `LegacyGroup`. -/* v8 ignore start -- inert seam, populated only when a legacy group returns */ -const LEGACY_GROUP_FEEDS: Record< - string, - { - rows: (data: IcDashboardData | undefined) => BulletMetric[]; - errored: (data: IcDashboardData | undefined) => boolean; - } -> = {}; -/* v8 ignore stop */ - export interface EngineeringDashboardV2Props { personId: string; person?: IdentityPerson | null; @@ -89,12 +53,8 @@ export function EngineeringDashboardV2({ personId, person, }: EngineeringDashboardV2Props) { - const { period, dateRange, setPeriod } = usePeriod(); + const { period, dateRange } = usePeriod(); const { focusMode } = useSettings(); - const catalog = useCatalog(); - const dashQ = useIcDashboardData(personId, period, dateRange, { - keepPrevious: true, - }); const entityId = normalizePersonId(personId); const entity = { type: "person" as const, ids: [entityId] }; @@ -115,7 +75,6 @@ export function EngineeringDashboardV2({ ); const [openGroup, setOpenGroup] = useState(null); - const data = dashQ.data; // Full collection for the open metrics group only (drives the drilldown's // chart blocks + peer story). Disabled while nothing is open — empty ids @@ -130,104 +89,27 @@ export function EngineeringDashboardV2({ openMetricDef ? entity : CLOSED_ENTITY, dateRange ); - const legacyRowsByGroup: Record = Object.fromEntries( - GROUPS.filter((def) => def.kind === "legacy").map((def) => [ - def.id, - orderRowsForSection(def.id, LEGACY_GROUP_FEEDS[def.id]?.rows(data) ?? []), - ]) - ); const displayName = person?.display_name ?? personId; const role = person?.job_title; - // KPI row: legacy tiles from the shared batch, metric tiles from the - // collection; placeholders per source while a tile has no data yet. - const legacyTiles = legacyKpiTiles( - data?.kpis ?? [], - catalog.byMetricKey, - focusMode - ); - const metricTiles = metricKpiTiles( + const tiles = metricKpiTiles( kpiData.byKey, kpiData.previousByKey, entityId, focusMode ); - const tiles = kpiRowTiles(legacyTiles, metricTiles); const tilesByKey = new Map( tiles.map((tile) => [tile.key, tile]) ); - const legacyKpiLabels = useMemo( - () => - new Map( - (catalog.data?.metrics ?? []) - .filter((m) => m.metric_key?.startsWith(IC_KPI_PREFIX)) - .map((m) => [ - (m.metric_key ?? "").slice(IC_KPI_PREFIX.length), - m.label, - ]) - ), - [catalog.data] - ); - - const attentionItems = [ - ...legacyAttentionItems( - GROUPS.filter((def) => def.kind === "legacy").map((def) => ({ - id: def.id, - rows: legacyRowsByGroup[def.id] ?? [], - })), - catalog.byMetricKey - ), - ...metricGroups().flatMap((def) => - metricAttentionItems( - def, - groupData.get(def.id)?.byKey ?? new Map(), - entityId - ) - ), - ]; - const hasLegacyKpiData = (data?.kpis ?? []).some((k) => k.raw_value !== null); - // Period views zero-fill sums, so "has data" means observed (peer - // target_value), not merely a non-null zero-filled total — otherwise the - // empty state becomes unreachable for fully unmeasured people. - const hasMetricKpiData = [...kpiData.byKey.values()].some((metric) => - entityObserved(metric, entityId) - ); - const hasLegacyGroupData = Object.values(legacyRowsByGroup).some((rows) => - rows.some(hasBulletValue) - ); - const hasMetricGroupData = [...groupData.values()].some((result) => - [...result.byKey.values()].some((metric) => - entityObserved(metric, entityId) + const attentionItems = metricGroups().flatMap((def) => + metricAttentionItems( + def, + groupData.get(def.id)?.byKey ?? new Map(), + entityId ) ); - const metricsSettled = - !kpiData.isPending && - [...groupData.values()].every((result) => !result.isPending); - // Failed unified queries must surface as error cards with retry — never as - // "you have no data". - const anyMetricError = - kpiData.isError || [...groupData.values()].some((result) => result.isError); - const isAllEmpty = - Boolean(data) && - metricsSettled && - !anyMetricError && - !hasLegacyKpiData && - !hasMetricKpiData && - !hasLegacyGroupData && - !hasMetricGroupData; - // The page dim signals "the data you're already looking at is being - // replaced" (period/range change, where keepPreviousData shows the old - // values while new ones load). A collection's FIRST load must not dim the - // page — it has no prior data to replace and shows its own card spinner — - // so gate on revalidation (`isFetching && !isPending`), not bare fetching. - const isMetricsRevalidating = - (kpiData.isFetching && !kpiData.isPending) || - [...groupData.values()].some( - (result) => result.isFetching && !result.isPending - ); - const showFullSpinner = dashQ.isPending || (isAllEmpty && dashQ.isFetching); // Close any open drilldown when the viewed person changes. Render-phase // reset against the previous id rather than an effect (no cascading commit). @@ -246,154 +128,78 @@ export function EngineeringDashboardV2({ hasReports={(person?.subordinates?.length ?? 0) > 0} />
- {showFullSpinner ? ( -
- +
+

+ At a glance +

+
+ {KPI_ROW.map((source) => { + const key = + source.kind === "metric" ? source.metricKey : source.key; + const tile = tilesByKey.get(key); + if (tile) { + return ( + + ); + } + if (source.kind === "metric" && kpiData.isError) { + return ( + + ); + } + // Metric tiles reset to pending on a period change (no + // placeholder retention); show a spinner, not "Coming soon". + if (source.kind === "metric" && kpiData.isPending) { + return ; + } + return ; + })}
- ) : dashQ.isError && !data ? ( -
- dashQ.refetch()} /> -
- ) : isAllEmpty ? ( -
- -
- ) : ( -
-
-

- At a glance -

-
- {KPI_ROW.map((source) => { - const key = - source.kind === "legacy" ? source.key : source.metricKey; - const tile = tilesByKey.get(key); - if ( - tile && - (source.kind === "metric" || !data?.errors.kpis) - ) { - return ( - - ); - } - if (source.kind === "metric" && kpiData.isError) { - return ( - - ); - } - // Metric tiles reset to pending on a period change (no - // placeholder retention); show a spinner, not "Coming soon". - if (source.kind === "metric" && kpiData.isPending) { - return ; - } - return ( - - ); - })} -
-
- - - -
-

- Sections -

-
- {GROUPS.map((def) => { - if (def.kind === "metrics") { - const result = groupData.get(def.id); - if (!result) return null; - return ( - setOpenGroup(def.id)} - /> - ); - } - /* v8 ignore next -- unreachable while no legacy group exists */ - if (LEGACY_GROUP_FEEDS[def.id]?.errored(data)) { - return ( - {}} - unavailable - /> - ); - } - return ( - setOpenGroup(def.id)} - /> - ); - })} -
-
+
+ + + +
+

+ Sections +

+
+ {metricGroups().map((def) => { + const result = groupData.get(def.id); + if (!result) return null; + return ( + setOpenGroup(def.id)} + /> + ); + })}
- )} +
- {GROUPS.map((def) => ( + {metricGroups().map((def) => ( setOpenGroup(o ? def.id : null)} def={def} - rows={legacyRowsByGroup[def.id] ?? []} - metricTarget={ - def.kind === "metrics" - ? { - kind: "person", - entityId, - // The drilldown for the open group reads the full-collection - // query; closed sheets never render their body. - data: - def.id === openGroup - ? drilldownData - : CLOSED_DRILLDOWN_DATA, - } - : undefined - } + rows={[]} + metricTarget={{ + kind: "person", + entityId, + // The drilldown for the open group reads the full-collection + // query; closed sheets never render their body. + data: def.id === openGroup ? drilldownData : CLOSED_DRILLDOWN_DATA, + }} personId={personId} range={dateRange} period={period} From 5c3cee3f865d8f7452601f244359384ff716ef51 Mon Sep 17 00:00:00 2001 From: Aleksandr Barkhatov Date: Tue, 21 Jul 2026 16:34:17 +0200 Subject: [PATCH 2/2] test(ic-dashboard): cover EngineeringDashboardV2 view states Signed-off-by: Aleksandr Barkhatov --- .../engineering-dashboard-v2.test.tsx | 222 ++++++++++++++++++ 1 file changed, 222 insertions(+) create mode 100644 src/screens/ic-dashboard/engineering-dashboard-v2.test.tsx diff --git a/src/screens/ic-dashboard/engineering-dashboard-v2.test.tsx b/src/screens/ic-dashboard/engineering-dashboard-v2.test.tsx new file mode 100644 index 00000000..35c1deea --- /dev/null +++ b/src/screens/ic-dashboard/engineering-dashboard-v2.test.tsx @@ -0,0 +1,222 @@ +import { render, screen } from "@testing-library/react"; +import userEvent from "@testing-library/user-event"; +import { beforeEach, describe, expect, it, vi } from "vitest"; + +import { KPI_ROW, metricGroups } from "@/lib/insight/groups"; + +const METRIC_KEYS = KPI_ROW.flatMap((s) => + s.kind === "metric" ? [s.metricKey] : [] +); +const GROUP_IDS = metricGroups().map((def) => def.id); + +const kpiState = { + isPending: false, + isFetching: false, + isError: false, + refetch: vi.fn(), +}; +let tilesReturn: Array<{ key: string }> = []; +let attentionPerGroup: Array<{ key: string }> = []; +let omitGroupId: string | null = null; + +vi.mock("@/hooks/use-period", () => ({ + usePeriod: () => ({ + period: "month", + dateRange: { from: "2026-01-01", to: "2026-01-31" }, + setPeriod: vi.fn(), + }), +})); + +vi.mock("@/hooks/use-settings", () => ({ + useSettings: () => ({ focusMode: "all" }), +})); + +vi.mock("@/lib/insight/kpi-row", () => ({ + metricKpiTiles: () => tilesReturn, +})); + +vi.mock("@/lib/insight/attention", () => ({ + metricAttentionItems: () => attentionPerGroup, +})); + +vi.mock("@/queries/metric-results", () => ({ + useMetricCollection: () => ({ + byKey: new Map(), + previousByKey: null, + ...kpiState, + }), + useMetricCollectionSet: () => { + const map = new Map(); + for (const id of GROUP_IDS) { + if (id === omitGroupId) continue; + map.set(id, { + byKey: new Map(), + previousByKey: null, + isPending: false, + isFetching: false, + isError: false, + refetch: vi.fn(), + }); + } + return map; + }, +})); + +vi.mock("@/components/widgets/v2/dashboard-header", () => ({ + DashboardHeader: ({ title }: { title: string }) => ( +
{title}
+ ), +})); + +vi.mock("@/components/widgets/v2/ic-needs-attention", () => ({ + IcNeedsAttention: ({ items }: { items: unknown[] }) => ( +
{items.length}
+ ), +})); + +vi.mock("@/components/widgets/v2/kpi-tile", () => ({ + KpiTile: ({ tile }: { tile: { key: string } }) => ( +
{tile.key}
+ ), + KpiTileLoading: () =>
, + KpiTilePlaceholder: () =>
, +})); + +vi.mock("@/components/widgets/coming-soon", () => ({ + ComingSoon: ({ onRetry }: { onRetry?: () => void }) => ( + + ), +})); + +vi.mock("@/components/widgets/metric-views/metric-group-card", () => ({ + MetricGroupCard: ({ + def, + onOpen, + }: { + def: { id: string }; + onOpen: () => void; + }) => ( + + ), +})); + +vi.mock("@/components/widgets/v2/group-drilldown-sheet", () => ({ + GroupDrilldownSheet: ({ + def, + open, + }: { + def: { id: string }; + open: boolean; + }) => ( +
+ ), +})); + +import { EngineeringDashboardV2 } from "./engineering-dashboard-v2"; + +beforeEach(() => { + kpiState.isPending = false; + kpiState.isFetching = false; + kpiState.isError = false; + kpiState.refetch = vi.fn(); + tilesReturn = []; + attentionPerGroup = []; + omitGroupId = null; +}); + +describe("EngineeringDashboardV2", () => { + it("renders a KPI tile, a card per group, and the attention section", () => { + tilesReturn = METRIC_KEYS.map((key) => ({ key })); + attentionPerGroup = [{ key: "k" }]; + + render(); + + expect(screen.getByTestId("header")).toHaveTextContent("me@x.io"); + expect(screen.getAllByTestId("kpi-tile")).toHaveLength(METRIC_KEYS.length); + expect(screen.getAllByTestId("metric-card")).toHaveLength(GROUP_IDS.length); + expect(screen.getAllByTestId("drilldown")).toHaveLength(GROUP_IDS.length); + expect(screen.getByTestId("attention")).toBeInTheDocument(); + expect( + screen + .getAllByTestId("drilldown") + .every((el) => el.dataset.open === "false") + ).toBe(true); + }); + + it("shows a retryable error tile per KPI when the collection errored", async () => { + kpiState.isError = true; + + render(); + + const errors = screen.getAllByTestId("kpi-error"); + expect(errors).toHaveLength(METRIC_KEYS.length); + await userEvent.click(errors[0]!); + expect(kpiState.refetch).toHaveBeenCalled(); + }); + + it("shows a loading tile per KPI while the collection is pending", () => { + kpiState.isPending = true; + + render(); + + expect(screen.getAllByTestId("kpi-loading")).toHaveLength( + METRIC_KEYS.length + ); + }); + + it("shows a placeholder per KPI when settled with no data", () => { + render(); + + expect(screen.getAllByTestId("kpi-placeholder")).toHaveLength( + METRIC_KEYS.length + ); + }); + + it("renders no card for a group with no query result", () => { + omitGroupId = GROUP_IDS[0]!; + + render(); + + expect(screen.getAllByTestId("metric-card")).toHaveLength( + GROUP_IDS.length - 1 + ); + }); + + it("opens the drilldown for the group whose card is clicked", async () => { + render(); + + const firstGroup = GROUP_IDS[0]!; + await userEvent.click(screen.getByRole("button", { name: firstGroup })); + + const sheet = screen + .getAllByTestId("drilldown") + .find((el) => el.dataset.group === firstGroup); + expect(sheet?.dataset.open).toBe("true"); + }); + + it("closes any open drilldown when the viewed person changes", async () => { + const { rerender } = render(); + + await userEvent.click(screen.getByRole("button", { name: GROUP_IDS[0]! })); + expect( + screen + .getAllByTestId("drilldown") + .some((el) => el.dataset.open === "true") + ).toBe(true); + + rerender(); + expect( + screen + .getAllByTestId("drilldown") + .every((el) => el.dataset.open === "false") + ).toBe(true); + }); +});