Resolve a decision with a written-in option

What does this MR do and why?

Resolve a decision with a written-in option

An open decision could only be resolved by picking existing options or rejecting them all, so a user's own answer had no structured home. The write is shared with the update service, whose collapse already records a written-in option as the single selected one.

Related to #631044 (closed)

FE integration notes

The workItemDecisionResolve mutation now accepts a written-in answer via selectedOptionContent. This should be used when resolving open questions manually with a custom, user-inputted option, whether there are existing options or not.

The resolutionRationale here is optional, as with other resolution flows involving options.

Example payload:

{
  "input": {
    "id": "gid://gitlab/WorkItems::Decision/42",
    "selectedOptionContent": "Use SQLite for the prototype",
    "resolutionRationale": "Fastest to set up"
  }
}

Note - once the decision is resolved this way, this will become the sole selected option.

DB review notes

No migrations. This MR adds one new SQL statement shape; every other statement on the changed paths already has an approved plan in a previous MR.

Files for DB review

File Why
ee/app/models/work_items/decision.rb New #write_in_option!: the bulk update_all below, followed by an option insert, inside its own transaction
ee/app/services/work_items/decisions/resolve_service.rb New caller of #write_in_option! on the resolve path
ee/app/services/work_items/decisions/update_service.rb The MR 6 collapse now calls #write_in_option!, so its deselect statement changes shape (see below)

Queries

Deselect the selected options — ee/app/models/work_items/decision.rb:94

Emitted by options.where(selected: true).update_all(selected: false, updated_at: Time.current).

Note: The deselect from !257464 (merged) moved into Decision#write_in_option! so the resolve path can reuse it. Since the model has no loaded option list, it filters by selected = TRUE instead of an explicit id list, which is why the statement appears as a new shape here; the rows it touches are unchanged, and on the resolve path it matches nothing.

UPDATE "work_item_decision_options" SET "selected" = FALSE, "updated_at" = '2026-10-01 23:27:31.962902' WHERE "work_item_decision_options"."work_item_decision_id" = 31381 AND "work_item_decision_options"."selected" = TRUE

Plan on a resolved decision with two selected options out of three (write-in edit of comma-separated UX list, creating new custom option): https://explain.depesz.com/s/1oWO

Same statement on an open decision with three unselected options (resolve write-in, creating new custom option): https://explain.depesz.com/s/WPvu

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 #631044 (closed)

Edited by Richard Chan

Merge request reports

Loading
Loading