Skip to content

feat(usage): show when an organisation is over its plan limit - #8432

Open
talissoncosta wants to merge 6 commits into
mainfrom
feat/usage-over-limit-8187
Open

feat(usage): show when an organisation is over its plan limit#8432
talissoncosta wants to merge 6 commits into
mainfrom
feat/usage-over-limit-8187

Conversation

@talissoncosta

@talissoncosta talissoncosta commented Sep 1, 2026

Copy link
Copy Markdown
Contributor

Changes

Part of #8187.

A restricted organisation can now open the usage page. It was blocked from every route except the organisations list, so the page that explains the restriction was the one page it could not reach. The blocked screen links there too.

An organisation over its limit gets a banner and a line under the meter saying how far over it is. The date it crossed comes from usage the page already loads, so nothing new is fetched.

A restricted one gets different copy: what gets access back. Upgrading clears the block immediately, otherwise it lifts once usage has stayed under the limit for 30 days.

Charges are only mentioned to plans that can actually be billed for an overage, which is Start-Up and Scale-Up. Whether those are charged or covered by the grace period is #8264.

Not here: naming which services are paused, and telling a free organisation it has seven days. Both need fields the API does not return (#8256), and the seven days is consumable so we cannot promise it truthfully.

Screenshots

State
Over the limit image
Restricted image
Blocked screen, linking to usage image
At exactly 100%, red but not over paste

How did you test this code?

npm run test:unit -- --testPathPatterns="web/components/pages/usage"

Storybook, Pages/Usage Dashboard/Page: PaidOverTheLimit and EnterpriseOverTheLimit show the banner with and without the charge line, FreeAndRestricted and RestrictedButBackUnderTheLimit show the restricted copy with and without an overage left to report.

For the route, set block_access_to_admin on an organisation and check the usage page loads, the blocked screen links to it, and every other route still blocks.

QA notes

  • Over the limit — banner, plus "N calls over your X limit" under the meter.
  • Restricted — set block_access_to_admin; the usage page still loads and the banner says how to get access back.
  • Restricted with no overage — same, with usage under the limit. The banner points at support and promises nothing, since the block may not have come from usage.
  • Route — with that set: the usage page and /organisations load, every other route shows the blocked screen, and that screen links to usage.
  • Boundary — 106% shows the banner, 100% does not. Meter red at 100, orange from 75.
  • Charge line — only on plans billed for overages, Start-Up and Scale-Up. Enterprise and rolling windows never mention charges.
  • Filters — the meter never changes with the project filter; the chart ceiling disappears when a project is selected or on 90 days.
  • Errors — hit the five-a-minute throttle: the retry state appears, and a restricted organisation keeps its banner above it.

Not verified against real data: free plans, and anything on a billing period. Free's 50,000 limit is hardcoded, and billing term dates are read-only in admin because only the Chargebee webhook writes them. Both are covered by the Storybook stories above.

@vercel

vercel Bot commented Sep 1, 2026

Copy link
Copy Markdown

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

Project Deployment Actions Updated
flagsmith-frontend-preview Ready Ready Preview Sep 3, 2026 8:52pm UTC
flagsmith-frontend-staging Ready Ready Preview Sep 3, 2026 8:52pm UTC
1 Skipped Deployment
Project Deployment Actions Updated
docs Ignored Ignored Preview Sep 3, 2026 8:52pm UTC

Request Review

@coderabbitai

coderabbitai Bot commented Sep 1, 2026

Copy link
Copy Markdown

Review Change Stack

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
  • No new commits to review - use @coderabbitai full review for a full pass

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Team

Run ID: 3f0100bd-42c0-4508-a987-4c191ab4c0dd

📥 Commits

Reviewing files that changed from the base of the PR and between c152cf3 and d024325.

📒 Files selected for processing (6)
  • frontend/web/components/pages/usage/UsageDashboardPage.tsx
  • frontend/web/components/pages/usage/__tests__/overLimit.test.ts
  • frontend/web/components/pages/usage/__tests__/utils.test.ts
  • frontend/web/components/pages/usage/components/OverLimitBanner.tsx
  • frontend/web/components/pages/usage/overLimit.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.


📝 Walkthrough

Walkthrough

The usage page now uses dedicated layout, filter, heading, meter, chart, breakdown, and over-limit components. Allowance data is exposed as a raw usage response. New helpers calculate over-limit details and copy, with tests for limits, dates, charges, and notes. Storybook covers paid and enterprise over-limit states. Blocked accounts can access organisation and organisation-usage routes.

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

Merge Risk: ⚪ Minimal · up to d0243

This change adds over-limit usage messaging and permits restricted organisations to view usage details while retaining restrictions elsewhere. No concrete merge-blocking risk remains.


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.

@talissoncosta
talissoncosta force-pushed the feat/usage-over-limit-8187 branch from fb8ef44 to c5f44a2 Compare September 1, 2026 18:59
@github-actions github-actions Bot added feature New feature or request and removed feature New feature or request labels Sep 1, 2026
@talissoncosta
talissoncosta force-pushed the feat/usage-over-limit-8187 branch from c5f44a2 to fc3734e Compare September 1, 2026 19:05
@github-actions github-actions Bot added feature New feature or request and removed feature New feature or request labels Sep 1, 2026
@github-actions github-actions Bot added feature New feature or request and removed feature New feature or request labels Sep 1, 2026
@github-actions github-actions Bot added feature New feature or request and removed feature New feature or request labels Sep 1, 2026
@talissoncosta talissoncosta changed the title feat(usage): show when an organisation is over its plan limit feat(usage): show a paid organisation that it is over its plan limit Sep 2, 2026
@github-actions github-actions Bot removed the feature New feature or request label Sep 2, 2026
@github-actions

github-actions Bot commented Sep 3, 2026

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

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

passed  2 passed

Details

stats  2 tests across 2 suites
duration  55.7 seconds
commit  adeb660
info  🔄 Run: #20147 (attempt 1)

🗂️ Previous results
✅ private-cloud · depot-ubuntu-latest-arm-16 — run #20147 (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, 4 seconds
commit  adeb660
info  🔄 Run: #20147 (attempt 1)

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

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

passed  1 passed

Details

stats  1 test across 1 suite
duration  45.3 seconds
commit  adeb660
info  🔄 Run: #20147 (attempt 1)

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

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

passed  1 passed

Details

stats  1 test across 1 suite
duration  37.4 seconds
commit  adeb660
info  🔄 Run: #20147 (attempt 1)

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

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

passed  3 passed

Details

stats  3 tests across 3 suites
duration  33.1 seconds
commit  0140565
info  🔄 Run: #20146 (attempt 1)

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

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

passed  1 passed

Details

stats  1 test across 1 suite
duration  44.8 seconds
commit  0140565
info  🔄 Run: #20146 (attempt 1)

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

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

passed  24 passed
skipped  1 skipped

Details

stats  25 tests across 19 suites
duration  1 minute, 26 seconds
commit  0140565
info  🔄 Run: #20146 (attempt 1)

Skipped tests

firefox › tests/onboarding-tests.pw.ts › Onboarding › New user connects via the single-page onboarding flow @oss

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

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

passed  1 passed

Details

stats  1 test across 1 suite
duration  32.5 seconds
commit  0140565
info  🔄 Run: #20146 (attempt 1)

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

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

passed  3 passed

Details

stats  3 tests across 3 suites
duration  32.6 seconds
commit  adeb660
info  🔄 Run: #20145 (attempt 1)

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

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

passed  2 passed

Details

stats  2 tests across 2 suites
duration  37.1 seconds
commit  adeb660
info  🔄 Run: #20145 (attempt 1)

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

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

passed  1 passed

Details

stats  1 test across 1 suite
duration  1 minute, 2 seconds
commit  8f700fa
info  🔄 Run: #20144 (attempt 1)

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

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

passed  1 passed

Details

stats  1 test across 1 suite
duration  48.5 seconds
commit  adeb660
info  🔄 Run: #20145 (attempt 1)

@github-actions

github-actions Bot commented Sep 3, 2026

Copy link
Copy Markdown
Contributor

Visual Regression

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

@talissoncosta

Copy link
Copy Markdown
Contributor Author

@themis-blindfold review

Comment thread frontend/web/components/pages/usage/overLimit.ts Outdated
Comment thread frontend/documentation/components/UsageDashboard.stories.tsx
@themis-blindfold

Copy link
Copy Markdown
Contributor

⚖️ Themis review: 🟠 Fix before merge

Free organisations that exceed their allowance receive only generic over-limit copy, despite having a defined seven-day notification period. The completed CI checks passed; the focused frontend Jest command could not run locally because dependencies are not installed.

Area Score
🎯 Correctness 2/5
🧪 Test coverage 3/5
📐 Code quality 4/5
🚀 Product impact 3/5

🟠 Majors

  • frontend/web/components/pages/usage/overLimit.ts: Free plans are not told their seven-day action window.

🧹 Nits

  • frontend/documentation/components/UsageDashboard.stories.tsx: The Storybook harness no longer loads the filter-width style.
📝 Walkthrough
  • Blocked-route access - keeps the organisation usage route reachable while retaining the block elsewhere.
  • Over-limit state - derives the total, crossing date, banner, and meter note from the allowance-window response.
  • Usage-page composition - replaces the dashboard wrapper with separately composed layout, filters, chart, and breakdown components.
  • Stories and tests - adds over-limit copy coverage and story variants for paid and enterprise overages.
🧪 How to verify
  1. Put a free organisation over its API-call limit and confirm the banner gives the seven-day window before serving pauses.
  2. Put a billed organisation over its limit and confirm the billed-period banner includes the conditional overage wording and crossing date.
  3. Set block_access_to_admin and confirm the usage route loads while a project route still renders the blocked screen.
  4. Run cd frontend && npm run test:unit -- --runInBand --testPathPatterns='web/components/pages/usage'.
    Automate: Add a free-plan banner-copy test that asserts the seven-day notification.

Product take: This is important guidance for organisations at their cap. Paid overage messaging is covered, but free users currently lack the one deadline that tells them how urgently to act.

🧭 Assumptions & unverified claims

No unverified assumptions or claims.

A useful warning needs the clock, too · reviewed at 55d5e90

@Zaimwa9 Zaimwa9 left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

2 in progress comments

{canUpgrade && (
<Button
className='flex-shrink-0'
href={Constants.getUpgradeUrl('usage')}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

  getUpgradeUrl: (feature?: string) => {
    return Utils.isSaas()
      ? '/organisation-settings?tab=billing'
      : `https://www.flagsmith.com/pricing${
          feature ? `?utm_source=${feature}` : ''
        }`

organisation-settings is not allowed in App, we need to add it

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Good catch!!

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Hidden while blocked now, they still get the upgrade options there.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Correction to my earlier reply: I put the button back. Hiding it left the one page a blocked org can reach with no way out, which Themis flagged. It isn't a dead link either, organisation settings renders the blocked screen, which is the payment screen.

Comment on lines +36 to +39
const chargeWarning = (basis: UsageBasis): string =>
isBilledOnAPeriod(basis)
? ` Overage charges may apply over ${allowanceWindowLabel(basis)}.`
: ''

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Do we need to check Chargebee here too?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Chargebee is already implied, only its webhook writes the billing dates. But you were right that something was missing: only Start-Up and Scale-Up get billed, enterprise falls through the match and never is. Gated on plan now instead.

@talissoncosta

Copy link
Copy Markdown
Contributor Author

@themis-blindfold review

Comment thread frontend/web/components/pages/usage/UsageDashboardPage.tsx Outdated
@themis-blindfold

Copy link
Copy Markdown
Contributor

⚖️ Themis review: 🟠 Fix before merge

The over-limit calculation, period handling, and route exception are coherent, and captured CI is green. However, two promised recovery journeys remain incomplete: free organisations are not told about their seven-day window, and restricted organisations can now open Usage but have no restricted-state explanation or recovery action there.

Area Score
🎯 Correctness 2/5
🧪 Test coverage 3/5
📐 Code quality 4/5
🚀 Product impact 3/5

🟠 Majors

  • frontend/web/components/pages/usage/UsageDashboardPage.tsx:130 — restricted organisations reach a generic over-limit state without a recovery action.
  • frontend/web/components/pages/usage/overLimit.ts:41 — free plans still omit the seven-day action window.

⚖️ Acknowledged

📝 Walkthrough
  • Route access - permits the organisation list and usage route while the active organisation is blocked.
  • Usage state - derives the allowance-window total and first threshold-crossing date from existing usage data.
  • Over-limit presentation - adds banner and meter note, with plan-family-based charge wording.
  • Page structure and stories - splits the dashboard shell and filters into focused components and updates Storybook coverage.
🧪 How to verify
  1. With block_access_to_admin enabled, open /organisation/:id/usage and confirm the page explains the restricted state and offers a usable recovery action.
  2. Set a free organisation above its 30-day allowance and confirm it states the seven-day notification window before serving pauses.
  3. Exercise Start-Up/Scale-Up and Enterprise organisations above their limit; confirm only the eligible plan family receives the charge warning.
  4. Run npm run test:unit -- --testPathPatterns="web/components/pages/usage" from frontend.
    Automate: Add a page-level test for the blocked and free over-limit states, including their recovery copy and CTA.

Product take: This is a solid usage-visibility improvement, but the two users most affected by the limit cannot yet see the full next step. Completing those states is important before relying on this page as the recovery surface.

🧭 Assumptions & unverified claims

The focused Jest suite could not run locally because Jest is not installed in this checkout; the captured Unit Tests check completed successfully.

The meter knows the numbers; the blocked user still needs an exit. · reviewed at d024325

@talissoncosta

Copy link
Copy Markdown
Contributor Author

@themis-blindfold review

Comment thread frontend/web/components/pages/usage/UsageDashboardPage.tsx Outdated
@talissoncosta

Copy link
Copy Markdown
Contributor Author

@themis-blindfold review

AccountStore.getOrganisation().block_access_to_admin &&
pathname !== '/organisations'
AccountStore.getOrganisation()?.block_access_to_admin &&
!isAllowedWhileBlocked(pathname)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🟠 Major · ⚡ Quick win

Cover the restricted-route exception.

Observed: the only added tests are pure overLimit/utility tests; none exercises isAllowedWhileBlocked or the App guard. Predicted: a route change could again lock restricted organisations out of usage, or allow another blocked route, with CI still green. Add a routing or component test that asserts the usage URL renders for a blocked organisation and a neighbouring protected URL renders Blocked.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Not adding it here: isAllowedWhileBlocked reads web/routes, which imports App, so a test importing it pulls in the whole app. That cycle already broke the boot in this PR once. Fixing it properly, moving the route map into a component-free module, is a follow-up and the test comes with it.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

This remains open. The changed guard imports routes from a module that imports App back (frontend/web/components/App.js:32, frontend/web/routes.js:4); extracting the route paths in this PR removes that cycle and allows the required blocked-usage versus protected-route regression test. A follow-up cannot cover this PR's behaviour change.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Tracked in #8456, which moves the route table into a module that imports no components and brings the test with it. Kept separate because it touches the file every page depends on. Not blocking this one.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Not fixed. This PR still changes the blocked-route guard at frontend/web/components/App.js:283 using the circular route import at frontend/web/components/App.js:33; its added tests do not exercise either the usage exception or a neighbouring blocked route. #8456 cannot provide regression coverage for this PR's behaviour change. Extract the paths and add the test here, or revert the exception.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Not fixed: routePaths.test.ts:19 calls only the helper. It never renders the changed blocked-organisation guard at App.js:275, so a regression in that guard can still pass. Add the blocked usage versus protected-route render test.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Can't render it here: testEnvironment is node and the repo has no RTL or jsdom, nothing renders a component in a test today. The helper is covered by eight cases; what's left uncovered is one && in App. Standing up component tests is its own piece of work.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Not fixed. routePaths.test.ts:18 only calls the helper; it never executes the blocked-organisation branch in App.js:275. A node unit environment does not require adding a component-test stack: this repo already runs browser tests via Playwright (package.json:15). Add a restricted-organisation browser test for the usage route and a protected route.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

You are right and I was wrong to say it could not be rendered: the repo runs Playwright with a real e2e suite. No test there sets organisation-level flags yet, so it needs new setup rather than a new stack. Tracking it rather than doing it in this PR.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Not fixed. routePaths.test.ts:18 only exercises the helper; the blocked-organisation render branch at App.js:275 remains untested. Add the restricted-organisation browser coverage here or revert the exception.

Comment thread frontend/documentation/components/UsageDashboard.stories.tsx Outdated
@themis-blindfold

Copy link
Copy Markdown
Contributor

⚖️ Themis review: 🟠 Fix before merge

TL;DR: The restricted banner and recovery action are wired to the existing usage state, but the new exception to the blocked-route guard has no regression test. Completed checks, including unit tests and changed-file linting, were successful; the two private-cloud E2E runs were still in progress.

Area Score
🎯 Correctness 3/5
🧪 Test coverage 2/5
📐 Code quality 4/5
🚀 Product impact 4/5

Focused tests could not run locally because this checkout has no Jest executable.

🟠 Majors

  • frontend/web/components/App.js:284 — add regression coverage for the blocked-route exception.

🧹 Nits

  • frontend/documentation/components/UsageDashboard.stories.tsx:144 — render the under-limit restricted state the story describes.

⚖️ Acknowledged

  • The import-rule guidance already allows local imports — thread resolved by @coderabbitai[bot]
  • Free-plan copy intentionally avoids an unverifiable seven-day promise — thread resolved by @talissoncosta
  • The Storybook harness imports the shared filter stylesheet — thread resolved by @talissoncosta
  • The restricted banner retains a usable upgrade recovery path — thread resolved by @talissoncosta
📝 Walkthrough
  • Route access - allows restricted organisations to reach their usage page while retaining the existing block for other routes.
  • Usage state - derives the over-limit banner, meter note, and restricted recovery copy from the already-loaded allowance window.
  • Presentation - splits the dashboard into focused layout, filters, banner, and heading components, with matching Storybook states.
🧪 How to verify
  1. Mark an organisation as blocked and open /organisation/:id/usage; confirm its restricted banner and Upgrade plan action render.
  2. With the same organisation blocked, open a neighbouring protected route such as /organisation/:id/settings; confirm the blocked payment screen still renders.
  3. Use a rolling-window fixture that has fallen below the limit while block_access_to_admin remains set; confirm the restricted banner persists without an overage note.
  4. Run cd frontend && npm run test:unit -- --testPathPatterns="web/components/pages/usage".
    Automate: add a route-guard regression test covering both the allowed usage URL and a still-blocked neighbour.

Product take: Solid recovery-path improvement for organisations at their most frustrating moment.
It is high-impact for affected users, but small in day-to-day scope.

🧭 Assumptions & unverified claims

No unverified assumptions or claims.

A useful detour for a locked-out user, once the guard gets its seatbelt · reviewed at f1f1408

@talissoncosta

Copy link
Copy Markdown
Contributor Author

@themis-blindfold review

A restricted organisation is sent to the blocked screen on every route
except the organisations list, so the one page that would explain the
restriction is the one page it cannot open. The blocked screen links
there now too.

block_access_to_admin is enforced only in App. No permission,
middleware or view in the API acts on it, so the page loads its data as
usual once the route is allowed, and every other route still blocks.

The rule lives in web/routePaths rather than App, because web/routes
imports App and reading a path back from it is a cycle. Keeping it out
of that cycle is also what makes it testable.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Comment thread frontend/web/components/pages/usage/components/UsagePageLayout.tsx
@themis-blindfold

Copy link
Copy Markdown
Contributor

⚖️ Themis review: 🟠 Fix before merge

The new over-limit and restricted states are coherent, and the route allowlist keeps the exception narrowly scoped. One failure path still defeats the restricted-state goal: an analytics or limit request error removes the recovery banner along with the charts. Completed lint, unit, build, and security checks passed; end-to-end jobs were still running.

Area Score
🎯 Correctness 3/5
🧪 Test coverage 3/5
📐 Code quality 4/5
🚀 Product impact 3/5

🟠 Majors

  • frontend/web/components/pages/usage/components/UsagePageLayout.tsx:25 — errors hide the restricted organisation's recovery banner.

⚖️ Acknowledged

  • Align the frontend import guidance with local component imports — thread resolved by @coderabbitai[bot]
  • Free-plan copy must not promise an unverifiable seven-day deadline — thread resolved by @talissoncosta
  • Load the shared filter stylesheet in the Storybook harness — thread resolved by @talissoncosta
  • Preserve a recovery action for blocked organisations on the usage page — thread resolved by @talissoncosta
  • Render a restricted state independently of whether usage remains over the limit — thread resolved by @talissoncosta
  • Show the under-limit restricted state in its Storybook story — thread resolved by @talissoncosta
📝 Walkthrough
  • Restricted access — permits only the organisations list and exact usage route while an organisation is blocked.
  • Usage state — derives the allowance-window overage, crossing date, and eligible overage-charge warning from existing requests.
  • Recovery UX — adds restricted and over-limit banners plus an upgrade action.
  • Page structure — separates layout, filters, and display components and adds route-pattern coverage.
🧪 How to verify
  1. Block a free organisation, open its usage URL, and confirm the restricted banner and upgrade action remain available.
  2. Make either usage-data request fail for that blocked organisation and confirm the restricted recovery banner remains visible alongside the retry state.
  3. Check /organisation/<id>/usage remains reachable while /organisation/<id>/projects and /organisation-settings render the blocked screen.
  4. Run npm run test:unit -- --runInBand --testPathPatterns='(web/components/pages/usage|web/__tests__/routePaths)' from frontend.
    Automate: add a restricted-organisation error-state test that asserts the recovery banner survives a failed usage request.

Product take: A solid improvement for customers at their limit, but the recovery guidance needs to survive the transient data failures most likely to occur on this high-traffic page.

🧭 Assumptions & unverified claims

Focused local tests could not run because this checkout has no Jest executable; the completed unit-test check was successful.

A useful dashboard at a stressful moment—just keep the exit sign lit when the data hiccups. · reviewed at b7217a7

@talissoncosta

Copy link
Copy Markdown
Contributor Author

@themis-blindfold review

Comment thread frontend/web/components/pages/usage/utils.ts Outdated
@themis-blindfold

Copy link
Copy Markdown
Contributor

⚖️ Themis review: 🟠 Fix before merge

The restricted-organisation route and paid-limit messaging are well covered, but free organisations still do not receive the promised seven-day warning before their first restriction. The blocked-route exception also lacks an end-to-end regression test. CI is still running; the completed preview and pre-commit checks succeeded.

Area Score
🎯 Correctness 3/5
🧪 Test coverage 3/5
📐 Code quality 4/5
🚀 Product impact 3/5

🟠 Majors

  • frontend/web/components/pages/usage/utils.ts:12 — free-plan over-limit state omits the required seven-day warning.
  • frontend/web/components/App.js:275 — the blocked-route exception still lacks a browser-level regression test.

⚖️ Acknowledged

  • Import guidance now matches the component rule — thread resolved by @coderabbitai[bot].
  • The Storybook harness loads the shared filter styling — thread resolved by @talissoncosta.
  • Restricted organisations retain an upgrade recovery path — thread resolved by @talissoncosta.
  • The under-limit restricted Storybook state renders its banner — thread resolved by @talissoncosta.
📝 Walkthrough
  • Blocked-route access - centralises the two allowed paths and permits the usage route while keeping neighbouring routes blocked.
  • Usage status - derives the limit crossing date and adds distinct over-limit and restricted banners, including plan-specific overage wording.
  • Page composition - separates layout, filters, and presentation components; the alert persists across loading and error states.
🧪 How to verify
  1. Open a first-time free organisation above its limit and confirm the dashboard states the seven-day window before restriction.
  2. With block_access_to_admin set, open /organisation/:id/usage and confirm a neighbouring protected route still shows the blocked screen.
  3. Verify Start-Up and Scale-Up overages show the charge caveat, while Enterprise does not.
  4. Run npm run test:unit -- --testPathPatterns='(web/components/pages/usage|web/__tests__/routePaths)' from frontend.
    Automate: Add a browser test covering both the blocked usage route and a protected neighbouring route.

Product take: This is a solid recovery experience for restricted customers, but the absent free-plan deadline leaves a time-sensitive action unexplained.

🧭 Assumptions & unverified claims
  • Frontend Jest could not be run in this checkout because the installed jest executable is absent.

The route map has found its home; the free-plan clock still needs its cue. · reviewed at e4e2afd

talissoncosta and others added 5 commits September 3, 2026 17:25
An organisation over its limit gets a banner and a line under the meter
saying how far over. The day it crossed is read off the same running
total the chart draws, so the two cannot disagree, and nothing new is
fetched.

A restricted one gets different copy: what gets access back. Upgrading
clears the block at once; otherwise it lifts 30 days after usage drops
under the limit. That outlives the overage, so the banner keys off the
block rather than off being over.

Charges are only mentioned to plans that are actually billed for them.
charge_for_api_call_count_overages matches Start-Up and Scale-Up and
lets enterprise fall through, so an enterprise plan on a Chargebee term
was being warned about a charge that cannot happen.

The page composes itself from named parts rather than passing fifteen
props into one component, and the free plan is deliberately promised no
deadline: the seven-day window is spent after the first restriction and
the API does not say which case an organisation is in (#8256).

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Rule 2 forbade all relative imports, which contradicts the lint config
and every barrel in the codebase: prefer-alias only requires an alias
when the path goes up, and eslint --fix rewrites one back to relative
inside its own root.

Rule 8 required a folder and a barrel for every component. A barrel
re-exporting one file buys nothing, since the import specifier is the
same either way, so a file can become a folder later without touching a
caller.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Strings lived in three places. overLimit mixed computing an overage with
writing prose about it, utils carried the plan section and the project
contribution, and the two banners said the same thing in two slightly
different sentences as a result.

copy.ts now holds everything the page says about a plan and its limit,
so someone asking what a customer is told reads one file. overLimit
keeps the arithmetic, utils keeps periods and windows, and the tests
split the same way: copy.test asserts prose, overLimit.test asserts
numbers.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Neither recovery route works for a block support set by hand: the
plan-change hook and the unrestricting task both skip organisations
with no APILimitAccessBlock record. So an organisation blocked for any
other reason was being told to upgrade, which would not have helped.

With no overage in evidence the block could be either that or a usage
block whose usage has since dropped, and the API does not say which, so
the banner now points at support instead of promising a route that may
not exist.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Every sentence that does not depend on a number now sits in COPY, so
the prose can be read top to bottom without following the logic that
picks it. Two more came in from planSectionCopy, which had them inline.

The interpolated ones stay as functions: a placeholder in a data file
becomes an empty string at runtime, where a template literal is a
compile error.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@talissoncosta

Copy link
Copy Markdown
Contributor Author

@themis-blindfold review

Comment thread frontend/web/components/pages/usage/UsageDashboardPage.tsx
Comment thread frontend/web/components/pages/usage/copy.ts
@themis-blindfold

Copy link
Copy Markdown
Contributor

⚖️ Themis review: 🟠 Fix before merge

TL;DR: The route exception and over-limit presentation are on the right path, but two restricted-account cases still leave users without the information this page was added to provide. Completed unit, lint, build, and static-analysis checks passed; the two private-cloud E2E jobs were still running.

Area Score
🎯 Correctness 2/5
🧪 Test coverage 3/5
📐 Code quality 4/5
🚀 Product impact 3/5

🟠 Majors

  • frontend/web/components/pages/usage/UsageDashboardPage.tsx:75 — a failed organisation request removes the restricted-account recovery banner.
  • frontend/web/components/pages/usage/copy.ts:49 — the restricted state does not say which service was interrupted.

⚖️ Acknowledged

  • Import guidance now permits local component imports — thread resolved by @coderabbitai[bot]
  • Free-plan grace copy cannot truthfully name a first-breach deadline without API state — thread resolved by @talissoncosta
  • Storybook now loads the shared filter styling — thread resolved by @talissoncosta
  • The restricted usage page retains an upgrade path — thread resolved by @talissoncosta
  • Restricted organisations receive a dedicated banner, including after usage falls under the limit — thread resolved by @talissoncosta
  • The usage story covers the restricted, under-limit state — thread resolved by @talissoncosta
  • The recovery banner is rendered independently of usage loading and error content — thread resolved by @talissoncosta
📝 Walkthrough
  • Restricted routing - permits the organisations list and exact usage route while retaining the block elsewhere.
  • Usage state - computes an overage and crossing date from the already-loaded allowance window.
  • Presentation - splits the page into layout, filters, copy, meter, and banner components.
  • Recovery - presents distinct over-limit and restricted messaging with an upgrade action when payments are enabled.
🧪 How to verify
  1. Block an organisation and confirm its exact usage URL loads while a neighbouring protected route still shows the blocked screen.
  2. Force the organisation request to fail for that blocked account and confirm the restriction and upgrade guidance remains visible beside retry.
  3. Exercise restricted accounts with and without flag serving stopped and confirm the banner describes the actual affected service.
  4. Check paid and free accounts just over, exactly at, and below their limits, including a restriction that outlives the overage.
    Automate: add a Playwright restricted-route/error-state case that covers the guard and both restriction outcomes.

Product take: This makes the usage page a useful recovery surface for over-limit accounts, which is a solid improvement.
It needs accurate restriction-state copy and resilience to the page's organisation request failing before it can safely carry that responsibility.

🧭 Assumptions & unverified claims

No unverified assumptions or claims.

The escape hatch is nearly there; it just needs to say what escaped and stay open in bad weather · reviewed at adeb660

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.

Build the over-limit and restricted states for the usage page

3 participants