AR versions sort argument (monolith/S14 plan: 8/25)
What does this MR do and why?
This MR adds a sort argument to the Artifact Registry versions GraphQL connection, letting clients order versions by publication date or by version. It is step 8 of the 25-step monolith/S14 plan. The artifact_registry_ui feature flag is dark (default disabled).
Specifics:
- Adds a new
ArtifactRegistryVersionSortGraphQL enum with four values over the two columns the Artifact Registry contract sorts on:CREATED_AT_ASC,CREATED_AT_DESC,VERSION_ASC,VERSION_DESC. Each value carries its{ sort:, order: }pair. - The
versionsconnection on bothArtifactRegistryMavenPackageandArtifactRegistryNpmPackagetakes an optionalsortargument (milestone 19.4, experiment). It defaults to publication date descending (CREATED_AT_DESC), matching the endpoint's own default. An explicit null falls back to that default viareplace_null_with_default. VersionsResolversplits the enum's sort/order pair onto the client call and sends the pair unconditionally, so the order stays pinned even if the endpoint default ever moves.- The two
versionsfield descriptions now say "ordered by publication date descending by default" rather than stating an unconditional order, since the sort argument would otherwise make that false. Schema artifacts (GraphQL reference doc and introspection JSON) are regenerated.
Caveat: on a remote repository created_at is the cache-fill time, so the created_at sort orders by cache recency rather than upstream publish order. This is noted on the enum.
Testing:
- Enum spec asserts the exact value-to-pair map.
- Resolver spec (static only, per the GraphQL testing standard) asserts the returned connection type, the
FieldCallCountbudget, and the sort argument's type,default_value, andreplace_null_with_default. - Request spec runs a table over all four enum values asserting each reaches Artifact Registry as its sort/order pair over the wire; an explicit
sort: nullcase asserting the default pair still goes out; and a no-argument default case asserting created_at/desc and that no other sort is sent. All existing versions request stubs were updated for the now-always-sent sort/order params (WebMock exact-match query stubs). - RuboCop clean; GraphQL docs regenerated and in sync; frontend graphql jest suite passes.
References
- Plan: monolith/S14 plan, step 8
- Spec: S14 version list
Screenshots or screen recordings
N/A. Backend GraphQL schema change behind the dark artifact_registry_ui flag; no UI in this MR.
How to set up and validate locally
- Save the following script and run it with
bundle exec rails runner validate_step8.rb:
# Validates that the versions `sort` argument maps each enum value to the client's
# sort/order pair, and that an explicit null falls back to the default pair.
require 'webmock'
require 'rspec/mocks/standalone'
include WebMock::API
WebMock.enable!
WebMock.disable_net_connect!(allow: %w[gdk.test 127.0.0.1 localhost])
failures = []
check = ->(desc, cond) { puts("#{cond ? 'PASS' : 'FAIL'}: #{desc}"); failures << desc unless cond }
ActiveRecord::Base.transaction do
s = SecureRandom.hex(4)
org = FactoryBot.create(:organization, path: "arv-#{s}", name: "AR #{s}")
user = FactoryBot.create(:user, username: "arv-#{s}", email: "arv-#{s}@example.com")
FactoryBot.create(:organization_user, organization: org, user: user)
base = 'http://artifact-registry.test'
slug = 'resolved-handle'
repo = 'maven-releases'
pkg = 'a1b2c3d4'
Gitlab.config.artifact_registry['api_url'] = base
Feature.enable(:artifact_registry_ui, org)
# Skip the namespace-mapping round trip: return a resolved registry with a fixed slug.
registry = ArtifactRegistry::NamespaceMapping::Registry.new(slug: slug, status: 'active')
mapping = FactoryBot.create(:artifact_registry_namespace_mapping, organization: org)
allow_any_instance_of(ArtifactRegistry::NamespaceMapping).to receive(:registry).and_return(registry)
allow_any_instance_of(ArtifactRegistry::TokenExchange).to receive(:token_for).and_return('tok')
repo_url = "#{base}/api/v1/#{slug}/repositories/#{repo}"
pkgs_url = "#{repo_url}/maven/packages"
vers_url = "#{pkgs_url}/#{pkg}/versions"
jh = { 'Content-Type' => 'application/json' }
stub_request(:get, repo_url).to_return(status: 200, headers: jh,
body: { 'id' => 'r1', 'name' => repo, 'format' => 'maven', 'kind' => 'hosted',
'visibility' => 'private', 'downloads_count' => 0, 'size_bytes' => 0, 'settings' => {} }.to_json)
stub_request(:get, pkgs_url).with(query: hash_including({})).to_return(status: 200, headers: jh,
body: [{ 'id' => pkg, 'group_id' => 'com.example', 'artifact_id' => 'core' }].to_json)
stub_request(:get, vers_url).with(query: hash_including({})).to_return(status: 200, headers: jh, body: [].to_json)
query = <<~GQL
query($id: OrganizationsOrganizationID!, $name: String!, $sort: ArtifactRegistryVersionSort) {
organization(id: $id) {
artifactRegistryRepository(name: $name) {
packages(first: 20) {
nodes { ... on ArtifactRegistryMavenPackage { versions(first: 20, sort: $sort) { nodes { id } } } }
}
}
}
}
GQL
outbound_for = ->(sort_value) do
WebMock::RequestRegistry.instance.reset!
res = GitlabSchema.execute(query, context: { current_user: user },
variables: { 'id' => org.to_global_id.to_s, 'name' => repo, 'sort' => sort_value })
raise "errors: #{res['errors']}" if res['errors']
sig = WebMock::RequestRegistry.instance.requested_signatures.hash.keys.find { |k| k.uri.to_s.include?('/versions') }
q = sig ? Rack::Utils.parse_query(sig.uri.query) : {}
[q['sort'], q['order']]
end
expected = {
'CREATED_AT_ASC' => %w[created_at asc],
'CREATED_AT_DESC' => %w[created_at desc],
'VERSION_ASC' => %w[version asc],
'VERSION_DESC' => %w[version desc]
}
expected.each do |enum_value, (sort, order)|
check.call("#{enum_value} -> sort=#{sort} order=#{order}", outbound_for.call(enum_value) == [sort, order])
end
check.call('no sort argument -> created_at/desc', outbound_for.call(nil) == %w[created_at desc])
raise ActiveRecord::Rollback
end
WebMock.disable!
puts(failures.empty? ? "\nALL PASS" : "\nFAILURES: #{failures.join('; ')}")- Confirm the output ends with
ALL PASS(all five checks). This script was run locally and printsALL PASS.
MR acceptance checklist
Evaluate this MR against the MR acceptance checklist.
Feature flag artifact_registry_ui is dark; no changelog (dark), and the enum and argument text are schema text rather than i18n.
Related to #618410 (closed)