Artifact Registry: Add the repository create entry

What does this MR do and why?

This change is behind the feature flag :artifact_registry_ui (default off).

The hosted create form and its new/hosted route are already on master, but nothing on the repositories list navigates to them — the route is reachable only by typing the URL. monolith/S07 assigns every repository write affordance to this slice, so the create entry belongs here rather than to the read slices that host it. This is the create-entry half of S07 Step 5; the per-row edit/delete menu, Step 5's other affordance, ships in !248233 (merged).

The design proposal puts the entry in a page header above the search bar, so the page title moves out of the list view into a header component that carries the entry beside it.

Changes

  • repositories/components/create_button.vue (new) — a GlDisclosureDropdown (variant="confirm", placement="bottom-end") with one item, Hosted repository, whose to is the existing new/hosted route. Following the sibling precedent in virtual_registries/pages/index.vue. The component takes no props, so monolith/S04's toolbar or the detail page can mount it unchanged, which is what Step 5 asks of it.
  • repositories/list/repositories_header.vue (new) — wraps ~/vue_shared/components/page_heading.vue: the Repositories heading moved verbatim out of the list view, with the create entry in the #actions slot. The #description slot is deliberately left free — that is where the repository count and total storage land when the aggregate header ships.
  • repositories/list/repositories_list.vue — renders <repositories-header /> in place of <page-heading>, still inside the existing v-else so it stays hidden on the not-found state and visible on the empty and service-unavailable states.
  • Jest specs for both new components, the three existing list-heading assertions retargeted at the header, and one new case covering that the entry survives the empty state.
  • A create_button story, and one new locale/gitlab.pot entry.

No routes, no constants, no GraphQL, no Ruby.

The kind is chosen at the entry rather than in the form, which is why this is a dropdown rather than a plain button. Hosted is the only kind Artifact Registry accepts in Phase 1, so remote and virtual are absent rather than rendered disabled — an entry that cannot lead anywhere is not an affordance.

Screenshots or screen recordings

Before After
before-populated after-populated

The entry survives the empty state, so a first repository is creatable, and the dropdown open:

after-empty

How to set up and validate locally

  1. Enable the flag: Feature.enable(:artifact_registry_ui, Organizations::Organization.first).
  2. Visit /o/<org>/-/artifact_registry/acme/repositories (the slug is stubbed to acme; the controller 404s on anything else).
  3. The Repositories heading now sits in a header with a New repository dropdown on the right, on both the empty and populated states.
  4. Open it, choose Hosted repository, and confirm it client-side navigates to …/repositories/new/hosted with the browser tab reading Create hosted repository · ….
  5. Submit the existing form and confirm you land back on the list with the success toast and the new row.
  6. Keyboard: Enter on the toggle opens it, moves into the list, Enter navigates.

Agent Notes

Two deliberate deviations from the merged S07 plan, both worth a look:

  1. The plan calls this "the list-toolbar create button" and has it mounted into monolith/S04's repositories_toolbar.vue. The prototype puts it in the page header one level above the search bar, so I followed the design. The component is still prop-free, so if we later decide it belongs in the toolbar the move is a template change with no component change.
  2. The plan's repositories/landing.vue is moot — monolith/S04's list shipped first, which triggers the plan's own conditional ("if S04's list already exists … the affordance components mount onto that existing list and no competing view is created").

Deliberately excluded: the breadcrumb restructure. !248296 (merged) and !248233 (merged) both carry it because both need a dynamic crumb, which SpaBreadcrumbs cannot resolve under Vue 3. This MR adds no route and needs no dynamic crumb, so it carries none of it and does not depend on which of those two lands first.

Naming: I called the component repositories_header.vue rather than repositories_topbar.vue because monolith/S04 reserves repositories_toolbar.vue for the filtered-search bar, and a one-letter difference between two adjacent files seemed like a trap. Happy to rename.

Worth manual attention beyond the automated tests: the dark variant="confirm" toggle renders darker than the prototype's blue. That is the current Pajamas token for a primary confirm button rather than anything in this MR — virtual_registries uses the same variant — but a designer should confirm it is the intended treatment.

Resolved in review: @rchanila confirmed the #description slot works as the seam for the repository count and total storage, and flagged that the empty state still needs to match design as a follow-up. The toggle was renamed Create -> New repository per @bonnie-tsang, since toggleText is the toggle's accessible name and a bare verb is not understandable in isolation; the unused data-testid came off at the same time.

MR acceptance checklist

Evaluate this MR against the MR acceptance checklist.


This MR was authored with the assistance of an AI coding agent and reviewed by @zcuddy before submission.

Edited by Zack Cuddy

Merge request reports

Loading
Loading