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_COLUMNS and MANIFEST_SORT_COLUMNS stay 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, so sort=version_asc on a Docker artifact falls back to the default rather than reaching the read.
  • The externalSortCount remount key is ported from repositories_list.vue / repositories_table.vue. GlTable seeds its sort state from props in data() and never watches them, so a sort it did not raise itself would leave the arrow and aria-sort stuck 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-sorting is 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.vue as applyQueryParams, pushQuery, and CURSOR_QUERY_KEYS. It is named for the route query rather than for the sort, so Step 21's referrer preference extends it for nothing.
  • toSortEnumValue and toTableSort now take the column map as an argument instead of closing over REPOSITORY_SORT_COLUMNS, since the repositories table and the artifact tables sort on different sets. The two call sites in repositories_list.vue are updated accordingly.
  • The sort argument is typed String in typedefs.graphql, not as a local enum. The backend will add ArtifactRegistryVersionSort; a local type under that name would collide the way a local PageInfo once did in this same file.
  • The sort variable is declared twice per query document, once on the @client package / image field and once on the nested connection, because Apollo collects no variables from inside a removed @client field. The existing guard in queries_spec.js catches a missed declaration.
  • A sort-keyed connection policy is added in cache_config.js, kept separate from the unkeyed policy the repository-detail images / packages connections 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.vue keeps the table mounted and hands it is-loading, which the tables bind to GlTable's :busy alongside a #table-busy loading icon; only the first read, which has no rows to keep, stands the skeleton in for the table. This ports the :busy handling already used by repositories_table.vue.
  • Merge conflicts with master are resolved by rebasing onto master 90abf6596a59. The conflicts were in versions_section.vue and versions_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
s19-published-descending s19-version-ascending s19-version-sort-hint

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

  1. Enable the :artifact_registry_ui feature flag and install the local AR client stub.
  2. 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.
  3. Click Published. The URL gains ?sort=created_at_asc and the rows reorder oldest first.
  4. Click Version. The URL gains ?sort=version_asc and the rows reorder by collation, so 1.10.0 sorts before 1.9.0 ascending.
  5. Hover the Version header's help icon and confirm the tooltip shows the alphabetical-sort caveat.
  6. Open a Docker or OCI artifact. Confirm there is no Version column and that Digest, Type, and Size are not sortable.
  7. Load a ?sort=version_asc URL on a Docker artifact and confirm it renders Published descending rather than a broken page.
  8. 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.
  9. 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_asc and reorders the rows; the header takes the ascending arrow and Published gives its up. On a Docker artifact, a ?sort=version_asc URL 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 in version_list.stories.js renders the not-found state on this branch, including SortedByVersion (?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.pot gains back the string ArtifactRegistry|Sorts alphabetically, not by version order. Ascending puts 1.10.0 before 1.9.0., a net of one added string. The gettext pre-push hook passes.
  • Jest: 1581 tests pass across the 52 ee/spec/frontend/packages_and_registries/artifact_registry suites under the default Jest config. The same two repository_detail_spec.js failures are pre-existing, confirmed by running that spec on a clean origin/master worktree, 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::feature label Danger asked for has been applied.

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