AR namespace statistics: repositories list aggregate header
What does this MR do and why?
The Artifact Registry repositories page lists repositories but says nothing about the namespace as a whole. This MR renders two namespace-level figures under the page heading, each with an icon: how many repositories the namespace holds (3 repositories) and its deduplicated storage size (20.42 MiB).
Reads the artifactRegistry { statistics } GraphQL field that !256610 (merged) added (merged). The same figures appear in the disable confirmation on the settings page since !256621 (merged) (merged).
No user-visible change yet: the page is behind the artifact_registry_ui feature flag, which is disabled by default.
What changed
- New
get_namespace_statistics.query.graphql, taking only$organizationId. The figures do not depend on a filter, sort, or page, so they get their own document rather than riding on the repositories query: the list query iscache-and-networkand re-runs on every filter, sort, and page change, and each of those would otherwise trigger a second Artifact Registry read on the server. The repositories query is unchanged frommaster. repositories_list.vueruns the statistics query beside the list query withcache-and-network(one read per mount, none per filter, sort, or page change) and hands the result to the header. Both queries share abatchKey, soBatchHttpLinksends the first load as one HTTP request. A failed statistics read only drops the header figures; the list keeps its own error handling, unchanged frommaster. A failed re-read after an earlier success also drops the figures rather than keeping stale ones (vue-apollo re-applies the cached data aftererror()fires, so the clear lives inresult()too).cache_update.js: a successful create or delete also evicts the cachedstatisticsoff the registry object (throughcache.modifyon the organization'sartifactRegistryfield, leaving the parent the create form still reads), so the header re-reads the count instead of flashing the old one.repositories_header.vuetakes a nullablestatisticsprop and renders each figure as icon plus text (infrastructure-registryfor the count, the Artifact Registry navigation icon;archivefor the size, matching the AR sidebars),gl-gap-5apart, in the default text color. Each figure is nullable on its own and renders only when known, so an unknown figure never reads as an empty namespace; the storage figure carries a screen-reader-only "Storage used" label. The count is pluralized withn__.humanSizeClamped(merged with !256621 (merged)) clamps a negativededuplicatedSizeBytesto0 B; a reconciliation underflow can report one until it corrects (seenamespace_statistics_type.rb).- One new externalized string;
locale/gitlab.potregenerated.
The figures describe the whole namespace, not the loaded page, so they do not change when the list is filtered, sorted, or paged.
Tests
repositories_header_spec.js: both figures with their icons, layout classes, and the screen-reader label; string inputs that exercise the formatting (1,234 repositories,2.00 KiB); singular form; negative size renders as zero; only the known figure renders when the other is null; nothing renders when the prop or both figures are null.repositories_list_spec.js: statistics handed to the header; the statistics read happens once across a page change while the list reads twice; figures omitted while the table renders when the registry parent is null or the read fails; a failed re-read drops the earlier figures; the list error state is independent of a successful statistics read.cache_update_spec.js: create and delete drop only the statistics off the registry and keep the parent; a create or delete that reported errors leaves them alone.utils_spec.js:humanSizeClampedfor positive, nullish, and negative input.- New fixture generator
ee/spec/frontend/fixtures/artifact_registry/namespace_statistics.rb(the list spec reads its output; the user comes fromcreate(:user)and the organization fromuser.organization, per the organizations testing guide);ee/spec/features/artifact_registry/repositories_spec.rbstubsnamespace_statisticson the client double.
References
- Implements: #628183
- Reads: !256610 (merged) (GraphQL
statisticsfield on the registry type, merged) - Sibling: !256621 (merged) (same figures in the disable confirmation, merged)
- Parent: #627138 (closed)
Screenshots or screen recordings
Captured on GDK against a real local Artifact Registry (real per-user token exchange, no stubs) with three hosted repositories in the namespace. AR reported repositories_count: 3, deduplicated_size_bytes: 21408101.
| Before | After |
|---|---|
![]() |
![]() |
![]() |
![]() |
First load is one batched GraphQL request carrying getArtifactRegistryRepositories and getArtifactRegistryNamespaceStatistics; a filter or sort change sends only the list operation.
Figures stay namespace-wide under a filter, and follow a delete
| Format filter = Docker (1 of 3 rows), no statistics re-read | Back on the list after deleting a repository: 3 to 2 |
|---|---|
![]() |
Unknown figures are left out rather than shown as zero
Simulated by intercepting the getArtifactRegistryNamespaceStatistics response in the browser. Left: repositoriesCount: null, only the storage figure renders. Right: the registry parent resolves null with a GraphQL error, no figures render and the table is untouched.
| Count unknown | Statistics read failed |
|---|---|
![]() |
![]() |
How to set up and validate locally
Requires a running Artifact Registry the monolith can reach. The steps below are what produced the screenshots.
-
Point GDK at your local AR in
config/gitlab.ymlunderdevelopment:and restartrails-web:artifact_registry: api_url: http://127.0.0.1:8090 service_token: secret_file: /path/to/ar-gitlab-api-service-tokenAR must run with
auth.token_exchangepointed at GDK's issuer (http://gdk.test:3000, JWKShttp://gdk.test:3000/oauth/discovery/keys, audiencegitlab-artifact-registry) andauth.gitlab_api.service_tokenset to the same value as the secret file above. -
In
rails console, enable the flags and pick an organization you own that has exactly one top-level group (activation derives the billing anchor from it and rejects an organization with several, which rules out the GDK default organization):Feature.enable(:artifact_registry_ui) Feature.enable(:org_stage_experimental) # organization pages gate org = Organizations::Organization.find_by!(path: 'ar-e2e') Group.where(organization_id: org.id, parent_id: nil).count # => 1 -
Activate the registry and create a few repositories from
/api/graphqlas an owner (X-GitLab-Organization-ID: <org id>header, PAT withapi):mutation { artifactRegistryActivate(input: { slug: "gdk" }) { errors registry { slug status } } } mutation { artifactRegistryRepositoryCreate(input: { name: "docker-hosted", format: DOCKER, kind: HOSTED, visibility: PRIVATE }) { errors repository { name } } }Push something into a repository so the size is non-zero, for example
oras cp --to-plain-http docker.io/library/alpine:3.20 127.0.0.1:8090/gdk/container/docker-hosted/alpine:3.20. -
Confirm the field the header reads resolves against the real service:
{ organization(id: "gid://gitlab/Organizations::Organization/<org id>") { artifactRegistry { statistics { repositoriesCount deduplicatedSizeBytes } } } }Expected shape:
{"repositoriesCount":"3","deduplicatedSizeBytes":"21408101"}(your numbers will differ). -
Open
http://gdk.test:3000/o/<org path>/-/artifact_registry/gdk/repositories. Under theRepositoriesheading, theinfrastructure-registryicon carries<count> repositoriesand thearchiveicon the size, matching the query above. In the Network panel the first load is a single/api/graphqlrequest with two operations. Apply a Format filter and a Name sort: the figures do not change, and onlygetArtifactRegistryRepositoriesis sent. Delete a repository from its detail page: back on the list the count is one lower. -
Frontend specs:
yarn jest ee/spec/frontend/packages_and_registries/artifact_registry/repositories/list ee/spec/frontend/packages_and_registries/artifact_registry/graphql ee/spec/frontend/packages_and_registries/artifact_registry/utils_spec.jsThe list spec reads two generated GraphQL fixtures, so regenerate them first if they are stale:
bundle exec rspec ee/spec/frontend/fixtures/artifact_registry/repositories.rb ee/spec/frontend/fixtures/artifact_registry/namespace_statistics.rb.
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.






