Add a pull command drawer to the artifact version list

What does this MR do and why?

This change is behind the feature flag :artifact_registry_ui

The version table showed Version, Published, and Source only, so nothing gave the user the command that installs a specific version. The repository-level "Setup instructions" drawer (already merged) teaches client configuration, but only with placeholder coordinates. This adds an Actions column to the Maven/npm version table, with a per-row menu whose one item is "View pull command." It opens a drawer holding the version-pinned install command: a four-line mvn dependency:get for Maven, or npm install <name>@<version> for npm, scope-qualified when the package has a scope.

Changes

  • The commands carry no repository URL and no credential, only coordinates. This matches the design proposal, whose npm command is a bare npm install pkg@version with no registry either. The client configuration that points at the registry is what the repository's setup instructions already hand over, so repeating the URL inline would duplicate it and, for npm, would push the reader toward a fragile --registry flag instead of the scope mapping the setup drawer teaches. A spec in pull_snippets_spec.js asserts no snippet carries a credential.
  • The table hosts the drawer, not the page. versions_table.vue adds the actions column, a GlDisclosureDropdown menu, and one drawer keyed on activeVersion — mirroring the merged detail/repository_actions.vue, which holds its GlDisclosureDropdownItem and <setup-drawer> as siblings. pull_command_drawer.vue pairs MountingPortal with GlDrawer, controlled entirely by an open prop with no internal state, and renders each block through the merged setup_instructions/snippet_code_block.vue. One drawer serves the whole table because only one row's menu can be acted on at a time; a spec asserts exactly one drawer renders. This also kept the step out of version_list.vue's data and computed properties, which the manifests-table MR (!250140 (merged)) renamed wholesale; that MR has since merged, and this branch is rebased onto it.
  • A menu rather than a direct button, for one item today. Pajamas suggests a more-actions menu at two or more row actions, and this ships one. The slice spec assigns the design's remaining row items — manage tags, download the version, delete the version — to this same menu in Phase 2, gated only on AR endpoints that do not answer yet, so a direct tertiary button would be replaced rather than extended.
  • The composers live beside the drawer rather than in utils.js. pull_snippets.js mirrors setup_instructions/snippets.js's section shape ([{ heading, blocks: [{ code, copyText }] }]) but stays a separate module: the setup snippets are placeholder-based and vary by build tool, while these are concrete and tool-free, matching a pull drawer that has no tool selector in the design proposal.
  • Two departures from the design proposal. (a) The proposal shows two sections, a tag-keyed one ("Install by version" against a dist-tag such as latest) and a version-pinned one ("Install by version hash"). Only the version-pinned one ships; the tag-keyed one waits with the rest of the tag surface. With no tag section to contrast against, the shipped section is labelled "Install by version", because "version hash" misdescribes a semantic version. (b) The proposal closes the drawer body with "For more information, see the documentation." No Artifact Registry documentation page exists yet, so the line is left out rather than linked to nothing.
  • One visual difference left deliberately. The proposal draws each section in a card with a grey header band. The merged setup_snippets.vue renders the same kind of content as a plain heading above a code block, and the two drawers sit in the same feature, so this follows the merged component.
  • Container formats get nothing here. Docker and OCI rows live in the merged manifests table (!250140 (merged)), and their pull-by-digest command follows in its own step rather than riding along here. Verified the container version list is unchanged: header only, no table, no menu, no error.
  • Reuses the existing copy. Only three new i18n strings: ArtifactRegistry|Install by version, ArtifactRegistry|Pull command for %{version}, ArtifactRegistry|View pull command. The menu's accessible name reuses the existing ArtifactRegistry|More actions for %{name}, and the copy button reuses ArtifactRegistry|Copy the install command.
  • Tests. pull_snippets_spec.js covers both formats, the scoped and unscoped npm name, the no-credential assertion, and every empty case. pull_command_drawer_spec.js covers the section it renders and that it hands the shared drawer its title and open state. versions_table_spec.js covers the per-row menu, opening against the right row, reopening against a different row, and closing.
  • Extracts the shared drawer shell instead of duplicating it again. 47 of the 98 lines in the pull-command drawer were byte-identical to the merged setup-instructions drawer: the MountingPortal + GlDrawer pair, the open prop, the close emit, the uniqueId title id, the getContentWrapperHeight computed, DRAWER_Z_INDEX, and the #title heading markup. Those lines carry the mount-to="body" workaround (.panel-content is a containing block for fixed descendants, which otherwise clips the drawer to the panel) and the aria-labelledby pairing that gives the drawer its only accessible name. Of 61 GlDrawer usages in the monolith, only 3 set aria-labelledby at all, and only these 2 combine it with the body portal, so this was a local convention living in two hand-copied places that the next copier would have dropped. New components/instructions_drawer.vue owns the shell, taking a title, an open prop, a close emit, and a default slot; each drawer spec's duplicated shell tests moved into a shared spec for the new component.
  • Moves snippet_code_block.vue into components/. It already has three consumers across two pages, and the pull-command drawer was the only file in the feature reaching across page directories to import it from its old location. components/not_found.vue is the existing precedent for this: it is imported by four separate pages the same way.

The keyboard path was verified in a browser: the menu opens with Enter, ArrowDown then Enter opens the drawer, and Escape closes it. Focus staying on the row's menu toggle, which an earlier revision of this description read as a pass, is the gap instead — GlDrawer handles Escape and moves no focus of its own, and on close the <aside> is destroyed and focus falls to <body>. The shared shell now takes focus once the open transition finishes and hands it back to whatever opened it on close; two specs in instructions_drawer_spec.js assert document.activeElement in both directions. Trapping focus for the span in between belongs to GlDrawer and is left there. The drawer's aria-labelledby resolves to its own heading, which names the row the drawer was opened from. Clearing the active row on close does not blank the drawer mid-transition: GlDrawer keeps its subtree mounted for the ~200 ms slide-out, which was measured frame by frame rather than assumed.

The extraction changes no rendered output: two shell tests were removed from each of the two drawer specs and replaced by one pass-through test each, and the new instructions_drawer.vue spec covers the shell itself, the focus handoff included. It passes on both Vue 2 and Vue 3. Both drawers were re-verified in a browser for the one thing a unit test cannot check: that the portal still lets the drawer escape the panel rather than being clipped to it.

Screenshots or screen recordings

s22-row-menu

s22-maven-pull-drawer

s22-npm-pull-drawer

The drawer is pinned to the row it was opened from (1.1.0-rc.1 for the Maven capture); the npm capture shows the scope-qualified name.

The already-merged setup-instructions drawer, on the shared shell, unchanged — its tabs, tool selector, and snippets all render as before:

s22-setup-drawer-unchanged

How to set up and validate locally

  1. Enable the flag: Feature.enable(:artifact_registry_ui).
  2. Visit /o/<organization>/-/artifact_registry/acme/repositories.
  3. Open a Maven repository, click an artifact name to reach the version list.
  4. Confirm the Actions column renders last, after Source.
  5. Open a row's menu and choose "View pull command"; confirm the drawer's command carries that row's version.
  6. Open a different row's menu and confirm the drawer re-renders against the new row.
  7. Repeat on an npm repository with both a scoped and an unscoped package, confirming the scoped one renders @scope/name@version and the unscoped one a bare name@version.
  8. Confirm Escape closes the drawer and focus returns to the row's menu toggle rather than to the top of the page.
  9. Open a Docker or OCI artifact and confirm that page is unchanged.
  10. Back on the repository, open View setup instructions from the header menu and confirm the merged drawer is unaffected by the shared shell: it overlays the whole viewport rather than being clipped to the panel, its header stays put while the body scrolls, and Escape closes it.
  11. Artifact ids are generated from the repository name, so reach the version list by clicking through the repository detail table rather than by typing a URL.

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 Zack Cuddy

Merge request reports

Loading
Loading