Some items will be created by external services on behalf of users and
should not count against their creator's storage quota. Items flagged as
quota_excluded are now ignored by the creator storage compute backend.
The flag is part of the fields invalidating the cached storage used so
toggling it is reflected immediately in the entitlements.
The covering index is replaced by one whose condition also filters out
quota excluded rows, keeping the aggregation index-only. Both migrations
are safe on a large table: the AddField is catalog-only on PostgreSQL
11+ and the index swap runs CONCURRENTLY, adding the new index before
removing the old one.
ui-kit 0.28.0 replaces the share modal plain quick search input with an
invite field: selected users become chips next to a role dropdown and a
dedicated share button. The helper still targeted the old combobox label
and page-level share button, breaking every spec sharing an item.
ui-kit 0.28.0 pulls in read-excel-file, whose CommonJS build requires
saxen, an ESM-only package that jest cannot parse. No unit test parses
spreadsheets, so a stub parser is enough, following the pretty-bytes
precedent.
Plug the ui-kit share modal file import onto the new batch share
endpoint. The option only shows up when ALLOW_SHARE_IMPORT_FILE is
enabled in the config. Errors are rendered inside the import modal
instead of the global toast so the user can fix the file and retry
without losing context.
The e2e environment enables the flag to cover the flow end to end.
Sharing a folder with many contacts one call at a time is slow and can
leave a partial state when one of them fails. This endpoint accepts a
list of email/role rows, validates them all before any write, then
creates accesses for known users and invitations for unknown emails.
Rows targeting users already covered by an equal or higher role are
skipped and reported so the client can inform the user.
The feature is disabled by default and gated by the new
ALLOW_SHARE_IMPORT_FILE setting exposed in the config endpoint.
The descendants access synchronization logic was private to the accesses
viewset. Extracting it into core/services/accesses.py lets the upcoming
batch share endpoint reuse it without reaching into viewset internals.
The homepage shows the messages widget button when the widget is
configured. A test ensures the loader init command is queued and the
loader script is injected, so a config regression cannot silently
remove the button.
Anonymous visitors have no help menu, so the homepage loads the
widget floating button instead when the widget is configured. This
gives them the same support entry point as logged-in users.
The homepage needs the widget floating button while the help menu
opens the form directly. A WidgetHelper now owns the command queue
and script injection so both entry points share the same bootstrap,
and it fills the legacy queue key so older widget runtimes keep
working. The hook exposes showButton to init the loader script and
canLoadWidget so callers can check the config before rendering.
Some instances use the messages widget for support instead of a plain
mailto link. A new supportMessagesWidget flag in the help menu config
lets the "Contact us" entry open the widget, taking precedence over
the support email when both are configured.
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.
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.
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().
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.
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 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.
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.
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.
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.
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.
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.
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.
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.
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.
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 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.
A reinstall picked up new radix-ui and typescript-eslint patch
releases within the ranges already declared, so the lockfile is
updated to keep local installs and CI resolving the same versions.
The reset entry moved out of the options list when the filters
switched to the ui-kit built-in reset, leaving a pointless wrapping
spread and a comment describing an option that no longer exists.
The ui-kit Filter emits an empty key when the active option is
deselected. Wrapping it in a structured value anyway made the filter
look active while carrying no usable bounds, so we now propagate
undefined to actually clear it.
The updated ui-kit renders the selected menu item indicator as an
SVG with the c__dropdown-menu-item__check class instead of the old
material-icons checked span, so the share e2e tests could no longer
find the icon and failed in CI.
On mobile the filter buttons overflowed the viewport with no way to
reach the last ones. The bar is now wrapped in a SmartScroller, like
in the search modal, and the mobile background moves to the container
so the surface color covers the whole scrollable area. The explorer
container also takes the full width under the tablet breakpoint.
Storing only the computed date bounds in the filters lost the selected
preset, so the filter could not restore its own state and the custom
range needed extra local state and top-down resets. The filters now
carry a frontend-only "modified" value (preset key or custom range)
and the drivers convert it to updated_at_* query params right before
each request. The custom calendar is hidden on mobile where it does
not have enough room to render comfortably.
The ui-kit now provides a dedicated user search filter with the reset
button, the empty state and the selected row rendering built in. The
bespoke SearchFilter wiring (manual reset entry, custom item renderer)
is no longer needed.
The Filter component now ships its own reset button, so the custom
reset option at the top of the type and location lists duplicated it.
Icons are also aligned with the design review: mini file icons and
small location icons.
The ui-kit renamed the accessible name of the user menu trigger from
"Open user menu" to "User menu". The tests use this button to detect
that the layout is fully loaded, so the selectors must follow.
Same cause as the empty doc baseline: the collabora update renders the
canvas 951px wide so the April screenshot fails on the size check. The
new baseline is the image rendered by the CI runner, taken from the
attempt matching the majority of retries (one retry showed a transient
scroll offset that the screenshot polling absorbs at comparison time).
Clearing the django session cookie can trigger a navigation while the
localStorage evaluation runs, which destroys the execution context and
fails the test on webkit. Retrying until the page settles removes the
race.
The CI pulls collabora/code:latest and a recent release changed the
canvas rendering: it is now 8px narrower and loses the page border, so
the screenshot comparison fails on the size check before even diffing.
The new baseline is the image produced by the CI runner itself.
The published ui-kit 0.24.0 renders the selected dropdown indicator as
a material icon span (.material-icons.checked). The class used before,
c__dropdown-menu-item__check, only exists in a not yet released ui-kit
version, so the locator matched nothing on the CI.
These tests never passed since their introduction. The modal title is
not rendered as a heading, so we target the dialog role instead. The
placeholder-injecting routes shadowed the convert ability mock and,
through route.continue(), sent the convert POST to the real backend
which denied it: they now re-inject the ability and use
route.fallback() so earlier mocks still apply. The confirming test
also mocks the placeholder GET, otherwise the poller sees a real 404
and removes the placeholder as a failed conversion.
Since ui-kit 0.24 the selected item indicator in dropdown menus is an
SVG instead of a material icon, so the item text no longer contains
"check" and the share link reach tests fail.
We verify the help menu shows the configured options, drops the contact
entry when no support email is set, and stays hidden when the config is
empty. The e2e env gets a help menu config so the menu is reachable.
When the backend provides FRONTEND_HELP_MENU_CONFIG, we render the
ui-kit HelpMenu in the left panel footer so users can reach the
documentation, legal pages and support. The menu is hidden when no
config is set to avoid an empty footer. We also hide the unused
cunningham toast container which would otherwise capture clicks on the
footer.
The frontend needs a configurable help menu pointing to documentation,
legal pages and a support contact. We expose FRONTEND_HELP_MENU_CONFIG
through the config endpoint so deployments can tune these links without
a frontend rebuild.
Committing the picked range on the calendar's onChange called setRange,
which rebuilt the Filter options memo and remounted the calendar. That
detached its OK button mid-interaction, so clicking OK raced against the
remount and timed out on firefox and webkit. We now stash the range in a
ref while picking and only commit it on OK, when the panel closes anyway.
The icons subpath import only exists in the ui-kit package.json
"exports" map. Jest's ts-jest transform forced classic "node"
module resolution, which ignores "exports" and failed to find
"@gouvfr-lasuite/ui-kit/icons". Switching to "bundler" matches
the app tsconfig so the unit tests compile again.
The new test picks a custom range through the calendar, asserts the
items are filtered to the selected window, checks the option label
reflects the bounds, and that resetting falls back to the default label.
The icons now live under the dedicated `@gouvfr-lasuite/ui-kit/icons`
entry point. Importing them from there keeps the icon imports aligned
with the package layout.
The filter row is wrapped in a SmartScroller so it overflows gracefully
when the filters do not fit. The global reset button is dropped since
each filter already exposes its own reset, making it redundant.
The custom range relied on a detached DateRangePicker rendered below the
filter. We now render a CalendarRange as the custom option's subContent
so the date selection stays inside the filter popover, and the option
label reflects the selected bounds once a range is picked.
The contact filter used to load the frequent contacts and filter them
locally, falling back to a separate user search above a threshold. The
backend now handles the search through the `q` param, so we pass the
filters straight to the contacts endpoint and let it return the matches.
The query only runs while the popover is open, so navigating folders no
longer triggers contact requests the user never asked for.
The dropdown menu test was failing on firefox because
context.grantPermissions does not accept the clipboard-* strings
on that browser. Use the shared helper which skips firefox where
clipboard support is enabled at launch via firefoxUserPrefs.
Cover the folder explorer header on a public folder: authenticated
users see no anonymous CTA; anonymous users see AnonymousCTA with
a login button that redirects to /authenticate/. A third case
exercises the anonymous dropdown menu — copy link writes the
folder URL to the clipboard, and switching language updates the
visible CTA text.
Cover the contextual CTAs on the standalone file preview page:
authenticated users get MyFilesCTA redirecting to /my-files, and
anonymous users get AnonymousCTA with the default "/" try-out
fallback plus a login button that redirects to /authenticate/.
A dedicated case mocks FRONTEND_EXTERNAL_HOME_URL to ensure the
try-out href follows the config.
UserProfile now renders AnonymousCTA along with a dropdown menu
(copy link, language picker) when no user is authenticated, so
visitors on a public page can still act on it without the user
menu. CustomFilesPreview gains a CONTEXTUAL mode that swaps in
the right CTA based on auth state, used by the standalone file
preview page which is now wrapped in GlobalLayout to expose the
auth context.
AnonymousCTA offers a try-out link and a sign-in button to visitors
landing on a public resource. MyFilesCTA is the authenticated
counterpart, sending the user back to their drive. Translations
cover both CTAs and the anonymous dropdown menu that will live next
to them. Components are consumed in a follow-up commit.
Detailed FilePreview behavior is now covered by Playwright CT inside
the ui-kit repo, so the drive e2e suite only keeps integration smoke
checks: upload -> open -> render for PDF, image, video, audio, plus
prev/next navigation across files.
The ui-kit ships a complete FilePreview (PDF, image, video, audio,
WOPI, error/unsupported, navigation, controls), so we drop the in-app
implementation under features/ui/preview/ and keep only a thin
CustomFilesPreview wrapper that maps Item to FilePreviewType, plugs
in the share modal, info sidebar and posthog event.
The WOPI editor is moved to its own features/wopi/ folder and
openWopiInNewTab now takes a FilePreviewType to match the ui-kit
onOpenInEditor callback. The file_preview translation block moves to
the ui-kit too.
The ui-kit now ships FileIcon along with mime-category and icon-set
utilities, so we drop the local catalog (MIME_MAP, KNOWN_EXTENSIONS,
getMimeCategory, getIconByMimeType, FolderIcon, WorkspaceIcon) and
keep only the drive-specific extensions: folders and suspicious
uploads, exposed through getItemExtendedIcon. ItemIcon falls back to
ui-kit FileIcon for everything else.
Duplication is currently invisible to product analytics because the
event is fired from the API rather than the UI. Emit it from the
viewset right after the duplicate row is created so the duplicated
item context (workspace, mimetype, ...) is attached to the event.
Track when users customize their column layout so product can see
which columns get adopted vs ignored. The event carries the slot
plus old and new column types to support funnel analysis.
Asserting only on the event name is not enough when the same event is
emitted from multiple call sites with different payloads. Expose
expectEventSentWithProps so specs can pin down the expected slot or
context, and keep the captured properties available alongside the
existing names array.
Scalingo's Ubuntu runtime ships a newer libmagic that returns
application/CDFV2 for legacy .doc/.xls/.ppt files, where Alpine's
older libmagic (used in our Docker image) returns the older label
application/x-ole-storage. Both describe the same OLE2 / Composite
Document File V2 container.
detect_mimetype() already treats application/x-ole-storage as a
generic content-detection result and falls back to extension-based
detection (yielding application/msword, application/vnd.ms-excel,
etc.). Without the same handling for CDFV2, legacy .doc uploads on
Scalingo are rejected by the mimetype allow-list with
"mimetype not allowed application/CDFV2 for filename ...".
Cover the per-page-dimensions path with a 17-page fixture
spanning portrait, landscape, square, wide-banner and tall-strip
ratios. Asserts that rendered pages and thumbnails match each
page's real aspect ratio, that page-input jumps land precisely
across mixed ratios, and that the virtual-list total height
matches the sum of per-page heights.
Pages were laid out with a hardcoded A4 portrait ratio, so
landscape, square and extreme-ratio pages rendered with wrong
row heights and page-input jumps could land on a neighbour.
A new usePdfPageDimensions hook lazily fetches each page's
viewport from pdfjs and dedupes concurrent requests. The main
viewer and thumbnail sidebar now size rows from the cache and
fall back to A4 only until real dimensions arrive. Page-input
jumps await the prefix's dimensions before computing the scroll
offset so the viewport center lands inside the requested page.
Record the metrics filter, entitlement payload additions, and the
frontend disclaimer feature shipped on this branch, alongside the
already-landed organization metrics entry.
Cover visibility rules (enabled / disabled / flagged reasons), the
seen-flag lifecycle, the showPotentialOperators branch with and
without signupUrl, and the OK-click close interaction.
Show a one-shot modal when the user's can_upload is blocked for a
recoverable reason (not_activated / no_organization), with an optional
list of potential operators where they can self-register. Tracks
"seen" per disclaimer in localStorage so users are not nagged on
every load, but re-shows the disclaimer if the underlying entitlement
changes and then regresses.
Type the new backend fields (\`can_upload.reason\`, \`context\`) and
the new \`FRONTEND_ENTITLEMENTS_DISCLAIMERS\` config key so downstream
components can consume them with no casts.
Add a dict-shaped config exposed to the frontend so operators can
enable or disable entitlement disclaimers per reason without code
changes. Also pin the Test settings to the local entitlements backend
so test runs do not depend on external state.
Add account_type=organization support to the usage metrics
endpoint. When requested, the API filters users by a shared
OIDC claim (account_id_key/account_id_value) and returns
aggregated storage across all matching users.
Add handling of account_id_key/account_id_value pair, allowing
filtering on allowed parameters in a more generic way. At the
moment `account_email` filter is still needed.
Also include the context of the entitlement in the /entitlements
response.
Change compute_storage_used signature from a single user
to a queryset of users. This enables aggregating storage
usage across multiple users, which is needed for
organization-level metrics.
Restructure the entitlements module from flat files into a
backends/ package with a factory pattern. This enables
constructor kwargs injection for backend configuration.
Rename ANCT backend to DeployCenter to match the actual
service name, and rename Dummy backend to Static for clarity.
The factory now passes ENTITLEMENTS_BACKEND_PARAMETERS to
the backend constructor, making configuration more explicit
and testable.
Some request parameters get updated because of changes on
deploy center side that need to be reflected in services.
Cover the two main picker outcomes: cancelling leaves the item
private, and confirming exposes a publicly reachable URL. The
reach check opens the returned URL in a fresh anonymous context
and waits for the download event, since the backend serves
attachments with Content-Disposition and navigation aborts.
To run the test in CI, build the drive SDK package and start the
sdk-consumer dev server before the playwright job.
Switch to useMutationUpdateLinkConfiguration and preserve the
item's existing link_role. Also add the missing sdk.explorer.cancel
translations for en and nl so the Cancel button label renders
correctly. This should have been done when migration to this new
API route. The existing update did not have any effect.
The invitation validity duration was hardcoded to 7 days. Expose it as
an INVITATION_VALIDITY_DURATION env var so operators can tune it per
deployment without patching settings. Default remains 7 days.
`ItemAccessViewSet.get_serializer_class` depends on `self.item`, which
reads `self.kwargs["resource_id"]`. This is unavailable during
drf-spectacular introspection, so spectacular fails to resolve the
serializer and drops the requestBody for POST/PUT/PATCH operations.
Declare the schema statically via `@extend_schema` and
`@extend_schema_view` so the generated OpenAPI exposes the proper
request and response payloads for item access endpoints.
The WOPI page is a dynamic route keyed by item UUID, so nginx needs to
fall back to the [id].html shell when the URL is hit directly (reload,
external link). Mirrors the existing /explorer/items/[id] rule.
The search e2e fixture items lacked a filename, which made the seeded
file items diverge from real uploads where the filename is present.
We need the filename to be predictive so we can make assertion in the
e2e tests about the viewer that should open or not.
Drop a redundant clearTimeout guard, reformat the WopiEditorFrame
props, assert launch_url is defined at render time, and add a comment
clarifying the WOPI page spinner branch.
The error, not-supported, suspicious and WOPI placeholder viewers all
rendered the same "icon + title + description + action" layout with
near-duplicate scss. Extract a PreviewMessage component so styling and
backdrop-close behaviour live in one place, and update the e2e
selectors to match the new markup.
Cover the backdrop-click-to-close behavior for both image
and PDF viewers: clicking the backdrop closes the preview,
clicking the content does not, and dragging across the image
viewer does not trigger a close.
Users can now dismiss the preview by clicking the blurry area
around the content. Each viewer marks its backdrop zones with
a data attribute so the handler in FilesPreview can distinguish
backdrop from content clicks. The image viewer also guards
against accidental close during pan-drag gestures.
Ensures Print on an image creates a hidden iframe that loads the
same preview URL, waits for the image to decode, and calls
window.print() — all without opening a new tab.
Previously clicking Print on an image opened it in a new tab,
leaving the user to trigger the print dialog themselves. Now a
hidden iframe loads the image and calls window.print() directly.
Switch the non-printable file test from DOCX to an audio
file so it no longer relies on WOPI (which opens in a new
tab). Wait for the PDF page to render before interacting
with the page input to avoid flaky failures. Remove an
unused variable.
Remove the unused isSidebarOpen prop from PdfControls and
strip unnecessary position/min-height rules from the video
player that were left over from earlier layout iterations.