diff --git a/app/services/ci/workloads/run_workload_service.rb b/app/services/ci/workloads/run_workload_service.rb index 8acad44b0b2284da5f43d1a3b061369f7395392a..c07ffb36118bb82581f6fe6b129fece1edadc87e 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( @@ -53,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. 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 e1a66a87eb80a1775dfbcfa6386ad7d23dff33b2..c22da85846935d229f56a69e295468527ea163b9 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 0b2ed5aed899524d1577fde009f84bac521be353..7cffefc8b74df461f9148aafb83e13f5ae6b9f81 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 6bb421173f5876a55dfd4769fe2bd8c9fbfaa17a..a558213da445ce0eb5c94406a23436f1e6697046 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 5eeafab8bd493387eb88e83bf0b7dbf7e7e78db0..881413f3793671621fa1719732c51cb6ded2282b 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 b6892766d32c79df525d365bb3764eba5b0ea903..1615f1b10a1b036e10046f252184c04f3be0395f 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, @@ -185,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