[UI] Add the billable usage export review page

What does this MR do and why?

Adds a read-only admin page that previews the air-gapped billable usage export before the admin downloads it.

It lives at Admin > Subscription > "Export usage report".

An air-gapped self-managed instance can't send usage data to the Customers Portal over the network, so an admin downloads a JSON report and uploads it by hand instead. Until now there was no way to see what the report contained before downloading it.

The page shows the report period, when the term was last exported, a warning naming any days with no recorded data, per-product capability consumption panels, a seats panel, and Export JSON / Cancel actions, all covered in the screenshots below. Exporting downloads the file and returns the admin to the subscription page with a success alert.

Each report covers one subscription term. The page first loads the exportable terms from the terms endpoint, then the report for the current term. When there is a previous term (after a renewal), a "Subscription term" dropdown lets the admin switch to it. Picking a term reloads the report, and the seats panel and the downloaded file follow the selected term.

Notable design decisions

  1. Meter display names mirror CustomersDot's Billing::Usage::Product::PRODUCT_LIST and Billing::Usage::FlowTypes::FLOW_TYPE_LIST, which decide how uploaded usage is named and grouped. The frontend catalog is keyed on the workflow, the first /-delimited segment of metadata.feature_qualified_name - similar to CustomersDot's own fixtures like secrets_read/v1 and code_review/v1.
  2. The days-with-no-data warning is derived only from seat link coverage. Seat link is written daily by a cron regardless of activity, so a missing day proves the instance recorded nothing. A billable usage row only exists once an event happens, so a missing day there is indistinguishable from a quiet day, and CoverageService deliberately reports only a count for it.
  3. Two small deviations from the prototype. The last export is tracked per term, so a term that was never exported says "No report has been exported for this term yet." instead of "from this instance yet". The last-exported time shows in the viewer's timezone (for example "Aug 1, 2026, 9:12 AM GMT"), where the design shows UTC.
  4. On Admin > Subscription, Export usage report replaces the Export license usage file button whenever the report is available (the billable_usage_export flag on and an offline licence). This follows the design issue ("The Export license usage file button on Admin › Subscription becomes Export usage report for offline licences on 19.6 or later"). The JSON carries the same daily seat link series as the CSV, and CustomersDot already accepts it on upload for both Flex and non-Flex subscriptions. The CSV export itself stays: /admin/license/usage_export.csv is unchanged, and the monthly reminder banner still links to it until the banner is updated in a follow-up. One gap remains: CustomersDot's email route (OfflineProvisioningMailbox) only reads the CSV, so admins who email the file rather than upload it still need the CSV.

Reviewer notes:

  • Targets the branch 18552-export-endpoint, not master. Stacked on the export payload, terms and download endpoints in !257491 (merged).
  • Behind the billable_usage_export feature flag, default_enabled: false.

Known gaps, deliberately not in this MR:

  • Export CSV for review button is removed - since exported data is collapsed without root_namespace_id - an explicit preview is not needed anymore.
  • No Duo Agent Platform data yet: DAP emits from the AI Gateway and no monolith endpoint receives it, tracked in https://gitlab.com/groups/gitlab-org/-/work_items/23209; the page renders Secrets Manager meters only until it lands. (Screenshot above show DAP based on local seeded data)

References

Screenshots or screen recordings

Scenario Screen recording / Screenshot
User Flow
Admin / Subscription Page Screenshot 2026-10-01 at 1.26.03 PM.png
Admin / Subscription Page / Billable usage report (Part 1) Screenshot 2026-10-14 at 11.00.05 AM.png
Admin / Subscription Page / Billable usage report (Part 2) Screenshot 2026-10-14 at 11.00.17 AM.png
Alert after download completes & user is redirected back Screenshot 2026-10-01 at 1.41.58 PM.png
Preview of downloaded JSON file Screenshot 2026-10-01 at 1.38.17 PM.png

How to set up and validate locally

  1. Check out this branch (it's based on 18552-export-endpoint).
  2. In the Rails console, enable the flags: Feature.enable(:billable_usage_export) and Feature.enable(:local_billing_persistence).
  3. An offline cloud licence is required, because local_persistence_enabled? requires License.current to not be an online cloud licence. Note that this can't be minted on a local GDK, since only the public key is present. I worked around it locally by temporarily stubbing that method, which is not part of this MR.
  4. Seed some data: create Utilization::BillableUsage::DailyAggregate records with the :billable_usage_daily_aggregate factory across a date range, plus some HistoricalData rows. Skip a couple of days of HistoricalData so the no-data warning renders.
  5. Go to Admin > Subscription and click "Export usage report".
  6. Check the period, the capability panels, the seats panel and the no-data warning, then click "Export JSON" and confirm the file downloads and the subscription page shows the success alert.
  7. To see the "Subscription term" dropdown, add a second, expired offline licence to the licence history. Without a previous term the dropdown is hidden. Switch terms and check that the dates, licensed seats and the download filename change.
  8. Run the frontend tests: node_modules/.bin/jest --config jest.config.js ee/spec/frontend/admin/subscriptions/

MR acceptance checklist

Evaluate this MR against the MR acceptance checklist. It helps you analyze changes to reduce risks in quality, performance, reliability, security, and maintainability.

Edited by Harsh Vardhan Sharma

Merge request reports

Loading
Loading