Add artifact registry hosted repository create form

What does this MR do and why?

MR 3 of 3 in the monolith/S07 Step 6 chain. Targets !247968 (merged) (MR 1), not master.

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

Makes the create flow reachable and complete: the form's common fields, the contract validation, the create mutation, and the route.

Changes

  • repositories/create/repositories_create_form.vue — the whole create flow in one component: the four common fields, validation, the mutation, payload-error mapping, cache eviction, the success toast, and navigation back to the list.
  • router/index.js — registers /new/hosted, and a kind-less /new that redirects to it rather than falling through to not-found. /new/remote and /new/virtual still 404 deliberately — they are Phase 2 and do not exist yet.
  • constants.js — format options, the Private visibility value, the route name and title.
  • repositories/index.js — installs GlToast, which is not global and which this app did not previously have. It lands here because this is where the first toast appears.

Why one component rather than a form plus a wiring wrapper

An earlier revision of this MR split it that way — a presentational repositories_form.vue with heading/submitButtonText/initialValues/hiddenFields/readonlyFields props, a submit emit and an advanced-settings slot, behind a thin create wrapper — so a future edit flow could reuse the fields.

That edit flow does not exist, so the seam had zero consumers, and it cost roughly 270 lines: 42 of props, the field-variation machinery, the slot, a second .stories.js, and ~160 lines of tests asserting the prop contract rather than any behaviour. The shared part gets extracted when the edit form is built and both callers are visible, rather than guessed at now.

Validation splits by origin

Client-side rules block submission, so a value the contract rejects never reaches the mutation. The bounds come from api/openapi/v1.yaml, as implemented in internal/managementapi/create.go:

RepositoryName: minLength 1, maxLength 255, pattern ^[a-z0-9](?:[a-z0-9._-]*[a-z0-9])?$
Description:    maxLength 1024

minLength: 1 needs no separate rule — the pattern already excludes the empty string.

Anything still coming back in the mutation payload is an API error by definition, so it surfaces through createAlert rather than being matched back onto a field. A duplicate name always arrives that way, because the client cannot know what already exists — so "Name has already been taken." is a dismissible page alert, not a field error. That is a deliberate change from the previous revision, which string-matched payload messages onto fields; that matcher, its TODO, and the bookkeeping that retracted stale field errors on retype are all gone.

Description length uses GlFormTextarea's built-in characterCountLimit, which announces the remaining count to assistive technology on a debounce. Visibility offers Private alone, per the closed-beta narrowing.

The cache eviction lives outside the component

graphql/utils/cache_update.js exports the mutation update factory, matching the convention the container registry settings already use (packages_and_registries/settings/project/graphql/utils/cache_update.js), so the component holds no cache surgery. It drops the connection field without arguments and then collects — evicting one argument-keyed entry would leave the other filter and sort variants stale.

Not in scope

No entry affordance — no toolbar create button, no per-row actions menu. Those are Step 5, deliberately left open so monolith/S04 can extend the list view without conflict. The route is reached directly by URL for now.

Screenshots or screen recordings

The form A name the contract rejects
create-form name-rejected

Verified end to end against the GDK with the chain assembled locally: /new redirects to /new/hosted; Bad Name? is blocked client-side; a valid submit lands on the list with a toast and the repository appears in MR 2's readback with the chosen format; re-submitting the same name renders "Name has already been taken." against the name field, not as a page-level alert, and stays on the create view.

⚠️ On the size of this MR

734 added lines It is one component (~245) and its spec (~325). Collapsing the two-MR version actually reduced the total under review from 1146 to 734, but it concentrates it in one place. Splitting again would mean inventing a boundary rather than finding one: the only remaining cut is "fields now, wiring later", which would ship a form whose submit does nothing and then rewrite it. Happy to do that if you would rather.

How to set up and validate locally

  1. echo "Feature.enable(:artifact_registry_ui)" | bundle exec rails c
  2. /o/<your-org>/-/artifact_registry/acme/repositories/new — should redirect to .../new/hosted. acme is required; it is Organizations::ArtifactRegistry::STUB_SLUG.
  3. Submit blank — "Name is required.", no mutation fired.
  4. Try -, ?, UPPERCASE, trailing- — each blocked against the contract pattern.
  5. Fill a valid name, pick a format, submit — success toast, back on the list, and with MR 2 present the repository appears in the readback with the chosen format.
  6. Browser Back (in-SPA, so the mock's store survives), submit the same name again — "Name has already been taken." on the field, still on the create view.
  7. A full reload resets the store; it is in-memory by design.

The chain

master ─→ MR 1 ─┬─→ MR 2  repositories list reads from local state
                └─→ MR 3  hosted repository create form  (this)

Merge order: MR 1, then MR 2 and MR 3 in either order.

Note

MR 2 and this MR both modify repositories/app_spec.js — MR 2 adds the Apollo provider the list query needs, this one adds the create-route render and focus tests. Whichever merges second needs a small union resolution there. Verified locally: the union is the two providers plus both provide values plus all five tests, and it passes.

MR acceptance checklist

Evaluate this MR against the MR acceptance checklist.

  • No changelog: dark behind a default-off feature flag.
  • No backend code, no database surface, no documentation change.

Written by GitLab Duo

Edited by Zack Cuddy

Merge request reports

Loading
Loading