AR delete surface/Step 13: Manifest row delete on the manifests table
What does this MR do and why?
Renders the row menu's Delete manifest item on the manifests table for Docker and OCI
images, addressing the row by digest through ArtifactRegistryManifestDelete
(!253536 (merged), merged).
Step 13 of 19 in the monolith artifact delete surface plan. Closes #627403 (closed).
Behind the artifact_registry_ui feature flag, which is dark.
Three things this does differently from the version row delete it otherwise copies
The confirmation body comes from the row, not the modal. What a manifest delete costs
depends on what the manifest is. Deleting an index leaves its children behind as untagged,
standalone manifests
(Figma),
and deleting a signature leaves the image it signed verifying as unsigned
(Figma).
A modal deriving its own copy cannot say either, which is why
#627401 (closed) made the copy caller-supplied.
The kind-to-body mapping is manifestDeleteBody in utils.js, so it is unit-testable
away from the table.
The refetch is conditional. Artifact Registry refuses to delete a manifest that another
manifest indexes, and the mutation reports that refusal through its payload errors rather
than by raising. So the mutation resolves, and a refetchQueries handed to apollo.mutate
would re-read a list nothing was removed from. The refetch therefore runs only after the
helper reports acceptance, via
this.$apollo.getClient().refetchQueries({ include: [getArtifactManifestsQuery] }).
A refusal shows no success toast and triggers no refetch.
The refusal is not pre-empted client-side. The design greys out Delete manifest on a
manifest a parent index references, but
S14
assigns that rule to the delete contract. So the item stays enabled and the 409 reaches
the user as the error the shared helper already raises, naming the blocking digests, which
the contract calls the caller's next move.
Props threaded through
The mutation takes name, imageId, and digest. manifests_table.vue declared none of
the first two and versions_section.vue passed it neither, though it already passes both to
versions_table.vue. Both now thread through. This is the one place Steps 12 and 13 differ
in size.
Refetch, not cache eviction
The plan file's ## Approach still mandates cache eviction and forbids a refetch. That rule
was dropped on 2026-09-09 for the reasons recorded on
#627401 (closed). The plan has not been amended, so
where the two disagree the issues are current.
Stacked on
Targets 627401-ar-shared-delete-modal-and-version-row-delete
(!254357 (merged)), which adds both
repositories/components/delete_confirmation_modal.vue and
graphql/utils/delete_mutation.js. Neither exists on master, so this cannot target
master yet. Retarget and rebase once that merges.
Screenshots or screen recordings
A single walk through against a live Artifact Registry: the affordance and per-kind
confirmation copy (Index, Image, Signature), Cancel sending nothing, the 409 refusal naming the
blocking parent digest, and the accepted index delete that the re-read reflects.
How to set up and validate locally
The manifest list now reads from the real Artifact Registry API (image.manifests, wired up
in !254165 (merged)), so this validates as one
real-backend flow rather than the mock-list-plus-real-mutation split it used to need.
Prerequisites
-
Artifact Registry running on
:8080, with a hostedDOCKERorOCIrepository holding a real OCI index that has per-platform children and at least one tag. Push a signature manifest too if you want to see all three confirmation-copy variants. -
GDK up. Sign in on
:3333, not:3000:/users/sign_in404s on the http-router. -
Enable the dark flag in
rails console:Feature.enable(:artifact_registry_ui)
Walk through
Navigate to a container image's manifest list
(.../artifact_registry/repositories/<name>/artifacts/<image-id>). The rows are real, read
from image.manifests.
-
Every row offers the action. Each row's overflow menu contains Delete manifest, rendered destructive (red). The menu toggle's accessible name is
More actions for <short digest>, not just "More actions". -
The confirmation wording follows the row's kind. Open Delete manifest on each of these rows and confirm the body:
Row kind Body must say Index (Type reads Index)its child manifests are not deleted and will remain in Artifact Registry as untagged, standalone manifests Plain image (Type reads Image)only that the manifest and any tags pointing to it go Signature (Type reads Signature for ...)the image it signs will then verify as unsigned All must end with This action cannot be undone, and none may ask you to type anything to confirm (ADR-010 scopes typed confirmation to repository delete).
-
Dismissing sends nothing. Open the confirmation, press Cancel, and confirm no GraphQL request left the page.
-
The refusal (
409). Confirm the delete on a child manifest the index references. Artifact Registry refuses it, and because that refusal comes back through the mutation's payloaderrors, the shared helper raises an error alert naming the blocking parent digest. No success toast fires and the list is unchanged. -
The acceptance. Confirm the delete on the index digest. The mutation is accepted, the success toast appears, and the conditional refetch re-reads the list: the index and the tags that pointed at it are gone, its children remain as untagged, standalone manifests. Artifact Registry accepts rather than completes the request, so the row clears once the re-read lands.
MR acceptance checklist
- Tests added for this feature/bug (Jest specs for
manifests_table.vue,versions_section.vue, andmanifestDeleteBody) - Conforms to the code review guidelines
- Conforms to the Vue style guide
- New strings extracted to
locale/gitlab.pot - No changelog entry: the feature flag is dark