Add note_id support to TodoCreate mutation
Summary
This is the backend half of adding a to-do for a specific comment in a discussion thread on an issue or merge request. It extends the to-do service and the TodoCreate GraphQL mutation so a to-do can reference an individual comment while keeping the parent issue or MR as its target. The change is behind a feature flag and is fully backward compatible with existing callers. The frontend comes in follow-up MRs.
Reviewer note — this is MR 1 of 3 in a stack.
- !251726 (merged) (this MR) — backend:
noteIdon theTodoCreatemutation ← review this one- !251727 — frontend: per-comment to-do button on issue/MR comments
- !251848 — frontend: per-comment to-do button on work item comments
To test the full feature end-to-end, check out !251848. That branch includes the whole stack. Instructions are in its description.
What this MR does
TodoService#mark_todoaccepts an optionalnote:keyword. When given, the created to-do keeps the parent issue or merge request as its target and also references the specific comment through the existingnote_idcolumn. This mirrors how mention to-dos already attach a note.TodoCreateGraphQL mutation gains an optionalnoteIdargument, marked as an experiment. WhennoteIdis provided and the flag is on, the mutation loads the note, checks that it belongs to the target and that the user can read it, then creates the to-do referencing the comment.targetIdstays required and behaves exactly as before, so the existing schema and callers are untouched.- The pending to-do dedup now includes the note, so a to-do for one comment, a to-do for another comment, and the plain to-do on the issuable are distinct. Without this, an existing pending to-do on the issuable suppressed every comment to-do and the mutation returned a null todo with no errors.
Feature flag
Behind todo_for_comment (type: wip, default disabled). When the flag is off, noteId is ignored entirely: the note is not looked up or validated, and the to-do is created without a note reference, as if the argument did not exist.
Authorization
Reuses the read_note check on the comment plus the existing create_todo check on the target through authorized_find!. The note must belong to the target. A note the user cannot read raises resource-not-available.
Database
No migration. The todos.note_id column and the Todo belongs_to :note association already exist.
Testing
- RSpec for the service covering the note path and the note-aware dedup (plain plus comment, two comments, exact duplicates).
- RSpec for the mutation covering: flag on with a valid note, a note that does not belong to the target, a note the user cannot read, flag off ignoring the note, including one the user cannot read.
- GraphQL schema docs and introspection schemas regenerated.
What it looks like
The frontend halves are !251727 and !251848. With the stack applied, each comment's More actions menu gets an "Add a to-do" entry and the To-Do List shows the comment text:
| Comment More actions menu | To-Do List |
|---|---|
![]() |
![]() |

