Skip to content

feat(team): rebuild team view on the unified metric-results grid - #211

Merged
aleksdotbar merged 9 commits into
mainfrom
feat/unified-metrics-team-ui
Jul 22, 2026
Merged

feat(team): rebuild team view on the unified metric-results grid#211
aleksdotbar merged 9 commits into
mainfrom
feat/unified-metrics-team-ui

Conversation

@aleksdotbar

@aleksdotbar aleksdotbar commented Jul 22, 2026

Copy link
Copy Markdown
Contributor

Summary

Rebuilds the team dashboard on the unified /v1/metric-results path and
unifies its surfaces around one semantic members grid. Also reworks dashboard
loading into a single page spinner and fixes a cross-surface name mismatch.

Team view

  • Members grid — a semantic <table> (rows = members linking to their IC
    view, columns = metrics) replaces the old heatmap widget and the group
    drilldown body. Each cell shows the member's own value, trend vs the previous
    period, and colour from their standing vs their own department cohort.
    One component, two call sites: the dashboard (fixed cross-family columns) and
    the group drilldown (the group's full collection). table-fixed with a
    per-column min-width, so wide groups scroll instead of crushing headers.
  • Standing vocabulary unified across grid chips, section cards, and the
    attention list — "N behind / N ahead / on par" (behind wins), derived from
    the shared derivePeerStanding + section scoring rather than per-surface
    logic.
  • Section cards reworked: composition bars removed; each preview row states
    the roster's standing in the shared vocabulary, "no peer data" when nobody is
    rankable (rows never silently drop).
  • Cell detail on hover via base-ui PreviewCard (hover-only, no click-pin):
    value, gap-vs-median magnitude, trend, standing.
  • Compact column headers driven by the server short_label (full label in the
    tooltip); drilldown drops the peer view and computes a group-local standing
    facet from its own cells.

Formatting

  • Runaway period-over-period deltas render as a multiple ("56×") past the same
    threshold as the vs-median gap, via a shared formatMultiple — fixes absurd
    "+5460%" readouts on tiles and cells.
  • Cohort label threaded through grid / overview / drilldown (no hardcoded
    "department").

Loading

  • Person and team dashboards gate behind a single centered page spinner while
    their queries load; a period change re-trips the same gate. No per-widget
    spinners or partial paints.
  • The app shell waits for viewer identity once (root pendingComponent), so the
    sidebar org tree, headers, and bottom identity mount complete — nothing pops
    in. A subordinate's identity seeds from the cached viewer tree for an instant
    named header; headers never fall back to a raw email.

Fixes

  • The team grid renders the identity-service name (matching the sidebar and
    person header) instead of the analytics display_name, so a person whose
    directory and HR name are different transliterations no longer appears under
    two spellings on one screen.

Depends on

  • Backend short_label addition (constructorfabric/insight) for the compact
    headers; degrades to full labels without it.

Testing

  • tsc -b, pnpm lint, pnpm test — 321 passed

Summary by CodeRabbit

  • New Features

    • Added a sortable, scrollable Members × Metrics overview with peer standings, trends, median context, and detailed previews.
    • Added mobile member triage cards showing ahead, behind, or on-par status and the most concerning metric.
    • Added chart/table switching and CSV/XLSX exports for time-series data.
    • Added compact metric labels and clearer large-change formatting.
  • Bug Fixes

    • Improved loading states with a consistent centered spinner.
    • Grand totals are now hidden when no total values are available.
    • Improved member names and dashboard identity loading.

Signed-off-by: Aleksandr Barkhatov <pm@aleks.bar>
Signed-off-by: Aleksandr Barkhatov <pm@aleks.bar>
…trailing list

Signed-off-by: Aleksandr Barkhatov <pm@aleks.bar>
Signed-off-by: Aleksandr Barkhatov <pm@aleks.bar>
…members grid

Signed-off-by: Aleksandr Barkhatov <pm@aleks.bar>
… spinner

Signed-off-by: Aleksandr Barkhatov <pm@aleks.bar>
The grid took the member name from the analytics row (BambooHR) while the
sidebar and header use the identity-service name (Entra); for a person whose
transliterations differ the same person_id showed two spellings. The roster
is the identity source, so the grid now renders the identity name it already
holds and analytics supplies only the metric values.

Signed-off-by: Aleksandr Barkhatov <pm@aleks.bar>
@aleksdotbar
aleksdotbar requested a review from a team as a code owner July 22, 2026 10:08
@coderabbitai

coderabbitai Bot commented Jul 22, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

This PR migrates V2 team views to metric-backed member grids, adds shared loading, timeseries, formatting, and preview-card components, updates metric contracts and queries, and removes legacy heatmap, department-distribution, and per-tile loading implementations.

Changes

Metric-backed member dashboard

Layer / File(s) Summary
Member grid and overview
src/components/widgets/v2/members-grid.tsx, src/components/widgets/v2/members-overview.tsx, src/components/widgets/v2/triage-list.tsx
Adds sortable metric tables, peer-standing facets, trend indicators, preview cards, responsive triage rendering, and related tests.
Dashboard and drilldown integration
src/screens/team-view-v2.tsx, src/components/widgets/metric-views/team-collection-drilldown.tsx, src/components/widgets/v2/group-drilldown-sheet.tsx
Replaces legacy heatmap and pre-fetched drilldown data with useMemberGridData, MembersOverview, and metric-group drilldowns.
Shared metric and query foundations
src/api/*, src/lib/metrics/*, src/lib/insight/*, src/queries/v2/member-grid.ts, src/queries/metric-results.ts
Adds short labels, heatmap configuration, delta helpers, worst-entry selection, peer-standing delegation, and collection pending detection.
Loading and supporting UI
src/components/widgets/centered-spinner.tsx, src/components/ui/preview-card.tsx, src/routes/__root.tsx, src/screens/ic-dashboard/engineering-dashboard-v2.tsx
Adds shared centered loading and preview-card primitives, root pending rendering, and page-level dashboard loading.
Timeseries chrome and exports
src/components/widgets/metric-views/metric-timeseries-*.tsx
Extracts presentation toggling, CSV/XLSX export, and loading/error/content rendering into shared components and suppresses empty grand-total rows.
Legacy path removal and cleanup
src/components/widgets/v2/members-heatmap/*, src/queries/v2/team-extras.ts, src/lib/insight/v2/team-member-status.ts, src/mocks/*, src/lib/peers.ts
Removes obsolete heatmap, department-distribution, bullet-query, peer-status, and mock metric implementations.
Peer and KPI presentation updates
src/components/widgets/metric-views/peer-comparison.tsx, src/components/widgets/metric-views/team-metric-group-card.tsx, src/components/widgets/v2/kpi-tile.tsx
Adjusts peer arrow positioning, updates standing text, and removes the per-tile loading component.

Estimated code review effort: 4 (Complex) | ~60 minutes

Sequence Diagram(s)

sequenceDiagram
  participant TeamViewV2
  participant useMemberGridData
  participant MetricCollectionQueries
  participant MembersOverview
  TeamViewV2->>useMemberGridData: request current and previous member metrics
  useMemberGridData->>MetricCollectionQueries: fetch period, peer, and previous-period views
  MetricCollectionQueries-->>useMemberGridData: return keyed metric maps and query state
  useMemberGridData-->>TeamViewV2: provide grid data and loading/error state
  TeamViewV2->>MembersOverview: render members with metric maps
  MembersOverview-->>TeamViewV2: render desktop grid or mobile triage list
Loading

Possibly related PRs

Suggested reviewers: ktursunov, cyberantonz

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 37.31% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and concisely reflects the main change: rebuilding the team view around the unified metric-results grid.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/unified-metrics-team-ui

Warning

There were issues while running some tools. Please review the errors and either fix the tool's configuration or disable the tool if it's a critical failure.

🔧 ESLint

If the error stems from missing dependencies, add them to the package.json file. For unrecoverable errors (e.g., due to private dependencies), disable the tool in the CodeRabbit configuration.

ESLint install timed out. The project may have too many dependencies for the sandbox.


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

… hook

Signed-off-by: Aleksandr Barkhatov <pm@aleks.bar>
…he bar

Signed-off-by: Aleksandr Barkhatov <pm@aleks.bar>

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🧹 Nitpick comments (2)
src/components/widgets/metric-views/metric-timeseries-chrome.tsx (1)

58-78: 🩺 Stability & Availability | 🔵 Trivial | 💤 Low value

Clean extraction; export failures fail silently.

exportXlsx resets isExporting in finally but has no catch, so a rejection from downloadMetricTimeseriesXlsx becomes an unhandled promise rejection with no user feedback beyond the spinner disappearing. Low risk given the export path is mostly local blob generation, so not blocking.

Also applies to: 100-104

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/components/widgets/metric-views/metric-timeseries-chrome.tsx` around
lines 58 - 78, Update TimeseriesExportMenu’s exportXlsx to catch
downloadMetricTimeseriesXlsx failures, provide user-facing feedback using the
component’s established notification/error mechanism, and retain the existing
isExporting reset in finally.
src/components/widgets/metric-views/metric-timeseries-csv.ts (1)

1-1: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Extract a shared hasGrandTotal(model) predicate instead of triplicating the condition.

The same model.dimensions.length > 0 && model.grandTotals.some((value) => value != null) check now lives in three separate export/render paths. Since this PR already had to update all three in lockstep, a shared helper (e.g. exported from metric-timeseries-model.ts) would prevent CSV/XLSX/table exports from silently drifting apart on a future change.

  • src/components/widgets/metric-views/metric-timeseries-csv.ts#L79-82: replace the inline condition with a call to the shared hasGrandTotal(model) helper.
  • src/components/widgets/metric-views/metric-timeseries-xlsx.ts#L197-200: replace the inline if (...) condition with hasGrandTotal(model).
  • src/components/widgets/metric-views/metric-timeseries-table.tsx#L193-194: replace the inline ternary condition with hasGrandTotal(model).
♻️ Proposed helper
+export function hasGrandTotal(model: MetricTimeseriesModel): boolean {
+  return (
+    model.dimensions.length > 0 &&
+    model.grandTotals.some((value) => value != null)
+  );
+}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/components/widgets/metric-views/metric-timeseries-csv.ts` at line 1,
Extract the repeated grand-total check into a shared hasGrandTotal(model)
helper, preferably in metric-timeseries-model.ts, preserving the existing
dimensions and non-null grandTotals logic. Update the CSV export, XLSX export,
and table rendering paths to call this helper instead of duplicating the inline
condition.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/components/widgets/v2/members-grid.tsx`:
- Around line 206-210: Initialize the sort state in the members grid from
hasIssuesFacet: use "issues" when the issues facet is visible and "name" when it
is hidden. Preserve the existing reversed value and sorting behavior so the
visible Name control accurately reflects the initial ordering.

In `@src/queries/v2/member-grid.ts`:
- Around line 75-77: Update the member-grid query status mapping to include the
previous-period result: in the object containing isPending, isFetching, and
isError, combine cur and prev so pending remains true while either query is
pending and errors remain visible when either query fails. Preserve the existing
isFetching behavior and false defaults when neither result reports the status.

---

Nitpick comments:
In `@src/components/widgets/metric-views/metric-timeseries-chrome.tsx`:
- Around line 58-78: Update TimeseriesExportMenu’s exportXlsx to catch
downloadMetricTimeseriesXlsx failures, provide user-facing feedback using the
component’s established notification/error mechanism, and retain the existing
isExporting reset in finally.

In `@src/components/widgets/metric-views/metric-timeseries-csv.ts`:
- Line 1: Extract the repeated grand-total check into a shared
hasGrandTotal(model) helper, preferably in metric-timeseries-model.ts,
preserving the existing dimensions and non-null grandTotals logic. Update the
CSV export, XLSX export, and table rendering paths to call this helper instead
of duplicating the inline condition.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 726280f0-da3b-4f77-bf7a-e6e6d546cd02

📥 Commits

Reviewing files that changed from the base of the PR and between 79b3ed7 and 456f5e5.

📒 Files selected for processing (58)
  • src/api/metric-registry.ts
  • src/api/metric-results-client.ts
  • src/components/ui/preview-card.tsx
  • src/components/widgets/centered-spinner.tsx
  • src/components/widgets/metric-views/metric-timeseries-chrome.tsx
  • src/components/widgets/metric-views/metric-timeseries-csv.ts
  • src/components/widgets/metric-views/metric-timeseries-model.ts
  • src/components/widgets/metric-views/metric-timeseries-presentation.ts
  • src/components/widgets/metric-views/metric-timeseries-table.tsx
  • src/components/widgets/metric-views/metric-timeseries-view.tsx
  • src/components/widgets/metric-views/metric-timeseries-xlsx.ts
  • src/components/widgets/metric-views/peer-comparison.tsx
  • src/components/widgets/metric-views/team-collection-drilldown.test.tsx
  • src/components/widgets/metric-views/team-collection-drilldown.tsx
  • src/components/widgets/metric-views/team-metric-group-card.test.tsx
  • src/components/widgets/metric-views/team-metric-group-card.tsx
  • src/components/widgets/v2/group-drilldown-sheet.test.tsx
  • src/components/widgets/v2/group-drilldown-sheet.tsx
  • src/components/widgets/v2/kpi-tile.test.tsx
  • src/components/widgets/v2/kpi-tile.tsx
  • src/components/widgets/v2/members-grid.test.tsx
  • src/components/widgets/v2/members-grid.tsx
  • src/components/widgets/v2/members-heatmap/index.test.tsx
  • src/components/widgets/v2/members-heatmap/index.tsx
  • src/components/widgets/v2/members-heatmap/member-details-sheet.tsx
  • src/components/widgets/v2/members-heatmap/triage-list.tsx
  • src/components/widgets/v2/members-overview.test.tsx
  • src/components/widgets/v2/members-overview.tsx
  • src/components/widgets/v2/team-members-attention.test.tsx
  • src/components/widgets/v2/team-members-attention.tsx
  • src/components/widgets/v2/triage-list.test.tsx
  • src/components/widgets/v2/triage-list.tsx
  • src/lib/format.ts
  • src/lib/insight/groups.ts
  • src/lib/insight/kpi-row.ts
  • src/lib/insight/team-metrics.ts
  • src/lib/insight/v2/team-member-status.test.ts
  • src/lib/insight/v2/team-member-status.ts
  • src/lib/metrics/collection.ts
  • src/lib/metrics/delta.test.ts
  • src/lib/metrics/delta.ts
  • src/lib/metrics/gap.ts
  • src/lib/metrics/peer-story.ts
  • src/lib/peers.ts
  • src/mocks/factories.ts
  • src/mocks/handlers.ts
  • src/queries/ic-dashboard.ts
  • src/queries/metric-results.test.tsx
  • src/queries/metric-results.ts
  • src/queries/team-view.ts
  • src/queries/v2/member-grid.test.tsx
  • src/queries/v2/member-grid.ts
  • src/queries/v2/team-extras.ts
  • src/routes/__root.tsx
  • src/screens/ic-dashboard/engineering-dashboard-v2.test.tsx
  • src/screens/ic-dashboard/engineering-dashboard-v2.tsx
  • src/screens/team-view-v2.test.tsx
  • src/screens/team-view-v2.tsx
💤 Files with no reviewable changes (12)
  • src/lib/insight/v2/team-member-status.test.ts
  • src/components/widgets/v2/members-heatmap/index.test.tsx
  • src/components/widgets/v2/members-heatmap/index.tsx
  • src/lib/insight/v2/team-member-status.ts
  • src/queries/v2/team-extras.ts
  • src/components/widgets/v2/members-heatmap/triage-list.tsx
  • src/components/widgets/v2/kpi-tile.test.tsx
  • src/components/widgets/v2/kpi-tile.tsx
  • src/components/widgets/v2/members-heatmap/member-details-sheet.tsx
  • src/mocks/factories.ts
  • src/lib/peers.ts
  • src/mocks/handlers.ts

Comment on lines +206 to +210
const hasIssuesFacet = showIssues ?? countsByMember != null;
const [sort, setSort] = useState<SortState>({
key: "issues",
reversed: false,
});

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Default the initial sort to name when the issues facet is hidden.

When hasIssuesFacet is false, only the Name toggle renders on the Member header — the "Most behind" control is gone. But the initial sort key is still "issues", so rows load ordered by counts.bottom with no active-sort indicator and no way for the user to return to that ordering once they click Name. Seed the state from hasIssuesFacet so the visible control matches the actual order.

Proposed fix
   const [sort, setSort] = useState<SortState>({
-    key: "issues",
+    key: hasIssuesFacet ? "issues" : "name",
     reversed: false,
   });
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const hasIssuesFacet = showIssues ?? countsByMember != null;
const [sort, setSort] = useState<SortState>({
key: "issues",
reversed: false,
});
const hasIssuesFacet = showIssues ?? countsByMember != null;
const [sort, setSort] = useState<SortState>({
key: hasIssuesFacet ? "issues" : "name",
reversed: false,
});
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/components/widgets/v2/members-grid.tsx` around lines 206 - 210,
Initialize the sort state in the members grid from hasIssuesFacet: use "issues"
when the issues facet is visible and "name" when it is hidden. Preserve the
existing reversed value and sorting behavior so the visible Name control
accurately reflects the initial ordering.

Comment on lines +75 to +77
isPending: cur?.isPending ?? false,
isFetching: (cur?.isFetching ?? false) || (prev?.isFetching ?? false),
isError: cur?.isError ?? false,

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Wait for the previous-period query too.

isPending and isError ignore prev, so the grid can paint without trends or hide a previous-period failure when only the current request has settled.

Proposed fix
-    isPending: cur?.isPending ?? false,
+    isPending: (cur?.isPending ?? false) || (prev?.isPending ?? false),
     isFetching: (cur?.isFetching ?? false) || (prev?.isFetching ?? false),
-    isError: cur?.isError ?? false,
+    isError: (cur?.isError ?? false) || (prev?.isError ?? false),
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
isPending: cur?.isPending ?? false,
isFetching: (cur?.isFetching ?? false) || (prev?.isFetching ?? false),
isError: cur?.isError ?? false,
isPending: (cur?.isPending ?? false) || (prev?.isPending ?? false),
isFetching: (cur?.isFetching ?? false) || (prev?.isFetching ?? false),
isError: (cur?.isError ?? false) || (prev?.isError ?? false),
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/queries/v2/member-grid.ts` around lines 75 - 77, Update the member-grid
query status mapping to include the previous-period result: in the object
containing isPending, isFetching, and isError, combine cur and prev so pending
remains true while either query is pending and errors remain visible when either
query fails. Preserve the existing isFetching behavior and false defaults when
neither result reports the status.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment