Skip to content

fix(usage): meter comparability and unbounded loading - #8357

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

fix(usage): meter comparability and unbounded loading#8357
talissoncosta wants to merge 5 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 reported against the usage dashboard after #8320 merged.

The meter compared numbers that do not belong together (#8353, #8354). The total moved with both filters, the allowance never did. max_api_calls is a 30-day, organisation-wide figure, so "Last 90 days" read about three times too high and picking a project measured that project against the whole organisation's allowance.

Those turned out to be two different problems. A project filter is a labelling problem: a project's usage over the organisation's allowance is a real number, that project's share of what you are allowed, and only the wording was wrong. A 90-day window is not: ninety days against a thirty-day allowance cannot be reworded into meaning. So the meter now names the project and says the percentage is "of your allowance", and only drops to a plain count for the 90-day window.

The chart drew a ceiling a filtered view can never reach (per Matt's July design review). It keeps the plan limit line for the organisation over a comparable period, and loses it when a project is selected, since one project has no trajectory towards the organisation's limit.

Failing to load left you reloading the page. The error state now offers a retry, which refetches the organisation, the usage and the subscription metadata.

Fixes #8353, #8354

How did you test this code?

47 unit tests over the two rules and the meter copy.

Worth checking on the deploy preview:

  • Last 30 days, no project: percentage, plan limit line on the chart
  • Pick a project: percentage stays, header reads "Plan usage · ", caption reads "of your allowance", chart loses its limit line
  • Last 90 days: plain count, allowance labelled per 30 days, no limit line
  • Self-hosted or no subscription metadata: plain count, no bar
  • Kill the network, reload, then restore it and press Try again: the page recovers

@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 26, 2026 12:42pm
flagsmith-frontend-staging Ready Ready Preview Aug 26, 2026 12:42pm
1 Skipped Deployment
Project Deployment Actions Updated (UTC)
docs Ignored Ignored Preview Aug 26, 2026 12:42pm

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.

@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.

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
frontend/web/components/pages/usage/UsageDashboard.tsx (1)

76-85: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Keep the comparable default consistent across both children.

When comparable is omitted, UsageMeter defaults it to true, but UsageOverTime receives undefined and omits the allowance limit. The meter can therefore show a percentage and allowance bar while the chart does not show the allowance. Normalise the value once, such as const isComparable = comparable ?? true, and pass it to both components.


ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 679f84d7-0cff-4693-a17a-4f17aad50e29

📥 Commits

Reviewing files that changed from the base of the PR and between 59946bf and e08d939.

📒 Files selected for processing (7)
  • 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; 7 remain after this review.

talissoncosta and others added 5 commits August 26, 2026 09:38
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>
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

1 participant