Add GraphQL single-version field to Artifact Registry (monolith/S06 plan: 5/22)
What does this MR do and why?
Step 5 of the monolith/S06 "version detail" plan (work item #623231). It adds the GraphQL single-version field and the foundations later steps build on.
Add a version(id:, artifactId:) field on Types::ArtifactRegistry::RepositoryDetailsType, resolved by a new VersionResolver and returning the version element type (VersionType via the ArtifactRegistryVersionDetails detail type). The single-version read returns the same ArtifactRegistryVersion shape the versions connection does; the detail type carries the child fields later steps mount (files connection, statistics), which stay off the versions list connection so a per-row fan-out cannot happen.
Add a VersionPresenter that pairs the version value object with the repository and organization it was read through, so the later child fields can reach the slug, repository name/format, and client.
The resolver holds the repository format ahead of the client call (a docker/oci repository resolves null with no request), resolves null on 404 (existence-hiding), and applies an artifact-pairing check: it resolves null when the version's package_id names a different package. The pairing check fails open when Artifact Registry omits package_id, because rejecting an absent key would null every version on a deployment predating the serialization patch.
Add sizeBytes (nullable BigInt) to the version element type; it is null for a Maven version until Artifact Registry serializes the column, and on a remote repository.
Review feedback addressed
- Coerce
ArtifactRegistry::Version#package_idto a String so a JSON-number package_id from Artifact Registry compares correctly against the coerced GraphQL ID argument (previously a numeric package_id would fail the pairing check and null every version). - Coerce
ArtifactRegistry::Version#sizeto an Integer so a numeric-string or float from Artifact Registry cannot render a non-numericsizeBytesthrough BigInt; a non-numeric value reads nil. - Cover
sizeByteson the versions connection path in the request spec (it lives on the shared element type, so it is live on the list too). - Add type/nullability/argument/resolver assertions for the new version field in the repository details type spec.
- Make the mock-resolver spec's parent-answered-fields exemption earned, by asserting the version resolver returns each exempted field on its object.
- Restore the load-bearing note on
VersionTypethat a bare version has no policy, so dropping the authorization skip would raise.
References
- Work item: #623231
Screenshots or screen recordings
N/A. Schema text only, behind the dark artifact_registry_ui feature flag, with no rendered UI change in this MR.
Danger's multiversion-compatibility warning is not a concern here: the frontend change is typedefs-only and the version field stays @client, so nothing new is asked of the server in this MR.
How to set up and validate locally
Two parts: a rails runner script that asserts the value-object coercion (the headline review fix), and the spec commands.
Save the following script and run it with bundle exec rails runner <file> (observed output: OK: package_id and size coercion behave as expected).
# Validates the review-feedback fixes on ArtifactRegistry::Version coercion:
# - package_id (JSON number) coerces to String so the resolver pairing check compares correctly
# - size (numeric string / non-numeric) coerces to Integer or nil so BigInt renders numerically
numeric_pkg = ArtifactRegistry::Version.new('id' => 'v1', 'package_id' => 123)
raise "package_id not coerced to String" unless numeric_pkg.package_id == '123'
absent_pkg = ArtifactRegistry::Version.new('id' => 'v1')
raise "absent package_id should be nil (fail-open)" unless absent_pkg.package_id.nil?
int_size = ArtifactRegistry::Version.new('id' => 'v1', 'size' => 4_294_967_296)
raise "integer size should pass through" unless int_size.size == 4_294_967_296
str_size = ArtifactRegistry::Version.new('id' => 'v1', 'size' => '987654')
raise "numeric-string size should coerce to Integer" unless str_size.size == 987_654
bad_size = ArtifactRegistry::Version.new('id' => 'v1', 'size' => 'not-a-number')
raise "non-numeric size should coerce to nil" unless bad_size.size.nil?
puts "OK: package_id and size coercion behave as expected"Then the spec commands:
- Value-object unit spec (uses
fast_spec_helper, run alone):bundle exec rspec ee/spec/lib/artifact_registry/version_spec.rb-> 14 examples, 0 failures. - GraphQL unit specs:
bundle exec rspec ee/spec/graphql/resolvers/artifact_registry/version_resolver_spec.rb ee/spec/graphql/types/artifact_registry/version_type_spec.rb ee/spec/graphql/types/artifact_registry/repository_details_type_spec.rb-> 32 examples, 0 failures. - Request specs (need
VITE_ENABLED=true):VITE_ENABLED=true bundle exec rspec ee/spec/requests/api/graphql/organizations/artifact_registry_version_spec.rb ee/spec/requests/api/graphql/organizations/artifact_registry_versions_spec.rb-> 50 examples, 0 failures. - Frontend Jest (the Apollo composition and mock/query suites):
node_modules/.bin/jest ee/spec/frontend/packages_and_registries/artifact_registry/graphql/-> 238 tests pass across 6 suites. - GraphQL gates:
bundle exec rake gitlab:graphql:validateandnode scripts/frontend/graphql_possible_types_extraction.js --check, plus an Apollo schema composition check of the frontend typedefs against the dumped server schema -> all pass.
Note: the local Artifact Registry service could not be booted this session (its build now requires an iam block or authorization.unenforced that the local-stack config snapshot predates, unrelated to this MR), so validation is via the WebMock-backed request specs, shaped to the AR v1 OpenAPI contract.
MR acceptance checklist
Evaluate this MR against the MR acceptance checklist.