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