Skip to content

fix(usage): give the meter its own window and scope - #8357

Draft
talissoncosta wants to merge 6 commits into
mainfrom
fix/usage-meter-comparability
Draft

fix(usage): give the meter its own window and scope#8357
talissoncosta wants to merge 6 commits into
mainfrom
fix/usage-meter-comparability

Conversation

@talissoncosta

@talissoncosta talissoncosta commented Aug 25, 2026

Copy link
Copy Markdown
Contributor
  • 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

Fixes raised against the usage dashboard after #8320 merged, by Wadii on that PR and by Matt on the preview.

The meter followed the filters, so it kept comparing things that do not belong together (#8353, #8354). The numerator moved with whatever was on screen while the allowance stayed put: "Last 90 days" read about three times too high, and picking a project measured one project against the whole organisation's allowance.

The fix is to stop the filters driving it. The meter answers one question, where the organisation stands against its plan, and it measures over the window the allowance actually covers: the billing period where there is one, the trailing 30 days otherwise. It says which, so a period change that leaves it unmoved does not look like a bug. The selectors drive the chart and the breakdown, which is what they were for.

That removes the mismatch rather than annotating it, so there is no pro-rata and nothing labelled indicative, which were the options Wadii raised.

A project filter now adds a line instead of re-pointing the meter. "Checkout accounts for 40% of that usage." Share of usage rather than share of allowance, because a project on 4M of a 50M plan is 8% of the allowance but could be 40% of everything the organisation did, and the second number is the one that says whether it matters.

The chart drops its plan ceiling for a filtered view. It keeps it for the organisation, and loses it when a project is selected, since one project has no trajectory towards the organisation's limit. That is Matt's point about the per-endpoint chart from the July design review, applied to the filter that remains.

Two smaller ones. Failing to load left you reloading the page, so the error state now offers a retry. And the usage queries opt out of refetchOnFocus: switching tabs and back reloaded the page behind a loader, which Wadii spotted, and usage-data is throttled at five requests a minute per user, so tab switching quietly spent the budget the page needs.

Fixes #8353, #8354

How did you test this code?

59 unit tests over the window rules, the contribution note and the meter copy.

Worth checking on the deploy preview:

  • Change the period: the chart and breakdown follow it, the meter does not, and the meter says which window it is showing
  • Pick a project: the meter stays on the organisation, a line appears saying what share that project accounts for, and the chart loses its limit line
  • Self-hosted or no subscription metadata: plain count, no percentage, no bar
  • Kill the network, reload, restore it and press Try again: the page recovers
  • Switch to another tab and back: no reload, no loader

@talissoncosta
talissoncosta requested a review from a team as a code owner August 25, 2026 12:18
@talissoncosta
talissoncosta requested review from kyle-ssg and removed request for a team August 25, 2026 12:18
@vercel

vercel Bot commented Aug 25, 2026

Copy link
Copy Markdown

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

Project Deployment Actions Updated (UTC)
flagsmith-frontend-preview Ready Ready Preview Aug 27, 2026 8:04pm
flagsmith-frontend-staging Ready Ready Preview Aug 27, 2026 8:04pm
1 Skipped Deployment
Project Deployment Actions Updated (UTC)
docs Ignored Ignored Preview Aug 27, 2026 8:04pm

Request Review

@coderabbitai

coderabbitai Bot commented Aug 25, 2026

Copy link
Copy Markdown

Review Change Stack

Important

Draft PR not reviewed

Draft PRs are not automatically reviewed by default.

  • Trigger a manual review

To automatically review draft PRs, update your CodeRabbit configuration:

reviews:
  auto_review:
    drafts: true

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

The change adds billing-period comparability checks and project-aware usage rendering. It adds a timer hook for loading requests that exceed 20 seconds. The usage dashboard now renders stalled and error states and supports retry callbacks that refetch usage and subscription data. Usage meters now adjust copy and tone and hide the allowance bar when comparison is not valid. Tests cover period comparability, meter copy, and meter tones.

Estimated code review effort: 3 (Moderate) | ~25 minutes

Merge Risk: 🟡 Moderate · up to e08d9

The dashboard now handles non-comparable usage and stalled requests, but the current head can still show inconsistent allowance behavior, hide subscription-metadata failures without a retry action, and briefly display stale timeout states after request changes. These bounded UI correctness and recovery issues should be fixed or explicitly accepted before merging.


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.

@github-actions github-actions Bot added the fix label Aug 25, 2026
@github-actions

github-actions Bot commented Aug 25, 2026

Copy link
Copy Markdown
Contributor

Docker builds report

Image Build Status Security report
ghcr.io/flagsmith/flagsmith-api-test:pr-8357 Finished ✅ Skipped
ghcr.io/flagsmith/flagsmith-e2e:pr-8357 Finished ✅ Skipped
ghcr.io/flagsmith/flagsmith-api:pr-8357 Finished ✅ Results
ghcr.io/flagsmith/flagsmith-private-cloud:pr-8357 Finished ✅ Results
ghcr.io/flagsmith/flagsmith-frontend:pr-8357 Finished ✅ Results

@github-actions

github-actions Bot commented Aug 25, 2026

Copy link
Copy Markdown
Contributor
❌ private-cloud · depot-ubuntu-latest-16 — run #19757 (attempt 1)

Playwright Test Results (private-cloud - depot-ubuntu-latest-16)

failed  3 failed

Details

stats  3 tests across 3 suites
duration  22.3 seconds
commit  e08d939
info  📦 Artifacts: View test results and HTML report
🔄 Run: #19757 (attempt 1)

Failed tests

firefox › tests/change-request-test.pw.ts › Change Request Tests › Change requests can be created, approved, and published with four-eyes approval @enterprise
firefox › tests/roles-test.pw.ts › Roles Tests › Roles can be created with project and environment permissions @enterprise
firefox › tests/sso-test.pw.ts › SCIM Tests › SCIM configuration can be created, regenerated, and deleted @enterprise

🗂️ Previous results
✅ private-cloud · depot-ubuntu-latest-16 — run #19759 (attempt 1)

Playwright Test Results (private-cloud - depot-ubuntu-latest-16)

passed  3 passed

Details

stats  3 tests across 3 suites
duration  56.2 seconds
commit  b0b20db
info  🔄 Run: #19759 (attempt 1)

✅ private-cloud · depot-ubuntu-latest-arm-16 — run #19759 (attempt 1)

Playwright Test Results (private-cloud - depot-ubuntu-latest-arm-16)

passed  3 passed

Details

stats  3 tests across 3 suites
duration  1 minute, 10 seconds
commit  b0b20db
info  🔄 Run: #19759 (attempt 1)

✅ oss · depot-ubuntu-latest-16 — run #19759 (attempt 1)

Playwright Test Results (oss - depot-ubuntu-latest-16)

passed  1 passed

Details

stats  1 test across 1 suite
duration  38.1 seconds
commit  b0b20db
info  🔄 Run: #19759 (attempt 1)

✅ oss · depot-ubuntu-latest-arm-16 — run #19757 (attempt 1)

Playwright Test Results (oss - depot-ubuntu-latest-arm-16)

passed  1 passed

Details

stats  1 test across 1 suite
duration  46.1 seconds
commit  e08d939
info  🔄 Run: #19757 (attempt 1)

✅ oss · depot-ubuntu-latest-16 — run #19757 (attempt 1)

Playwright Test Results (oss - depot-ubuntu-latest-16)

passed  1 passed

Details

stats  1 test across 1 suite
duration  41.7 seconds
commit  e08d939
info  🔄 Run: #19757 (attempt 1)

✅ private-cloud · depot-ubuntu-latest-16 — run #19746 (attempt 1)

Playwright Test Results (private-cloud - depot-ubuntu-latest-16)

passed  3 passed

Details

stats  3 tests across 3 suites
duration  36.6 seconds
commit  59946bf
info  🔄 Run: #19746 (attempt 1)

✅ private-cloud · depot-ubuntu-latest-arm-16 — run #19746 (attempt 1)

Playwright Test Results (private-cloud - depot-ubuntu-latest-arm-16)

passed  2 passed

Details

stats  2 tests across 2 suites
duration  1 minute
commit  59946bf
info  🔄 Run: #19746 (attempt 1)

✅ oss · depot-ubuntu-latest-arm-16 — run #19746 (attempt 1)

Playwright Test Results (oss - depot-ubuntu-latest-arm-16)

passed  1 passed

Details

stats  1 test across 1 suite
duration  46 seconds
commit  59946bf
info  🔄 Run: #19746 (attempt 1)

✅ oss · depot-ubuntu-latest-16 — run #19746 (attempt 1)

Playwright Test Results (oss - depot-ubuntu-latest-16)

passed  1 passed

Details

stats  1 test across 1 suite
duration  41.9 seconds
commit  59946bf
info  🔄 Run: #19746 (attempt 1)

✅ private-cloud · depot-ubuntu-latest-arm-16 — run #19743 (attempt 1)

Playwright Test Results (private-cloud - depot-ubuntu-latest-arm-16)

passed  1 passed

Details

stats  1 test across 1 suite
duration  59.5 seconds
commit  359c224
info  🔄 Run: #19743 (attempt 1)

✅ private-cloud · depot-ubuntu-latest-16 — run #19743 (attempt 1)

Playwright Test Results (private-cloud - depot-ubuntu-latest-16)

passed  2 passed

Details

stats  2 tests across 2 suites
duration  36.2 seconds
commit  359c224
info  🔄 Run: #19743 (attempt 1)

@github-actions

github-actions Bot commented Aug 25, 2026

Copy link
Copy Markdown
Contributor

Visual Regression

15 screenshots compared. See report for details.
View full report

@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: 9


ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 8e46f0f1-f8fb-4bf7-be3e-438740499572

📥 Commits

Reviewing files that changed from the base of the PR and between 763938b and 70ec520.

📒 Files selected for processing (13)
  • frontend/CHANGE_REQUEST_STUCK_BUTTON_INVESTIGATION.md
  • frontend/CSS_TOKEN_GAPS_PLAN.md
  • frontend/MV_IDENTITY_OVERRIDE_EDITOR_NOTES.md
  • frontend/USAGE_DASHBOARD_DECISIONS.md
  • frontend/USAGE_DASHBOARD_PARITY.md
  • frontend/common/hooks/useTakingTooLong.ts
  • frontend/web/components/pages/usage/UsageDashboard.tsx
  • frontend/web/components/pages/usage/UsageDashboardPage.tsx
  • frontend/web/components/pages/usage/__tests__/utils.test.ts
  • frontend/web/components/pages/usage/components/UsageMeter/UsageMeter.tsx
  • frontend/web/components/pages/usage/components/UsageMeter/__tests__/utils.test.ts
  • frontend/web/components/pages/usage/components/UsageMeter/utils.ts
  • frontend/web/components/pages/usage/utils.ts

Included review availability: Your plan provides up to 8 included reviews per hour; 6 remain after this review.


## How to reproduce

### Symptom — deterministic, no Zoox data needed

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Add blank lines after the affected headings.

markdownlint-cli2 reports MD022 warnings because these headings are not followed by a blank line. Add one blank line after each heading at Lines 52, 60, 65, and 70.

Also applies to: 60-60, 65-65, 70-70

🧰 Tools
🪛 markdownlint-cli2 (0.23.2)

[warning] 52-52: Headings should be surrounded by blank lines
Expected: 1; Actual: 0; Below

(MD022, blanks-around-headings)

Source: Linters/SAST tools

Comment thread frontend/CHANGE_REQUEST_STUCK_BUTTON_INVESTIGATION.md Outdated
Comment thread frontend/common/hooks/useTakingTooLong.ts Outdated
Comment thread frontend/CSS_TOKEN_GAPS_PLAN.md Outdated
Comment thread frontend/USAGE_DASHBOARD_PARITY.md Outdated
a route-level component that fetches and renders. None of them is paired, so it
does not name the rendering half, but it names the fetching half exactly:

```

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Add a language to the fenced code block.

Line [186] opens a fenced code block without a language. Markdownlint MD040 reports this. Use text for this filename map, or the correct language identifier, so documentation lint passes.

This addresses the markdownlint warning reported for Line [186].

Proposed fix
-```
+```text
📝 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
```
🧰 Tools
🪛 markdownlint-cli2 (0.23.2)

[warning] 186-186: Fenced code blocks should have a language specified

(MD040, fenced-code-language)

Source: Linters/SAST tools

Comment thread frontend/web/components/pages/usage/components/UsageMeter/UsageMeter.tsx Outdated
Comment thread frontend/web/components/pages/usage/UsageDashboard.tsx Outdated
Comment thread frontend/web/components/pages/usage/UsageDashboard.tsx Outdated
Comment thread frontend/web/components/pages/usage/UsageDashboardPage.tsx

@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: 3


ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: efad4046-5647-40ef-9d40-cec84cc76248

📥 Commits

Reviewing files that changed from the base of the PR and between 70ec520 and 67cfa22.

📒 Files selected for processing (6)
  • frontend/common/hooks/useTakingTooLong.ts
  • frontend/web/components/pages/usage/UsageDashboard.tsx
  • frontend/web/components/pages/usage/UsageDashboardPage.tsx
  • frontend/web/components/pages/usage/components/UsageMeter/UsageMeter.tsx
  • frontend/web/components/pages/usage/components/UsageMeter/__tests__/utils.test.ts
  • frontend/web/components/pages/usage/components/UsageMeter/utils.ts

Included review availability: Your plan provides up to 8 included reviews per hour; 5 remain after this review.

return () => clearTimeout(timer)
}, [active, afterMs, attempt])

return tooLong

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

🔎 Supported by static analysis

🏁 Script executed:

#!/bin/bash
set -eu

printf '%s\n' '--- frontend/common/hooks/useTakingTooLong.ts ---'
cat -n frontend/common/hooks/useTakingTooLong.ts

printf '%s\n' '--- UsageDashboard references ---'
rg -n -C 8 'useTakingTooLong|tooLong' frontend --glob '*.{ts,tsx}'

Repository: Flagsmith/flagsmith

Length of output: 4719


🏁 Script executed:

#!/bin/bash
set -eu

printf '%s\n' '--- UsageDashboard render branches ---'
sed -n '33,125p' frontend/web/components/pages/usage/UsageDashboard.tsx

printf '%s\n' '--- directly named hook tests/usages ---'
rg -n -C 5 'useTakingTooLong|STALLED_AFTER_MS|stalled' frontend --glob '*.{test,spec}.{ts,tsx}' --glob '*.{ts,tsx}'

Repository: Flagsmith/flagsmith

Length of output: 11878


Return active && tooLong from useTakingTooLong.

When active changes to false after the timer expires, the hook can return the previous tooLong state for one render. UsageDashboard can therefore render the timeout state after loading completes. Add a regression test for this transition.

Comment on lines 6 to 7
import UsageMeter from './components/UsageMeter'
import UsageOverTime from './components/UsageOverTime'

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

📐 Maintainability & Code Quality | 🟠 Major | ⚡ Quick win

Use permitted import aliases for the changed imports.

The changed imports use relative paths in files under frontend/.

  • frontend/web/components/pages/usage/UsageDashboard.tsx#L6-L7: import UsageMeter and UsageOverTime through their components/ aliases.
  • frontend/web/components/pages/usage/UsageDashboardPage.tsx#L11-L18: import the added isComparableToAllowance symbol through components/pages/usage/utils.

As per coding guidelines, frontend/**/*.{js,jsx,ts,tsx} must “Use only common/, components/, and project/ import paths; do not use relative imports.”

📍 Affects 2 files
  • frontend/web/components/pages/usage/UsageDashboard.tsx#L6-L7 (this comment)
  • frontend/web/components/pages/usage/UsageDashboardPage.tsx#L11-L18

Source: Coding guidelines

Comment on lines +62 to +68
const {
data: subscriptionMeta,
isLoading: loadingLimit,
refetch: refetchLimit,
} = useGetSubscriptionMetadataQuery(
organisationId ? { id: organisationId } : skipToken,
)

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

🔎 Supported by static analysis

🏁 Script executed:

#!/bin/bash
set -eu

printf '%s\n' '--- changed hunk ---'
git diff -- frontend/web/components/pages/usage/UsageDashboardPage.tsx

printf '%s\n' '--- page definitions and usage ---'
sed -n '1,180p' frontend/web/components/pages/usage/UsageDashboardPage.tsx
fd -i 'UsageDashboard' frontend
rg -n -C 5 'useGetSubscriptionMetadataQuery|isError=|Try again|loadingLimit|subscriptionMeta' frontend/web/components/pages/usage frontend/common frontend/components frontend/project 2>/dev/null || true

Repository: Flagsmith/flagsmith

Length of output: 17401


🏁 Script executed:

#!/bin/bash
set -eu

printf '%s\n' '--- UsageDashboard contract ---'
sed -n '1,120p' frontend/web/components/pages/usage/UsageDashboard.tsx

printf '%s\n' '--- subscription metadata endpoint ---'
sed -n '1,80p' frontend/common/services/useSubscriptionMetadata.ts

printf '%s\n' '--- shared service base ---'
rg -n -C 6 'const service|createApi|fetchBaseQuery|baseQuery' frontend/common/services frontend/common | head -160

Repository: Flagsmith/flagsmith

Length of output: 17024


🏁 Script executed:

#!/bin/bash
set -eu

printf '%s\n' '--- RTK Query dependency and base service ---'
rg -n '"`@reduxjs/toolkit`"|reduxjs/toolkit' package.json frontend/package.json frontend/*/package.json 2>/dev/null || true
fd -i 'service' frontend/common -t f | head -40
fd -i 'package.json' . -t f -E node_modules -E dist -E build | head -30

Repository: Flagsmith/flagsmith

Length of output: 334


Surface subscription metadata failures.

Destructure isError: limitFailed from useGetSubscriptionMetadataQuery and include it in UsageDashboard's isError prop. Otherwise, a failed request can render the dashboard without a plan limit or the Try again action.

talissoncosta and others added 6 commits August 27, 2026 17:03
max_api_calls is a 30 day figure, so Last 90 days measured three months
of usage against one month of allowance and read about three times too
high. The meter reports a count for that window instead, names the
allowance as a 30 day one, and the chart drops its ceiling with it.

The headline also stopped repeating the fraction beside it, and stopped
taking its colour from a percentage it was no longer showing.

Fixes #8353

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Picking a project re-pointed the meter at that project while still
calling it plan usage, so the same panel meant different things
depending on a control at the far side of the page.

The meter now always answers for the organisation, and a note underneath
says what share of that usage the chosen project accounts for. Share of
usage rather than share of allowance: a project on 4M of a 50M plan is
8% of the allowance but could be 40% of everything the organisation did,
and the second number is the one that says whether it matters.

The organisation query carries no projectId, so RTK fetches it once and
reuses it across project changes, and dedupes it with the scoped query
when no project is chosen.

Fixes #8354

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The meter and the chart answer different questions, so they need
different rules. The meter reports the organisation, so a project filter
does not touch it. The chart follows the filter, and a ceiling there
implies a trajectory towards a limit one project will never reach, which
is the point Matt made about the per-endpoint chart in July.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Failing to load left you reloading the page. The retry refetches the
organisation first, since a failed organisation query leaves the usage
query skipped and retrying it alone cannot recover the page.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The base API sets refetchOnFocus for everything, which Wadii spotted on
the breakdown: switching tabs and back reloads the page behind a loader.

Worse than the flicker, usage-data is throttled at five requests a minute
per user, so tab switching quietly spends the budget the page needs. Off
for both usage queries, since the listener is what the features page
relies on.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The period selector drove the meter, so the numerator moved to whatever
was on screen while the allowance stayed put. Ninety days of usage read
three times too high against a thirty day allowance.

The meter now measures the window the allowance actually covers, the
billing period where there is one and the trailing thirty days otherwise,
and says which. The selectors drive the chart and the breakdown, which is
what they were for.

That removes the comparability rule rather than working around it: with
the meter no longer following the selector there is nothing left to be
incomparable.

Fixes #8353

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@talissoncosta
talissoncosta force-pushed the fix/usage-meter-comparability branch from 1441618 to e80a32b Compare August 27, 2026 20:03
@github-actions github-actions Bot added fix and removed fix labels Aug 27, 2026
@talissoncosta talissoncosta changed the title fix(usage): meter comparability and unbounded loading fix(usage): give the meter its own window and scope Aug 28, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

fix front-end Issue related to the React Front End Dashboard

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Meter compares the selected period against a 30-day allowance

2 participants