From e117043064a463cccd62f1679ea9f888801da193 Mon Sep 17 00:00:00 2001
From: Surabhi Suman <ssuman@gitlab.com>
Date: Tue, 11 Mar 2025 08:40:31 +0000
Subject: [PATCH] Adds workflow execution status to graphql api

This adds a workflow execution status
to display the granular workflow state
of Duo Workflow execution

EE:true
---
 doc/api/graphql/reference/_index.md           |  1 +
 .../ai/duo_workflows/workflow_event_type.rb   | 25 ++++-----
 .../workflow_checkpoint_event_presenter.rb    |  7 +++
 ...orkflow_checkpoint_event_presenter_spec.rb | 56 +++++++++++++++++++
 .../workflow_events_updated_spec.rb           |  4 +-
 5 files changed, 78 insertions(+), 15 deletions(-)

diff --git a/doc/api/graphql/reference/_index.md b/doc/api/graphql/reference/_index.md
index bd41f9f4fc1dd..de38e93344f25 100644
--- a/doc/api/graphql/reference/_index.md
+++ b/doc/api/graphql/reference/_index.md
@@ -25099,6 +25099,7 @@ Events that describe the history and progress of a Duo Workflow.
 | ---- | ---- | ----------- |
 | <a id="duoworkfloweventcheckpoint"></a>`checkpoint` | [`JsonString`](#jsonstring) | Checkpoint of the event. |
 | <a id="duoworkfloweventerrors"></a>`errors` | [`[String!]`](#string) | Message errors. |
+| <a id="duoworkfloweventexecutionstatus"></a>`executionStatus` {{< icon name="warning-solid" >}} | [`String!`](#string) | **Introduced** in GitLab 17.10. **Status**: Experiment. Granular status of workflow execution. |
 | <a id="duoworkfloweventmetadata"></a>`metadata` | [`JsonString`](#jsonstring) | Metadata associated with the event. |
 | <a id="duoworkfloweventparenttimestamp"></a>`parentTimestamp` | [`Time`](#time) | Time of the parent event. |
 | <a id="duoworkfloweventtimestamp"></a>`timestamp` | [`Time`](#time) | Time of the event. |
diff --git a/ee/app/graphql/types/ai/duo_workflows/workflow_event_type.rb b/ee/app/graphql/types/ai/duo_workflows/workflow_event_type.rb
index 99c658484f01d..9e0f4f61fea8f 100644
--- a/ee/app/graphql/types/ai/duo_workflows/workflow_event_type.rb
+++ b/ee/app/graphql/types/ai/duo_workflows/workflow_event_type.rb
@@ -9,40 +9,37 @@ class WorkflowEventType < Types::BaseObject
         present_using ::Ai::DuoWorkflows::WorkflowCheckpointEventPresenter
         authorize :read_duo_workflow_event
 
+        def self.authorization_scopes
+          [:api, :read_api, :ai_features]
+        end
+
         field :checkpoint, Types::JsonStringType,
-          scopes: [:api, :read_api, :ai_features],
           description: 'Checkpoint of the event.'
 
         field :metadata, Types::JsonStringType,
-          scopes: [:api, :read_api, :ai_features],
           description: 'Metadata associated with the event.'
 
         field :workflow_status, Types::Ai::DuoWorkflows::WorkflowStatusEnum,
-          scopes: [:api, :read_api, :ai_features],
           description: 'Status of the workflow.'
 
-        field :timestamp,
-          Types::TimeType,
-          scopes: [:api, :read_api, :ai_features],
+        field :execution_status, GraphQL::Types::String,
+          null: false, description: 'Granular status of workflow execution.',
+          experiment: { milestone: '17.10' }
+
+        field :timestamp, Types::TimeType,
           description: 'Time of the event.'
 
-        field :parent_timestamp,
-          Types::TimeType,
-          scopes: [:api, :read_api, :ai_features],
+        field :parent_timestamp, Types::TimeType,
           description: 'Time of the parent event.'
 
         field :errors, [GraphQL::Types::String],
-          null: true,
-          scopes: [:api, :read_api, :ai_features],
-          description: 'Message errors.'
+          null: true, description: 'Message errors.'
 
         # rubocop:disable GraphQL/ExtractType -- no need to extract two fields into a separate field
         field :workflow_goal, GraphQL::Types::String,
-          scopes: [:api, :read_api, :ai_features],
           description: 'Goal of the workflow.'
 
         field :workflow_definition, GraphQL::Types::String,
-          scopes: [:api, :read_api, :ai_features],
           description: 'Duo Workflow type based on its capabilities.'
         # rubocop:enable GraphQL/ExtractType -- we want to keep this way for backwards compatibility
       end
diff --git a/ee/app/presenters/ai/duo_workflows/workflow_checkpoint_event_presenter.rb b/ee/app/presenters/ai/duo_workflows/workflow_checkpoint_event_presenter.rb
index 70cbb8ad2f8a0..fba12e3a0b7ca 100644
--- a/ee/app/presenters/ai/duo_workflows/workflow_checkpoint_event_presenter.rb
+++ b/ee/app/presenters/ai/duo_workflows/workflow_checkpoint_event_presenter.rb
@@ -24,6 +24,13 @@ def workflow_goal
       def workflow_definition
         event.workflow.workflow_definition
       end
+
+      def execution_status
+        graph_state = event.checkpoint.dig('channel_values', 'status')
+        return graph_state unless graph_state.nil? || graph_state == 'Not Started'
+
+        event.workflow.human_status_name.titleize
+      end
     end
   end
 end
diff --git a/ee/spec/presenters/ai/duo_workflows/workflow_checkpoint_event_presenter_spec.rb b/ee/spec/presenters/ai/duo_workflows/workflow_checkpoint_event_presenter_spec.rb
index 1a2fe9961d9c4..fc07be015295d 100644
--- a/ee/spec/presenters/ai/duo_workflows/workflow_checkpoint_event_presenter_spec.rb
+++ b/ee/spec/presenters/ai/duo_workflows/workflow_checkpoint_event_presenter_spec.rb
@@ -43,6 +43,62 @@
     end
   end
 
+  describe 'execution_status' do
+    context 'when checkpoint channel values is empty' do
+      it 'returns the workflow status' do
+        expect(presenter.execution_status).to eq(checkpoint.workflow.human_status_name.titleize)
+      end
+    end
+
+    context 'when graph execution has started' do
+      let(:checkpoint_data) do
+        {
+          'channel_values' =>
+            {
+              'plan' => { 'steps' => [] },
+              'status' => 'Planning',
+              'handover' => [],
+              'ui_chat_log' => [],
+              'last_human_input' => nil,
+              'conversation_history' => {}
+            }
+        }
+      end
+
+      before do
+        checkpoint.checkpoint = checkpoint_data
+      end
+
+      it 'returns the graph execution status' do
+        expect(presenter.execution_status).to eq('Planning')
+      end
+    end
+
+    context 'when graph execution has not started' do
+      let(:checkpoint_data) do
+        {
+          'channel_values' =>
+            {
+              'plan' => { 'steps' => [] },
+              'status' => 'Not Started',
+              'handover' => [],
+              'ui_chat_log' => [],
+              'last_human_input' => nil,
+              'conversation_history' => {}
+            }
+        }
+      end
+
+      before do
+        checkpoint.checkpoint = checkpoint_data
+      end
+
+      it 'returns the workflow status' do
+        expect(presenter.execution_status).to eq(checkpoint.workflow.human_status_name.titleize)
+      end
+    end
+  end
+
   describe 'workflow_goal' do
     it 'returns the workflow goal' do
       expect(presenter.workflow_goal).to eq(checkpoint.workflow.goal)
diff --git a/ee/spec/requests/api/graphql/subscriptions/ai/duo_workflows/workflow_events_updated_spec.rb b/ee/spec/requests/api/graphql/subscriptions/ai/duo_workflows/workflow_events_updated_spec.rb
index f1530f4aa44e1..d36e2a82e14b6 100644
--- a/ee/spec/requests/api/graphql/subscriptions/ai/duo_workflows/workflow_events_updated_spec.rb
+++ b/ee/spec/requests/api/graphql/subscriptions/ai/duo_workflows/workflow_events_updated_spec.rb
@@ -18,6 +18,7 @@
           metadata
           errors
           workflowStatus
+          executionStatus
         }
       }
     SUBSCRIPTION
@@ -76,7 +77,8 @@
         expect(updated_workflow['checkpoint']).to eq(checkpoint.checkpoint.to_json)
         expect(updated_workflow['metadata']).to eq(checkpoint.metadata.to_json)
         expect(updated_workflow['errors']).to eq([])
-        expect(updated_workflow['workflowStatus']).to eq("CREATED")
+        expect(updated_workflow['workflowStatus']).to eq('CREATED')
+        expect(updated_workflow['executionStatus']).to eq('Created')
       end
 
       context 'when duo_features_enabled settings is turned off' do
-- 
GitLab