Add sortable columns to the artifact version list
What does this MR do and why?
This change is behind the feature flag :artifact_registry_ui, which is default-off.
Maven/npm versions sort on Version and Published. Docker/OCI manifests sort on Published alone, since created_at is the only column the manifests sort enum offers. Both default to Published descending.
| Format | Sortable columns | Default |
|---|---|---|
| Maven / npm versions | Version, Published | Published descending |
| Docker / OCI manifests | Published | Published descending |
The contract's version sort orders by database collation, not by semantic version precedence, so ascending order places 1.10.0 before 1.9.0. Rather than withhold the column, the Version header carries a help icon whose tooltip and aria-label both read "Sorts alphabetically, not by version order. Ascending puts 1.10.0 before 1.9.0." A tooltip alone never reaches a screen reader, which is why the same sentence is the icon's accessible name. This matches acceptance criterion 15 of the merged S14 spec. An earlier revision of this MR shipped the column unsortable; that is reverted here per review.
Changes
VERSION_SORT_COLUMNSandMANIFEST_SORT_COLUMNSstay separate, because Maven/npm and Docker/OCI back different column sets: the Maven/npm endpoint offers Version and Published, the Docker/OCI endpoint offers Published alone. The view picks the set per format, and a sort named in the route query is validated against the set the active table offers, sosort=version_ascon a Docker artifact falls back to the default rather than reaching the read.- The
externalSortCountremount key is ported fromrepositories_list.vue/repositories_table.vue.GlTableseeds its sort state from props indata()and never watches them, so a sort it did not raise itself would leave the arrow andaria-sortstuck on the previous column; remounting is the only way in. The counter only advances for a sort the table did not ask for, so a header click keeps the reader's focus. no-local-sortingis set on both tables, so the rendered order is always the page the endpoint sorted, not a browser-side reshuffle of one page.- The cursor-drop rule comes over from
repositories_list.vueasapplyQueryParams,pushQuery, andCURSOR_QUERY_KEYS. It is named for the route query rather than for the sort, so Step 21's referrer preference extends it for nothing. toSortEnumValueandtoTableSortnow take the column map as an argument instead of closing overREPOSITORY_SORT_COLUMNS, since the repositories table and the artifact tables sort on different sets. The two call sites inrepositories_list.vueare updated accordingly.- The sort argument is typed
Stringintypedefs.graphql, not as a local enum. The backend will addArtifactRegistryVersionSort; a local type under that name would collide the way a localPageInfoonce did in this same file. - The
sortvariable is declared twice per query document, once on the@clientpackage/imagefield and once on the nested connection, because Apollo collects no variables from inside a removed@clientfield. The existing guard inqueries_spec.jscatches a missed declaration. - A sort-keyed connection policy is added in
cache_config.js, kept separate from the unkeyed policy the repository-detailimages/packagesconnections share, since those take no sort argument. - The mock resolver falls back to the default ordering on an unrecognized sort value rather than reaching through, because the sort travels the route query and a hand-edited or stale link should render the default order instead of a broken page.
- A re-read for a new sort or page no longer hides the whole table behind a skeleton, per review.
versions_section.vuekeeps the table mounted and hands itis-loading, which the tables bind toGlTable's:busyalongside a#table-busyloading icon; only the first read, which has no rows to keep, stands the skeleton in for the table. This ports the:busyhandling already used byrepositories_table.vue. - Merge conflicts with master are resolved by rebasing onto master
90abf6596a59. The conflicts were inversions_section.vueandversions_section_spec.js, against the empty-state step !250733 (merged) which merged in the meantime; the resolution keeps both the empty state and the sort wiring.
Screenshots or screen recordings
Driven against the local GDK with the AR client stub, on a Maven package (com.example.maven-releases:core). Source and Actions carry no sort affordance.
| Published descending (default) | Version ascending | The Version sort caveat |
|---|---|---|
![]() |
![]() |
![]() |
These screenshots cannot demonstrate the 1.10.0 before 1.9.0 case the tooltip warns about, because the client stub builds versions as 1.#{n / 10}.#{n % 10} with n capped at 40, so every seeded ladder stops at 1.4.0 and every segment stays a single digit, leaving collation and semantic ordering in agreement on that data. The tooltip is therefore the only place the caveat is visible locally; showing the ordering difference itself needs either real data or a change to the stub.
How to set up and validate locally
- Enable the
:artifact_registry_uifeature flag and install the local AR client stub. - Open a Maven or npm artifact's version list. Confirm Published is the active descending sort and that Version is also sortable, while Source and Actions offer none.
- Click Published. The URL gains
?sort=created_at_ascand the rows reorder oldest first. - Click Version. The URL gains
?sort=version_ascand the rows reorder by collation, so1.10.0sorts before1.9.0ascending. - Hover the Version header's help icon and confirm the tooltip shows the alphabetical-sort caveat.
- Open a Docker or OCI artifact. Confirm there is no Version column and that Digest, Type, and Size are not sortable.
- Load a
?sort=version_ascURL on a Docker artifact and confirm it renders Published descending rather than a broken page. - On an artifact with more than 20 rows, page to page two and then click a sortable header. The cursor should leave the URL and the pager should return to page one.
- Apply a sort on an artifact that already has rows and confirm the table stays on screen, marked busy, rather than being replaced by the skeleton.
Agent Notes
- Verified in the browser against the GDK with the AR client stub. Clicking the Version header writes
?sort=version_ascand reorders the rows; the header takes the ascending arrow and Published gives its up. On a Docker artifact, a?sort=version_ascURL renders Published descending with no Version column, and Digest, Type, and Size stay unsortable — so the per-format validation holds end to end. ⚠️ Every story inversion_list.stories.jsrenders the not-found state on this branch, includingSortedByVersion(?sort=version_asc), which this MR restores. Confirmed pre-existing. So the story here is present for consistency but is not exercising anything, and the automated axe run over the sort affordance is not doing what it looks like it is doing. Cause not diagnosed; it predates this step.locale/gitlab.potgains back the stringArtifactRegistry|Sorts alphabetically, not by version order. Ascending puts 1.10.0 before 1.9.0., a net of one added string. Thegettextpre-push hook passes.- Jest: 1581 tests pass across the 52
ee/spec/frontend/packages_and_registries/artifact_registrysuites under the default Jest config. The same tworepository_detail_spec.jsfailures are pre-existing, confirmed by running that spec on a cleanorigin/masterworktree, where it fails identically. The Vue 3 variant was not run locally this pass. ESLint and Prettier are clean. - !252327 (merged) (Step 21) is stacked on this branch and has been rebased onto this revision and force-pushed.
- The
~type::featurelabel Danger asked for has been applied.
This MR was authored with the assistance of an AI coding agent and reviewed by @zcuddy before submission.


