diff --git a/src/api/catalog-client.ts b/src/api/catalog-client.ts index 78fa190..c81759d 100644 --- a/src/api/catalog-client.ts +++ b/src/api/catalog-client.ts @@ -121,7 +121,6 @@ export function prefixForBulletSection(section: string): string { if (section === 'code_quality') return 'code_quality_bullet_rows'; if (section === 'ai_adoption') return 'ai_bullet_rows'; if (section === 'collaboration') return 'collab_bullet_rows'; - if (section === 'wiki') return 'wiki_bullet_rows'; return 'task_delivery_bullet_rows'; } diff --git a/src/api/metric-registry.ts b/src/api/metric-registry.ts index 77043d1..c0a6dc6 100644 --- a/src/api/metric-registry.ts +++ b/src/api/metric-registry.ts @@ -6,7 +6,6 @@ export const METRIC_REGISTRY = { TEAM_BULLET_COLLAB: "00000000-0000-0000-0001-000000000005", TEAM_BULLET_AI: "00000000-0000-0000-0001-000000000006", TEAM_BULLET_GIT: "00000000-0000-0000-0001-000000000007", - TEAM_BULLET_WIKI: "00000000-0000-0000-0001-000000000040", IC_KPIS: "00000000-0000-0000-0001-000000000010", IC_BULLET_DELIVERY: "00000000-0000-0000-0001-000000000011", @@ -17,7 +16,6 @@ export const METRIC_REGISTRY = { IC_DRILL: "00000000-0000-0000-0001-000000000016", IC_TIMEOFF: "00000000-0000-0000-0001-000000000017", IC_BULLET_GIT: "00000000-0000-0000-0001-000000000018", - IC_BULLET_WIKI: "00000000-0000-0000-0001-000000000041", CRM_KPIS: "00000000-0000-0000-0001-000000000020", CRM_CHART_FLOW: "00000000-0000-0000-0001-000000000021", diff --git a/src/components/widgets/v2/group-drilldown-sheet.test.tsx b/src/components/widgets/v2/group-drilldown-sheet.test.tsx index 29e5d3c..a8f38e9 100644 --- a/src/components/widgets/v2/group-drilldown-sheet.test.tsx +++ b/src/components/widgets/v2/group-drilldown-sheet.test.tsx @@ -31,7 +31,11 @@ const METRIC_DEF: GroupDef = { card: { preview: [] }, drilldown: [], }; -const LEGACY_DEF: GroupDef = { kind: "legacy", id: "wiki", title: "Wiki" }; +const LEGACY_DEF: GroupDef = { + kind: "legacy", + id: "wiki", + title: "Legacy section", +}; const EMPTY_RESULT: MetricCollectionResult = { byKey: new Map(), @@ -84,7 +88,7 @@ describe("GroupDrilldownSheet", () => { it("routes a legacy group to the legacy body", () => { renderSheet(LEGACY_DEF); - expect(screen.getByText("Wiki")).toBeInTheDocument(); + expect(screen.getByText("Legacy section")).toBeInTheDocument(); expect( screen.getByText(/No data for this section/i), ).toBeInTheDocument(); diff --git a/src/lib/insight/groups.ts b/src/lib/insight/groups.ts index 47bc759..e72e882 100644 --- a/src/lib/insight/groups.ts +++ b/src/lib/insight/groups.ts @@ -285,6 +285,29 @@ const COLLABORATION_COLLECTION: MetricCollectionConfig = { ], }; +const WIKI_COLLECTION: MetricCollectionConfig = { + metrics: [ + { + key: "wiki.pages_created", + views: [ + { view: "period" }, + { view: "peer" }, + { view: "timeseries", bucket: "auto" }, + ], + }, + { + key: "wiki.edits", + views: [ + { view: "period" }, + { view: "peer" }, + { view: "timeseries", bucket: "auto" }, + ], + }, + { key: "wiki.pages_edited", views: [{ view: "period" }, { view: "peer" }] }, + { key: "wiki.comments", views: [{ view: "period" }, { view: "peer" }] }, + ], +}; + export const GROUPS: readonly GroupDef[] = [ { kind: "metrics", @@ -386,7 +409,22 @@ export const GROUPS: readonly GroupDef[] = [ }, ], }, - { kind: "legacy", id: "wiki", title: "Wiki" }, + { + kind: "metrics", + id: "wiki", + title: "Wiki", + collection: WIKI_COLLECTION, + card: { + preview: ["wiki.pages_created", "wiki.edits", "wiki.comments"], + }, + drilldown: [ + { + chart: "line", + view: "timeseries", + metrics: ["wiki.pages_created", "wiki.edits"], + }, + ], + }, ]; export function groupById(id: GroupId): GroupDef { diff --git a/src/lib/insight/v2/bullet-defs.ts b/src/lib/insight/v2/bullet-defs.ts index afc9d08..85516d1 100644 --- a/src/lib/insight/v2/bullet-defs.ts +++ b/src/lib/insight/v2/bullet-defs.ts @@ -31,9 +31,4 @@ export const BULLET_DESCRIPTION_BY_KEY: ReadonlyMap = new Map< build_success: "CI runs passed vs total", pr_cycle_time: "Hours from PR open to merge", bugs_fixed: "Bug-type issues closed", - - wiki_pages_created: "Wiki pages authored (Confluence/Outline)", - wiki_edits: "Wiki page revisions authored", - wiki_comments: "Comments received on the person's wiki pages", - wiki_active_authors: "Members who authored or edited the wiki this period", })); diff --git a/src/lib/insight/v2/metric-order.ts b/src/lib/insight/v2/metric-order.ts index e11b3c2..d525d83 100644 --- a/src/lib/insight/v2/metric-order.ts +++ b/src/lib/insight/v2/metric-order.ts @@ -5,12 +5,6 @@ export const METRIC_ORDER_BY_SECTION: Record = { "pr_cycle_time", "prs_per_dev", ], - wiki: [ - "wiki_active_authors", - "wiki_pages_created", - "wiki_edits", - "wiki_comments", - ], }; export function orderRowsForSection( diff --git a/src/queries/ic-dashboard.ts b/src/queries/ic-dashboard.ts index 0b77591..8169b81 100644 --- a/src/queries/ic-dashboard.ts +++ b/src/queries/ic-dashboard.ts @@ -92,7 +92,6 @@ const BULLET_SECTIONS = { collaboration: METRIC_REGISTRY.IC_BULLET_COLLAB, ai_adoption: METRIC_REGISTRY.IC_BULLET_AI, git_output: METRIC_REGISTRY.IC_BULLET_GIT, - wiki: METRIC_REGISTRY.IC_BULLET_WIKI, } as const; export type IcBulletSectionId = keyof typeof BULLET_SECTIONS; @@ -214,7 +213,6 @@ export type IcDashboardSection = | "git_output" | "ai_adoption" | "collaboration" - | "wiki" | "loc_trend" | "delivery_trend" | "time_off"; @@ -226,7 +224,6 @@ export interface IcDashboardData { gitOutput: BulletMetric[]; aiAdoption: BulletMetric[]; collaboration: BulletMetric[]; - wiki: BulletMetric[]; locTrend: LocDataPoint[]; deliveryTrend: DeliveryDataPoint[]; timeOff: TimeOffNotice | null; @@ -295,11 +292,6 @@ export function useIcDashboardData( metric_id: METRIC_REGISTRY.IC_BULLET_COLLAB, $filter: filter, }, - { - id: "wiki", - metric_id: METRIC_REGISTRY.IC_BULLET_WIKI, - $filter: filter, - }, { id: "loc_trend", metric_id: METRIC_REGISTRY.IC_CHART_LOC, @@ -391,14 +383,6 @@ export function useIcDashboardData( "ic", cat, ), - wiki: transformBulletMetrics( - get("wiki") ?? [], - "wiki", - period, - undefined, - "ic", - cat, - ), locTrend: transformLocTrend( get("loc_trend") ?? [], period, @@ -418,7 +402,6 @@ export function useIcDashboardData( git_output: sectionErrored("git_output"), ai_adoption: sectionErrored("ai_adoption"), collaboration: sectionErrored("collaboration"), - wiki: sectionErrored("wiki"), loc_trend: sectionErrored("loc_trend"), delivery_trend: sectionErrored("delivery_trend"), time_off: sectionErrored("time_off"), diff --git a/src/queries/team-view.test.ts b/src/queries/team-view.test.ts new file mode 100644 index 0000000..5d6ea2d --- /dev/null +++ b/src/queries/team-view.test.ts @@ -0,0 +1,15 @@ +import { describe, expect, it } from "vitest"; + +import { isTeamBulletSectionId } from "@/queries/team-view"; + +describe("isTeamBulletSectionId", () => { + it("accepts ids backed by a team bullet section", () => { + expect(isTeamBulletSectionId("collaboration")).toBe(true); + expect(isTeamBulletSectionId("task_delivery")).toBe(true); + }); + + it("rejects ids with no team bullet section", () => { + expect(isTeamBulletSectionId("wiki")).toBe(false); + expect(isTeamBulletSectionId("nonsense")).toBe(false); + }); +}); diff --git a/src/queries/team-view.ts b/src/queries/team-view.ts index 33243bd..040ac4e 100644 --- a/src/queries/team-view.ts +++ b/src/queries/team-view.ts @@ -137,11 +137,14 @@ const TEAM_BULLET_SECTIONS = { estimation: METRIC_REGISTRY.TEAM_BULLET_DELIVERY, collaboration: METRIC_REGISTRY.TEAM_BULLET_COLLAB, ai_adoption: METRIC_REGISTRY.TEAM_BULLET_AI, - wiki: METRIC_REGISTRY.TEAM_BULLET_WIKI, } as const; export type TeamBulletSectionId = keyof typeof TEAM_BULLET_SECTIONS; +export function isTeamBulletSectionId(id: string): id is TeamBulletSectionId { + return id in TEAM_BULLET_SECTIONS; +} + /** * Scope a team bullet aggregate to the members actually shown — the * identity-tree subtree the screen renders (transitive + active) — by @@ -237,7 +240,11 @@ export function useTeamBulletSections( range.to, catalogKey, ], - enabled: Boolean(teamId) && Boolean(options?.roster?.length) && Boolean(catalog), + enabled: + sectionIds.length > 0 && + Boolean(teamId) && + Boolean(options?.roster?.length) && + Boolean(catalog), placeholderData: options?.keepPrevious ? keepPreviousData : undefined, queryFn: async () => { const roster = options?.roster; diff --git a/src/screens/ic-dashboard/engineering-dashboard-v2.tsx b/src/screens/ic-dashboard/engineering-dashboard-v2.tsx index 835fb4c..0a7f5e8 100644 --- a/src/screens/ic-dashboard/engineering-dashboard-v2.tsx +++ b/src/screens/ic-dashboard/engineering-dashboard-v2.tsx @@ -64,14 +64,18 @@ const CLOSED_DRILLDOWN_DATA = { refetch: () => {}, } as const; -// The one per-key seam that survives coexistence: which legacy batch field -// feeds each legacy group's rows. Dies with `LegacyGroup`. -const LEGACY_GROUP_ROWS: Record< +// 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, - (data: IcDashboardData | undefined) => BulletMetric[] -> = { - wiki: (data) => data?.wiki ?? [], -}; + { + rows: (data: IcDashboardData | undefined) => BulletMetric[]; + errored: (data: IcDashboardData | undefined) => boolean; + } +> = {}; +/* v8 ignore stop */ export interface EngineeringDashboardV2Props { personId: string; @@ -128,7 +132,10 @@ export function EngineeringDashboardV2({ Object.fromEntries( GROUPS.filter((def) => def.kind === "legacy").map((def) => [ def.id, - orderRowsForSection(def.id, LEGACY_GROUP_ROWS[def.id]?.(data) ?? []), + orderRowsForSection( + def.id, + LEGACY_GROUP_FEEDS[def.id]?.rows(data) ?? [], + ), ]), ); @@ -337,7 +344,8 @@ export function EngineeringDashboardV2({ /> ); } - if (data?.errors[def.id]) { + /* v8 ignore next -- unreachable while no legacy group exists */ + if (LEGACY_GROUP_FEEDS[def.id]?.errored(data)) { return ( def.id); +// The map/filter callbacks are inert while no group is `kind: "legacy"` +// (the array is empty) — retained so the legacy team-bullet path lights up +// again if a legacy group returns. +/* v8 ignore start -- map/filter callbacks run only when a legacy group exists */ +const LEGACY_GROUP_IDS = legacyGroups() + .map((def) => def.id) + .filter((id): id is Extract => + isTeamBulletSectionId(id), + ); +/* v8 ignore stop */ // Team surfaces request period + peer only: a per-member timeseries over a // large roster would exceed the backend's all-or-nothing row limit and fail @@ -181,8 +192,11 @@ export function TeamViewV2Screen({ teamId, viewerEmail }: TeamViewV2ScreenProps) memberEntityIds, ); - const sectionsPending = sectionsQ.isPending; - const sectionsFetching = sectionsQ.isFetching; + // With no legacy groups the bullet query is disabled and never leaves + // TanStack's "pending" state — an empty section set is settled, not loading. + const hasLegacySections = LEGACY_GROUP_IDS.length > 0; + const sectionsPending = hasLegacySections && sectionsQ.isPending; + const sectionsFetching = hasLegacySections && sectionsQ.isFetching; // Only a metric group's revalidation dims the page (replacing data already // shown); its first load has no prior data and shows its own card spinner. const isMetricsRevalidating = [...metricGroupData.values()].some(