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 is cache-and-network and 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 from master.
  • repositories_list.vue runs the statistics query beside the list query with cache-and-network (one read per mount, none per filter, sort, or page change) and hands the result to the header. Both queries share a batchKey, so BatchHttpLink sends the first load as one HTTP request. A failed statistics read only drops the header figures; the list keeps its own error handling, unchanged from master. A failed re-read after an earlier success also drops the figures rather than keeping stale ones (vue-apollo re-applies the cached data after error() fires, so the clear lives in result() too).
  • cache_update.js: a successful create or delete also evicts the cached statistics off the registry object (through cache.modify on the organization's artifactRegistry field, leaving the parent the create form still reads), so the header re-reads the count instead of flashing the old one.
  • repositories_header.vue takes a nullable statistics prop and renders each figure as icon plus text (infrastructure-registry for the count, the Artifact Registry navigation icon; archive for the size, matching the AR sidebars), gl-gap-5 apart, 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 with n__.
  • humanSizeClamped (merged with !256621 (merged)) clamps a negative deduplicatedSizeBytes to 0 B; a reconciliation underflow can report one until it corrects (see namespace_statistics_type.rb).
  • One new externalized string; locale/gitlab.pot regenerated.

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: humanSizeClamped for 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 from create(:user) and the organization from user.organization, per the organizations testing guide); ee/spec/features/artifact_registry/repositories_spec.rb stubs namespace_statistics on the client double.

References

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
00-before-header-closeup rf2-header-infrastructure-registry-closeup
00-before-list-default rf2-list-infrastructure-registry

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
rf-03-filtered-docker-icons rf-04-after-delete-count-refreshed
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
rf-countNull-SIMULATED rf-statisticsError-SIMULATED

How to set up and validate locally

Requires a running Artifact Registry the monolith can reach. The steps below are what produced the screenshots.

  1. Point GDK at your local AR in config/gitlab.yml under development: and restart rails-web:

    artifact_registry:
      api_url: http://127.0.0.1:8090
      service_token:
        secret_file: /path/to/ar-gitlab-api-service-token

    AR must run with auth.token_exchange pointed at GDK's issuer (http://gdk.test:3000, JWKS http://gdk.test:3000/oauth/discovery/keys, audience gitlab-artifact-registry) and auth.gitlab_api.service_token set to the same value as the secret file above.

  2. 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
  3. Activate the registry and create a few repositories from /api/graphql as an owner (X-GitLab-Organization-ID: <org id> header, PAT with api):

    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.

  4. 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).

  5. Open http://gdk.test:3000/o/<org path>/-/artifact_registry/gdk/repositories. Under the Repositories heading, the infrastructure-registry icon carries <count> repositories and the archive icon the size, matching the query above. In the Network panel the first load is a single /api/graphql request with two operations. Apply a Format filter and a Name sort: the figures do not change, and only getArtifactRegistryRepositories is sent. Delete a repository from its detail page: back on the list the count is one lower.

  6. 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.js

    The 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.

Edited by Narendran

Merge request reports

Loading
Loading