Create links between workflows and work_items

What does this MR do and why?

Stacked MR, that uses the tables created in !239375 (merged)

This MR adds the model-level layer on top of the join tables, for workflow -> work_items

  • Added the Ai::DuoWorkflows::WorkflowWorkItem model on the workflow to work-items join table, with a link_type enum (Two link types added here: source, and created).
  • Extracted shared join-model behavior into the Ai::DuoWorkflows::WorkflowLinkable concern (associations, validations, and a links_workflow_to macro).
  • Added the generic Ai::DuoWorkflows::LinkArtifactService as the single entry point for creating workflow-artifact links, dispatching to the right join model by artifact type.
  • Leverages the header added in gitlab-org#22201 (closed) to track any work items created by a flow.

References

Starts the linking for #600655 (closed)

Uses the tables created in #599435 (closed)

How to set up and validate locally

  1. Go to an issue in your GDK
  2. Add a comment tagging developer, telling it to create a new issue, e.g. @duo-developer-gitlab-duo Create an issue with this title: "Create a list of cars in cars.txt"
  3. In your rails console, confirm the link was created from the source issue, and to the created issue:

[20] pry(main)> workflow = Ai::DuoWorkflows::Workflow.last

[21] pry(main)> workflow.linked_work_items
...
=> [#<WorkItem id:759 gitlab-duo/ai-assist-2#6>, #<WorkItem id:761 gitlab-duo/ai-assist-2#8>]

[22] pry(main)> workflow.work_item_links
...
=> [#<Ai::DuoWorkflows::WorkflowWorkItem:0x0000000102e09108
  id: 3,
  workflow_id: 4536,
  work_item_id: 759,
  project_id: 78,
  namespace_id: nil,
  created_at: "2026-06-19 22:04:48.766891000 +0000",
  updated_at: "2026-06-19 22:04:48.766891000 +0000",
  link_type: "source">,
 #<Ai::DuoWorkflows::WorkflowWorkItem:0x0000000102e08fc8
  id: 4,
  workflow_id: 4536,
  work_item_id: 761,
  project_id: 78,
  namespace_id: nil,
  created_at: "2026-06-19 22:05:58.106203000 +0000",
  updated_at: "2026-06-19 22:05:58.106203000 +0000",
  link_type: "created">]

[23] pry(main)> workflow.work_item_links.where(link_type: :source)
...
=> [#<Ai::DuoWorkflows::WorkflowWorkItem:0x0000000139ba9d90
  id: 3,
  workflow_id: 4536,
  work_item_id: 759,
  project_id: 78,
  namespace_id: nil,
  created_at: "2026-06-19 22:04:48.766891000 +0000",
  updated_at: "2026-06-19 22:04:48.766891000 +0000",
  link_type: "source">]
[24] pry(main)> workflow.work_item_links.where(link_type: :created)
...
=> [#<Ai::DuoWorkflows::WorkflowWorkItem:0x0000000139ba4890
  id: 4,
  workflow_id: 4536,
  work_item_id: 761,
  project_id: 78,
  namespace_id: nil,
  created_at: "2026-06-19 22:05:58.106203000 +0000",
  updated_at: "2026-06-19 22:05:58.106203000 +0000",
  link_type: "created">]

We can also check the inverse:

[4] pry(main)> project = workflow.project

[5] pry(main)> work_item   = project&.work_items&.find_by_iid(6)

[6] pry(main)> work_item.linked_duo_workflows
...
=> [#<Ai::DuoWorkflows::Workflow:0x0000000138376688
  id: 4536,
  user_id: 1,
  project_id: 78,

An additional test: Click "Implement work item" on a work_item and validate that it has a source link:

> workflow.work_item_links

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.

Edited by Roman Eisner

Merge request reports

Loading
Loading