feat(usage): Show usage against the plan limit for the current billing period - #8320
feat(usage): Show usage against the plan limit for the current billing period#8320talissoncosta wants to merge 19 commits into
Conversation
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
|
Note Reviews pausedIt 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 Use the following commands to manage reviews:
Use the checkboxes below for quick actions:
📝 WalkthroughWalkthroughThe change adds a feature-flagged usage dashboard with billing-period and project filters. It introduces reusable usage bars, meters, charts, threshold markers, loading and error states, and usage aggregation utilities. It updates subscription and period types, improves nullable number formatting, replaces the previous Estimated code review effort: 4 (Complex) | ~60 minutes Merge Risk: 🔴 Critical · up to The replacement usage dashboard currently contains a compile-blocking reference and can also show incorrect loading or usage states in several edge cases, while some accessibility and zero-limit handling still need follow-up. Merge should be blocked until the compilation and loading correctness issues are fixed. 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. Comment |
Docker builds report
|
bc0e15c to
d07ef2a
Compare
There was a problem hiding this comment.
Actionable comments posted: 11
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: ASSERTIVE
Plan: Pro Plus
Run ID: fe27ff58-f01f-49de-a201-6baefc45fcf3
📒 Files selected for processing (22)
frontend/common/types/responses.tsfrontend/common/utils/__tests__/format.test.tsfrontend/common/utils/format.tsfrontend/documentation/components/UsageBar.stories.tsxfrontend/documentation/components/UsageDashboard.stories.tsxfrontend/documentation/components/UsageMeter.stories.tsxfrontend/documentation/components/UsageOverTime.stories.tsxfrontend/web/components/charts/LineChart.tsxfrontend/web/components/organisation-settings/usage/UsageDashboard/UsageDashboard.scssfrontend/web/components/organisation-settings/usage/UsageDashboard/UsageDashboard.tsxfrontend/web/components/organisation-settings/usage/UsageDashboard/UsageDashboardView.tsxfrontend/web/components/organisation-settings/usage/UsageDashboard/index.tsfrontend/web/components/organisation-settings/usage/UsageMeter/UsageMeter.scssfrontend/web/components/organisation-settings/usage/UsageMeter/UsageMeter.tsxfrontend/web/components/organisation-settings/usage/UsageMeter/index.tsfrontend/web/components/organisation-settings/usage/UsageOverTime/UsageOverTime.tsxfrontend/web/components/organisation-settings/usage/UsageOverTime/index.tsfrontend/web/components/pages/OrganisationUsagePage.tsxfrontend/web/components/shared/UsageBar.tsxfrontend/web/components/shared/UsageBar/UsageBar.scssfrontend/web/components/shared/UsageBar/UsageBar.tsxfrontend/web/components/shared/UsageBar/index.ts
💤 Files with no reviewable changes (1)
- frontend/web/components/shared/UsageBar.tsx
Included review availability: Your plan provides up to 8 included reviews per hour; 5 remain after this review.
✅ private-cloud · depot-ubuntu-latest-arm-16 — run #19696 (attempt 1)Playwright Test Results (private-cloud - depot-ubuntu-latest-arm-16)Details
🗂️ Previous results✅ oss · depot-ubuntu-latest-16 — run #19696 (attempt 1)Playwright Test Results (oss - depot-ubuntu-latest-16)Details
✅ private-cloud · depot-ubuntu-latest-16 — run #19696 (attempt 1)Playwright Test Results (private-cloud - depot-ubuntu-latest-16)Details
✅ oss · depot-ubuntu-latest-arm-16 — run #19696 (attempt 1)Playwright Test Results (oss - depot-ubuntu-latest-arm-16)Details
✅ private-cloud · depot-ubuntu-latest-16 — run #19685 (attempt 1)Playwright Test Results (private-cloud - depot-ubuntu-latest-16)Details
✅ private-cloud · depot-ubuntu-latest-arm-16 — run #19685 (attempt 1)Playwright Test Results (private-cloud - depot-ubuntu-latest-arm-16)Details
✅ private-cloud · depot-ubuntu-latest-16 — run #19683 (attempt 1)Playwright Test Results (private-cloud - depot-ubuntu-latest-16)Details
✅ oss · depot-ubuntu-latest-arm-16 — run #19685 (attempt 1)Playwright Test Results (oss - depot-ubuntu-latest-arm-16)Details
✅ private-cloud · depot-ubuntu-latest-arm-16 — run #19683 (attempt 1)Playwright Test Results (private-cloud - depot-ubuntu-latest-arm-16)Details
✅ oss · depot-ubuntu-latest-16 — run #19685 (attempt 1)Playwright Test Results (oss - depot-ubuntu-latest-16)Details
✅ oss · depot-ubuntu-latest-16 — run #19683 (attempt 1)Playwright Test Results (oss - depot-ubuntu-latest-16)Details
✅ oss · depot-ubuntu-latest-arm-16 — run #19683 (attempt 1)Playwright Test Results (oss - depot-ubuntu-latest-arm-16)Details
|
Visual Regression19 screenshots compared. See report for details. |
There was a problem hiding this comment.
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/organisation-settings/usage/UsageDashboard/UsageDashboard.tsx (1)
48-56: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick winAvoid rendering cached usage after filter changes.
billing_periodandprojectIdcreate new query keys, but RTK Query retainsdatawhile fetching andisLoadingis false after the first result. UseisFetchinginUsageDashboardView, or usecurrentDatafor the active filters. Add a filter-change test.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: ASSERTIVE
Plan: Pro Plus
Run ID: 7d514ea7-d3c3-494e-9999-de4aba7f2312
📒 Files selected for processing (5)
frontend/documentation/components/UsageDashboard.stories.tsxfrontend/web/components/organisation-settings/usage/UsageDashboard/UsageDashboard.tsxfrontend/web/components/organisation-settings/usage/UsageDashboard/UsageDashboardView.tsxfrontend/web/components/organisation-settings/usage/UsageOverTime/UsageOverTime.tsxfrontend/web/components/pages/OrganisationUsagePage.tsx
Included review availability: Your plan provides up to 8 included reviews per hour; 2 remain after this review.
f350ec3 to
ee40e96
Compare
There was a problem hiding this comment.
Actionable comments posted: 2
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: ASSERTIVE
Plan: Pro Plus
Run ID: f9fbe040-8f6e-4a07-be5a-c1285f1a57bc
📒 Files selected for processing (10)
frontend/documentation/components/UsageDashboard.stories.tsxfrontend/documentation/components/UsageMeter.stories.tsxfrontend/documentation/components/UsageOverTime.stories.tsxfrontend/web/components/organisation-settings/usage/UsageDashboard/UsageDashboard.tsxfrontend/web/components/organisation-settings/usage/UsageMeter/utils.tsfrontend/web/components/organisation-settings/usage/UsageOverTime/UsageOverTime.tsxfrontend/web/components/pages/OrganisationUsagePage.tsxfrontend/web/components/shared/UsageBar/UsageBar.scssfrontend/web/components/shared/UsageBar/UsageBarThresholds.scssfrontend/web/components/shared/UsageBar/UsageBarThresholds.tsx
Included review availability: Your plan provides up to 8 included reviews per hour; 6 remain after this review.
talissoncosta
left a comment
There was a problem hiding this comment.
Notes on the parts that are not self-evident.
There was a problem hiding this comment.
Actionable comments posted: 1
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: ASSERTIVE
Plan: Pro Plus
Run ID: 3bbc8a58-9980-48b0-9ffa-e2411d5051de
📒 Files selected for processing (1)
frontend/web/components/organisation-settings/usage/UsageDashboard/UsageDashboardView.tsx
Included review availability: Your plan provides up to 8 included reviews per hour; 7 remain after this review.
There was a problem hiding this comment.
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (2)
frontend/web/components/shared/UsageBar/UsageBar.tsx (2)
1-14: 📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick winReplace the three relative imports in
UsageBar.tsxwith approvedcommon/,components/, orproject/paths.Source: Coding guidelines
23-33: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick winRequire an accessible name for every progressbar.
labelandariaLabelare optional, soaria-label={label ?? ariaLabel}can be undefined. RequirelabelorariaLabelwith a named union type.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: ASSERTIVE
Plan: Pro Plus
Run ID: 97e6155e-7eb1-41f0-8a02-1aeb005c9b8a
📒 Files selected for processing (1)
frontend/web/components/shared/UsageBar/UsageBar.tsx
Included review availability: Your plan provides up to 8 included reviews per hour; 7 remain after this review.
There was a problem hiding this comment.
Actionable comments posted: 1
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: ASSERTIVE
Plan: Pro Plus
Run ID: f65d6a5d-0ef0-45a9-a0fe-d83c8cb701fd
📒 Files selected for processing (3)
frontend/web/components/ProjectFilter.tsxfrontend/web/components/organisation-settings/usage/UsageDashboard/UsageDashboard.tsxfrontend/web/components/organisation-settings/usage/UsageDashboard/UsageDashboardView.tsx
Included review availability: Your plan provides up to 8 included reviews per hour; 7 remain after this review.
There was a problem hiding this comment.
Actionable comments posted: 2
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/UsageDashboardPage.tsx (1)
19-113: 📐 Maintainability & Code Quality | 🟠 Major | 🏗️ Heavy liftMove the new components to the required component-folder layout.
The new dashboard components do not consistently provide a component folder, barrel, implementation file, and co-located Sass file.
frontend/web/components/pages/usage/UsageDashboardPage.tsx#L19-L113: move this component toUsageDashboardPage/UsageDashboardPage.tsxand add its barrel.frontend/web/components/pages/usage/UsageDashboard.tsx#L24-L80: move this component toUsageDashboard/UsageDashboard.tsx, add its barrel, and co-locate its Sass file.frontend/web/components/pages/usage/components/UsageOverTime/UsageOverTime.tsx#L26-L85: add and importUsageOverTime.scssin this component folder.frontend/web/components/pages/usage/UsageDashboardPage.scss#L1-L4: move this stylesheet into theUsageDashboardPagecomponent folder and import it from the component.As per coding guidelines, “Each new component must live in its own folder with a barrel
index.ts, aComponentName/ComponentName.tsxfile, co-locatedComponentName.scss.”Source: Coding guidelines
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: ASSERTIVE
Plan: Pro Plus
Run ID: d8af2905-8555-48c2-914b-06c4079a2636
📒 Files selected for processing (19)
frontend/documentation/components/UsageDashboard.stories.tsxfrontend/documentation/components/UsageMeter.stories.tsxfrontend/documentation/components/UsageOverTime.stories.tsxfrontend/web/components/pages/OrganisationUsagePage.tsxfrontend/web/components/pages/usage/UsageDashboard.tsxfrontend/web/components/pages/usage/UsageDashboardPage.scssfrontend/web/components/pages/usage/UsageDashboardPage.tsxfrontend/web/components/pages/usage/__tests__/utils.test.tsfrontend/web/components/pages/usage/components/UsageMeter/UsageMeter.scssfrontend/web/components/pages/usage/components/UsageMeter/UsageMeter.tsxfrontend/web/components/pages/usage/components/UsageMeter/__tests__/utils.test.tsfrontend/web/components/pages/usage/components/UsageMeter/index.tsfrontend/web/components/pages/usage/components/UsageMeter/utils.tsfrontend/web/components/pages/usage/components/UsageOverTime/UsageOverTime.tsxfrontend/web/components/pages/usage/components/UsageOverTime/__tests__/utils.test.tsfrontend/web/components/pages/usage/components/UsageOverTime/index.tsfrontend/web/components/pages/usage/components/UsageOverTime/utils.tsfrontend/web/components/pages/usage/index.tsfrontend/web/components/pages/usage/utils.ts
Included review availability: Your plan provides up to 8 included reviews per hour; 7 remain after this review.
Zaimwa9
left a comment
There was a problem hiding this comment.
Overall good, a couple of comments as I think there is a sort of conflict related to the billing periods.
If I understand correctly, this is just the first step of a ~5/6 other PRs right ?
| atLimit ? 'danger' : 'warning' | ||
| }`} | ||
| > | ||
| Notify {threshold}% |
| organisationId: number | undefined | ||
| } | ||
|
|
||
| /** Behind `usage_dashboard`. Owns the fetching; the view draws the result. */ |
There was a problem hiding this comment.
Can we remove this kind of comments? Same goes for the one L20 that would otherwise be valid for half the props type definition
| import { PeriodOption } from 'common/types/requests' | ||
| import UsageDashboard from './UsageDashboard' | ||
| import { | ||
| hasBillingPeriod as deriveHasBillingPeriod, |
There was a problem hiding this comment.
IMO something is wrong with the naming if we need to always use an alias to use the function. In the downstream variable or in the function name
There was a problem hiding this comment.
Agreed. Renamed to planHasBillingPeriod. 482f03a
| const hasBillingPeriod = deriveHasBillingPeriod(subscription, isFreePlan) | ||
|
|
||
| const [chosenPeriod, setChosenPeriod] = useState<PeriodSelection>('default') | ||
| const billingPeriod = resolvePeriod(chosenPeriod, hasBillingPeriod) |
There was a problem hiding this comment.
Something looks wrong to me here. The hasBillingPeriod is derived from the organisation subscription right ?
Meaning that as long as the organisation as a billing period, it will be shown the 4 period options, including last 30/90 days (rolling window).
So if you select one of those 2, the data will be drawn against the wrong plan limit (90 days usage against monthly limit)
It's actually also visible without a billing period:
There was a problem hiding this comment.
Good catch. Split in two: planHasBillingPeriod decides which options are offered, isBillingPeriodSelected decides whether the chart draws against the limit. Rolling windows no longer get a limit line. 482f03a
| const { | ||
| data, | ||
| isError: usageFailed, | ||
| isLoading: loadingUsage, |
There was a problem hiding this comment.
isFetching would allow to show the loader again on period updates while isLoading will be true only during the initial fetch if I recall well.
Is it intentional?
There was a problem hiding this comment.
Not intentional. Switched to isFetching. 482f03a
| <div className='p-4 border border-default rounded-lg bg-surface-default'> | ||
| <div className='d-flex align-items-baseline justify-content-between gap-3 mb-3'> | ||
| <strong> | ||
| {isBillingPeriod ? 'Usage vs plan limit' : 'Daily usage'} |
There was a problem hiding this comment.
For self hosted there is no limit, so maybe we could fallback to plain Usage or Cumulative usage if limit is null ?
There was a problem hiding this comment.
Falls back to Cumulative usage when there's no limit
| import { useGetSubscriptionMetadataQuery } from 'common/services/useSubscriptionMetadata' | ||
| import UsageChartFilters from 'components/organisation-settings/usage/components/UsageChartFilters' | ||
| import UsageChartTotals from 'components/organisation-settings/usage/components/UsageChartTotals' | ||
| import UsageDashboard from './usage' |
There was a problem hiding this comment.
Sorry to nitpick on the import naming but that is a bit confusing versus UsageDashboardPage
There was a problem hiding this comment.
Good call. Renamed to UsageDashboardPage. 482f03a
|
Thanks for the thoughtful review @Zaimwa9 |
Math.log10(0) is -Infinity, so a count of zero came back as NaN rather than '0'. Any surface rendering a total while its query is in flight showed it, including OrganisationLimit. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Moves the flat file into a folder and separates the header and the threshold strip, each with its own stylesheet. The percentage, clamping and tone maths move to utils and are tested. Adds thresholds, warnAt and ariaLabel. warnAt defaults to 85, so ProjectUsageTab and EnvironmentOverrideUsage render exactly as before. The fill now reads its colour from the border tokens instead of Bootstrap's theme colours: the values are identical, but the tokens follow the theme. aria-valuenow is clamped to the declared range, since usage can exceed the limit while a progressbar cannot report past its maximum. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The y-axis grows to include it, so a threshold above the data stays in view. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
billingPeriods also held the rolling windows, and freePeriods is not free-only: enterprise agreements without a Chargebee term use it too. Typing both arrays as PeriodOption narrows their value from string to the billing_period union, which is what the query actually accepts. Also types has_active_billing_periods, which the dashboard reads to decide whether usage accumulates towards a reset. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…g period Closes #8185 Behind usage_dashboard, off everywhere. The new page replaces the old one rather than extending it, so nothing changes until the flag is on. - a meter: percentage consumed, used against limit, notification thresholds - usage over time: cumulative against the limit where there is a billing period, daily volume where there is not, because a rolling window's total falls as old days leave it - loading, failure and empty states, so a failed query no longer reads as an organisation that made no calls The rules live in utils modules and are tested: whether a plan has a billing term, how a chosen period resolves against the default, daily aggregation, and the meter's two readings. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Most cannot be reached on a dev machine: an enterprise agreement has no billing term, so the cumulative chart and its ceiling never render locally. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The usage query ran before the organisation loaded, so a billed organisation asked for the rolling window first and corrected itself once the plan arrived: an extra request and a visibly wrong period on the way. The subscription metadata query was also unwatched, so while it loaded the meter read as an organisation with no plan limit rather than as loading. Its failure stays non-fatal, since no metadata is what self-hosted looks like. PlanLimit replaces the nullable-number union repeated across six files. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Changing the period or project filter moved the bar in one jump. Honours prefers-reduced-motion. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
UsageDashboardView was eight lines of JSX and a props type it never read, spreading everything straight through to a second component. The split cost a duplicated contract and two names that both meant "the part that renders". The states only needed to sit inside the layout so the heading survives them, which a variable does as well as a component. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Nineteen lines, one consumer, three props forwarded straight from UsageBar's own, and a body of two spans in a flex row. It was a file, not a component. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The selects had no accessible name, so both announced as a bare combobox. The page this replaces labelled all three. Uses FieldLabel with htmlFor wired to the control's inputId, so the label is visible as well as announced. The subscription metadata query no longer blocks the page. Its failure was already treated as non-fatal, since no metadata means no limit, so waiting on it was the inconsistent half. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Failure staying non-fatal was right, but the wait is not the same thing. Until the metadata answers, limit is undefined and the meter reports "no plan limit", which is a claim rather than an absence, then corrects itself. Reverses the loading half of 8b04ea1; the failure half stands. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
It is the route once the flag is on, and OrganisationUsagePage does nothing but render it. Follows pages/sdk-keys and pages/feature-lifecycle: a feature folder holding the XPage.tsx route plus its own components/. UsageDashboardView becomes UsageDashboard, since the page suffix now carries the fetching half and the plain name can go to the part that renders. Everything the flag still needs stays in organisation-settings/usage, so removing the flag becomes a delete rather than another move. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Two test files each carried their own event and response builders, both casting because they omitted totals. The shared response now computes totals from the events it was given, so the fixture cannot claim a figure its own events disagree with, and the cast goes. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…cted hasBillingPeriod came from the subscription, but it also decided whether the chart accumulated against the plan limit. A billed organisation is still offered the rolling windows, so picking "Last 90 days" drew 90 days of usage against a monthly allowance. The chart now follows the period being viewed; which options are offered still follows the subscription. Also from review: - isFetching rather than isLoading, so changing period shows the loader instead of the previous period's figures - the chart heading no longer promises a comparison self-hosted cannot make, falling back to "Cumulative usage" when there is no limit - planHasBillingPeriod, so no call site has to alias it - the threshold labels clear the ticker and the right edge - OrganisationUsagePage imports UsageDashboardPage under its own name Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Removes the docblocks and prop comments that described what a component is, and the inline notes explaining decisions. The record moved to frontend/USAGE_DASHBOARD_DECISIONS.md, which is where someone about to change one of these should look. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Docblocks, prop comments and inline reasoning removed across the dashboard, the meter, the chart and the shared bar. The record is in frontend/USAGE_DASHBOARD_DECISIONS.md. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>


Reviewing this
Every state is in Storybook under Pages > Usage Dashboard if you want to scan them quickly.
docs/if required so people know about the feature.Changes
Closes #8185
Shows usage against the plan allowance. Behind
usage_dashboard, off everywhere. Replaces the old page rather than extending it, so nothing changes until the flag is on.Worth knowing:
UsageBargainsthresholds,warnAtandariaLabel.warnAtdefaults to 85, so the two other pages using it are unchangedFormat.shortenNumber, which returnedNaNfor0.OrganisationLimithad the same exposureNot here: period dates (#8258), projection (#8188), over-limit and restricted states (#8187), breakdown (#8186). The flag should stay internal until #8186, since swapping the page loses the request-type totals and the SDK view.
How did you test this code?
99 unit tests over the period rules, the daily aggregation, the bar maths and the meter copy.
To QA, with
usage_dashboardon: