From 15ced1ae55f8fdff904ae5c79402c6c862bc4efa Mon Sep 17 00:00:00 2001 From: Ezekiel <3397881-ekigbo@users.noreply.gitlab.com> Date: Wed, 8 Oct 2025 16:58:57 +1100 Subject: [PATCH 1/8] Adds User link component for the datatable Adds the user link component and additional stories for use with the datatable. --- .../data_table/user_link.stories.js | 88 +++++++++++++++++++ .../visualizations/data_table/user_link.vue | 41 +++++++++ 2 files changed, 129 insertions(+) create mode 100644 ee/app/assets/javascripts/analytics/analytics_dashboards/components/visualizations/data_table/user_link.stories.js create mode 100644 ee/app/assets/javascripts/analytics/analytics_dashboards/components/visualizations/data_table/user_link.vue diff --git a/ee/app/assets/javascripts/analytics/analytics_dashboards/components/visualizations/data_table/user_link.stories.js b/ee/app/assets/javascripts/analytics/analytics_dashboards/components/visualizations/data_table/user_link.stories.js new file mode 100644 index 00000000000000..88927deccd0166 --- /dev/null +++ b/ee/app/assets/javascripts/analytics/analytics_dashboards/components/visualizations/data_table/user_link.stories.js @@ -0,0 +1,88 @@ +import DataTable from './data_table.vue'; +import UserLink from './user_link.vue'; + +export default { + component: UserLink, + title: 'ee/analytics/analytics_dashboards/components/visualizations/data_table/user_link', +}; + +const firstChild = { + user: { + id: 'gid://ai-cool/User/1010101', + name: 'Ayanami Rei', + avatarUrl: 'https://www.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?s=80&d=identicon', + username: 'ramiel', + webUrl: 'https://gitlab.com/fakeuser', + lastDuoActivityOn: '2025-10-07', // TODO: why isnt this a timestamp? + }, + codeSuggestionsAcceptedCount: 77, + duoChatInteractionsCount: 4, +}; + +const nodes = [ + firstChild, + { + user: { + id: 'gid://ai-cool/User/20202020', + name: 'Shikinami-Langley Asuka', + avatarUrl: + 'https://www.gravatar.com/avatar/c4ab964b90c3049c47882b319d3c5cc0?s=80\u0026d=identicon', + username: 'sachiel', + webUrl: 'https://gitlab.com/fakeuser', + lastDuoActivityOn: '2025-10-07', // TODO: why isnt this a timestamp? + }, + codeSuggestionsAcceptedCount: 14, + duoChatInteractionsCount: 1, + }, + { + user: { + id: 'gid://ai-cool/User/3030303', + name: 'Makinami Mari', + avatarUrl: + 'https://www.gravatar.com/avatar/00afb8fb6ab07c3ee3e9c1f38777e2f4?s=80&d=identicon', + username: 'leliel', + webUrl: 'https://gitlab.com/fakeuser', + lastDuoActivityOn: '2025-10-07', // TODO: why isnt this a timestamp? + }, + codeSuggestionsAcceptedCount: 14, + duoChatInteractionsCount: 34, + }, +]; + +const Template = (args, { argTypes }) => ({ + components: { UserLink }, + props: Object.keys(argTypes), + template: ``, +}); + +const TableTemplate = (args, { argTypes }) => ({ + components: { DataTable }, + props: Object.keys(argTypes), + template: ``, +}); + +export const Default = Template.bind({}); +Default.args = { ...firstChild.user }; + +export const InTable = TableTemplate.bind({}); +InTable.args = { + data: { + nodes: [ + { + title: 'No assignees', + user: { nodes: [] }, + }, + { + title: 'Single assignee', + user: { nodes: nodes.slice(0, 1) }, + }, + { + title: 'Multiple assignee', + user: { nodes }, + }, + ], + }, + options: { + fields: [{ key: 'user.name' }, { key: 'user', label: 'User', component: 'User' }], + }, +}; diff --git a/ee/app/assets/javascripts/analytics/analytics_dashboards/components/visualizations/data_table/user_link.vue b/ee/app/assets/javascripts/analytics/analytics_dashboards/components/visualizations/data_table/user_link.vue new file mode 100644 index 00000000000000..57e173282daf41 --- /dev/null +++ b/ee/app/assets/javascripts/analytics/analytics_dashboards/components/visualizations/data_table/user_link.vue @@ -0,0 +1,41 @@ + + -- GitLab From 62630f9827ac510cc48ec740d130e907a3c9f361 Mon Sep 17 00:00:00 2001 From: Ezekiel <3397881-ekigbo@users.noreply.gitlab.com> Date: Wed, 8 Oct 2025 17:02:34 +1100 Subject: [PATCH 2/8] Adds the code suggestions accepted by user table Adds the new YAML config for the table and includes it in the default config for the GitLab Duo SDLC dashboard. Adds the user_ai_usage_data data source --- .../visualizations/data_table/data_table.vue | 1 + .../data_table/user_link.stories.js | 28 +++----- .../data_sources/index.js | 1 + .../data_sources/user_ai_usage_data.js | 42 +++++++++++ .../get_user_ai_user_metrics.query.graphql | 70 +++++++++++++++++++ .../dora/components/static_data/shared.js | 2 +- .../analytics/dashboards/visualization.rb | 1 + .../json_schemas/analytics_visualization.json | 6 +- .../ai_impact_dashboard/dashboard.yaml | 7 ++ .../visualizations/user_metrics_table.yaml | 14 ++++ 10 files changed, 150 insertions(+), 22 deletions(-) create mode 100644 ee/app/assets/javascripts/analytics/analytics_dashboards/data_sources/user_ai_usage_data.js create mode 100644 ee/app/assets/javascripts/analytics/analytics_dashboards/graphql/queries/get_user_ai_user_metrics.query.graphql create mode 100644 ee/lib/gitlab/analytics/ai_impact_dashboard/visualizations/user_metrics_table.yaml diff --git a/ee/app/assets/javascripts/analytics/analytics_dashboards/components/visualizations/data_table/data_table.vue b/ee/app/assets/javascripts/analytics/analytics_dashboards/components/visualizations/data_table/data_table.vue index 72192b005af0b5..c6262bd4802815 100644 --- a/ee/app/assets/javascripts/analytics/analytics_dashboards/components/visualizations/data_table/data_table.vue +++ b/ee/app/assets/javascripts/analytics/analytics_dashboards/components/visualizations/data_table/data_table.vue @@ -23,6 +23,7 @@ export default { ChangePercentageIndicator: () => import('./change_percentage_indicator.vue'), MetricLabel: () => import('./metric_label.vue'), TrendLine: () => import('./trend_line.vue'), + UserLink: () => import('./user_link.vue'), }, props: { data: { diff --git a/ee/app/assets/javascripts/analytics/analytics_dashboards/components/visualizations/data_table/user_link.stories.js b/ee/app/assets/javascripts/analytics/analytics_dashboards/components/visualizations/data_table/user_link.stories.js index 88927deccd0166..1a6f3c2ab224d0 100644 --- a/ee/app/assets/javascripts/analytics/analytics_dashboards/components/visualizations/data_table/user_link.stories.js +++ b/ee/app/assets/javascripts/analytics/analytics_dashboards/components/visualizations/data_table/user_link.stories.js @@ -13,7 +13,7 @@ const firstChild = { avatarUrl: 'https://www.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?s=80&d=identicon', username: 'ramiel', webUrl: 'https://gitlab.com/fakeuser', - lastDuoActivityOn: '2025-10-07', // TODO: why isnt this a timestamp? + lastDuoActivityOn: '2025-10-07', }, codeSuggestionsAcceptedCount: 77, duoChatInteractionsCount: 4, @@ -29,7 +29,7 @@ const nodes = [ 'https://www.gravatar.com/avatar/c4ab964b90c3049c47882b319d3c5cc0?s=80\u0026d=identicon', username: 'sachiel', webUrl: 'https://gitlab.com/fakeuser', - lastDuoActivityOn: '2025-10-07', // TODO: why isnt this a timestamp? + lastDuoActivityOn: '2025-10-07', }, codeSuggestionsAcceptedCount: 14, duoChatInteractionsCount: 1, @@ -42,7 +42,7 @@ const nodes = [ 'https://www.gravatar.com/avatar/00afb8fb6ab07c3ee3e9c1f38777e2f4?s=80&d=identicon', username: 'leliel', webUrl: 'https://gitlab.com/fakeuser', - lastDuoActivityOn: '2025-10-07', // TODO: why isnt this a timestamp? + lastDuoActivityOn: '2025-10-07', }, codeSuggestionsAcceptedCount: 14, duoChatInteractionsCount: 34, @@ -56,7 +56,7 @@ const Template = (args, { argTypes }) => ({ }); const TableTemplate = (args, { argTypes }) => ({ - components: { DataTable }, + components: { DataTable, UserLink }, props: Object.keys(argTypes), template: ``, }); @@ -67,22 +67,12 @@ Default.args = { ...firstChild.user }; export const InTable = TableTemplate.bind({}); InTable.args = { data: { - nodes: [ - { - title: 'No assignees', - user: { nodes: [] }, - }, - { - title: 'Single assignee', - user: { nodes: nodes.slice(0, 1) }, - }, - { - title: 'Multiple assignee', - user: { nodes }, - }, - ], + nodes, }, options: { - fields: [{ key: 'user.name' }, { key: 'user', label: 'User', component: 'User' }], + fields: [ + { key: 'user.name', label: 'Name' }, + { key: 'user', label: 'User', component: 'UserLink' }, + ], }, }; 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 287ea8c1650959..735ab7fb17cf50 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 @@ -49,4 +49,5 @@ export default { code_suggestions_acceptance_by_language: () => import('./code_suggestions_acceptance_by_language'), code_generation_volume_over_time: () => import('./code_generation_volume_over_time'), + user_ai_usage_data: () => import('./user_ai_usage_data'), }; diff --git a/ee/app/assets/javascripts/analytics/analytics_dashboards/data_sources/user_ai_usage_data.js b/ee/app/assets/javascripts/analytics/analytics_dashboards/data_sources/user_ai_usage_data.js new file mode 100644 index 00000000000000..46909724a0f5c7 --- /dev/null +++ b/ee/app/assets/javascripts/analytics/analytics_dashboards/data_sources/user_ai_usage_data.js @@ -0,0 +1,42 @@ +import UserAiUserMetricsQuery from 'ee/analytics/analytics_dashboards/graphql/queries/get_user_ai_user_metrics.query.graphql'; +import { startOfToday } from 'ee/analytics/dora/components/static_data/shared'; +import { getStartDate } from 'ee/analytics/analytics_dashboards/components/filters/utils'; +import { DATE_RANGE_OPTION_LAST_30_DAYS } from 'ee/analytics/analytics_dashboards/components/filters/constants'; +import { defaultClient } from '../graphql/client'; + +const extractAiUserMetrics = (response) => { + const { + aiUserMetrics: { nodes = [], pageInfo = {} }, + } = response.data?.group || response.data?.project || {}; + return { + nodes, + pageInfo, + }; +}; + +export default async function fetch({ + namespace: fullPath, + query: { dateRange = DATE_RANGE_OPTION_LAST_30_DAYS }, + filters: { filtersStartDate, endDate = startOfToday } = {}, + setVisualizationOverrides, + queryOverrides: { pagination: { first, startCursor: before, endCursor: after } = {} } = {}, + visualizationOptions: visualizationOptionOverrides, +}) { + const startDate = filtersStartDate || getStartDate(dateRange); + + const response = await defaultClient.query({ + query: UserAiUserMetricsQuery, + variables: { + fullPath, + startDate, + endDate, + first, + before, + after, + }, + }); + + setVisualizationOverrides({ visualizationOptionOverrides }); + + return extractAiUserMetrics(response); +} diff --git a/ee/app/assets/javascripts/analytics/analytics_dashboards/graphql/queries/get_user_ai_user_metrics.query.graphql b/ee/app/assets/javascripts/analytics/analytics_dashboards/graphql/queries/get_user_ai_user_metrics.query.graphql new file mode 100644 index 00000000000000..6d5a852c150647 --- /dev/null +++ b/ee/app/assets/javascripts/analytics/analytics_dashboards/graphql/queries/get_user_ai_user_metrics.query.graphql @@ -0,0 +1,70 @@ +query getUserAiUserMetrics( + $fullPath: ID! + $startDate: Date! + $endDate: Date! + $before: String + $after: String + $last: Int + $first: Int = 20 +) { + group(fullPath: $fullPath) { + id + aiUserMetrics( + startDate: $startDate + endDate: $endDate + after: $after + before: $before + first: $first + last: $last + ) { + nodes { + user { + id + name + avatarUrl + username + webUrl + lastDuoActivityOn + } + codeSuggestionsAcceptedCount + duoChatInteractionsCount + } + pageInfo { + startCursor + endCursor + hasNextPage + hasPreviousPage + } + } + } + project(fullPath: $fullPath) { + id + aiUserMetrics( + startDate: $startDate + endDate: $endDate + after: $after + before: $before + first: $first + last: $last + ) { + nodes { + user { + id + name + avatarUrl + username + webUrl + lastDuoActivityOn + } + codeSuggestionsAcceptedCount + duoChatInteractionsCount + } + pageInfo { + startCursor + endCursor + hasNextPage + hasPreviousPage + } + } + } +} diff --git a/ee/app/assets/javascripts/analytics/dora/components/static_data/shared.js b/ee/app/assets/javascripts/analytics/dora/components/static_data/shared.js index 0a2e3e012e5ea4..326d85928144db 100644 --- a/ee/app/assets/javascripts/analytics/dora/components/static_data/shared.js +++ b/ee/app/assets/javascripts/analytics/dora/components/static_data/shared.js @@ -19,7 +19,7 @@ export const LAST_180_DAYS = 'LAST_180_DAYS'; // Compute all relative dates based on the _beginning_ of today. // We use this date as the end date for the charts. This causes // the current date to be the last day included in the graph. -const startOfToday = getStartOfDay(new Date(), { utc: true }); +export const startOfToday = getStartOfDay(new Date(), { utc: true }); // We use this date as the "to" parameter for the API. This allows // us to get DORA 4 metrics about the current day. diff --git a/ee/app/models/analytics/dashboards/visualization.rb b/ee/app/models/analytics/dashboards/visualization.rb index 5396ce0077357e..d9cc40491d3cc0 100644 --- a/ee/app/models/analytics/dashboards/visualization.rb +++ b/ee/app/models/analytics/dashboards/visualization.rb @@ -70,6 +70,7 @@ class Visualization pipeline_metrics_table code_suggestions_acceptance_rate_by_language_chart code_generation_volume_trends_chart + user_metrics_table ].freeze CONTRIBUTIONS_DASHBOARD_PATH = 'ee/lib/gitlab/analytics/contributions_dashboard/visualizations' diff --git a/ee/app/validators/json_schemas/analytics_visualization.json b/ee/app/validators/json_schemas/analytics_visualization.json index 25b56765a265e0..4d9d3e803738eb 100644 --- a/ee/app/validators/json_schemas/analytics_visualization.json +++ b/ee/app/validators/json_schemas/analytics_visualization.json @@ -79,7 +79,8 @@ "merge_request_counts", "mean_time_to_merge", "code_suggestions_acceptance_by_language", - "code_generation_volume_over_time" + "code_generation_volume_over_time", + "user_ai_usage_data" ] }, "query": { @@ -146,7 +147,8 @@ "MergeRequestLink", "ChangePercentageIndicator", "MetricLabel", - "TrendLine" + "TrendLine", + "UserLink" ] } } diff --git a/ee/lib/gitlab/analytics/ai_impact_dashboard/dashboard.yaml b/ee/lib/gitlab/analytics/ai_impact_dashboard/dashboard.yaml index 33dd51c5d9b4e7..a06a454f69e096 100644 --- a/ee/lib/gitlab/analytics/ai_impact_dashboard/dashboard.yaml +++ b/ee/lib/gitlab/analytics/ai_impact_dashboard/dashboard.yaml @@ -74,3 +74,10 @@ panels: width: 12 height: 4 options: {} + - title: 'Code Suggestions accepted by user' + visualization: user_metrics_table + gridAttributes: + yPos: 20 + xPos: 0 + width: 12 + height: 12 diff --git a/ee/lib/gitlab/analytics/ai_impact_dashboard/visualizations/user_metrics_table.yaml b/ee/lib/gitlab/analytics/ai_impact_dashboard/visualizations/user_metrics_table.yaml new file mode 100644 index 00000000000000..87222f38726c12 --- /dev/null +++ b/ee/lib/gitlab/analytics/ai_impact_dashboard/visualizations/user_metrics_table.yaml @@ -0,0 +1,14 @@ +--- +version: 1 +type: DataTable +data: + type: user_ai_usage_data + query: {} +options: + fields: + - key: 'user' + component: 'UserLink' + - key: 'user.lastDuoActivityOn' + label: 'Last duo activity' + - key: 'codeSuggestionsAcceptedCount' + label: 'Code suggestions accepted' -- GitLab From 2ea1c181687e08cd1748e990cea8bb9564fbbfa5 Mon Sep 17 00:00:00 2001 From: Ezekiel <3397881-ekigbo@users.noreply.gitlab.com> Date: Mon, 20 Oct 2025 16:37:28 +1100 Subject: [PATCH 3/8] Right align code suggestions accepted column --- .../ai_impact_dashboard/visualizations/user_metrics_table.yaml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/ee/lib/gitlab/analytics/ai_impact_dashboard/visualizations/user_metrics_table.yaml b/ee/lib/gitlab/analytics/ai_impact_dashboard/visualizations/user_metrics_table.yaml index 87222f38726c12..19852c82302b7f 100644 --- a/ee/lib/gitlab/analytics/ai_impact_dashboard/visualizations/user_metrics_table.yaml +++ b/ee/lib/gitlab/analytics/ai_impact_dashboard/visualizations/user_metrics_table.yaml @@ -12,3 +12,5 @@ options: label: 'Last duo activity' - key: 'codeSuggestionsAcceptedCount' label: 'Code suggestions accepted' + thClass: "gl-text-right" + tdClass: "gl-text-right" -- GitLab From 2545cfdb3022e30ae79f647bf83b8150bd05950f Mon Sep 17 00:00:00 2001 From: Ezekiel <3397881-ekigbo@users.noreply.gitlab.com> Date: Mon, 20 Oct 2025 16:53:10 +1100 Subject: [PATCH 4/8] Update visualization specs --- ee/spec/models/analytics/dashboards/visualization_spec.rb | 1 + .../api/graphql/analytics/dashboards/visualizations_spec.rb | 1 + 2 files changed, 2 insertions(+) diff --git a/ee/spec/models/analytics/dashboards/visualization_spec.rb b/ee/spec/models/analytics/dashboards/visualization_spec.rb index 8e308e63f54d63..e3c13c9f414881 100644 --- a/ee/spec/models/analytics/dashboards/visualization_spec.rb +++ b/ee/spec/models/analytics/dashboards/visualization_spec.rb @@ -51,6 +51,7 @@ pipeline_metrics_table code_suggestions_acceptance_rate_by_language_chart code_generation_volume_trends_chart + user_metrics_table ] end 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 66a53310045f3e..da3c9d403f7d9b 100644 --- a/ee/spec/requests/api/graphql/analytics/dashboards/visualizations_spec.rb +++ b/ee/spec/requests/api/graphql/analytics/dashboards/visualizations_spec.rb @@ -74,6 +74,7 @@ 6 | 'AiImpactTable' | 'Pipeline metrics for the %{namespaceName} %{namespaceType}' 7 | 'BarChart' | 'Code suggestions acceptance rate by language (Last 30 days)' 8 | 'AreaChart' | 'Code generation volume trends (Last 180 days)' + 9 | 'DataTable' | 'Code Suggestions accepted by user' end with_them do -- GitLab From dbd403073ba2aa15ae983bf2f0224499a8d5a47c Mon Sep 17 00:00:00 2001 From: Ezekiel <3397881-ekigbo@users.noreply.gitlab.com> Date: Mon, 20 Oct 2025 17:27:44 +1100 Subject: [PATCH 5/8] Add jest tests Adds data source jest tests for the user_ai_usage_data data source --- .../data_sources/user_ai_usage_data.js | 4 +- .../dora/components/static_data/shared.js | 2 +- .../user_ai_usage_data_spec.js.snap | 43 ++++ .../data_sources/user_ai_usage_data_spec.js | 206 ++++++++++++++++++ 4 files changed, 252 insertions(+), 3 deletions(-) create mode 100644 ee/spec/frontend/analytics/analytics_dashboards/components/data_sources/__snapshots__/user_ai_usage_data_spec.js.snap create mode 100644 ee/spec/frontend/analytics/analytics_dashboards/components/data_sources/user_ai_usage_data_spec.js diff --git a/ee/app/assets/javascripts/analytics/analytics_dashboards/data_sources/user_ai_usage_data.js b/ee/app/assets/javascripts/analytics/analytics_dashboards/data_sources/user_ai_usage_data.js index 46909724a0f5c7..ffa764a6d2a0f0 100644 --- a/ee/app/assets/javascripts/analytics/analytics_dashboards/data_sources/user_ai_usage_data.js +++ b/ee/app/assets/javascripts/analytics/analytics_dashboards/data_sources/user_ai_usage_data.js @@ -1,5 +1,5 @@ import UserAiUserMetricsQuery from 'ee/analytics/analytics_dashboards/graphql/queries/get_user_ai_user_metrics.query.graphql'; -import { startOfToday } from 'ee/analytics/dora/components/static_data/shared'; +import { startOfTomorrow } from 'ee/analytics/dora/components/static_data/shared'; import { getStartDate } from 'ee/analytics/analytics_dashboards/components/filters/utils'; import { DATE_RANGE_OPTION_LAST_30_DAYS } from 'ee/analytics/analytics_dashboards/components/filters/constants'; import { defaultClient } from '../graphql/client'; @@ -17,7 +17,7 @@ const extractAiUserMetrics = (response) => { export default async function fetch({ namespace: fullPath, query: { dateRange = DATE_RANGE_OPTION_LAST_30_DAYS }, - filters: { filtersStartDate, endDate = startOfToday } = {}, + filters: { filtersStartDate, endDate = startOfTomorrow } = {}, setVisualizationOverrides, queryOverrides: { pagination: { first, startCursor: before, endCursor: after } = {} } = {}, visualizationOptions: visualizationOptionOverrides, diff --git a/ee/app/assets/javascripts/analytics/dora/components/static_data/shared.js b/ee/app/assets/javascripts/analytics/dora/components/static_data/shared.js index 326d85928144db..0a2e3e012e5ea4 100644 --- a/ee/app/assets/javascripts/analytics/dora/components/static_data/shared.js +++ b/ee/app/assets/javascripts/analytics/dora/components/static_data/shared.js @@ -19,7 +19,7 @@ export const LAST_180_DAYS = 'LAST_180_DAYS'; // Compute all relative dates based on the _beginning_ of today. // We use this date as the end date for the charts. This causes // the current date to be the last day included in the graph. -export const startOfToday = getStartOfDay(new Date(), { utc: true }); +const startOfToday = getStartOfDay(new Date(), { utc: true }); // We use this date as the "to" parameter for the API. This allows // us to get DORA 4 metrics about the current day. diff --git a/ee/spec/frontend/analytics/analytics_dashboards/components/data_sources/__snapshots__/user_ai_usage_data_spec.js.snap b/ee/spec/frontend/analytics/analytics_dashboards/components/data_sources/__snapshots__/user_ai_usage_data_spec.js.snap new file mode 100644 index 00000000000000..6fd6f75651f464 --- /dev/null +++ b/ee/spec/frontend/analytics/analytics_dashboards/components/data_sources/__snapshots__/user_ai_usage_data_spec.js.snap @@ -0,0 +1,43 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`User ai usage data source with data available returns data and pagination information 1`] = ` +{ + "nodes": [ + { + "__typename": "AiUserMetrics", + "codeSuggestionsAcceptedCount": 0, + "duoChatInteractionsCount": 0, + "user": { + "__typename": "AddOnUser", + "avatarUrl": "https://www.gravatar.com/avatar/3dd3516a13d37b25b66c7915ab9bedfe7b20ffaf32da3f50c3cb5ee9f3f8aba6?s=80&d=identicon", + "id": "gid://gitlab/User/106", + "lastDuoActivityOn": null, + "name": "P Dawn Turner", + "username": "p-user-dawn-turner-e5c194af9850", + "webUrl": "http://gdk.test:3001/p-user-dawn-turner-e5c194af9850", + }, + }, + { + "__typename": "AiUserMetrics", + "codeSuggestionsAcceptedCount": 0, + "duoChatInteractionsCount": 0, + "user": { + "__typename": "AddOnUser", + "avatarUrl": "https://www.gravatar.com/avatar/142fcdcab9580b9edd2b623335dde4e6559a628daea89773acf60ba419da66bb?s=80&d=identicon", + "id": "gid://gitlab/User/105", + "lastDuoActivityOn": null, + "name": "P Homer Hodkiewicz", + "username": "p-user-homer-hodkiewicz-e5c194af9850", + "webUrl": "http://gdk.test:3001/p-user-homer-hodkiewicz-e5c194af9850", + }, + }, + ], + "pageInfo": { + "__typename": "PageInfo", + "endCursor": "this-is-an-end-cursor", + "hasNextPage": false, + "hasPreviousPage": false, + "startCursor": "this-is-a-start-cursor", + }, +} +`; diff --git a/ee/spec/frontend/analytics/analytics_dashboards/components/data_sources/user_ai_usage_data_spec.js b/ee/spec/frontend/analytics/analytics_dashboards/components/data_sources/user_ai_usage_data_spec.js new file mode 100644 index 00000000000000..39e56c29507ccf --- /dev/null +++ b/ee/spec/frontend/analytics/analytics_dashboards/components/data_sources/user_ai_usage_data_spec.js @@ -0,0 +1,206 @@ +import fetch from 'ee/analytics/analytics_dashboards/data_sources/user_ai_usage_data'; +import { defaultClient } from 'ee/analytics/analytics_dashboards/graphql/client'; +import * as utils from 'ee/analytics/analytics_dashboards/components/filters/utils'; +import { + DATE_RANGE_OPTION_LAST_30_DAYS, + DATE_RANGE_OPTION_LAST_60_DAYS, +} from 'ee/analytics/analytics_dashboards/components/filters/constants'; + +const mockPageInfo = { + hasNextPage: false, + hasPreviousPage: false, + startCursor: 'this-is-a-start-cursor', + endCursor: 'this-is-an-end-cursor', + __typename: 'PageInfo', +}; + +const mockUserAiMetrics = [ + { + user: { + id: 'gid://gitlab/User/106', + name: 'P Dawn Turner', + avatarUrl: + 'https://www.gravatar.com/avatar/3dd3516a13d37b25b66c7915ab9bedfe7b20ffaf32da3f50c3cb5ee9f3f8aba6?s=80\u0026d=identicon', + username: 'p-user-dawn-turner-e5c194af9850', + webUrl: 'http://gdk.test:3001/p-user-dawn-turner-e5c194af9850', + lastDuoActivityOn: null, + __typename: 'AddOnUser', + }, + codeSuggestionsAcceptedCount: 0, + duoChatInteractionsCount: 0, + __typename: 'AiUserMetrics', + }, + { + user: { + id: 'gid://gitlab/User/105', + name: 'P Homer Hodkiewicz', + avatarUrl: + 'https://www.gravatar.com/avatar/142fcdcab9580b9edd2b623335dde4e6559a628daea89773acf60ba419da66bb?s=80\u0026d=identicon', + username: 'p-user-homer-hodkiewicz-e5c194af9850', + webUrl: 'http://gdk.test:3001/p-user-homer-hodkiewicz-e5c194af9850', + lastDuoActivityOn: null, + __typename: 'AddOnUser', + }, + codeSuggestionsAcceptedCount: 0, + duoChatInteractionsCount: 0, + __typename: 'AiUserMetrics', + }, +]; + +const mockUserAiUsageDataResponseData = { + aiUserMetrics: { + nodes: mockUserAiMetrics, + pageInfo: mockPageInfo, + }, +}; + +const fields = [ + { + key: 'user', + component: 'UserLink', + }, + { + key: 'user.lastDuoActivityOn', + label: 'Last duo activity', + }, + { + key: 'codeSuggestionsAcceptedCount', + label: 'Code suggestions accepted', + }, +]; + +const mockResolvedQuery = ({ aiUserMetrics = [] } = {}) => + jest.spyOn(defaultClient, 'query').mockResolvedValue({ data: { project: { aiUserMetrics } } }); + +const expectQueryWithVariables = (variables) => + expect(defaultClient.query).toHaveBeenCalledWith( + expect.objectContaining({ + variables: expect.objectContaining({ + ...variables, + }), + }), + ); + +describe('User ai usage data source', () => { + let mockSetVisualizationOverrides; + let res; + + const namespace = 'test-namespace'; + const defaultQueryParams = { + dateRange: DATE_RANGE_OPTION_LAST_30_DAYS, + }; + + afterEach(() => { + jest.clearAllMocks(); + }); + + beforeEach(() => { + mockSetVisualizationOverrides = jest.fn(); + }); + + it('sets the visualization options', async () => { + mockResolvedQuery(); + + res = await fetch({ + setVisualizationOverrides: mockSetVisualizationOverrides, + namespace, + visualizationOptions: { fields }, + query: { + ...defaultQueryParams, + }, + }); + + expect(mockSetVisualizationOverrides).toHaveBeenCalledWith({ + visualizationOptionOverrides: { + fields, + }, + }); + }); + + it('can override default query parameters', async () => { + mockResolvedQuery(); + + res = await fetch({ + setVisualizationOverrides: mockSetVisualizationOverrides, + namespace, + query: { + ...defaultQueryParams, + dateRange: DATE_RANGE_OPTION_LAST_60_DAYS, + }, + }); + + expectQueryWithVariables({ + fullPath: namespace, + startDate: new Date('2020-05-08'), + endDate: new Date('2020-07-07'), + }); + + expect(defaultClient.query).toHaveBeenCalledTimes(1); + }); + + it('can override default pagination', async () => { + mockResolvedQuery(); + + res = await fetch({ + setVisualizationOverrides: mockSetVisualizationOverrides, + namespace, + query: { + ...defaultQueryParams, + }, + queryOverrides: { + pagination: { startCursor: 'start' }, + }, + }); + + expectQueryWithVariables({ + fullPath: namespace, + startDate: new Date('2020-06-07'), + endDate: new Date('2020-07-07'), + before: 'start', + }); + + expect(defaultClient.query).toHaveBeenCalledTimes(1); + }); + + describe('with data available', () => { + beforeEach(async () => { + mockResolvedQuery(mockUserAiUsageDataResponseData); + + res = await fetch({ + setVisualizationOverrides: mockSetVisualizationOverrides, + namespace, + query: defaultQueryParams, + }); + }); + + it('sets the correct query parameters', () => { + expectQueryWithVariables({ + fullPath: namespace, + startDate: new Date('2020-06-07'), + endDate: new Date('2020-07-07'), + }); + + expect(defaultClient.query).toHaveBeenCalledTimes(1); + }); + + it('returns data and pagination information', () => { + expect(res).toMatchSnapshot(); + }); + }); + + describe('with no data available', () => { + beforeEach(async () => { + mockResolvedQuery(); + + res = await fetch({ + setVisualizationOverrides: mockSetVisualizationOverrides, + namespace, + query: defaultQueryParams, + }); + }); + + it('returns an empty array', () => { + expect(res.nodes).toHaveLength(0); + }); + }); +}); -- GitLab From 3bce59a9e1fc157df2b5bc5f443410c910a99ae6 Mon Sep 17 00:00:00 2001 From: Ezekiel <3397881-ekigbo@users.noreply.gitlab.com> Date: Tue, 21 Oct 2025 11:29:50 +1100 Subject: [PATCH 6/8] Minor linting fixes --- .../__snapshots__/user_ai_usage_data_spec.js.snap | 4 ++-- .../components/data_sources/user_ai_usage_data_spec.js | 6 +++--- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/ee/spec/frontend/analytics/analytics_dashboards/components/data_sources/__snapshots__/user_ai_usage_data_spec.js.snap b/ee/spec/frontend/analytics/analytics_dashboards/components/data_sources/__snapshots__/user_ai_usage_data_spec.js.snap index 6fd6f75651f464..d373444b5a3635 100644 --- a/ee/spec/frontend/analytics/analytics_dashboards/components/data_sources/__snapshots__/user_ai_usage_data_spec.js.snap +++ b/ee/spec/frontend/analytics/analytics_dashboards/components/data_sources/__snapshots__/user_ai_usage_data_spec.js.snap @@ -11,7 +11,7 @@ exports[`User ai usage data source with data available returns data and paginati "__typename": "AddOnUser", "avatarUrl": "https://www.gravatar.com/avatar/3dd3516a13d37b25b66c7915ab9bedfe7b20ffaf32da3f50c3cb5ee9f3f8aba6?s=80&d=identicon", "id": "gid://gitlab/User/106", - "lastDuoActivityOn": null, + "lastDuoActivityOn": "2025-10-01", "name": "P Dawn Turner", "username": "p-user-dawn-turner-e5c194af9850", "webUrl": "http://gdk.test:3001/p-user-dawn-turner-e5c194af9850", @@ -25,7 +25,7 @@ exports[`User ai usage data source with data available returns data and paginati "__typename": "AddOnUser", "avatarUrl": "https://www.gravatar.com/avatar/142fcdcab9580b9edd2b623335dde4e6559a628daea89773acf60ba419da66bb?s=80&d=identicon", "id": "gid://gitlab/User/105", - "lastDuoActivityOn": null, + "lastDuoActivityOn": "2025-10-04", "name": "P Homer Hodkiewicz", "username": "p-user-homer-hodkiewicz-e5c194af9850", "webUrl": "http://gdk.test:3001/p-user-homer-hodkiewicz-e5c194af9850", diff --git a/ee/spec/frontend/analytics/analytics_dashboards/components/data_sources/user_ai_usage_data_spec.js b/ee/spec/frontend/analytics/analytics_dashboards/components/data_sources/user_ai_usage_data_spec.js index 39e56c29507ccf..9bec7bda945d2c 100644 --- a/ee/spec/frontend/analytics/analytics_dashboards/components/data_sources/user_ai_usage_data_spec.js +++ b/ee/spec/frontend/analytics/analytics_dashboards/components/data_sources/user_ai_usage_data_spec.js @@ -1,6 +1,6 @@ import fetch from 'ee/analytics/analytics_dashboards/data_sources/user_ai_usage_data'; import { defaultClient } from 'ee/analytics/analytics_dashboards/graphql/client'; -import * as utils from 'ee/analytics/analytics_dashboards/components/filters/utils'; + import { DATE_RANGE_OPTION_LAST_30_DAYS, DATE_RANGE_OPTION_LAST_60_DAYS, @@ -23,7 +23,7 @@ const mockUserAiMetrics = [ 'https://www.gravatar.com/avatar/3dd3516a13d37b25b66c7915ab9bedfe7b20ffaf32da3f50c3cb5ee9f3f8aba6?s=80\u0026d=identicon', username: 'p-user-dawn-turner-e5c194af9850', webUrl: 'http://gdk.test:3001/p-user-dawn-turner-e5c194af9850', - lastDuoActivityOn: null, + lastDuoActivityOn: '2025-10-01', __typename: 'AddOnUser', }, codeSuggestionsAcceptedCount: 0, @@ -38,7 +38,7 @@ const mockUserAiMetrics = [ 'https://www.gravatar.com/avatar/142fcdcab9580b9edd2b623335dde4e6559a628daea89773acf60ba419da66bb?s=80\u0026d=identicon', username: 'p-user-homer-hodkiewicz-e5c194af9850', webUrl: 'http://gdk.test:3001/p-user-homer-hodkiewicz-e5c194af9850', - lastDuoActivityOn: null, + lastDuoActivityOn: '2025-10-04', __typename: 'AddOnUser', }, codeSuggestionsAcceptedCount: 0, -- GitLab From 6358e34298f8d2315f56c72d6331449097e19bc4 Mon Sep 17 00:00:00 2001 From: Ezekiel <3397881-ekigbo@users.noreply.gitlab.com> Date: Wed, 22 Oct 2025 16:51:35 +1100 Subject: [PATCH 7/8] Address minor review comments Remove some unused code for extracting the graphql result and setting visualization options. Deduplicate query fragments --- .../data_table/user_link.stories.js | 12 ----- .../data_sources/user_ai_usage_data.js | 22 ++------- .../ai_user_metric_item.fragment.graphql | 12 +++++ .../get_user_ai_user_metrics.query.graphql | 35 +++------------ .../data_sources/user_ai_usage_data_spec.js | 45 +------------------ .../data_table/user_link_spec.js | 42 +++++++++++++++++ 6 files changed, 66 insertions(+), 102 deletions(-) create mode 100644 ee/app/assets/javascripts/analytics/analytics_dashboards/graphql/fragments/ai_user_metric_item.fragment.graphql create mode 100644 ee/spec/frontend/analytics/analytics_dashboards/components/visualizations/data_table/user_link_spec.js diff --git a/ee/app/assets/javascripts/analytics/analytics_dashboards/components/visualizations/data_table/user_link.stories.js b/ee/app/assets/javascripts/analytics/analytics_dashboards/components/visualizations/data_table/user_link.stories.js index 1a6f3c2ab224d0..d65e7b81bf0688 100644 --- a/ee/app/assets/javascripts/analytics/analytics_dashboards/components/visualizations/data_table/user_link.stories.js +++ b/ee/app/assets/javascripts/analytics/analytics_dashboards/components/visualizations/data_table/user_link.stories.js @@ -8,44 +8,32 @@ export default { const firstChild = { user: { - id: 'gid://ai-cool/User/1010101', name: 'Ayanami Rei', avatarUrl: 'https://www.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?s=80&d=identicon', username: 'ramiel', webUrl: 'https://gitlab.com/fakeuser', - lastDuoActivityOn: '2025-10-07', }, - codeSuggestionsAcceptedCount: 77, - duoChatInteractionsCount: 4, }; const nodes = [ firstChild, { user: { - id: 'gid://ai-cool/User/20202020', name: 'Shikinami-Langley Asuka', avatarUrl: 'https://www.gravatar.com/avatar/c4ab964b90c3049c47882b319d3c5cc0?s=80\u0026d=identicon', username: 'sachiel', webUrl: 'https://gitlab.com/fakeuser', - lastDuoActivityOn: '2025-10-07', }, - codeSuggestionsAcceptedCount: 14, - duoChatInteractionsCount: 1, }, { user: { - id: 'gid://ai-cool/User/3030303', name: 'Makinami Mari', avatarUrl: 'https://www.gravatar.com/avatar/00afb8fb6ab07c3ee3e9c1f38777e2f4?s=80&d=identicon', username: 'leliel', webUrl: 'https://gitlab.com/fakeuser', - lastDuoActivityOn: '2025-10-07', }, - codeSuggestionsAcceptedCount: 14, - duoChatInteractionsCount: 34, }, ]; diff --git a/ee/app/assets/javascripts/analytics/analytics_dashboards/data_sources/user_ai_usage_data.js b/ee/app/assets/javascripts/analytics/analytics_dashboards/data_sources/user_ai_usage_data.js index ffa764a6d2a0f0..37ed25f904fe12 100644 --- a/ee/app/assets/javascripts/analytics/analytics_dashboards/data_sources/user_ai_usage_data.js +++ b/ee/app/assets/javascripts/analytics/analytics_dashboards/data_sources/user_ai_usage_data.js @@ -2,41 +2,27 @@ import UserAiUserMetricsQuery from 'ee/analytics/analytics_dashboards/graphql/qu import { startOfTomorrow } from 'ee/analytics/dora/components/static_data/shared'; import { getStartDate } from 'ee/analytics/analytics_dashboards/components/filters/utils'; import { DATE_RANGE_OPTION_LAST_30_DAYS } from 'ee/analytics/analytics_dashboards/components/filters/constants'; +import { extractQueryResponseFromNamespace } from '~/analytics/shared/utils'; import { defaultClient } from '../graphql/client'; -const extractAiUserMetrics = (response) => { - const { - aiUserMetrics: { nodes = [], pageInfo = {} }, - } = response.data?.group || response.data?.project || {}; - return { - nodes, - pageInfo, - }; -}; - export default async function fetch({ namespace: fullPath, query: { dateRange = DATE_RANGE_OPTION_LAST_30_DAYS }, - filters: { filtersStartDate, endDate = startOfTomorrow } = {}, - setVisualizationOverrides, queryOverrides: { pagination: { first, startCursor: before, endCursor: after } = {} } = {}, - visualizationOptions: visualizationOptionOverrides, }) { - const startDate = filtersStartDate || getStartDate(dateRange); + const startDate = getStartDate(dateRange); const response = await defaultClient.query({ query: UserAiUserMetricsQuery, variables: { fullPath, startDate, - endDate, + endDate: startOfTomorrow, first, before, after, }, }); - setVisualizationOverrides({ visualizationOptionOverrides }); - - return extractAiUserMetrics(response); + return extractQueryResponseFromNamespace({ result: response, resultKey: 'aiUserMetrics' }); } diff --git a/ee/app/assets/javascripts/analytics/analytics_dashboards/graphql/fragments/ai_user_metric_item.fragment.graphql b/ee/app/assets/javascripts/analytics/analytics_dashboards/graphql/fragments/ai_user_metric_item.fragment.graphql new file mode 100644 index 00000000000000..2ba2c7b951f6fd --- /dev/null +++ b/ee/app/assets/javascripts/analytics/analytics_dashboards/graphql/fragments/ai_user_metric_item.fragment.graphql @@ -0,0 +1,12 @@ +fragment AiUserMetricItem on AiUserMetrics { + user { + id + name + avatarUrl + username + webUrl + lastDuoActivityOn + } + codeSuggestionsAcceptedCount + duoChatInteractionsCount +} diff --git a/ee/app/assets/javascripts/analytics/analytics_dashboards/graphql/queries/get_user_ai_user_metrics.query.graphql b/ee/app/assets/javascripts/analytics/analytics_dashboards/graphql/queries/get_user_ai_user_metrics.query.graphql index 6d5a852c150647..ecbe1dbe3aade5 100644 --- a/ee/app/assets/javascripts/analytics/analytics_dashboards/graphql/queries/get_user_ai_user_metrics.query.graphql +++ b/ee/app/assets/javascripts/analytics/analytics_dashboards/graphql/queries/get_user_ai_user_metrics.query.graphql @@ -1,3 +1,6 @@ +#import "~/graphql_shared/fragments/page_info.fragment.graphql" +#import "../fragments/ai_user_metric_item.fragment.graphql" + query getUserAiUserMetrics( $fullPath: ID! $startDate: Date! @@ -18,22 +21,10 @@ query getUserAiUserMetrics( last: $last ) { nodes { - user { - id - name - avatarUrl - username - webUrl - lastDuoActivityOn - } - codeSuggestionsAcceptedCount - duoChatInteractionsCount + ...AiUserMetricItem } pageInfo { - startCursor - endCursor - hasNextPage - hasPreviousPage + ...PageInfo } } } @@ -48,22 +39,10 @@ query getUserAiUserMetrics( last: $last ) { nodes { - user { - id - name - avatarUrl - username - webUrl - lastDuoActivityOn - } - codeSuggestionsAcceptedCount - duoChatInteractionsCount + ...AiUserMetricItem } pageInfo { - startCursor - endCursor - hasNextPage - hasPreviousPage + ...PageInfo } } } diff --git a/ee/spec/frontend/analytics/analytics_dashboards/components/data_sources/user_ai_usage_data_spec.js b/ee/spec/frontend/analytics/analytics_dashboards/components/data_sources/user_ai_usage_data_spec.js index 9bec7bda945d2c..4374374a656a78 100644 --- a/ee/spec/frontend/analytics/analytics_dashboards/components/data_sources/user_ai_usage_data_spec.js +++ b/ee/spec/frontend/analytics/analytics_dashboards/components/data_sources/user_ai_usage_data_spec.js @@ -54,21 +54,6 @@ const mockUserAiUsageDataResponseData = { }, }; -const fields = [ - { - key: 'user', - component: 'UserLink', - }, - { - key: 'user.lastDuoActivityOn', - label: 'Last duo activity', - }, - { - key: 'codeSuggestionsAcceptedCount', - label: 'Code suggestions accepted', - }, -]; - const mockResolvedQuery = ({ aiUserMetrics = [] } = {}) => jest.spyOn(defaultClient, 'query').mockResolvedValue({ data: { project: { aiUserMetrics } } }); @@ -82,7 +67,6 @@ const expectQueryWithVariables = (variables) => ); describe('User ai usage data source', () => { - let mockSetVisualizationOverrides; let res; const namespace = 'test-namespace'; @@ -94,34 +78,10 @@ describe('User ai usage data source', () => { jest.clearAllMocks(); }); - beforeEach(() => { - mockSetVisualizationOverrides = jest.fn(); - }); - - it('sets the visualization options', async () => { - mockResolvedQuery(); - - res = await fetch({ - setVisualizationOverrides: mockSetVisualizationOverrides, - namespace, - visualizationOptions: { fields }, - query: { - ...defaultQueryParams, - }, - }); - - expect(mockSetVisualizationOverrides).toHaveBeenCalledWith({ - visualizationOptionOverrides: { - fields, - }, - }); - }); - it('can override default query parameters', async () => { mockResolvedQuery(); res = await fetch({ - setVisualizationOverrides: mockSetVisualizationOverrides, namespace, query: { ...defaultQueryParams, @@ -142,7 +102,6 @@ describe('User ai usage data source', () => { mockResolvedQuery(); res = await fetch({ - setVisualizationOverrides: mockSetVisualizationOverrides, namespace, query: { ...defaultQueryParams, @@ -167,7 +126,6 @@ describe('User ai usage data source', () => { mockResolvedQuery(mockUserAiUsageDataResponseData); res = await fetch({ - setVisualizationOverrides: mockSetVisualizationOverrides, namespace, query: defaultQueryParams, }); @@ -193,14 +151,13 @@ describe('User ai usage data source', () => { mockResolvedQuery(); res = await fetch({ - setVisualizationOverrides: mockSetVisualizationOverrides, namespace, query: defaultQueryParams, }); }); it('returns an empty array', () => { - expect(res.nodes).toHaveLength(0); + expect(res).toHaveLength(0); }); }); }); diff --git a/ee/spec/frontend/analytics/analytics_dashboards/components/visualizations/data_table/user_link_spec.js b/ee/spec/frontend/analytics/analytics_dashboards/components/visualizations/data_table/user_link_spec.js new file mode 100644 index 00000000000000..57d97f44ee11ad --- /dev/null +++ b/ee/spec/frontend/analytics/analytics_dashboards/components/visualizations/data_table/user_link_spec.js @@ -0,0 +1,42 @@ +import { GlAvatarLabeled, GlAvatarLink } from '@gitlab/ui'; +import { shallowMountExtended } from 'helpers/vue_test_utils_helper'; +import UserLink from 'ee/analytics/analytics_dashboards/components/visualizations/data_table/user_link.vue'; + +const mockData = { + name: 'Shikinami-Langley Asuka', + avatarUrl: + 'https://www.gravatar.com/avatar/c4ab964b90c3049c47882b319d3c5cc0?s=80\u0026d=identicon', + username: 'sachiel', + webUrl: 'https://gitlab.com/fakeuser', +}; + +describe('UserLink', () => { + let wrapper; + + const findAvatarLink = () => wrapper.findComponent(GlAvatarLink); + const findLabeledAvatar = () => wrapper.findComponent(GlAvatarLabeled); + + describe('default', () => { + beforeEach(() => { + wrapper = shallowMountExtended(UserLink, { + propsData: { ...mockData }, + }); + }); + + it('renders the avatar link', () => { + expect(findAvatarLink().attributes()).toEqual({ + href: 'https://gitlab.com/fakeuser', + target: 'blank', + }); + }); + + it('renders the labeled avatar', () => { + expect(findLabeledAvatar().attributes()).toMatchObject({ + label: 'Shikinami-Langley Asuka', + sublabel: '@sachiel', + alt: 'sachiel avatar', + size: '32', + }); + }); + }); +}); -- GitLab From 2f604f5803c1817d1a4107012cccb9f121399e68 Mon Sep 17 00:00:00 2001 From: Ezekiel <3397881-ekigbo@users.noreply.gitlab.com> Date: Thu, 23 Oct 2025 18:19:17 +1100 Subject: [PATCH 8/8] Fix pagination when going to previous page --- .../data_sources/user_ai_usage_data.js | 9 +++++---- .../queries/get_user_ai_user_metrics.query.graphql | 2 +- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/ee/app/assets/javascripts/analytics/analytics_dashboards/data_sources/user_ai_usage_data.js b/ee/app/assets/javascripts/analytics/analytics_dashboards/data_sources/user_ai_usage_data.js index 37ed25f904fe12..2632104251842f 100644 --- a/ee/app/assets/javascripts/analytics/analytics_dashboards/data_sources/user_ai_usage_data.js +++ b/ee/app/assets/javascripts/analytics/analytics_dashboards/data_sources/user_ai_usage_data.js @@ -8,7 +8,7 @@ import { defaultClient } from '../graphql/client'; export default async function fetch({ namespace: fullPath, query: { dateRange = DATE_RANGE_OPTION_LAST_30_DAYS }, - queryOverrides: { pagination: { first, startCursor: before, endCursor: after } = {} } = {}, + queryOverrides: { pagination = { first: 20 } } = {}, }) { const startDate = getStartDate(dateRange); @@ -18,9 +18,10 @@ export default async function fetch({ fullPath, startDate, endDate: startOfTomorrow, - first, - before, - after, + first: pagination.first, + last: pagination.last, + before: pagination.startCursor, + after: pagination.endCursor, }, }); diff --git a/ee/app/assets/javascripts/analytics/analytics_dashboards/graphql/queries/get_user_ai_user_metrics.query.graphql b/ee/app/assets/javascripts/analytics/analytics_dashboards/graphql/queries/get_user_ai_user_metrics.query.graphql index ecbe1dbe3aade5..7ac4f3621686ff 100644 --- a/ee/app/assets/javascripts/analytics/analytics_dashboards/graphql/queries/get_user_ai_user_metrics.query.graphql +++ b/ee/app/assets/javascripts/analytics/analytics_dashboards/graphql/queries/get_user_ai_user_metrics.query.graphql @@ -8,7 +8,7 @@ query getUserAiUserMetrics( $before: String $after: String $last: Int - $first: Int = 20 + $first: Int ) { group(fullPath: $fullPath) { id -- GitLab