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@versionwith 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--registryflag instead of the scope mapping the setup drawer teaches. A spec inpull_snippets_spec.jsasserts no snippet carries a credential. - The table hosts the drawer, not the page.
versions_table.vueadds the actions column, aGlDisclosureDropdownmenu, and one drawer keyed onactiveVersion— mirroring the mergeddetail/repository_actions.vue, which holds itsGlDisclosureDropdownItemand<setup-drawer>as siblings.pull_command_drawer.vuepairsMountingPortalwithGlDrawer, controlled entirely by anopenprop with no internal state, and renders each block through the mergedsetup_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 ofversion_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.jsmirrorssetup_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.vuerenders 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 existingArtifactRegistry|More actions for %{name}, and the copy button reusesArtifactRegistry|Copy the install command. - Tests.
pull_snippets_spec.jscovers both formats, the scoped and unscoped npm name, the no-credential assertion, and every empty case.pull_command_drawer_spec.jscovers the section it renders and that it hands the shared drawer its title and open state.versions_table_spec.jscovers 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+GlDrawerpair, theopenprop, thecloseemit, theuniqueIdtitle id, thegetContentWrapperHeightcomputed,DRAWER_Z_INDEX, and the#titleheading markup. Those lines carry themount-to="body"workaround (.panel-contentis a containing block for fixed descendants, which otherwise clips the drawer to the panel) and thearia-labelledbypairing that gives the drawer its only accessible name. Of 61GlDrawerusages in the monolith, only 3 setaria-labelledbyat 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. Newcomponents/instructions_drawer.vueowns the shell, taking a title, anopenprop, acloseemit, and a default slot; each drawer spec's duplicated shell tests moved into a shared spec for the new component. - Moves
snippet_code_block.vueintocomponents/. 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.vueis 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
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:
How to set up and validate locally
- Enable the flag:
Feature.enable(:artifact_registry_ui). - Visit
/o/<organization>/-/artifact_registry/acme/repositories. - Open a Maven repository, click an artifact name to reach the version list.
- Confirm the Actions column renders last, after Source.
- Open a row's menu and choose "View pull command"; confirm the drawer's command carries that row's version.
- Open a different row's menu and confirm the drawer re-renders against the new row.
- Repeat on an npm repository with both a scoped and an unscoped package, confirming the scoped
one renders
@scope/name@versionand the unscoped one a barename@version. - Confirm Escape closes the drawer and focus returns to the row's menu toggle rather than to the top of the page.
- Open a Docker or OCI artifact and confirm that page is unchanged.
- 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
Escapecloses it. - 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.



