Skip to content

✨(global) add storage gauge#764

Merged
NathanVss merged 19 commits into
mainfrom
feat/quota-gauge
Jul 23, 2026
Merged

✨(global) add storage gauge#764
NathanVss merged 19 commits into
mainfrom
feat/quota-gauge

Conversation

@NathanVss

Copy link
Copy Markdown
Contributor

Purpose

Add a storage gauge, indicating the user how much storage is remaining.

See https://www.figma.com/design/XDjWHDLIiNY4mtpyj21EVK/Drive?node-id=14155-57350&t=BDVBrHbIVUYgp1JN-0

Capture d’écran 2026-07-07 à 15 23 22 Capture d’écran 2026-07-07 à 15 23 42

@NathanVss
NathanVss marked this pull request as draft July 7, 2026 13:23
@NathanVss
NathanVss force-pushed the feat/quota-gauge branch 3 times, most recently from a51d815 to c98755a Compare July 13, 2026 12:57
@NathanVss
NathanVss marked this pull request as ready for review July 13, 2026 13:14

@kernicPanel kernicPanel left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

I'm not sure that we should use a cache for the quota, the added index should be fast enough for big files count. Also I don't like signals...
Did you benchmark reply times with and without cache ?
IMHO, we should keep thing as simple as possible.

Comment thread src/backend/core/api/viewsets.py Outdated
Comment thread src/backend/core/entitlements/backends/base.py Outdated
Comment thread src/backend/core/signals.py Outdated
NathanVss added 16 commits July 23, 2026 17:08
The upload gates returned a generic "permission_denied" code whatever
the entitlements backend's refusal reason. Forwarding the backend's
reason as the DRF error code lets the frontend map each rejection to
a specific, translatable message instead of a catch-all one.
The frontend needs the storage usage and limit to render a quota
gauge. Entitlements backends can now return a quota (usage/limit,
locked when the organization quota is reached, or an explicit error
state) and the DeployCenter backend derives the can_upload reason
from the resolve level when the service does not provide one, so
the gauge and the upload errors stay consistent.
The quota gauge reads the storage usage from cache (directly for the
local backend, via DeployCenter's metrics for the remote one). Both
caches must be dropped as soon as a write changes a user's usage,
otherwise the gauge shows stale numbers until the timeout. A post_save
signal covers item saves, and the code paths doing bulk updates that
bypass signals (signup invitations, user reconciliation, hard delete
of a tree) invalidate explicitly for every impacted creator.
Hard-deleted items no longer occupy storage but still counted in the
creator's usage, inflating the quota gauge and blocking uploads for
users who had cleaned up their trash. The new partial covering index
keeps the per-creator sum an index-only scan now that the quota check
runs it on every uncached upload.
Deployments without a DeployCenter service had no way to enforce
storage quotas. This backend computes each user's usage locally and
applies a configurable default limit, overridable per user in the
admin (0 meaning unlimited) and skippable for users created before a
cutoff so quotas can be rolled out to new users only. The quota is
soft: can_upload runs before the file size is known, so one upload
can overshoot before the next one is blocked.
Both actions grow the acting user's storage usage without going
through an upload: duplicating makes them creator of a new sized
copy, and moving a file to the root without a direct access
reassigns its creator. An over-quota user could use them to keep
taking ownership of storage. Moving also invalidates the previous
creator's usage cache since the reassignment only triggers the
post_save invalidation for the new creator.
Operators may document how storage quotas work for their users. The
optional FRONTEND_STORAGE_GAUGE_INFORMATION_LINK setting is exposed
through the config API so the frontend gauge can link to that page.
Brings the StorageGaugeButton and StorageGaugeInformation components
needed to render the storage quota gauge.
Uploads, moves to the root and duplications can now be refused by the
backend quota gates. The API error code carries the can_upload reason,
so each surface (upload list, move toast, duplicate toast) maps it to
a dedicated translated message instead of a generic failure, and the
40x redirect is disabled on those calls so the user stays in place
and sees the toast. The move mutation handles its own error feedback
to avoid double toasting through the global handler.
Renders the quota returned by the entitlements API as a gauge in the
left panel footer, with a locked state when the organization quota
is reached and a tooltip carrying the error when the quota cannot be
computed. Clicking it opens a settings modal with the detailed gauge
and an optional documentation link. The footer is reworked to host
the gauge on mobile too (user profile and app grid move there), and
the entitlements query is refetched after uploads and hard deletes
so the gauge follows the usage.
Several keys were missing in some languages (Dutch search filters,
delete menu labels, restore toasts, file picker caption), silently
falling back to English. Sync them and add a test failing whenever a
key exists in one language but not the others, so gaps are caught at
review time instead of in production.
The frontend storage gauge deserved its own changelog entry, and the
local backend line moves after the already released entries to keep
the section in insertion order.
DeployCenter now accepts the usage metrics directly in the entitlements
request body, so we switch from GET to POST and send a "usage_metrics"
list with the user's storage and the aggregated storage of the active
users sharing the same organization claim. This avoids the extra HTTP
roundtrip DeployCenter previously made to our usage metrics API when
resolving entitlements.

The entries reuse the Usage serializers so the pushed body and the
pulled external API cannot drift. The organization serializer now takes
the users queryset to be usable from both call sites, which also moves
the storage computation out of the viewset. The organization claim key
is configurable through a new "organization_claim" backend parameter
defaulting to "siret".
The entitlements enums exposed "excedeed" in both their names and their
wire values. Fix the spelling now, while the codes are only consumed by
this branch, as they are part of the API contract with the frontend.
Align the driver enums, quota state, translation keys and e2e fixtures
with the backend error codes now spelled "exceeded".
Following review, replace the post_save signal with a direct call in
the model method. The invalidation logic stays close to the domain and
is easier to trace than an implicit signal. Bulk queryset updates keep
their explicit invalidation since they bypass save().
Apply a review suggestion to keep the quota lookup and its guard on a
single line in the entitlements endpoint.
Duplicating an item consumes storage and moving an item to or from the
root can change who owns its usage. Invalidate the entitlements cache
on success so the gauge reflects the new usage without a reload.
The home and simple layouts rendered their own mobile-only panel with
just the gaufre. Reusing the explorer footer keeps the settings entry
and user profile consistent everywhere and removes the duplicate
component. The index page is also reformatted by prettier on the way.
@sonarqubecloud

Copy link
Copy Markdown

@NathanVss
NathanVss merged commit 635135d into main Jul 23, 2026
33 checks passed
@NathanVss
NathanVss deleted the feat/quota-gauge branch July 23, 2026 15:22
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants