Skip to content

feat: Feature Analytics label grouping (#6067)#7215

Open
talissoncosta wants to merge 18 commits intomainfrom
feat/feature-analytics-6067
Open

feat: Feature Analytics label grouping (#6067)#7215
talissoncosta wants to merge 18 commits intomainfrom
feat/feature-analytics-6067

Conversation

@talissoncosta
Copy link
Copy Markdown
Contributor

@talissoncosta talissoncosta commented Apr 10, 2026

  • I have read the Contributing Guide.
  • I have added information to docs/ if required so people know about the feature.
  • I have filled in the "Changes" section below.
  • I have filled in the "How did you test this code" section below.

Changes

Closes #6067

Feature Analytics label grouping

When the API returns labelled evaluation buckets (per-SDK, via user_agent / client_application_name), the chart now stacks bars by label value with matching colours and exposes a MultiSelect filter to narrow visible SDKs. When no usable labels are present, the chart falls back to the existing environment-grouped view.

How to review this PR

The diff is grouped by theme. Suggested reading order:

1. The feature itself

  • web/components/feature-page/FeatureNavTab/FeatureAnalytics.tsx — the consumer
  • web/components/feature-page/FeatureNavTab/utils.tshasLabelledData, aggregateByLabels
  • web/components/feature-page/FeatureNavTab/useEnvChartProps.ts — env-grouped chart config (colour + label + stable ordering), reusable for the legacy chart migration (see follow-up PR refactor: Consolidate charts with BarChart component #7223)
  • common/services/useFeatureAnalytics.ts — now returns { chartData, rawEntries } (split so labelled-mode can group raw entries without a rules-of-hooks violation)
  • common/types/responses.tsenvironmentAnalytics.labels added

2. Shared chart infra (new)

  • web/components/charts/BarChart.tsx — reusable stacked bar chart wrapping recharts. Supports seriesLabels (dataKey → display name), optional showLegend, and colour tokens passed as prop values directly
  • web/components/charts/ChartTooltip.tsx — typed against recharts' Payload<ValueType, NameType>, uses Bootstrap utilities + semantic token classes
  • web/components/charts/buildChartColorMap.ts — pure helper for building a label → palette-colour Record<string, string>, no hook / DOM read
  • web/components/ColorSwatch.tsx — reusable colour indicator (sm 8px / md 12px / lg 16px)

3. Flat token constants pattern

  • scripts/generate-tokens.mjs + regenerated common/theme/tokens.ts — emits 74 flat camelCase constants (e.g. colorChart1, colorTextSecondary, radiusMd) as var(--token, #hex) CSS value strings. Pass directly to recharts props, inline styles, anywhere a CSS value is accepted. Browsers resolve var() in SVG presentation attributes and in dark mode via the CSS cascade — no hook, no DOM read, no theme-toggle staleness
  • New chart colour tokens: --color-chart-1 through --color-chart-10 with light/dark variants
  • web/styles/3rdParty/_recharts.scss — new partial with legacy tooltip rules; deleted entirely in the follow-up PR once the two remaining raw-recharts consumers migrate

4. Storybook stories

  • Components/BarChart — WithLabelledBuckets (interactive filter), WithoutLabels, SingleSeries
  • Components/MultiSelect — 7 variants (Default, WithLabel, WithColors, PreSelected, Disabled, Inline, Small)

Key design decisions

  • var() in SVG presentation attributes works in modern browsers (Chrome 85+, Firefox 80+, Safari 14+), so we no longer need a useChartColors hook reading getComputedStyle. Passing var(--color-chart-1, #0aaddf) directly to <Bar fill={...}> lets the browser resolve the variable at render, and CSS cascade updates it live on theme toggle (fixes an implicit bug where the old hook memoised hex values at mount).
  • colorMap as Record<string, string> everywhere (BarChart, MultiSelect, aggregateByLabels, buildChartColorMap). Previously a mix of Map and Record — unified.
  • Env mode uses Utils.getTagColour(indexInProjectEnvList) so bar colours match the env chip colours 1:1; labelled mode uses CHART_COLOURS.
  • Tooltip renders seriesLabels?.[dataKey] ?? dataKey so numeric env ids (22, 1848, …) surface as "Production", "Staging", etc.

Follow-ups (separate PRs)

  • refactor: Consolidate charts with BarChart component #7223 — Consolidate org-usage charts (OrganisationUsage, SingleSDKLabelsChart) into BarChart, drop the legacy _recharts.scss partial
  • Tighten the any cluster in common/services/useFeatureAnalytics.ts (pre-existing, flagged but out of scope here)
  • Opportunistic adoption of the flat token constants across inline-style consumers in the codebase

How did you test this code?

In Storybook

  1. npm run storybook
  2. Components/BarChart — all 3 stories render with fake data, filter + tooltip + legend behave correctly in light and dark mode
  3. Components/MultiSelect — all 7 variants render in both themes

In the app

  1. ENV=local npm run dev
  2. Navigate to a feature flag → Usage tab → chart renders with env-grouped stacks (colours match env chips, no redundant legend, tooltip shows env names like "Production: 91")
  3. If labelled data exists, bars stack per-SDK with a MultiSelect filter; chart legend shows SDK names
  4. Toggle dark mode — bars + axis recolour live without remount

Automated

  • npx eslint — 0 errors on touched files
  • npm run typecheck — 0 new errors (pre-existing any cluster in useFeatureAnalytics.ts is unchanged)
  • npm run test:unit — 20 passing (17 utils, 3 buildChartColorMap)

🤖 Generated with Claude Code

@vercel
Copy link
Copy Markdown

vercel bot commented Apr 10, 2026

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
flagsmith-frontend-preview Ready Ready Preview, Comment Apr 13, 2026 7:39pm
flagsmith-frontend-staging Ready Ready Preview, Comment Apr 13, 2026 7:39pm
1 Skipped Deployment
Project Deployment Actions Updated (UTC)
docs Ignored Ignored Preview Apr 13, 2026 7:39pm

Request Review

@github-actions github-actions bot added front-end Issue related to the React Front End Dashboard feature New feature or request labels Apr 10, 2026
@github-actions github-actions bot added feature New feature or request and removed feature New feature or request labels Apr 10, 2026
@github-actions github-actions bot added feature New feature or request and removed feature New feature or request labels Apr 10, 2026
@github-actions github-actions bot added feature New feature or request and removed feature New feature or request labels Apr 10, 2026
@github-actions github-actions bot added feature New feature or request and removed feature New feature or request labels Apr 10, 2026
@github-actions github-actions bot added feature New feature or request and removed feature New feature or request labels Apr 10, 2026
@talissoncosta talissoncosta marked this pull request as ready for review April 10, 2026 18:49
@talissoncosta talissoncosta requested a review from a team as a code owner April 10, 2026 18:49
@talissoncosta talissoncosta requested review from kyle-ssg and removed request for a team April 10, 2026 18:49
Copy link
Copy Markdown

@claude claude bot left a comment

Choose a reason for hiding this comment

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

⚠️ Code review skipped — your organization's overage spend limit has been reached.

Code review is billed via overage credits. To resume reviews, an organization admin can raise the monthly limit at claude.ai/admin-settings/claude-code.

Once credits are available, reopen this pull request to trigger a review.

…ull labels

Three bugs caused labelled analytics to look wrong in real data:

1. hasLabelledData was too permissive — passed `{ user_agent: null }`
   as "labelled" because the key exists. Every entry then fell through
   to the 'Unknown' fallback in aggregation, collapsing all data into
   a single series. Tightened to require at least one entry with a
   non-empty `user_agent` or `client_application_name` value.

2. chartData from aggregateByLabels had no date ordering guarantee —
   dates appeared in whatever order the raw entries landed (visible
   in prod as `2026-04-03` rendered AFTER `2026-04-12`). Fixed by
   having aggregateByLabels seed its output from a caller-supplied
   day axis (chronologically pre-built by useFeatureAnalytics for
   the env-path chart).

3. Labelled chart only included days with events, so the x-axis was
   sparse — whereas the env path pre-builds all 30 days. Fixed by
   reusing the env-path day axis, giving both paths the same complete
   date range and the same 'Do MMM' display format.

hasData check updated — labelledChartData now always has one bucket
per day (including empty ones), so `.length > 0` no longer indicates
presence of data. Switched to "any day has a non-zero count for a
label series".

Tests: 20 passing (17 utils + 3 buildChartColorMap), includes new
cases for null-label-values, caller-provided day order, and out-of-
range entries being dropped.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Copy link
Copy Markdown
Contributor

@Zaimwa9 Zaimwa9 left a comment

Choose a reason for hiding this comment

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

Looks good overall but couple of comments:

  • The one i'd like being fixed if possible is to use a Map for the chartData, that might reduce computation complexity
  • NIT on a couple of single character variables that could be made a bit more explicit

Another one is that while testing, I realized we are not sending the labels. I'm looking into why but it looks like it's coming from the edge-api that is not tracking the labels for POST analytics/flags while it does for the usage. I'm looking into it

The new BarChart introduced two regressions for the env-grouped path:

1. Legend showed raw env IDs (e.g. "22", "1848") because the new
   BarChart unconditionally renders recharts' <Legend /> using each
   series' dataKey. The env tags at the top already serve as a
   colour legend, so a second one was redundant and broken.
   - Add `showLegend?: boolean` to BarChart (default false)
   - FeatureAnalytics only enables it for the labelled path (where
     the filter above the chart doesn't carry colour per series)
   - Stories keep `showLegend` on (no filter UI above them)

2. Bar colours didn't match the env tag chip colours. The new code
   used `buildChartColorMap(environmentIds)` — which indexes into
   our chart palette by selection order. Old prod used
   `Utils.getTagColour(indexInProjectEnvList)` — the same function
   that colours the env chip, indexed by the env's position in the
   project's env list. Restored that behaviour:
   - Fetch envs via `useGetEnvironmentsQuery`
   - Sort selected envs by their project-list position (stable)
   - Build envColorMap with `Color(Utils.getTagColour(idx)).alpha(0.75)`
   - Pass sortedEnvIds as the chart series

Labelled path keeps the new `CHART_COLOURS` palette (intentional — SDK
names aren't in the tag-colour palette, and this is a net-new feature).

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Two remaining Feature Analytics tooltip regressions:

1. Raw dataKeys surfaced in the tooltip — env mode showed "22: 0" /
   "1848: 0" instead of "Production: 0" / "Staging: 0". Added a
   `seriesLabels?: Record<string, string>` prop to BarChart, threaded
   through to ChartTooltip (and recharts' <Legend formatter> when the
   optional legend is enabled). FeatureAnalytics builds an env-id →
   env-name map from useGetEnvironmentsQuery and passes it for the
   env path; labelled path leaves it undefined (dataKeys are already
   SDK names).

2. Tooltip label text used `text-secondary` which in the theme's
   tooltip-on-surface context rendered with poor contrast (muted
   yellow-ish on white). Switched label + value to `text-default`
   with a semibold weight on the value — keeps the label/value
   hierarchy while matching the tooltip header's contrast.

Incidental type fixes (both introduced earlier this session):
- ChartTooltip.formatNumber now accepts recharts' ValueType (was
  string | number | undefined — ValueType is broader and can be an
  array)
- FeatureAnalytics passes `projectId: Number(projectId)` to
  useGetEnvironmentsQuery — the Req type expects number, old code
  was silently typechecking as string against a loose upstream
  signature

The pre-existing `any` cluster in useFeatureAnalytics.ts is unchanged
(out of scope — separate follow-up).

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…to Record

Three targeted cleanups after auditing the PR for overengineering:

1. Extract useEnvChartProps({ projectId, environmentIds }) — returns
   { series, colorMap, seriesLabels } for env-grouped charts. Bundles
   the env-list query, project-position-based sorting, tag-colour
   mapping (Utils.getTagColour + alpha), and env-name lookup into one
   hook. Real reuse ahead: the planned legacy chart migration
   (SingleSDKLabelsChart, OrganisationUsage) needs the same env
   colouring to match their tag chips — this hook is the single
   source of that logic. FeatureAnalytics.tsx drops ~30 lines of
   derivation in the process.

2. Remove BarChart's `stacked` and `height` props. Both defaulted
   sensibly (stacked=true, height=400) and no consumer ever overrode
   them (grep'd every <BarChart /> callsite, including the legacy
   chart migration targets). YAGNI — cheaper to add them back later
   than to carry unused API surface now.

3. Switch every `colorMap: Map<string, string>` to
   `Record<string, string>` — BarChart, MultiSelect, aggregateByLabels,
   buildChartColorMap, plus all tests and stories. Previous mix of
   Map (for colours) and Record (for seriesLabels) wasn't principled,
   just historical. Record is more idiomatic React, simpler to
   inspect in devtools, and callsites are slightly cleaner (`m[k]`
   vs `m.get(k)`). Tree-shakeable, no behavioural change.

Labelled-mode derivations (aggregateByLabels call, labelOptions,
filteredLabels) stay inline as component-local useMemos — extracting
them into a second hook would be packaging for packaging's sake, with
no reuse today.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@github-actions github-actions bot added feature New feature or request and removed feature New feature or request labels Apr 13, 2026
talissoncosta added a commit that referenced this pull request Apr 13, 2026
…rts SCSS

Replaces the two raw-recharts consumers with the shared <BarChart />
component introduced in #7215. Both pages keep their behaviour and
visual contract; the JSX collapses substantially.

BarChart API additions to support these consumers:
- `barSize?: number` — fixed bar width in pixels (OrganisationUsage
  uses 14px to fit four series per day comfortably)
- `verticalGrid?: boolean` (default true) — toggles CartesianGrid's
  vertical lines (legacy charts hide them)

Migrations:
- OrganisationUsage.container.tsx (4 metric series stacked, custom
  display labels via seriesLabels, selection-driven series filter,
  pre-formatted day axis)
- SingleSDKLabelsChart.tsx (per-SDK stacked, palette colour map
  passed in from parent, MultiSelect-driven SDK filter)
- OrganisationUsageMetrics.container.tsx — pre-formats day to
  'D MMM' (matches the new chart-side day-as-display-string contract),
  switches userAgentColorMap from Map to Record (consistent with
  BarChart's prop after #7215)

Cleanup:
- Delete web/styles/3rdParty/_recharts.scss entirely — its rules
  existed solely to style the two legacy charts' tooltips. With both
  consumers migrated, no consumer of recharts global classNames
  remains; the new BarChart's ChartTooltip uses Bootstrap utilities +
  semantic token classes directly. Drop the @import too.
- SingleSDKLabelsChart loses unused props (`metricKey`, `colours`)
  that the migration made redundant.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
talissoncosta added a commit that referenced this pull request Apr 13, 2026
…rts SCSS

Replaces the two raw-recharts consumers with the shared <BarChart />
component introduced in #7215. Both pages keep their behaviour and
visual contract; the JSX collapses substantially.

BarChart API additions to support these consumers:
- `barSize?: number` — fixed bar width in pixels (OrganisationUsage
  uses 14px to fit four series per day comfortably)
- `verticalGrid?: boolean` (default true) — toggles CartesianGrid's
  vertical lines (legacy charts hide them)

Migrations:
- OrganisationUsage.container.tsx (4 metric series stacked, custom
  display labels via seriesLabels, selection-driven series filter,
  pre-formatted day axis)
- SingleSDKLabelsChart.tsx (per-SDK stacked, palette colour map
  passed in from parent, MultiSelect-driven SDK filter)
- OrganisationUsageMetrics.container.tsx — pre-formats day to
  'D MMM' (matches the new chart-side day-as-display-string contract),
  switches userAgentColorMap from Map to Record (consistent with
  BarChart's prop after #7215)

Cleanup:
- Delete web/styles/3rdParty/_recharts.scss entirely — its rules
  existed solely to style the two legacy charts' tooltips. With both
  consumers migrated, no consumer of recharts global classNames
  remains; the new BarChart's ChartTooltip uses Bootstrap utilities +
  semantic token classes directly. Drop the @import too.
- SingleSDKLabelsChart loses unused props (`metricKey`, `colours`)
  that the migration made redundant.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Two PR review comments from @Zaimwa9:

1. useFeatureAnalytics queryFn used Array.find() per entry to locate
   each entry's day bucket in preBuiltData — O(days × entries). Switch
   to a `day → bucket` Map, same pattern aggregateByLabels uses. O(1)
   lookups, matters for long periods / high entry counts.

2. Rename single-character callback vars across the PR's touched files:
   - ChartTooltip: `n`/`v` → `value`, `el` → `entry`
   - BarChart: `v` → `value` in the tick formatter
   - useFeatureAnalytics: `v` → `response`, `i` kept (idiomatic index)
   - useEnvChartProps: `e` → `env`; dedupe the findIndex call into a
     local helper, rename `a,b` to `idA,idB` for clarity
   - utils: `d` → `day` in the days.map

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

feature New feature or request front-end Issue related to the React Front End Dashboard

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Implement dashboard functionality for displaying SDK metrics in Feature Analytics charts

2 participants