From acfc3822b93f1e4be947d3c1e99df9e731b59d31 Mon Sep 17 00:00:00 2001 From: GitLab Duo Date: Tue, 12 Aug 2025 09:47:22 +0000 Subject: [PATCH] Duo Workflow: Resolve issue #560731 --- .../ai_usage/ai_usage_event_type_enum.rb | 1 + .../clean_stuck_workflows_service.rb | 2 +- .../update_workflow_status_service.rb | 30 +++++++++ .../cleanup_stuck_agent_platform_session.yml | 2 +- .../events/start_agent_platform_session.yml | 24 ++++++++ ee/lib/gitlab/tracking/ai_tracking.rb | 2 + .../ai_tracking_unified_approach_spec.rb | 22 +++++++ .../clean_stuck_workflows_service_spec.rb | 2 +- .../update_workflow_status_service_spec.rb | 61 +++++++++++++++++-- 9 files changed, 139 insertions(+), 7 deletions(-) create mode 100644 ee/config/events/start_agent_platform_session.yml diff --git a/ee/app/graphql/types/analytics/ai_usage/ai_usage_event_type_enum.rb b/ee/app/graphql/types/analytics/ai_usage/ai_usage_event_type_enum.rb index d0ccafe571fa7d..116a5ea7b80b9b 100644 --- a/ee/app/graphql/types/analytics/ai_usage/ai_usage_event_type_enum.rb +++ b/ee/app/graphql/types/analytics/ai_usage/ai_usage_event_type_enum.rb @@ -22,6 +22,7 @@ def self.declare_event(event_name, description) declare_event('code_suggestion_direct_access_token_refresh', "Code Suggestion token was refreshed.") declare_event('request_duo_chat_response', "Duo Chat response was requested.") declare_event('troubleshoot_job', "Troubleshoot job feature was used.") + declare_event('start_agent_platform_session', "Agent platform session was started.") end end end diff --git a/ee/app/services/ai/duo_workflows/clean_stuck_workflows_service.rb b/ee/app/services/ai/duo_workflows/clean_stuck_workflows_service.rb index fc75571babbcdc..fbda0fd3f3332e 100644 --- a/ee/app/services/ai/duo_workflows/clean_stuck_workflows_service.rb +++ b/ee/app/services/ai/duo_workflows/clean_stuck_workflows_service.rb @@ -24,7 +24,7 @@ def execute project: w.project, name: w.project.namespace, additional_properties: { - label: "workflow_finish_event", + label: w.workflow_definition || 'unknown', value: w.id, property: "failed", category: w.workflow_definition diff --git a/ee/app/services/ai/duo_workflows/update_workflow_status_service.rb b/ee/app/services/ai/duo_workflows/update_workflow_status_service.rb index 34deb5b6a5dd47..e097d3df98e5e6 100644 --- a/ee/app/services/ai/duo_workflows/update_workflow_status_service.rb +++ b/ee/app/services/ai/duo_workflows/update_workflow_status_service.rb @@ -21,6 +21,31 @@ def execute private + def trigger_start_agent_platform_session_event + flow_type = @workflow.workflow_definition || 'unknown' + + # Prepare event context + event_context = { + user: @current_user, + label: flow_type, + value: @workflow.id + } + + # Add project context if available + if @workflow.project + event_context[:project] = @workflow.project + event_context[:namespace] = @workflow.project.namespace + elsif @workflow.namespace + event_context[:namespace] = @workflow.namespace + end + + # Trigger the internal event + Gitlab::InternalEvents.track_event( + 'start_agent_platform_session', + **event_context + ) + end + def handle_status_event workflow_events = ::Ai::DuoWorkflows::Workflow.state_machines[:status].events.map { |event| event.name.to_s } unless workflow_events.include?(@status_event) @@ -33,6 +58,11 @@ def handle_status_event @workflow.fire_status_event(@status_event) + # Track agent platform session start event + if @status_event == 'start' + trigger_start_agent_platform_session_event + end + GraphqlTriggers.workflow_events_updated(@workflow.checkpoints.last) if @workflow.checkpoints.any? ServiceResponse.success(payload: { workflow: @workflow }, message: "Workflow status updated") diff --git a/ee/config/events/cleanup_stuck_agent_platform_session.yml b/ee/config/events/cleanup_stuck_agent_platform_session.yml index d7f9c926857b6e..15ac0ca0b1083b 100644 --- a/ee/config/events/cleanup_stuck_agent_platform_session.yml +++ b/ee/config/events/cleanup_stuck_agent_platform_session.yml @@ -9,7 +9,7 @@ identifiers: - namespace additional_properties: label: - description: hardcoded value "workflow_finish_event" + description: flow type (e.g. chat, software_development) property: description: workflow's new status eg. failed or paused value: diff --git a/ee/config/events/start_agent_platform_session.yml b/ee/config/events/start_agent_platform_session.yml new file mode 100644 index 00000000000000..afee31ec1eaef1 --- /dev/null +++ b/ee/config/events/start_agent_platform_session.yml @@ -0,0 +1,24 @@ +--- +description: Tracks duo agent platform session starts. Important for Duo Agent Platform analytics +internal_events: true +status: active +action: start_agent_platform_session +identifiers: +- project +- user +- namespace +additional_properties: + label: + description: flow type (e.g. chat, software_development) + value: + description: id of the session (workflow_id) +product_group: duo_workflow +product_categories: +- duo_workflow +milestone: '18.3' +introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/issues/560731 +tiers: +- premium +- ultimate +extra_trackers: + - tracking_class: Gitlab::Tracking::AiTracking \ No newline at end of file diff --git a/ee/lib/gitlab/tracking/ai_tracking.rb b/ee/lib/gitlab/tracking/ai_tracking.rb index 8b6d196b4810a3..cf164a40592773 100644 --- a/ee/lib/gitlab/tracking/ai_tracking.rb +++ b/ee/lib/gitlab/tracking/ai_tracking.rb @@ -28,6 +28,8 @@ module AiTracking merge_request_id: context['job'].pipeline&.merge_request_id } end + + events(start_agent_platform_session: 8) end class << self diff --git a/ee/spec/lib/gitlab/tracking/ai_tracking_unified_approach_spec.rb b/ee/spec/lib/gitlab/tracking/ai_tracking_unified_approach_spec.rb index 80b1bb7cf2e36e..9b0fd8d91a9788 100644 --- a/ee/spec/lib/gitlab/tracking/ai_tracking_unified_approach_spec.rb +++ b/ee/spec/lib/gitlab/tracking/ai_tracking_unified_approach_spec.rb @@ -201,5 +201,27 @@ it_behaves_like 'standard ai usage event tracking' end + + context 'for `start_agent_platform_session` event' do + let(:event_name) { 'start_agent_platform_session' } + + let(:expected_pg_attributes) do + { + user_id: current_user.id, + event: event_name, + extras: {} + } + end + + let(:expected_ch_attributes) do + { + user_id: current_user.id, + event: Ai::UsageEvent.events[event_name], + extras: {}.to_json + } + end + + it_behaves_like 'standard ai usage event tracking' + end end end diff --git a/ee/spec/services/ai/duo_workflows/clean_stuck_workflows_service_spec.rb b/ee/spec/services/ai/duo_workflows/clean_stuck_workflows_service_spec.rb index 4ac103a6934830..ab6efef0bd029f 100644 --- a/ee/spec/services/ai/duo_workflows/clean_stuck_workflows_service_spec.rb +++ b/ee/spec/services/ai/duo_workflows/clean_stuck_workflows_service_spec.rb @@ -44,7 +44,7 @@ user: workflow.user, project: workflow.project, additional_properties: { - label: "workflow_finish_event", + label: workflow.workflow_definition, value: workflow.id, property: "failed", category: workflow.workflow_definition diff --git a/ee/spec/services/ai/duo_workflows/update_workflow_status_service_spec.rb b/ee/spec/services/ai/duo_workflows/update_workflow_status_service_spec.rb index befd4e75001fe2..5ef1bea7a879ff 100644 --- a/ee/spec/services/ai/duo_workflows/update_workflow_status_service_spec.rb +++ b/ee/spec/services/ai/duo_workflows/update_workflow_status_service_spec.rb @@ -146,11 +146,64 @@ let(:workflow_initial_status_enum) { 0 } # status created it "can start a workflow", :aggregate_failures do - result = described_class.new(workflow: workflow, current_user: user, status_event: "start").execute + expect do + result = described_class.new(workflow: workflow, current_user: user, status_event: "start").execute + + expect(result[:status]).to eq(:success) + expect(result[:message]).to eq("Workflow status updated") + expect(workflow.reload.human_status_name).to eq("running") + end.to trigger_internal_events("start_agent_platform_session") + .with( + user: user, + project: project, + namespace: project.namespace, + label: workflow.workflow_definition, + value: workflow.id + ) + end - expect(result[:status]).to eq(:success) - expect(result[:message]).to eq("Workflow status updated") - expect(workflow.reload.human_status_name).to eq("running") + it "can start a chat workflow and trigger event", :aggregate_failures do + chat_workflow = create(:duo_workflows_workflow, :agentic_chat, project: project, user: user, status: 0) + + expect do + result = described_class.new(workflow: chat_workflow, current_user: user, status_event: "start").execute + + expect(result[:status]).to eq(:success) + expect(chat_workflow.reload.human_status_name).to eq("running") + end.to trigger_internal_events("start_agent_platform_session") + .with( + user: user, + project: project, + namespace: project.namespace, + label: 'chat', + value: chat_workflow.id + ) + end + + it "can start a namespace-level workflow and trigger event", :aggregate_failures do + namespace_workflow = create(:duo_workflows_workflow, namespace: group, user: user, status: 0) + + expect do + result = described_class.new(workflow: namespace_workflow, current_user: user, status_event: "start").execute + + expect(result[:status]).to eq(:success) + expect(namespace_workflow.reload.human_status_name).to eq("running") + end.to trigger_internal_events("start_agent_platform_session") + .with( + user: user, + namespace: group, + label: namespace_workflow.workflow_definition, + value: namespace_workflow.id + ) + end + + it "does not trigger event for non-start status changes", :aggregate_failures do + expect do + result = described_class.new(workflow: workflow, current_user: user, status_event: "pause").execute + + expect(result[:status]).to eq(:success) + expect(workflow.reload.human_status_name).to eq("paused") + end.not_to trigger_internal_events("start_agent_platform_session") end end -- GitLab