From 37fd176427cca55a4310ede999058cdc9d5106ce Mon Sep 17 00:00:00 2001 From: Surabhi Suman Date: Fri, 17 Oct 2025 16:22:54 +0530 Subject: [PATCH 1/2] chore: Adds additional context for merge requests EE: true --- .../ci/workloads/run_workload_service.rb | 24 +++++++++++++++++-- .../duo_workflows/start_workflow_service.rb | 1 + .../services/ai/flow_triggers/run_service.rb | 1 + .../ai/flow_triggers/run_service_spec.rb | 2 ++ .../ci/workloads/workload_definition_spec.rb | 2 +- .../ci/workloads/run_workload_service_spec.rb | 1 + 6 files changed, 28 insertions(+), 3 deletions(-) diff --git a/app/services/ci/workloads/run_workload_service.rb b/app/services/ci/workloads/run_workload_service.rb index 8acad44b0b2284..1a4989a56d92d0 100644 --- a/app/services/ci/workloads/run_workload_service.rb +++ b/app/services/ci/workloads/run_workload_service.rb @@ -12,10 +12,11 @@ module Workloads # become first class. For that reason we abstract users from the underlying `Ci::Pipeline` semantics. class RunWorkloadService def initialize( - project:, current_user:, source:, workload_definition:, create_branch: false, source_branch: nil, + project:, current_user:, human_user:, source:, workload_definition:, create_branch: false, source_branch: nil, ci_variables_included: []) @project = project @current_user = current_user + @human_user = human_user @source = source @workload_definition = workload_definition @create_branch = create_branch @@ -26,7 +27,8 @@ def initialize( def execute validate_source! @ref = @create_branch ? create_repository_branch(@source_branch) : default_branch - + target_branch = @source_branch || default_branch + add_merge_request_context(@ref, target_branch) @workload_definition.add_variable(:CI_WORKLOAD_REF, @ref) service = ::Ci::CreatePipelineService.new(@project, @current_user, ref: @ref) response = service.execute( @@ -89,6 +91,24 @@ def validate_source! raise ArgumentError, "unsupported source `#{@source}` for workloads" end + + def add_merge_request_context(source_branch, target_branch) + branch_context = { + "Category" => "merge_request", + "Content" => ::Gitlab::Json.dump({ + "target_branch" => target_branch, + "source_branch" => source_branch, + "reviewer_id" => @human_user.id.to_s + }) + } + + existing_context = @workload_definition.variables[:DUO_WORKFLOW_ADDITIONAL_CONTEXT_CONTENT] + context_array = existing_context ? ::Gitlab::Json.parse(existing_context) : [] + context_array << branch_context + + additional_context = ::Gitlab::Json.dump(context_array) + @workload_definition.add_variable(:DUO_WORKFLOW_ADDITIONAL_CONTEXT_CONTENT, additional_context) + end end end end diff --git a/ee/app/services/ai/duo_workflows/start_workflow_service.rb b/ee/app/services/ai/duo_workflows/start_workflow_service.rb index e1a66a87eb80a1..c22da85846935d 100644 --- a/ee/app/services/ai/duo_workflows/start_workflow_service.rb +++ b/ee/app/services/ai/duo_workflows/start_workflow_service.rb @@ -37,6 +37,7 @@ def execute service = ::Ci::Workloads::RunWorkloadService.new( project: project, current_user: @workload_user, + human_user: @current_user, source: :duo_workflow, workload_definition: workload_definition, create_branch: true, diff --git a/ee/app/services/ai/flow_triggers/run_service.rb b/ee/app/services/ai/flow_triggers/run_service.rb index 0b2ed5aed89952..7cffefc8b74df4 100644 --- a/ee/app/services/ai/flow_triggers/run_service.rb +++ b/ee/app/services/ai/flow_triggers/run_service.rb @@ -83,6 +83,7 @@ def run_workload(params) response = ::Ci::Workloads::RunWorkloadService.new( project: project, current_user: flow_trigger_user, + human_user: current_user, source: :duo_workflow, workload_definition: workload_definition, ci_variables_included: flow_definition['variables'] || [], diff --git a/ee/spec/services/ai/flow_triggers/run_service_spec.rb b/ee/spec/services/ai/flow_triggers/run_service_spec.rb index 6bb421173f5876..a558213da445ce 100644 --- a/ee/spec/services/ai/flow_triggers/run_service_spec.rb +++ b/ee/spec/services/ai/flow_triggers/run_service_spec.rb @@ -609,6 +609,7 @@ expect(Ci::Workloads::RunWorkloadService).to receive(:new).with( project: project, current_user: service_account, + human_user: current_user, source: :duo_workflow, workload_definition: an_instance_of(Ci::Workloads::WorkloadDefinition), ci_variables_included: %w[API_KEY DATABASE_URL], @@ -625,6 +626,7 @@ expect(Ci::Workloads::RunWorkloadService).to receive(:new).with( project: project, current_user: service_account, + human_user: current_user, source: :duo_workflow, workload_definition: an_instance_of(Ci::Workloads::WorkloadDefinition), ci_variables_included: %w[API_KEY DATABASE_URL], diff --git a/spec/lib/ci/workloads/workload_definition_spec.rb b/spec/lib/ci/workloads/workload_definition_spec.rb index 5eeafab8bd4933..881413f3793671 100644 --- a/spec/lib/ci/workloads/workload_definition_spec.rb +++ b/spec/lib/ci/workloads/workload_definition_spec.rb @@ -39,7 +39,7 @@ it 'builds a workload_definition that can be run by RunWorkloadService' do run_service = Ci::Workloads::RunWorkloadService - .new(project: project, current_user: user, source: source, workload_definition: definition) + .new(project: project, current_user: user, human_user: user, source: source, workload_definition: definition) result = run_service.execute expect(result).to be_success diff --git a/spec/services/ci/workloads/run_workload_service_spec.rb b/spec/services/ci/workloads/run_workload_service_spec.rb index b6892766d32c79..1568c538208918 100644 --- a/spec/services/ci/workloads/run_workload_service_spec.rb +++ b/spec/services/ci/workloads/run_workload_service_spec.rb @@ -28,6 +28,7 @@ .new( project: project, current_user: user, + human_user: user, source: source, workload_definition: workload_definition, create_branch: create_branch, -- GitLab From a7cf0c26027c41e235cb3c4a2ec518fb66f53f58 Mon Sep 17 00:00:00 2001 From: Surabhi Suman Date: Thu, 23 Oct 2025 13:04:45 +0530 Subject: [PATCH 2/2] Adds spec for newly added method --- .../ci/workloads/run_workload_service.rb | 39 +++---- .../ci/workloads/run_workload_service_spec.rb | 102 ++++++++++++++++++ 2 files changed, 123 insertions(+), 18 deletions(-) diff --git a/app/services/ci/workloads/run_workload_service.rb b/app/services/ci/workloads/run_workload_service.rb index 1a4989a56d92d0..c07ffb36118bb8 100644 --- a/app/services/ci/workloads/run_workload_service.rb +++ b/app/services/ci/workloads/run_workload_service.rb @@ -55,6 +55,27 @@ def execute ServiceResponse.success(payload: workload) end + def add_merge_request_context(source_branch, target_branch) + existing_context = @workload_definition.variables[:DUO_WORKFLOW_ADDITIONAL_CONTEXT_CONTENT] + context_array = existing_context ? ::Gitlab::Json.parse(existing_context) : [] + + # Return early if merge_request context already exists + return if context_array.any? { |context| context["Category"] == "merge_request" } + + branch_context = { + "Category" => "merge_request", + "Content" => ::Gitlab::Json.dump({ + "target_branch" => target_branch, + "source_branch" => source_branch, + "reviewer_id" => @human_user.id.to_s + }) + } + + context_array << branch_context + additional_context = ::Gitlab::Json.dump(context_array) + @workload_definition.add_variable(:DUO_WORKFLOW_ADDITIONAL_CONTEXT_CONTENT, additional_context) + end + private # By default a Workload will not get any of the CI variables configured at the project/group/instance level. @@ -91,24 +112,6 @@ def validate_source! raise ArgumentError, "unsupported source `#{@source}` for workloads" end - - def add_merge_request_context(source_branch, target_branch) - branch_context = { - "Category" => "merge_request", - "Content" => ::Gitlab::Json.dump({ - "target_branch" => target_branch, - "source_branch" => source_branch, - "reviewer_id" => @human_user.id.to_s - }) - } - - existing_context = @workload_definition.variables[:DUO_WORKFLOW_ADDITIONAL_CONTEXT_CONTENT] - context_array = existing_context ? ::Gitlab::Json.parse(existing_context) : [] - context_array << branch_context - - additional_context = ::Gitlab::Json.dump(context_array) - @workload_definition.add_variable(:DUO_WORKFLOW_ADDITIONAL_CONTEXT_CONTENT, additional_context) - end end end end diff --git a/spec/services/ci/workloads/run_workload_service_spec.rb b/spec/services/ci/workloads/run_workload_service_spec.rb index 1568c538208918..1615f1b10a1b03 100644 --- a/spec/services/ci/workloads/run_workload_service_spec.rb +++ b/spec/services/ci/workloads/run_workload_service_spec.rb @@ -186,4 +186,106 @@ end end end + + describe '#add_merge_request_context' do + let(:service) do + described_class.new( + project: project, + current_user: user, + human_user: user, + source: source, + workload_definition: workload_definition, + create_branch: create_branch, + source_branch: source_branch + ) + end + + let(:source_branch) { 'feature-branch' } + let(:target_branch) { 'main' } + + context 'when no existing additional context' do + before do + workload_definition.add_variable(:DUO_WORKFLOW_ADDITIONAL_CONTEXT_CONTENT, nil) + end + + it 'creates new additional context with merge request data' do + service.send(:add_merge_request_context, source_branch, target_branch) + + additional_context = workload_definition.variables[:DUO_WORKFLOW_ADDITIONAL_CONTEXT_CONTENT] + parsed_context = Gitlab::Json.parse(additional_context) + + expect(parsed_context).to be_an(Array) + expect(parsed_context.length).to eq(1) + + merge_request_context = parsed_context.first + expect(merge_request_context['Category']).to eq('merge_request') + + content = Gitlab::Json.parse(merge_request_context['Content']) + expect(content['source_branch']).to eq(source_branch) + expect(content['target_branch']).to eq(target_branch) + expect(content['reviewer_id']).to eq(user.id.to_s) + end + end + + context 'when existing additional context is empty array' do + before do + workload_definition.add_variable(:DUO_WORKFLOW_ADDITIONAL_CONTEXT_CONTENT, '[]') + end + + it 'appends merge request context to existing empty array' do + service.send(:add_merge_request_context, source_branch, target_branch) + + additional_context = workload_definition.variables[:DUO_WORKFLOW_ADDITIONAL_CONTEXT_CONTENT] + parsed_context = Gitlab::Json.parse(additional_context) + + expect(parsed_context).to be_an(Array) + expect(parsed_context.length).to eq(1) + + merge_request_context = parsed_context.first + expect(merge_request_context['Category']).to eq('merge_request') + + content = Gitlab::Json.parse(merge_request_context['Content']) + expect(content['source_branch']).to eq(source_branch) + expect(content['target_branch']).to eq(target_branch) + expect(content['reviewer_id']).to eq(user.id.to_s) + end + end + + context 'when existing additional context has other items' do + let(:existing_context) do + [ + { + 'Category' => 'other', + 'Content' => Gitlab::Json.dump({ 'key' => 'value' }) + } + ] + end + + before do + workload_definition.add_variable(:DUO_WORKFLOW_ADDITIONAL_CONTEXT_CONTENT, Gitlab::Json.dump(existing_context)) + end + + it 'appends merge request context to existing context array' do + service.send(:add_merge_request_context, source_branch, target_branch) + + additional_context = workload_definition.variables[:DUO_WORKFLOW_ADDITIONAL_CONTEXT_CONTENT] + parsed_context = Gitlab::Json.parse(additional_context) + + expect(parsed_context).to be_an(Array) + expect(parsed_context.length).to eq(2) + + # First item should be the existing context + expect(parsed_context.first['Category']).to eq('other') + + # Second item should be the new merge request context + merge_request_context = parsed_context.last + expect(merge_request_context['Category']).to eq('merge_request') + + content = Gitlab::Json.parse(merge_request_context['Content']) + expect(content['source_branch']).to eq(source_branch) + expect(content['target_branch']).to eq(target_branch) + expect(content['reviewer_id']).to eq(user.id.to_s) + end + end + end end -- GitLab