Expose duoCreatedSessionId on the note GraphQL payload
What does this MR do and why?
backend MR to unblock !250722 (merged), which adds a "View session" button to agent replies in work items
- Adds
duoCreatedSessionIdto the GraphQLNotetype, resolving the Duo Agent Platform session that created the note through a batched lookup ofcreatedworkflow-note links - Returns the session id only to users with
read_duo_workflow, and declares the matching granular token permission - Triggers the note-updated GraphQL subscription when a session is linked to a note
- Work item notes are pushed to clients by subscription the moment they are created, which is before the created link is written, so the field would resolve to
nulland the button (added in the next MR in the stack) would only appear after a page reload. The extra push lets it re-resolve the field once the link exists
- Work item notes are pushed to clients by subscription the moment they are created, which is before the created link is written, so the field would resolve to
Reasons we couldn't use the existing fields:
duoWorkflowLinksis capped at one call per query so it cannot resolve a list of notesduoTriggeredSessionresolvestriggeredlinks while agent replies havecreatedlinks
Example query and payload
query {
note(id: "gid://gitlab/Note/1597") {
id
duoCreatedSessionId
}
}{
"data": {
"note": {
"id": "gid://gitlab/Note/1597",
"duoCreatedSessionId": "gid://gitlab/Ai::DuoWorkflows::Workflow/288"
}
}
}duoCreatedSessionId is null when the note has no created session, or when the current user cannot read it.
Database
All queries below run once per page of notes when duoCreatedSession is selected, driven by the new created_for_notes scope.
- The scope finds the
createdlinks for the page of notes:
SELECT "duo_workflows_workflow_notes".*
FROM "duo_workflows_workflow_notes"
WHERE "duo_workflows_workflow_notes"."link_type" = 1
AND "duo_workflows_workflow_notes"."note_id" IN (3584429628, 3584429682, 3584429724, 3584429783, 3612707137, 3612707788, 3668099973)
ORDER BY "duo_workflows_workflow_notes"."id" ASCExecution plan: https://console.postgres.ai/gitlab/projects/gitlab-production-main/sessions/55078/commands/158502 (thank you @brytannia - !249882 (comment 3706893441))
Index scan using index_duo_wf_wf_notes_on_note_id returning 7 rows, 15 buffers (~120 KiB). The IN list is bounded by the notes page size.
- The scope's
preload(workflow: [:project, :namespace, :user])loads the linked sessions:
SELECT "duo_workflows_workflows".*
FROM "duo_workflows_workflows"
WHERE "duo_workflows_workflows"."id" IN (284, 285, 286, 288, 289)Execution plan: https://postgres.ai/console/gitlab/gitlab-production-main/sessions/55049/commands/158380
Index scan using duo_workflows_workflows_pkey, 5 buffers (~40 KiB).
- The nested preload loads the sessions' projects, used by the
read_duo_workflowpolicy check:
SELECT "projects".*
FROM "projects"
WHERE "projects"."id" IN (1000000)- The nested preload loads the session owners, also used by the policy check:
SELECT "users".*
FROM "users"
WHERE "users"."id" IN (1, 105)- When the batch contains namespace-level sessions, the nested preload loads their namespaces, also used by the policy check (no query is issued when all sessions are project-level):
SELECT "namespaces".*
FROM "namespaces"
WHERE "namespaces"."id" IN (1, 145, 146)Queries 2-5 are batched primary-key lookups served by each table's primary-key index.
References
- Related to FE: Add session link to agent reply comments (w... (#605905 - closed) • Allison Villa • 19.4 • Needs attention
- Related frontend MR: Add a View session button to agent replies in w... (!250722 - merged) • Allison Villa • 19.4
Screenshots or screen recordings
No UI changes in this MR - button is introduced in !250722 (merged)
How to set up and validate locally
-
Prerequisite: Have GitLab Duo set up in your GDK, with a Duo-enabled group and project
-
Prerequisite: Have GitLab Runner working
-
@mentionan agent on an issue and ask it a question -
In a Rails console, confirm the session owner receives the session as a global ID:
note = Ai::DuoWorkflows::WorkflowNote.link_type_created.last.note owner = note.duo_created_workflow_link.workflow.user query = "query { note(id: \"#{note.to_gid}\") { id duoCreatedSessionId } }" GitlabSchema.execute(query, context: { current_user: owner }).dig('data', 'note', 'duoCreatedSessionId') #=> "gid://gitlab/Ai::DuoWorkflows::Workflow/<id>" -
Confirm a user who can read the note but not the session gets
null. Mention-triggered sessions run in thewebenvironment, which any Duo-enabled project member can read, so temporarily make the session owner-only:workflow = note.duo_created_workflow_link.workflow original_env = workflow.environment workflow.update!(environment: :ide) member = User.human.active.where.not(id: note.project.team.members.map(&:id)).first note.project.add_developer(member) result = GitlabSchema.execute(query, context: { current_user: member }) result.dig('data', 'note', 'id') #=> the note gid (note is readable) result.dig('data', 'note', 'duoCreatedSessionId') #=> nil workflow.update!(environment: original_env) note.project.members.find_by(user_id: member.id)&.destroy! -
Confirm an ordinary comment resolves the field as
null:plain = note.noteable.notes.user.where.not(id: Ai::DuoWorkflows::WorkflowNote.select(:note_id)).last plain_query = "query { note(id: \"#{plain.to_gid}\") { duoCreatedSessionId } }" GitlabSchema.execute(plain_query, context: { current_user: owner }).dig('data', 'note', 'duoCreatedSessionId') #=> nil -
Confirm the lookup batches. Tail the log, then in GraphiQL run a notes-list query for the issue selecting
duoCreatedSessionIdon each note:tail -f log/development.log | grep duo_workflows_workflow_notesYou should see one query with
note_id IN (...), not one per note.
The no-reload behavior from the subscription trigger is validated end-to-end in the frontend MR.
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.