From 01024f0cb4eb3cca1b4b5885b95d3ccf90e986bf Mon Sep 17 00:00:00 2001 From: Alex Pennells Date: Wed, 3 Dec 2025 13:40:59 -0500 Subject: [PATCH 1/4] Add Flows usage table to GitLab Duo and SDLC trends dashboard Adds a new analytics table for GitLab Duo Agent Platform flow metrics Changelog: added EE: true --- .../ai_agent_platform_flow_metrics.js | 56 +++++ .../data_sources/index.js | 1 + ...platform_flow_metric_item.fragment.graphql | 11 + ..._agent_platform_flow_metrics.query.graphql | 16 ++ .../analytics/dashboards/visualization.rb | 1 + .../json_schemas/analytics_visualization.json | 1 + .../ai_impact_dashboard/dashboard.yaml | 14 +- .../duo_flow_metrics_table.yaml | 30 +++ ...i_agent_platform_flow_metrics_spec.js.snap | 199 ++++++++++++++++++ .../ai_agent_platform_flow_metrics_spec.js | 112 ++++++++++ .../dashboards/visualization_spec.rb | 1 + 11 files changed, 439 insertions(+), 3 deletions(-) create mode 100644 ee/app/assets/javascripts/analytics/analytics_dashboards/data_sources/ai_agent_platform_flow_metrics.js create mode 100644 ee/app/assets/javascripts/analytics/dashboards/ai_impact/graphql/ai_agent_platform_flow_metric_item.fragment.graphql create mode 100644 ee/app/assets/javascripts/analytics/dashboards/ai_impact/graphql/ai_agent_platform_flow_metrics.query.graphql create mode 100644 ee/lib/gitlab/analytics/ai_impact_dashboard/visualizations/duo_flow_metrics_table.yaml create mode 100644 ee/spec/frontend/analytics/analytics_dashboards/components/data_sources/__snapshots__/ai_agent_platform_flow_metrics_spec.js.snap create mode 100644 ee/spec/frontend/analytics/analytics_dashboards/components/data_sources/ai_agent_platform_flow_metrics_spec.js diff --git a/ee/app/assets/javascripts/analytics/analytics_dashboards/data_sources/ai_agent_platform_flow_metrics.js b/ee/app/assets/javascripts/analytics/analytics_dashboards/data_sources/ai_agent_platform_flow_metrics.js new file mode 100644 index 00000000000000..52eaf1e5e5ea4a --- /dev/null +++ b/ee/app/assets/javascripts/analytics/analytics_dashboards/data_sources/ai_agent_platform_flow_metrics.js @@ -0,0 +1,56 @@ +import { orderBy } from 'lodash'; +import AiAgentPlatformFlowMetricsQuery from 'ee/analytics/dashboards/ai_impact/graphql/ai_agent_platform_flow_metrics.query.graphql'; +import { + LAST_30_DAYS, + DORA_METRIC_QUERY_RANGES, + startOfTomorrow, +} from 'ee/analytics/analytics_dashboards/components/filters/constants'; +import { extractQueryResponseFromNamespace } from '~/analytics/shared/utils'; +import { defaultClient } from '../graphql/client'; + +const MAX_VISIBLE_NODES = 10; + +const requestFlowMetrics = async ({ namespace, startDate, endDate, sortBy, sortDesc = false }) => { + const result = await defaultClient.query({ + query: AiAgentPlatformFlowMetricsQuery, + variables: { + fullPath: namespace, + startDate, + endDate, + }, + }); + + const { + agentPlatform: { flowMetrics }, + } = extractQueryResponseFromNamespace({ + result, + resultKey: 'aiMetrics', + }); + + const nodes = sortBy ? orderBy(flowMetrics, sortBy, sortDesc ? 'desc' : 'asc') : flowMetrics; + return nodes.slice(0, MAX_VISIBLE_NODES); +}; + +export default async function fetch({ + namespace, + query: { dateRange = LAST_30_DAYS } = {}, + queryOverrides: { dateRange: dateRangeOverride = null, ...overridesRest } = {}, +}) { + const dateRangeKey = dateRangeOverride + ? dateRangeOverride.toUpperCase() + : dateRange.toUpperCase(); + + // Default to 30 days if an invalid date range is given + const startDate = DORA_METRIC_QUERY_RANGES[dateRangeKey] + ? DORA_METRIC_QUERY_RANGES[dateRangeKey] + : DORA_METRIC_QUERY_RANGES[LAST_30_DAYS]; + + const nodes = await requestFlowMetrics({ + startDate, + endDate: startOfTomorrow, + namespace, + ...overridesRest, + }); + + return { nodes }; +} diff --git a/ee/app/assets/javascripts/analytics/analytics_dashboards/data_sources/index.js b/ee/app/assets/javascripts/analytics/analytics_dashboards/data_sources/index.js index 1eb2af66df9511..a64564bf649146 100644 --- a/ee/app/assets/javascripts/analytics/analytics_dashboards/data_sources/index.js +++ b/ee/app/assets/javascripts/analytics/analytics_dashboards/data_sources/index.js @@ -40,6 +40,7 @@ export default { usage_count: () => import('./usage_count'), dora_metrics: () => import('./dora_metrics'), dora_metrics_by_project: () => import('./dora_metrics_by_project'), + ai_agent_platform_flow_metrics: () => import('./ai_agent_platform_flow_metrics'), ai_impact_over_time: () => import('./ai_impact_over_time'), contributions: () => import('./contributions'), namespace_metadata: () => import('./namespace_metadata'), diff --git a/ee/app/assets/javascripts/analytics/dashboards/ai_impact/graphql/ai_agent_platform_flow_metric_item.fragment.graphql b/ee/app/assets/javascripts/analytics/dashboards/ai_impact/graphql/ai_agent_platform_flow_metric_item.fragment.graphql new file mode 100644 index 00000000000000..790bd8d3bfcab1 --- /dev/null +++ b/ee/app/assets/javascripts/analytics/dashboards/ai_impact/graphql/ai_agent_platform_flow_metric_item.fragment.graphql @@ -0,0 +1,11 @@ +fragment AiAgentPlatformFlowMetricItem on AiMetrics { + agentPlatform { + flowMetrics { + flowType + medianExecutionTime + sessionsCount + usersCount + completionRate + } + } +} diff --git a/ee/app/assets/javascripts/analytics/dashboards/ai_impact/graphql/ai_agent_platform_flow_metrics.query.graphql b/ee/app/assets/javascripts/analytics/dashboards/ai_impact/graphql/ai_agent_platform_flow_metrics.query.graphql new file mode 100644 index 00000000000000..4a92fa972dfbc0 --- /dev/null +++ b/ee/app/assets/javascripts/analytics/dashboards/ai_impact/graphql/ai_agent_platform_flow_metrics.query.graphql @@ -0,0 +1,16 @@ +#import "./ai_agent_platform_flow_metric_item.fragment.graphql" + +query aiAgentPlatformFlowMetricsQuery($fullPath: ID!, $startDate: Date!, $endDate: Date!) { + project(fullPath: $fullPath) { + id + aiMetrics(startDate: $startDate, endDate: $endDate) { + ...AiAgentPlatformFlowMetricItem + } + } + group(fullPath: $fullPath) { + id + aiMetrics(startDate: $startDate, endDate: $endDate) { + ...AiAgentPlatformFlowMetricItem + } + } +} diff --git a/ee/app/models/analytics/dashboards/visualization.rb b/ee/app/models/analytics/dashboards/visualization.rb index 7d366c714a2cf9..468e171f0cc0b9 100644 --- a/ee/app/models/analytics/dashboards/visualization.rb +++ b/ee/app/models/analytics/dashboards/visualization.rb @@ -68,6 +68,7 @@ class Visualization duo_chat_usage_rate_over_time duo_usage_rate_over_time pipeline_metrics_table + duo_flow_metrics_table code_suggestions_acceptance_rate_by_language_chart code_generation_volume_trends_chart duo_code_review_usage_by_user diff --git a/ee/app/validators/json_schemas/analytics_visualization.json b/ee/app/validators/json_schemas/analytics_visualization.json index 17456a0b2acc7d..a895b9c6e275e4 100644 --- a/ee/app/validators/json_schemas/analytics_visualization.json +++ b/ee/app/validators/json_schemas/analytics_visualization.json @@ -73,6 +73,7 @@ "usage_count", "dora_metrics", "dora_metrics_by_project", + "ai_agent_platform_flow_metrics", "ai_impact_over_time", "contributions", "namespace_metadata", diff --git a/ee/lib/gitlab/analytics/ai_impact_dashboard/dashboard.yaml b/ee/lib/gitlab/analytics/ai_impact_dashboard/dashboard.yaml index dbda6339d151e9..a8e93747f8e64b 100644 --- a/ee/lib/gitlab/analytics/ai_impact_dashboard/dashboard.yaml +++ b/ee/lib/gitlab/analytics/ai_impact_dashboard/dashboard.yaml @@ -90,24 +90,32 @@ panels: width: 12 height: 3 options: {} + - title: 'Flow usage (Last 30 days)' + visualization: duo_flow_metrics_table + gridAttributes: + yPos: 20 + xPos: 0 + width: 12 + height: 3 + options: {} - title: 'GitLab Duo Code Suggestions usage by user (Last 30 days)' visualization: duo_code_suggestions_by_user gridAttributes: - yPos: 20 + yPos: 23 xPos: 0 width: 6 height: 4 - title: 'GitLab Duo Code Review usage by user (Last 30 days)' visualization: duo_code_review_usage_by_user gridAttributes: - yPos: 20 + yPos: 23 xPos: 6 width: 6 height: 4 - title: 'GitLab Duo Root Cause Analysis usage by user (Last 30 days)' visualization: duo_rca_usage_by_user gridAttributes: - yPos: 24 + yPos: 27 xPos: 0 width: 6 height: 4 diff --git a/ee/lib/gitlab/analytics/ai_impact_dashboard/visualizations/duo_flow_metrics_table.yaml b/ee/lib/gitlab/analytics/ai_impact_dashboard/visualizations/duo_flow_metrics_table.yaml new file mode 100644 index 00000000000000..6731f0ef79b985 --- /dev/null +++ b/ee/lib/gitlab/analytics/ai_impact_dashboard/visualizations/duo_flow_metrics_table.yaml @@ -0,0 +1,30 @@ +--- +version: 1 +type: DataTable +data: + type: ai_agent_platform_flow_metrics + query: {} +options: + refetchOnSort: true + fields: + - key: 'flowType' + label: 'Flow' + - key: 'sessionsCount' + label: 'Number of sessions' + sortable: true + thAlignRight: true + tdClass: "gl-text-right" + - key: 'medianExecutionTime' + label: 'Avg. median duration (min)' + sortable: true + thAlignRight: true + tdClass: "gl-text-right" + - key: 'usersCount' + label: 'Unique users' + sortable: true + thAlignRight: true + tdClass: "gl-text-right" + - key: 'completionRate' + label: 'Completion rate (%)' + thAlignRight: true + tdClass: "gl-text-right" diff --git a/ee/spec/frontend/analytics/analytics_dashboards/components/data_sources/__snapshots__/ai_agent_platform_flow_metrics_spec.js.snap b/ee/spec/frontend/analytics/analytics_dashboards/components/data_sources/__snapshots__/ai_agent_platform_flow_metrics_spec.js.snap new file mode 100644 index 00000000000000..1bb14d2d36e78b --- /dev/null +++ b/ee/spec/frontend/analytics/analytics_dashboards/components/data_sources/__snapshots__/ai_agent_platform_flow_metrics_spec.js.snap @@ -0,0 +1,199 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`AI Agent platform flow metrics data source applies correct sorting for (sortBy: medianExecutionTime, sortDesc: false) 1`] = ` +[ + { + "completionRate": 88.3, + "flowType": "TOOL_FLOW", + "medianExecutionTime": 850, + "sessionsCount": 203, + "usersCount": 48, + }, + { + "completionRate": 92.5, + "flowType": "AGENT_FLOW", + "medianExecutionTime": 1250, + "sessionsCount": 145, + "usersCount": 32, + }, + { + "completionRate": 95.1, + "flowType": "CUSTOM_FLOW", + "medianExecutionTime": 2100, + "sessionsCount": 87, + "usersCount": 19, + }, + { + "completionRate": 95.1, + "flowType": "BONUS_FLOW", + "medianExecutionTime": 2100, + "sessionsCount": 87, + "usersCount": 19, + }, +] +`; + +exports[`AI Agent platform flow metrics data source applies correct sorting for (sortBy: medianExecutionTime, sortDesc: true) 1`] = ` +[ + { + "completionRate": 95.1, + "flowType": "CUSTOM_FLOW", + "medianExecutionTime": 2100, + "sessionsCount": 87, + "usersCount": 19, + }, + { + "completionRate": 95.1, + "flowType": "BONUS_FLOW", + "medianExecutionTime": 2100, + "sessionsCount": 87, + "usersCount": 19, + }, + { + "completionRate": 92.5, + "flowType": "AGENT_FLOW", + "medianExecutionTime": 1250, + "sessionsCount": 145, + "usersCount": 32, + }, + { + "completionRate": 88.3, + "flowType": "TOOL_FLOW", + "medianExecutionTime": 850, + "sessionsCount": 203, + "usersCount": 48, + }, +] +`; + +exports[`AI Agent platform flow metrics data source applies correct sorting for (sortBy: sessionsCount, sortDesc: false) 1`] = ` +[ + { + "completionRate": 95.1, + "flowType": "CUSTOM_FLOW", + "medianExecutionTime": 2100, + "sessionsCount": 87, + "usersCount": 19, + }, + { + "completionRate": 95.1, + "flowType": "BONUS_FLOW", + "medianExecutionTime": 2100, + "sessionsCount": 87, + "usersCount": 19, + }, + { + "completionRate": 92.5, + "flowType": "AGENT_FLOW", + "medianExecutionTime": 1250, + "sessionsCount": 145, + "usersCount": 32, + }, + { + "completionRate": 88.3, + "flowType": "TOOL_FLOW", + "medianExecutionTime": 850, + "sessionsCount": 203, + "usersCount": 48, + }, +] +`; + +exports[`AI Agent platform flow metrics data source applies correct sorting for (sortBy: sessionsCount, sortDesc: true) 1`] = ` +[ + { + "completionRate": 88.3, + "flowType": "TOOL_FLOW", + "medianExecutionTime": 850, + "sessionsCount": 203, + "usersCount": 48, + }, + { + "completionRate": 92.5, + "flowType": "AGENT_FLOW", + "medianExecutionTime": 1250, + "sessionsCount": 145, + "usersCount": 32, + }, + { + "completionRate": 95.1, + "flowType": "CUSTOM_FLOW", + "medianExecutionTime": 2100, + "sessionsCount": 87, + "usersCount": 19, + }, + { + "completionRate": 95.1, + "flowType": "BONUS_FLOW", + "medianExecutionTime": 2100, + "sessionsCount": 87, + "usersCount": 19, + }, +] +`; + +exports[`AI Agent platform flow metrics data source applies correct sorting for (sortBy: usersCount, sortDesc: false) 1`] = ` +[ + { + "completionRate": 95.1, + "flowType": "CUSTOM_FLOW", + "medianExecutionTime": 2100, + "sessionsCount": 87, + "usersCount": 19, + }, + { + "completionRate": 95.1, + "flowType": "BONUS_FLOW", + "medianExecutionTime": 2100, + "sessionsCount": 87, + "usersCount": 19, + }, + { + "completionRate": 92.5, + "flowType": "AGENT_FLOW", + "medianExecutionTime": 1250, + "sessionsCount": 145, + "usersCount": 32, + }, + { + "completionRate": 88.3, + "flowType": "TOOL_FLOW", + "medianExecutionTime": 850, + "sessionsCount": 203, + "usersCount": 48, + }, +] +`; + +exports[`AI Agent platform flow metrics data source applies correct sorting for (sortBy: usersCount, sortDesc: true) 1`] = ` +[ + { + "completionRate": 88.3, + "flowType": "TOOL_FLOW", + "medianExecutionTime": 850, + "sessionsCount": 203, + "usersCount": 48, + }, + { + "completionRate": 92.5, + "flowType": "AGENT_FLOW", + "medianExecutionTime": 1250, + "sessionsCount": 145, + "usersCount": 32, + }, + { + "completionRate": 95.1, + "flowType": "CUSTOM_FLOW", + "medianExecutionTime": 2100, + "sessionsCount": 87, + "usersCount": 19, + }, + { + "completionRate": 95.1, + "flowType": "BONUS_FLOW", + "medianExecutionTime": 2100, + "sessionsCount": 87, + "usersCount": 19, + }, +] +`; diff --git a/ee/spec/frontend/analytics/analytics_dashboards/components/data_sources/ai_agent_platform_flow_metrics_spec.js b/ee/spec/frontend/analytics/analytics_dashboards/components/data_sources/ai_agent_platform_flow_metrics_spec.js new file mode 100644 index 00000000000000..fe83b56865afc7 --- /dev/null +++ b/ee/spec/frontend/analytics/analytics_dashboards/components/data_sources/ai_agent_platform_flow_metrics_spec.js @@ -0,0 +1,112 @@ +import fetch from 'ee/analytics/analytics_dashboards/data_sources/ai_agent_platform_flow_metrics'; +import { defaultClient } from 'ee/analytics/analytics_dashboards/graphql/client'; +import { LAST_WEEK } from 'ee/analytics/analytics_dashboards/components/filters/constants'; + +const mockFlowMetrics = [ + { + flowType: 'AGENT_FLOW', + medianExecutionTime: 1250, + sessionsCount: 145, + usersCount: 32, + completionRate: 92.5, + }, + { + flowType: 'TOOL_FLOW', + medianExecutionTime: 850, + sessionsCount: 203, + usersCount: 48, + completionRate: 88.3, + }, + { + flowType: 'CUSTOM_FLOW', + medianExecutionTime: 2100, + sessionsCount: 87, + usersCount: 19, + completionRate: 95.1, + }, + { + flowType: 'BONUS_FLOW', + medianExecutionTime: 2100, + sessionsCount: 87, + usersCount: 19, + completionRate: 95.1, + }, +]; + +const mockTooManyFlowMetrics = [...mockFlowMetrics, ...mockFlowMetrics, ...mockFlowMetrics]; + +describe('AI Agent platform flow metrics data source', () => { + const namespace = 'namespace'; + + const mockResolvedQuery = (flowMetrics = mockFlowMetrics) => + jest.spyOn(defaultClient, 'query').mockResolvedValueOnce({ + data: { + group: { id: 'gid://gitlab/Group/1', aiMetrics: { agentPlatform: { flowMetrics } } }, + }, + }); + + it('returns the flow metrics as nodes on success', async () => { + mockResolvedQuery(); + + const result = await fetch({ namespace }); + expect(result).toEqual({ nodes: mockFlowMetrics }); + }); + + it('uses 30 days as the default date range', async () => { + mockResolvedQuery(); + await fetch({ namespace }); + + expect(defaultClient.query).toHaveBeenCalledWith( + expect.objectContaining({ + variables: expect.objectContaining({ + fullPath: namespace, + startDate: new Date('2020-06-07'), + endDate: new Date('2020-07-07'), + }), + }), + ); + }); + + it('uses a custom date range when defined', async () => { + mockResolvedQuery(); + await fetch({ + namespace, + queryOverrides: { dateRange: LAST_WEEK }, + }); + + expect(defaultClient.query).toHaveBeenCalledWith( + expect.objectContaining({ + variables: expect.objectContaining({ + fullPath: namespace, + startDate: new Date('2020-06-30'), + endDate: new Date('2020-07-07'), + }), + }), + ); + }); + + it('limits to 10 returned nodes', async () => { + mockResolvedQuery(mockTooManyFlowMetrics); + const result = await fetch({ namespace }); + expect(result).toEqual({ + nodes: mockTooManyFlowMetrics.slice(0, 10), + }); + }); + + it.each([ + ['sessionsCount', true], + ['sessionsCount', false], + ['medianExecutionTime', true], + ['medianExecutionTime', false], + ['usersCount', true], + ['usersCount', false], + ])('applies correct sorting for (sortBy: %s, sortDesc: %s)', async (sortBy, sortDesc) => { + mockResolvedQuery(); + const result = await fetch({ + namespace, + queryOverrides: { sortBy, sortDesc }, + }); + + expect(result.nodes).toMatchSnapshot(); + }); +}); diff --git a/ee/spec/models/analytics/dashboards/visualization_spec.rb b/ee/spec/models/analytics/dashboards/visualization_spec.rb index 8a37e9d1d83184..13dec22ff6d1ab 100644 --- a/ee/spec/models/analytics/dashboards/visualization_spec.rb +++ b/ee/spec/models/analytics/dashboards/visualization_spec.rb @@ -49,6 +49,7 @@ duo_chat_usage_rate_over_time duo_usage_rate_over_time pipeline_metrics_table + duo_flow_metrics_table code_suggestions_acceptance_rate_by_language_chart code_generation_volume_trends_chart duo_code_review_usage_by_user -- GitLab From cf3d576bc5a8538058ec81a71516c0f775a25f42 Mon Sep 17 00:00:00 2001 From: Alex Pennells Date: Thu, 11 Dec 2025 10:06:32 -0500 Subject: [PATCH 2/4] Convert median execution time to min and update column name --- .../ai_agent_platform_flow_metrics.js | 6 +- .../duo_flow_metrics_table.yaml | 2 +- ...i_agent_platform_flow_metrics_spec.js.snap | 83 +++++++++++++------ .../ai_agent_platform_flow_metrics_spec.js | 6 +- 4 files changed, 67 insertions(+), 30 deletions(-) diff --git a/ee/app/assets/javascripts/analytics/analytics_dashboards/data_sources/ai_agent_platform_flow_metrics.js b/ee/app/assets/javascripts/analytics/analytics_dashboards/data_sources/ai_agent_platform_flow_metrics.js index 52eaf1e5e5ea4a..d6544aaf820148 100644 --- a/ee/app/assets/javascripts/analytics/analytics_dashboards/data_sources/ai_agent_platform_flow_metrics.js +++ b/ee/app/assets/javascripts/analytics/analytics_dashboards/data_sources/ai_agent_platform_flow_metrics.js @@ -1,4 +1,5 @@ import { orderBy } from 'lodash'; +import { secondsToMinutes } from '~/lib/utils/datetime/date_calculation_utility'; import AiAgentPlatformFlowMetricsQuery from 'ee/analytics/dashboards/ai_impact/graphql/ai_agent_platform_flow_metrics.query.graphql'; import { LAST_30_DAYS, @@ -28,7 +29,10 @@ const requestFlowMetrics = async ({ namespace, startDate, endDate, sortBy, sortD }); const nodes = sortBy ? orderBy(flowMetrics, sortBy, sortDesc ? 'desc' : 'asc') : flowMetrics; - return nodes.slice(0, MAX_VISIBLE_NODES); + return nodes.slice(0, MAX_VISIBLE_NODES).map(({ medianExecutionTime, ...rest }) => ({ + medianExecutionTime: secondsToMinutes(medianExecutionTime), + ...rest, + })); }; export default async function fetch({ diff --git a/ee/lib/gitlab/analytics/ai_impact_dashboard/visualizations/duo_flow_metrics_table.yaml b/ee/lib/gitlab/analytics/ai_impact_dashboard/visualizations/duo_flow_metrics_table.yaml index 6731f0ef79b985..3b37b703f79ffa 100644 --- a/ee/lib/gitlab/analytics/ai_impact_dashboard/visualizations/duo_flow_metrics_table.yaml +++ b/ee/lib/gitlab/analytics/ai_impact_dashboard/visualizations/duo_flow_metrics_table.yaml @@ -15,7 +15,7 @@ options: thAlignRight: true tdClass: "gl-text-right" - key: 'medianExecutionTime' - label: 'Avg. median duration (min)' + label: 'Median duration (min)' sortable: true thAlignRight: true tdClass: "gl-text-right" diff --git a/ee/spec/frontend/analytics/analytics_dashboards/components/data_sources/__snapshots__/ai_agent_platform_flow_metrics_spec.js.snap b/ee/spec/frontend/analytics/analytics_dashboards/components/data_sources/__snapshots__/ai_agent_platform_flow_metrics_spec.js.snap index 1bb14d2d36e78b..996355442fde0a 100644 --- a/ee/spec/frontend/analytics/analytics_dashboards/components/data_sources/__snapshots__/ai_agent_platform_flow_metrics_spec.js.snap +++ b/ee/spec/frontend/analytics/analytics_dashboards/components/data_sources/__snapshots__/ai_agent_platform_flow_metrics_spec.js.snap @@ -5,28 +5,28 @@ exports[`AI Agent platform flow metrics data source applies correct sorting for { "completionRate": 88.3, "flowType": "TOOL_FLOW", - "medianExecutionTime": 850, + "medianExecutionTime": 14.166666666666666, "sessionsCount": 203, "usersCount": 48, }, { "completionRate": 92.5, "flowType": "AGENT_FLOW", - "medianExecutionTime": 1250, + "medianExecutionTime": 20.833333333333332, "sessionsCount": 145, "usersCount": 32, }, { "completionRate": 95.1, "flowType": "CUSTOM_FLOW", - "medianExecutionTime": 2100, + "medianExecutionTime": 35, "sessionsCount": 87, "usersCount": 19, }, { "completionRate": 95.1, "flowType": "BONUS_FLOW", - "medianExecutionTime": 2100, + "medianExecutionTime": 35, "sessionsCount": 87, "usersCount": 19, }, @@ -38,28 +38,28 @@ exports[`AI Agent platform flow metrics data source applies correct sorting for { "completionRate": 95.1, "flowType": "CUSTOM_FLOW", - "medianExecutionTime": 2100, + "medianExecutionTime": 35, "sessionsCount": 87, "usersCount": 19, }, { "completionRate": 95.1, "flowType": "BONUS_FLOW", - "medianExecutionTime": 2100, + "medianExecutionTime": 35, "sessionsCount": 87, "usersCount": 19, }, { "completionRate": 92.5, "flowType": "AGENT_FLOW", - "medianExecutionTime": 1250, + "medianExecutionTime": 20.833333333333332, "sessionsCount": 145, "usersCount": 32, }, { "completionRate": 88.3, "flowType": "TOOL_FLOW", - "medianExecutionTime": 850, + "medianExecutionTime": 14.166666666666666, "sessionsCount": 203, "usersCount": 48, }, @@ -71,28 +71,28 @@ exports[`AI Agent platform flow metrics data source applies correct sorting for { "completionRate": 95.1, "flowType": "CUSTOM_FLOW", - "medianExecutionTime": 2100, + "medianExecutionTime": 35, "sessionsCount": 87, "usersCount": 19, }, { "completionRate": 95.1, "flowType": "BONUS_FLOW", - "medianExecutionTime": 2100, + "medianExecutionTime": 35, "sessionsCount": 87, "usersCount": 19, }, { "completionRate": 92.5, "flowType": "AGENT_FLOW", - "medianExecutionTime": 1250, + "medianExecutionTime": 20.833333333333332, "sessionsCount": 145, "usersCount": 32, }, { "completionRate": 88.3, "flowType": "TOOL_FLOW", - "medianExecutionTime": 850, + "medianExecutionTime": 14.166666666666666, "sessionsCount": 203, "usersCount": 48, }, @@ -104,28 +104,28 @@ exports[`AI Agent platform flow metrics data source applies correct sorting for { "completionRate": 88.3, "flowType": "TOOL_FLOW", - "medianExecutionTime": 850, + "medianExecutionTime": 14.166666666666666, "sessionsCount": 203, "usersCount": 48, }, { "completionRate": 92.5, "flowType": "AGENT_FLOW", - "medianExecutionTime": 1250, + "medianExecutionTime": 20.833333333333332, "sessionsCount": 145, "usersCount": 32, }, { "completionRate": 95.1, "flowType": "CUSTOM_FLOW", - "medianExecutionTime": 2100, + "medianExecutionTime": 35, "sessionsCount": 87, "usersCount": 19, }, { "completionRate": 95.1, "flowType": "BONUS_FLOW", - "medianExecutionTime": 2100, + "medianExecutionTime": 35, "sessionsCount": 87, "usersCount": 19, }, @@ -137,28 +137,28 @@ exports[`AI Agent platform flow metrics data source applies correct sorting for { "completionRate": 95.1, "flowType": "CUSTOM_FLOW", - "medianExecutionTime": 2100, + "medianExecutionTime": 35, "sessionsCount": 87, "usersCount": 19, }, { "completionRate": 95.1, "flowType": "BONUS_FLOW", - "medianExecutionTime": 2100, + "medianExecutionTime": 35, "sessionsCount": 87, "usersCount": 19, }, { "completionRate": 92.5, "flowType": "AGENT_FLOW", - "medianExecutionTime": 1250, + "medianExecutionTime": 20.833333333333332, "sessionsCount": 145, "usersCount": 32, }, { "completionRate": 88.3, "flowType": "TOOL_FLOW", - "medianExecutionTime": 850, + "medianExecutionTime": 14.166666666666666, "sessionsCount": 203, "usersCount": 48, }, @@ -170,30 +170,65 @@ exports[`AI Agent platform flow metrics data source applies correct sorting for { "completionRate": 88.3, "flowType": "TOOL_FLOW", - "medianExecutionTime": 850, + "medianExecutionTime": 14.166666666666666, "sessionsCount": 203, "usersCount": 48, }, { "completionRate": 92.5, "flowType": "AGENT_FLOW", - "medianExecutionTime": 1250, + "medianExecutionTime": 20.833333333333332, "sessionsCount": 145, "usersCount": 32, }, { "completionRate": 95.1, "flowType": "CUSTOM_FLOW", - "medianExecutionTime": 2100, + "medianExecutionTime": 35, "sessionsCount": 87, "usersCount": 19, }, { "completionRate": 95.1, "flowType": "BONUS_FLOW", - "medianExecutionTime": 2100, + "medianExecutionTime": 35, "sessionsCount": 87, "usersCount": 19, }, ] `; + +exports[`AI Agent platform flow metrics data source returns the flow metrics as nodes on success 1`] = ` +{ + "nodes": [ + { + "completionRate": 92.5, + "flowType": "AGENT_FLOW", + "medianExecutionTime": 20.833333333333332, + "sessionsCount": 145, + "usersCount": 32, + }, + { + "completionRate": 88.3, + "flowType": "TOOL_FLOW", + "medianExecutionTime": 14.166666666666666, + "sessionsCount": 203, + "usersCount": 48, + }, + { + "completionRate": 95.1, + "flowType": "CUSTOM_FLOW", + "medianExecutionTime": 35, + "sessionsCount": 87, + "usersCount": 19, + }, + { + "completionRate": 95.1, + "flowType": "BONUS_FLOW", + "medianExecutionTime": 35, + "sessionsCount": 87, + "usersCount": 19, + }, + ], +} +`; diff --git a/ee/spec/frontend/analytics/analytics_dashboards/components/data_sources/ai_agent_platform_flow_metrics_spec.js b/ee/spec/frontend/analytics/analytics_dashboards/components/data_sources/ai_agent_platform_flow_metrics_spec.js index fe83b56865afc7..a8e6a4aadf10e0 100644 --- a/ee/spec/frontend/analytics/analytics_dashboards/components/data_sources/ai_agent_platform_flow_metrics_spec.js +++ b/ee/spec/frontend/analytics/analytics_dashboards/components/data_sources/ai_agent_platform_flow_metrics_spec.js @@ -49,7 +49,7 @@ describe('AI Agent platform flow metrics data source', () => { mockResolvedQuery(); const result = await fetch({ namespace }); - expect(result).toEqual({ nodes: mockFlowMetrics }); + expect(result).toMatchSnapshot(); }); it('uses 30 days as the default date range', async () => { @@ -88,9 +88,7 @@ describe('AI Agent platform flow metrics data source', () => { it('limits to 10 returned nodes', async () => { mockResolvedQuery(mockTooManyFlowMetrics); const result = await fetch({ namespace }); - expect(result).toEqual({ - nodes: mockTooManyFlowMetrics.slice(0, 10), - }); + expect(result.nodes).toHaveLength(10); }); it.each([ -- GitLab From 50d4d0348d007fe9233ce61f253467cb0f91ca26 Mon Sep 17 00:00:00 2001 From: Alex Pennells Date: Thu, 11 Dec 2025 10:20:25 -0500 Subject: [PATCH 3/4] Use new date range options --- .../ai_agent_platform_flow_metrics.js | 20 ++++++++----------- .../ai_agent_platform_flow_metrics_spec.js | 12 +++++------ 2 files changed, 14 insertions(+), 18 deletions(-) diff --git a/ee/app/assets/javascripts/analytics/analytics_dashboards/data_sources/ai_agent_platform_flow_metrics.js b/ee/app/assets/javascripts/analytics/analytics_dashboards/data_sources/ai_agent_platform_flow_metrics.js index d6544aaf820148..d0792effc67a59 100644 --- a/ee/app/assets/javascripts/analytics/analytics_dashboards/data_sources/ai_agent_platform_flow_metrics.js +++ b/ee/app/assets/javascripts/analytics/analytics_dashboards/data_sources/ai_agent_platform_flow_metrics.js @@ -2,9 +2,8 @@ import { orderBy } from 'lodash'; import { secondsToMinutes } from '~/lib/utils/datetime/date_calculation_utility'; import AiAgentPlatformFlowMetricsQuery from 'ee/analytics/dashboards/ai_impact/graphql/ai_agent_platform_flow_metrics.query.graphql'; import { - LAST_30_DAYS, - DORA_METRIC_QUERY_RANGES, - startOfTomorrow, + DATE_RANGE_OPTION_LAST_30_DAYS, + DATE_RANGE_OPTIONS, } from 'ee/analytics/analytics_dashboards/components/filters/constants'; import { extractQueryResponseFromNamespace } from '~/analytics/shared/utils'; import { defaultClient } from '../graphql/client'; @@ -37,21 +36,18 @@ const requestFlowMetrics = async ({ namespace, startDate, endDate, sortBy, sortD export default async function fetch({ namespace, - query: { dateRange = LAST_30_DAYS } = {}, + query: { dateRange = DATE_RANGE_OPTION_LAST_30_DAYS } = {}, queryOverrides: { dateRange: dateRangeOverride = null, ...overridesRest } = {}, }) { - const dateRangeKey = dateRangeOverride - ? dateRangeOverride.toUpperCase() - : dateRange.toUpperCase(); - // Default to 30 days if an invalid date range is given - const startDate = DORA_METRIC_QUERY_RANGES[dateRangeKey] - ? DORA_METRIC_QUERY_RANGES[dateRangeKey] - : DORA_METRIC_QUERY_RANGES[LAST_30_DAYS]; + const dateRangeKey = dateRangeOverride ?? dateRange; + const { startDate, endDate } = DATE_RANGE_OPTIONS[dateRangeKey] + ? DATE_RANGE_OPTIONS[dateRangeKey] + : DATE_RANGE_OPTIONS[DATE_RANGE_OPTION_LAST_30_DAYS]; const nodes = await requestFlowMetrics({ startDate, - endDate: startOfTomorrow, + endDate, namespace, ...overridesRest, }); diff --git a/ee/spec/frontend/analytics/analytics_dashboards/components/data_sources/ai_agent_platform_flow_metrics_spec.js b/ee/spec/frontend/analytics/analytics_dashboards/components/data_sources/ai_agent_platform_flow_metrics_spec.js index a8e6a4aadf10e0..fdb3d38bb3c8ab 100644 --- a/ee/spec/frontend/analytics/analytics_dashboards/components/data_sources/ai_agent_platform_flow_metrics_spec.js +++ b/ee/spec/frontend/analytics/analytics_dashboards/components/data_sources/ai_agent_platform_flow_metrics_spec.js @@ -1,6 +1,6 @@ import fetch from 'ee/analytics/analytics_dashboards/data_sources/ai_agent_platform_flow_metrics'; import { defaultClient } from 'ee/analytics/analytics_dashboards/graphql/client'; -import { LAST_WEEK } from 'ee/analytics/analytics_dashboards/components/filters/constants'; +import { DATE_RANGE_OPTION_LAST_7_DAYS } from 'ee/analytics/analytics_dashboards/components/filters/constants'; const mockFlowMetrics = [ { @@ -60,8 +60,8 @@ describe('AI Agent platform flow metrics data source', () => { expect.objectContaining({ variables: expect.objectContaining({ fullPath: namespace, - startDate: new Date('2020-06-07'), - endDate: new Date('2020-07-07'), + startDate: new Date('2020-06-06'), + endDate: new Date('2020-07-06'), }), }), ); @@ -71,15 +71,15 @@ describe('AI Agent platform flow metrics data source', () => { mockResolvedQuery(); await fetch({ namespace, - queryOverrides: { dateRange: LAST_WEEK }, + queryOverrides: { dateRange: DATE_RANGE_OPTION_LAST_7_DAYS }, }); expect(defaultClient.query).toHaveBeenCalledWith( expect.objectContaining({ variables: expect.objectContaining({ fullPath: namespace, - startDate: new Date('2020-06-30'), - endDate: new Date('2020-07-07'), + startDate: new Date('2020-06-29'), + endDate: new Date('2020-07-06'), }), }), ); -- GitLab From daa82e211d2c29180f56cb6bcb3dc67d7b416865 Mon Sep 17 00:00:00 2001 From: Ezekiel <3397881-ekigbo@users.noreply.gitlab.com> Date: Fri, 12 Dec 2025 14:25:34 +1100 Subject: [PATCH 4/4] Fix failing test --- .../graphql/analytics/dashboards/visualizations_spec.rb | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/ee/spec/requests/api/graphql/analytics/dashboards/visualizations_spec.rb b/ee/spec/requests/api/graphql/analytics/dashboards/visualizations_spec.rb index 7b3a50d7690f3b..7e7df98012759b 100644 --- a/ee/spec/requests/api/graphql/analytics/dashboards/visualizations_spec.rb +++ b/ee/spec/requests/api/graphql/analytics/dashboards/visualizations_spec.rb @@ -76,9 +76,10 @@ 8 | 'AiImpactTable' | 'GitLab Duo usage metrics for the %{namespaceName} %{namespaceType}' 9 | 'AiImpactTable' | 'Development metrics for the %{namespaceName} %{namespaceType}' 10 | 'AiImpactTable' | 'Pipeline metrics for the %{namespaceName} %{namespaceType}' - 11 | 'DataTable' | 'GitLab Duo Code Suggestions usage by user (Last 30 days)' - 12 | 'DataTable' | 'GitLab Duo Code Review usage by user (Last 30 days)' - 13 | 'DataTable' | 'GitLab Duo Root Cause Analysis usage by user (Last 30 days)' + 11 | 'DataTable' | 'Flow usage (Last 30 days)' + 12 | 'DataTable' | 'GitLab Duo Code Suggestions usage by user (Last 30 days)' + 13 | 'DataTable' | 'GitLab Duo Code Review usage by user (Last 30 days)' + 14 | 'DataTable' | 'GitLab Duo Root Cause Analysis usage by user (Last 30 days)' end with_them do -- GitLab