Add an empty state to the artifact version list
What does this MR do and why?
This change is behind the feature flag :artifact_registry_ui
Step 23 of monolith/S14. Adds the empty state the artifact version list shows when an artifact has nothing published. Until now a zero-row read fell through to GlTable's default empty text, which is item 4 of #614161 — and the reason that item says the empty-state step and a show-empty fix must not both ship. This closes it.
The state words itself from the format, because the two families do not list the same thing: Maven and npm artifacts hold versions, Docker and OCI artifacts hold manifests.
Its action goes to the repository rather than rendering publish instructions inline. The repository owns that surface and has already merged it, which is also why this state is thinner than its sibling detail/artifacts_empty_state.vue — no tool selector and no snippet blocks.
This MR is stacked. It targets zcuddy-s14-step16-manifests-table (!250140 (merged)), not master. That branch is what first mounts a table region for a container artifact; before it, version_list.vue rendered the heading alone for Docker and OCI, so the manifests wording had no branch to render in or be tested against. Retarget to master once !250140 (merged) merges.
Changes
repositories/versions/version_list_empty_state.vue(new) — illustration, format-appropriate heading and support text, and the link to the repository.- The action is a router link in
GlEmptyState'sactionsslot rather than its ownprimary-button-link, which renders anhrefand would reload the whole SPA.<gl-button :to>is already how this app navigates (seedetail/repository_header.vue). repositories/versions/versions_section.vue— anameprop, and one template arm. It sits after the error arm and before both table arms, so a read that failed is never reported as one that came back empty.repositories/versions/version_list.vue— passes the repository name down, and announces the empty result through the existing live region instead of reusing the message meant for a populated one. A screen reader now hearsNo versions found for <name>.rather thanVersion list for <name> updated.over an empty table.- Specs:
version_list_empty_state_spec.js(new), plus the empty arm inversions_section_spec.jsandversion_list_spec.js. 1158 Jest tests pass on both Vue 2 and Vue 3. - Four Storybook stories, one per format, so the state is covered by the automated axe run.
Screenshots or screen recordings
| Maven and npm | Docker and OCI |
|---|---|
![]() |
![]() |
Both verified in the browser: the heading renders as an h2 under the page's own heading, and the action resolves to the repository route (/o/gitlab-org/-/artifact_registry/acme/repositories/my-repository) as a router link rather than an href.
How to set up and validate locally
The seeded mock generator ladders every artifact with at least two versions, so the empty state is not reachable by URL in the GDK. Drive it from Storybook:
yarn storybook:start- Open ee/artifact_registry/repositories/versions/version_list_empty_state and step through the Maven, Npm, Docker, and Oci stories.
- Confirm the Maven and npm stories say versions and the container stories say manifests.
- Confirm Go to repository renders as a link to the repository detail path, not to a server URL.
To see it in the page rather than in isolation, open version_list and use the WithoutVersions and WithoutManifests stories, which !250140 added and which previously rendered a headers-only table.
Agent Notes
- Why this is stacked rather than off
master. Every remaining S14 frontend step touchesversion_list.vueandconstants.js, which the four open MRs already claim, and!250140renames the data layer those steps build on (versionConnection→artifactConnection,versions→rows,readsVersions→readsManifests). Offmasterthis step would rebase twice and the second rebase would rewrite the very expression its empty arm is built on. It would also only be half-testable, since the container branch is unreachable there. - The setup-instructions affordance was a genuine fork. The spec asks for "a link to the repository's setup instructions, which
monolith/S05owns". The alternative was mounting S05's mergedSetupDrawerin place, whose public interface (open,name,format,close)!250529does not change. We chose the router link, reading the parenthetical as the reason it is a link rather than re-rendered content. If you would rather it opened the drawer without leaving the page, that is a small change and the drawer is already reusable. - The button says "Go to repository", not "View setup instructions". It navigates; a second click on the repository page opens the drawer. A label promising the instructions directly would not be honest about what the click does. A follow-up could deep-link the drawer open via a route query, but that touches
repository_actions.vue, which is S05's file. - Not in scope, deliberately. The spec has the container empty state name the Referrer manifests preference when reached with it off. That sentence belongs to the referrer-preference step (Step 21); both the spec and the plan say whichever of the two lands second carries it into place. This one lands first.
- Worth a manual look: the copy. Four new strings, drafted to mirror the merged sibling's conventions (
There are no packages in this repository yet/Publish your first package to get started.). I chose package and image over artifact in the headings because the page heading directly above already names the artifact. - Heads-up for whoever rebases next.
versions_section.vue's props block auto-merges wrongly between!250140and!250529: both add aformatprop at different offsets, so git keeps both copies with no conflict marker. ESLintno-dupe-keyscatches it, but it is a silent bad merge rather than a flagged one.
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.
This MR was authored with the assistance of an AI coding agent and reviewed by @zcuddy before submission.

