Billing usage/spend charts: selected series silently switches to a different product when the date range changes
Nobody has claimed this yet.
- Dominant language
- Python
- Stars
- 39.9k
- Forks
- 3.4k
- Avg merge
- 6h 51m
- Merged PRs (30d)
- 232
Description
Bug description
On /organization/billing/usage (and the spend tab), hiding series so that only one product is visible and then changing the date range can silently switch the visible series to a different product. The chart then shows another product's numbers under the user's intended selection, with no visual indication anything changed.
How to reproduce
- Open Organization → Billing → Usage.
- In the data table, untick every series except one (e.g. a mid-list product).
- Change the date range to a period in which the set of products with usage differs (e.g. a product had no usage in the new range, or a new one appears).
- The remaining visible series is now a different product than the one selected.
Same behavior on the Spend tab.
Root cause
The billing service assigns each series a positional id: it builds series only for usage types that have data in the requested range, sorts the keys, and numbers them with enumerate (usage) / a running counter (spend). So id is "index within this particular date range", not a stable product identifier.
The frontend then persists the user's selection by that positional id:
frontend/src/scenes/billing/billingUsageLogic.ts— theuserHiddenSeriesreducer is anumber[]of series ids whose only handler istoggleSeries; it survivessetDateRangereloads and is not cleared byresetFilterseither.frontend/src/scenes/billing/BillingLineGraph.tsx— hides viavisibility.excluded: hiddenSeries.includes(s.id)and colors bygetSeriesColor(s.id).frontend/src/scenes/billing/BillingDataTable.tsx— checkbox state, row dimming, and ribbons are keyed byrecord.idas well.frontend/src/scenes/billing/billingSpendLogic.ts— identical pattern for the spend chart.
When the date range changes and the set of present usage types shifts, the ids re-enumerate and the persisted hidden set now points at different products.
Impact
Org admins reading usage/spend can draw wrong conclusions about what they're being charged for — the chart quietly relabels the data they filtered to. Workaround is to re-tick the series after every date change. The underlying data is correct; only the selection mapping is wrong.
Suggested fix (frontend-only)
The API response already includes stable identifiers per series (label, breakdown_value), so no billing-service change is needed:
- Key
userHiddenSeriesby a stable series key (serializedbreakdown_value, falling back tolabel) instead of the numericid, in bothbillingUsageLogic.tsandbillingSpendLogic.ts(toggleSeries,emptySeriesIDs,finalHiddenSeries,toggleAllSeries). - Update
BillingLineGraph.tsx/BillingDataTable.tsxto exclude/dim by that key (coloring can stay onid, since chart and table ribbons derive it from the same response and stay consistent per render). - Clear the selection on
resetFilters. - Add logic tests: toggle a series → reload with a shifted series set → the same products remain selected (no such tests exist today).
Additional context
Reported by a customer via support. Internal references (PostHog staff only): support ticket 68008, self-driving inbox report (P2, ready). Relevant history: series selection introduced in a710a6250 (#32031), current chart rendering from 5ce37d37e9 (#77178).
Contributor guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Research direction
Start with frontend/src/scenes/billing/billingUsageLogic.ts and billingSpendLogic.ts, then trace the selection props in BillingLineGraph.tsx and BillingDataTable.tsx. Read the existing reducers and add logic tests that reload a shifted series set after toggling a product. Done means the same products remain selected across date-range changes and resetFilters clears the selection.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- react, typescript
- Domain
- analytics, frontend
- Issue type
- Bug
- Difficulty
- 3/5
- Estimated time
- 1-2 days
- Activity status
- Active
- Clarity
- Clearly specified
- Newbie friendliness
- 76/100