Support delete and archive for decisions
What does this MR do and why?
Adds state connection and filter to work item decisions
Adds delete and archive operations for decisions
Related to #627780 (closed)
Changelog: added
Note: Closely related to !254958 (merged). If it gets merged first, there may be conflicts in this MR that may require a rebase with master.
Database review notes
Files relevant for DB review
| File | Why |
|---|---|
| db/migrate/20260914000001_add_archived_at_to_work_item_decisions.rb | Adds nullable archived_at timestamptz, no default, no index |
| db/schema_migrations/20260914000001 | Checksum for the migration |
| db/structure.sql | One-line diff: the new column on work_item_decisions |
| ee/app/models/work_items/decision.rb | New scopes active, resolved, archived; with_state / │ │ │ with_states build the filtered relations |
| ee/app/models/work_items/widgets/decision_log.rb | Base relation the connection filters (unchanged, for │ │ │ context) |
| ee/app/graphql/types/work_items/widgets/decision_log_type.rb | decisions(states:) argument applies the scopes before │ │ │ keyset pagination |
| ee/app/services/work_items/decisions/archive_service.rb | New UPDATE via update_columns |
| ee/app/services/work_items/decisions/delete_service.rb | New DELETE via destroy!, cascading to options |
SQL queries
Note: values are local sample values used to generate db query plans using local GDK (this is a new feature and no data exists in prod yet).
~5k decisions and ~15.5k options were seeded for query plan generation.
Archive
Location: archive_service.rb:19
UPDATE "work_item_decisions" SET "archived_at" = '2026-09-15 03:16:16.332077', "updated_at" = '2026-09-15 03:16:16.332077' WHERE "work_item_decisions"."id" = 16380Query plan: https://explain.depesz.com/s/GgX5
DELETE by primary key; FK cascade deletes options
Location: delete_service.rb:16
-- Delete
DELETE FROM "work_item_decisions" WHERE "work_item_decisions"."id" = 16364
-- FK cascade
DELETE FROM work_item_decision_options WHERE work_item_decision_id = 12573Query plan (delete): https://explain.depesz.com/s/PczN9
Query plan (FK cascade): https://explain.depesz.com/s/4R5N
Filtered connection
Keyset pagination adds ORDER BY id DESC LIMIT 101 to the widget's base relation. One variant per state, plus the OR form for several states. Work item 4 held 9 open seeded rows; work item 5 held 7 resolved and 2 archived.
Location: decision_log_type.rb:33 with decision.rb:31-33, 64
-- states: [ACTIVE]
SELECT "work_item_decisions".* FROM "work_item_decisions" WHERE "work_item_decisions"."work_item_id" = 4 AND "work_item_decisions"."resolved_at" IS NULL ORDER BY "work_item_decisions"."id" DESC LIMIT 101Query plan: https://explain.depesz.com/s/Og8Y
-- states: [ACTIVE, RESOLVED]
SELECT "work_item_decisions".* FROM "work_item_decisions" WHERE "work_item_decisions"."work_item_id" = 5 AND ("work_item_decisions"."resolved_at" IS NULL OR "work_item_decisions"."resolved_at" IS NOT NULL AND "work_item_decisions"."archived_at" IS NULL) ORDER BY "work_item_decisions"."id" DESC LIMIT 101Query plan: https://explain.depesz.com/s/7dGE
FE integration notes
Note: values are sample values
Archiving a resolved decision
Mutation: workItemDecisionArchive
Input type: WorkItemDecisionArchiveInput.
Field │ Type │ Notes │
├──────────────────┼──────────────────────┼─────────────────────────────────────────────────────────────────────────────┤
│ id │ WorkItemsDecisionID! │ Global ID of the decision, for example gid://gitlab/WorkItems::Decision/123 │
├──────────────────┼──────────────────────┼─────────────────────────────────────────────────────────────────────────────┤
│ clientMutationId │ String │ Optional │
└──────────────────┴──────────────────────┴─────────────────────────────────────────────────────────────────────────────┘
mutation workItemDecisionArchive($input: WorkItemDecisionArchiveInput!) {
workItemDecisionArchive(input: $input) {
decision {
id
...
resolvedAt
}
errors
}
}
{
"input": {
"id": "gid://gitlab/WorkItems::Decision/123"
}
}Hard-deleting an open decision
Mutation: workItemDecisionDelete
Input type: WorkItemDecisionDeleteInput.
┌──────────────────┬──────────────────────┬─────────────────────────────────────────────────────────────────────────────┐
│ Field │ Type │ Notes │
├──────────────────┼──────────────────────┼─────────────────────────────────────────────────────────────────────────────┤
│ id │ WorkItemsDecisionID! │ Global ID of the decision, for example gid://gitlab/WorkItems::Decision/123 │
├──────────────────┼──────────────────────┼─────────────────────────────────────────────────────────────────────────────┤
│ clientMutationId │ String │ Optional │
└──────────────────┴──────────────────────┴─────────────────────────────────────────────────────────────────────────────┘
Payload: decision (WorkItemDecision, nullable), errors ([String!]!), clientMutationId.
mutation workItemDecisionDelete($input: WorkItemDecisionDeleteInput!) {
workItemDecisionDelete(input: $input) {
decision {
id
}
errors
}
}
{
"input": {
"id": "gid://gitlab/WorkItems::Decision/123"
}
}MR acceptance checklist
Evaluate this MR against the MR acceptance checklist. It helps you analyze changes to reduce risks in quality, performance, reliability, security, and maintainability.
Related to #627780 (closed)