From c1952b8160d39a9e9b9bcbdd2bb202d636169abc Mon Sep 17 00:00:00 2001
From: Ezekiel Kigbo <ekigbo@gitlab.com>
Date: Tue, 21 Jan 2025 12:42:25 +1100
Subject: [PATCH 1/6] Adds DORA metrics dashboard

Adds an experimental DORA metrics dashboard
that contains each metric over time and aggregated
over the specified time period.
---
 .../components/analytics_dashboard_panel.vue  |  6 +-
 ee/app/models/product_analytics/dashboard.rb  | 13 +++
 .../models/product_analytics/visualization.rb | 25 +++++-
 .../analytics/dora_metrics/dashboard.yaml     | 82 +++++++++++++++++++
 .../visualizations/change_failure_rate.yaml}  |  3 +-
 .../change_failure_rate_over_time.yaml        | 16 ++++
 .../deployment_frequency_average.yaml}        |  3 +-
 .../deployment_frequency_over_time.yaml       | 16 ++++
 .../lead_time_for_changes_median.yaml}        |  3 +-
 .../lead_time_for_changes_over_time.yaml      | 16 ++++
 .../time_to_restore_service_median.yaml}      |  3 +-
 .../time_to_restore_service_over_time.yaml    | 16 ++++
 .../groups/analytics/dashboards_spec.rb       |  8 +-
 13 files changed, 196 insertions(+), 14 deletions(-)
 create mode 100644 ee/lib/gitlab/analytics/dora_metrics/dashboard.yaml
 rename ee/lib/gitlab/analytics/{value_stream_dashboard/visualizations/change_failure_rate_over_time.yaml => dora_metrics/visualizations/change_failure_rate.yaml} (79%)
 create mode 100644 ee/lib/gitlab/analytics/dora_metrics/visualizations/change_failure_rate_over_time.yaml
 rename ee/lib/gitlab/analytics/{value_stream_dashboard/visualizations/deployment_frequency_over_time.yaml => dora_metrics/visualizations/deployment_frequency_average.yaml} (79%)
 create mode 100644 ee/lib/gitlab/analytics/dora_metrics/visualizations/deployment_frequency_over_time.yaml
 rename ee/lib/gitlab/analytics/{value_stream_dashboard/visualizations/lead_time_for_changes_over_time.yaml => dora_metrics/visualizations/lead_time_for_changes_median.yaml} (79%)
 create mode 100644 ee/lib/gitlab/analytics/dora_metrics/visualizations/lead_time_for_changes_over_time.yaml
 rename ee/lib/gitlab/analytics/{value_stream_dashboard/visualizations/time_to_restore_service_over_time.yaml => dora_metrics/visualizations/time_to_restore_service_median.yaml} (79%)
 create mode 100644 ee/lib/gitlab/analytics/dora_metrics/visualizations/time_to_restore_service_over_time.yaml

diff --git a/ee/app/assets/javascripts/analytics/analytics_dashboards/components/analytics_dashboard_panel.vue b/ee/app/assets/javascripts/analytics/analytics_dashboards/components/analytics_dashboard_panel.vue
index 9ac12c18ceb96a..dbe8ec4a1a6592 100644
--- a/ee/app/assets/javascripts/analytics/analytics_dashboards/components/analytics_dashboard_panel.vue
+++ b/ee/app/assets/javascripts/analytics/analytics_dashboards/components/analytics_dashboard_panel.vue
@@ -1,4 +1,5 @@
 <script>
+import { merge } from 'lodash';
 import { GlButton, GlLink, GlSprintf } from '@gitlab/ui';
 import isString from 'lodash/isString';
 import * as Sentry from '~/sentry/sentry_browser_wrapper';
@@ -157,10 +158,7 @@ export default {
       });
     },
     visualizationOptions() {
-      return {
-        ...this.visualization.options,
-        ...this.visualizationOptionOverrides,
-      };
+      return merge(this.visualization.options, this.visualizationOptionOverrides);
     },
   },
   watch: {
diff --git a/ee/app/models/product_analytics/dashboard.rb b/ee/app/models/product_analytics/dashboard.rb
index d1ed7e239a9436..b92e7d20878914 100644
--- a/ee/app/models/product_analytics/dashboard.rb
+++ b/ee/app/models/product_analytics/dashboard.rb
@@ -155,6 +155,18 @@ def self.contributions_dashboard(container, config_project)
       )
     end
 
+    def self.dora_metrics(container, config_project, _user)
+      config = load_yaml_dashboard_config('dashboard', 'ee/lib/gitlab/analytics/dora_metrics')
+
+      new(
+        slug: 'dora_metrics',
+        container: container,
+        config: config,
+        config_project: config_project,
+        user_defined: false
+      )
+    end
+
     def self.builtin_dashboards(container, config_project, user)
       builtin = []
 
@@ -162,6 +174,7 @@ def self.builtin_dashboards(container, config_project, user)
       builtin << value_stream_dashboard(container, config_project)
       builtin << ai_impact_dashboard(container, config_project, user)
       builtin << contributions_dashboard(container, config_project)
+      builtin << dora_metrics(container, config_project, user)
 
       builtin.flatten
     end
diff --git a/ee/app/models/product_analytics/visualization.rb b/ee/app/models/product_analytics/visualization.rb
index 5c18c2b161442d..5745b33abfc75e 100644
--- a/ee/app/models/product_analytics/visualization.rb
+++ b/ee/app/models/product_analytics/visualization.rb
@@ -27,6 +27,18 @@ class Visualization
       total_unique_users
     ].freeze
 
+    DORA_METRICS_VISUALIZATIONS_PATH = 'ee/lib/gitlab/analytics/dora_metrics/visualizations'
+    DORA_METRICS_VISUALIZATIONS = %w[
+      deployment_frequency_average
+      time_to_restore_service_median
+      lead_time_for_changes_median
+      change_failure_rate
+      deployment_frequency_over_time
+      change_failure_rate_over_time
+      time_to_restore_service_over_time
+      lead_time_for_changes_over_time
+    ].freeze
+
     VALUE_STREAM_DASHBOARD_PATH = 'ee/lib/gitlab/analytics/value_stream_dashboard/visualizations'
     VALUE_STREAM_DASHBOARD_VISUALIZATIONS = %w[
       dora_chart
@@ -36,10 +48,6 @@ class Visualization
       usage_overview
       dora_performers_score
       dora_projects_comparison
-      deployment_frequency_over_time
-      lead_time_for_changes_over_time
-      time_to_restore_service_over_time
-      change_failure_rate_over_time
     ].freeze
 
     AI_IMPACT_DASHBOARD_PATH = 'ee/lib/gitlab/analytics/ai_impact_dashboard/visualizations'
@@ -157,6 +165,12 @@ def self.ai_impact_dashboard_visualizations(is_project = false)
       unsafe_load_builtin_visualizations(AI_IMPACT_DASHBOARD_VISUALIZATIONS, AI_IMPACT_DASHBOARD_PATH, is_project)
     end
 
+    def self.dora_metrics_visualizations(is_project = false)
+      # TODO: include license check
+
+      load_visualizations(DORA_METRICS_VISUALIZATIONS, DORA_METRICS_VISUALIZATIONS_PATH, is_project)
+    end
+
     def self.builtin_visualizations(container, user)
       is_project = container.is_a?(Project)
 
@@ -167,6 +181,7 @@ def self.builtin_visualizations(container, user)
       end
 
       visualizations << value_stream_dashboard_visualizations(is_project) if container.vsd_dashboard_editor_enabled?
+      visualizations << dora_metrics_visualizations(is_project)
 
       if container.ai_impact_dashboard_available_for?(user)
         visualizations << ai_impact_dashboard_visualizations(is_project)
@@ -185,6 +200,8 @@ def get_path_for_visualization(data)
           AI_IMPACT_DASHBOARD_PATH
         elsif CONTRIBUTIONS_DASHBOARD_VISUALIZATIONS.include?(data)
           CONTRIBUTIONS_DASHBOARD_PATH
+        elsif DORA_METRICS_VISUALIZATIONS.include?(data)
+          DORA_METRICS_VISUALIZATIONS_PATH
         else
           PRODUCT_ANALYTICS_PATH
         end
diff --git a/ee/lib/gitlab/analytics/dora_metrics/dashboard.yaml b/ee/lib/gitlab/analytics/dora_metrics/dashboard.yaml
new file mode 100644
index 00000000000000..21ec63c7e2dfc8
--- /dev/null
+++ b/ee/lib/gitlab/analytics/dora_metrics/dashboard.yaml
@@ -0,0 +1,82 @@
+
+title: DORA Metrics
+description: DORA metrics stats and trends
+status: experiment
+filters:
+  dateRange:
+    enabled: true
+    options:
+      - 7d
+      - 30d
+      - 90d
+      - 180d
+      - custom
+    numberOfDaysLimit: 180
+panels:
+  - title: Deployment frequency average
+    gridAttributes:
+      width: 3
+      height: 1
+      yPos: 0
+      xPos: 0
+    queryOverrides: {}
+    visualization: deployment_frequency_average
+  - title: Change failure rate
+    gridAttributes:
+      width: 3
+      height: 1
+      yPos: 0
+      xPos: 3
+    queryOverrides: {}
+    visualization: change_failure_rate
+  - title: Time to restore service median
+    gridAttributes:
+      width: 3
+      height: 1
+      yPos: 0
+      xPos: 6
+    queryOverrides: {}
+    visualization: time_to_restore_service_median
+  - title: Lead time for changes median
+    gridAttributes:
+      width: 3
+      height: 1
+      yPos: 0
+      xPos: 9
+    queryOverrides: {}
+    visualization: lead_time_for_changes_median
+  - title: Deployment frequency over time
+    gridAttributes:
+      width: 6
+      height: 3
+      yPos: 1
+      xPos: 0
+    queryOverrides: {}
+    visualization: deployment_frequency_over_time
+  - title: Change failure rate over time
+    gridAttributes:
+      width: 6
+      height: 3
+      yPos: 1
+      xPos: 6
+    queryOverrides: {}
+    options: {}
+    visualization: change_failure_rate_over_time
+  - title: Time to restore service over time
+    gridAttributes:
+      width: 6
+      height: 3
+      yPos: 4
+      xPos: 0
+    queryOverrides: {}
+    options: {}
+    visualization: time_to_restore_service_over_time
+  - title: Lead time for changes over time
+    gridAttributes:
+      width: 6
+      height: 3
+      yPos: 4
+      xPos: 6
+    queryOverrides: {}
+    options: {}
+    visualization: lead_time_for_changes_over_time
diff --git a/ee/lib/gitlab/analytics/value_stream_dashboard/visualizations/change_failure_rate_over_time.yaml b/ee/lib/gitlab/analytics/dora_metrics/visualizations/change_failure_rate.yaml
similarity index 79%
rename from ee/lib/gitlab/analytics/value_stream_dashboard/visualizations/change_failure_rate_over_time.yaml
rename to ee/lib/gitlab/analytics/dora_metrics/visualizations/change_failure_rate.yaml
index b3c1149ac53352..6c489b4e463404 100644
--- a/ee/lib/gitlab/analytics/value_stream_dashboard/visualizations/change_failure_rate_over_time.yaml
+++ b/ee/lib/gitlab/analytics/dora_metrics/visualizations/change_failure_rate.yaml
@@ -5,7 +5,8 @@ data:
   type: dora_metrics
   query:
     metric: change_failure_rate
-    date_range: last_180_days
+    dateRange: 90d
+    interval: ALL
 options:
   unit: percent
   decimalPlaces: 1
diff --git a/ee/lib/gitlab/analytics/dora_metrics/visualizations/change_failure_rate_over_time.yaml b/ee/lib/gitlab/analytics/dora_metrics/visualizations/change_failure_rate_over_time.yaml
new file mode 100644
index 00000000000000..c183033d9d1b9f
--- /dev/null
+++ b/ee/lib/gitlab/analytics/dora_metrics/visualizations/change_failure_rate_over_time.yaml
@@ -0,0 +1,16 @@
+---
+version: 1
+type: LineChart
+data:
+  type: dora_metrics
+  query:
+    metric: change_failure_rate
+    dateRange: 90d
+    interval: DAILY
+options:
+  xAxis:
+    name: Date
+    type: category
+  yAxis:
+    name: "Percentage of failed deployments"
+    type: value
diff --git a/ee/lib/gitlab/analytics/value_stream_dashboard/visualizations/deployment_frequency_over_time.yaml b/ee/lib/gitlab/analytics/dora_metrics/visualizations/deployment_frequency_average.yaml
similarity index 79%
rename from ee/lib/gitlab/analytics/value_stream_dashboard/visualizations/deployment_frequency_over_time.yaml
rename to ee/lib/gitlab/analytics/dora_metrics/visualizations/deployment_frequency_average.yaml
index a93e3509b102f3..2c43dd3a241c1f 100644
--- a/ee/lib/gitlab/analytics/value_stream_dashboard/visualizations/deployment_frequency_over_time.yaml
+++ b/ee/lib/gitlab/analytics/dora_metrics/visualizations/deployment_frequency_average.yaml
@@ -5,7 +5,8 @@ data:
   type: dora_metrics
   query:
     metric: deployment_frequency
-    date_range: last_180_days
+    dateRange: 90d
+    interval: ALL
 options:
   unit: per_day
   decimalPlaces: 1
diff --git a/ee/lib/gitlab/analytics/dora_metrics/visualizations/deployment_frequency_over_time.yaml b/ee/lib/gitlab/analytics/dora_metrics/visualizations/deployment_frequency_over_time.yaml
new file mode 100644
index 00000000000000..b236b0d36e61fb
--- /dev/null
+++ b/ee/lib/gitlab/analytics/dora_metrics/visualizations/deployment_frequency_over_time.yaml
@@ -0,0 +1,16 @@
+---
+version: 1
+type: LineChart
+data:
+  type: dora_metrics
+  query:
+    metric: deployment_frequency
+    dateRange: 90d
+    interval: DAILY
+options:
+  xAxis:
+    name: Date
+    type: category
+  yAxis:
+    name: "Number of deployments"
+    type: value
diff --git a/ee/lib/gitlab/analytics/value_stream_dashboard/visualizations/lead_time_for_changes_over_time.yaml b/ee/lib/gitlab/analytics/dora_metrics/visualizations/lead_time_for_changes_median.yaml
similarity index 79%
rename from ee/lib/gitlab/analytics/value_stream_dashboard/visualizations/lead_time_for_changes_over_time.yaml
rename to ee/lib/gitlab/analytics/dora_metrics/visualizations/lead_time_for_changes_median.yaml
index 3c54e3a2301282..f73d6de4d118b1 100644
--- a/ee/lib/gitlab/analytics/value_stream_dashboard/visualizations/lead_time_for_changes_over_time.yaml
+++ b/ee/lib/gitlab/analytics/dora_metrics/visualizations/lead_time_for_changes_median.yaml
@@ -5,7 +5,8 @@ data:
   type: dora_metrics
   query:
     metric: lead_time_for_changes
-    date_range: last_180_days
+    dateRange: 90d
+    interval: ALL
 options:
   unit: days
   decimalPlaces: 1
diff --git a/ee/lib/gitlab/analytics/dora_metrics/visualizations/lead_time_for_changes_over_time.yaml b/ee/lib/gitlab/analytics/dora_metrics/visualizations/lead_time_for_changes_over_time.yaml
new file mode 100644
index 00000000000000..612aef0e36713a
--- /dev/null
+++ b/ee/lib/gitlab/analytics/dora_metrics/visualizations/lead_time_for_changes_over_time.yaml
@@ -0,0 +1,16 @@
+---
+version: 1
+type: LineChart
+data:
+  type: dora_metrics
+  query:
+    metric: lead_time_for_changes
+    dateRange: 90d
+    interval: DAILY
+options:
+  xAxis:
+    name: Date
+    type: time
+  yAxis:
+    name: "Days from merge to deploy"
+    type: value
diff --git a/ee/lib/gitlab/analytics/value_stream_dashboard/visualizations/time_to_restore_service_over_time.yaml b/ee/lib/gitlab/analytics/dora_metrics/visualizations/time_to_restore_service_median.yaml
similarity index 79%
rename from ee/lib/gitlab/analytics/value_stream_dashboard/visualizations/time_to_restore_service_over_time.yaml
rename to ee/lib/gitlab/analytics/dora_metrics/visualizations/time_to_restore_service_median.yaml
index 5a0c2eb1f46662..f41b8d0037c6ca 100644
--- a/ee/lib/gitlab/analytics/value_stream_dashboard/visualizations/time_to_restore_service_over_time.yaml
+++ b/ee/lib/gitlab/analytics/dora_metrics/visualizations/time_to_restore_service_median.yaml
@@ -5,7 +5,8 @@ data:
   type: dora_metrics
   query:
     metric: time_to_restore_service
-    date_range: last_180_days
+    dateRange: 90d
+    interval: ALL
 options:
   unit: days
   decimalPlaces: 1
diff --git a/ee/lib/gitlab/analytics/dora_metrics/visualizations/time_to_restore_service_over_time.yaml b/ee/lib/gitlab/analytics/dora_metrics/visualizations/time_to_restore_service_over_time.yaml
new file mode 100644
index 00000000000000..12e2f4cd17e0f8
--- /dev/null
+++ b/ee/lib/gitlab/analytics/dora_metrics/visualizations/time_to_restore_service_over_time.yaml
@@ -0,0 +1,16 @@
+---
+version: 1
+type: LineChart
+data:
+  type: dora_metrics
+  query:
+    metric: time_to_restore_service
+    dateRange: 90d
+    interval: DAILY
+options:
+  xAxis:
+    name: Date
+    type: time
+  yAxis:
+    name: "Days for an open incident"
+    type: value
diff --git a/ee/spec/features/groups/analytics/dashboards_spec.rb b/ee/spec/features/groups/analytics/dashboards_spec.rb
index 4e95190cdc41eb..175fe760aa43f9 100644
--- a/ee/spec/features/groups/analytics/dashboards_spec.rb
+++ b/ee/spec/features/groups/analytics/dashboards_spec.rb
@@ -54,9 +54,10 @@
 
             vsd_dashboard = dashboard_items[0]
             contribution_dashboard = dashboard_items[1]
-            custom_dashboard = dashboard_items[2]
+            dora_metrics_dashboard = dashboard_items[2]
+            custom_dashboard = dashboard_items[3]
 
-            expect(dashboard_items.length).to eq(3)
+            expect(dashboard_items.length).to eq(4)
 
             expect(vsd_dashboard).to have_content _('Value Streams Dashboard')
             expect(vsd_dashboard).to have_selector(dashboard_by_gitlab_testid)
@@ -64,6 +65,9 @@
             expect(contribution_dashboard).to have_content _('Contributions Dashboard')
             expect(contribution_dashboard).to have_selector(dashboard_by_gitlab_testid)
 
+            expect(dora_metrics_dashboard).to have_content _('Dora Metrics')
+            expect(dora_metrics_dashboard).to have_selector(dashboard_by_gitlab_testid)
+
             expect(custom_dashboard).to have_content _('Custom VSD')
             expect(custom_dashboard).to have_content _('VSD from fixture')
             expect(custom_dashboard).not_to have_selector(dashboard_by_gitlab_testid)
-- 
GitLab


From 19f14d69e3f377f13c1c5f326ab898b3828d024f Mon Sep 17 00:00:00 2001
From: Ezekiel Kigbo <ekigbo@gitlab.com>
Date: Wed, 19 Feb 2025 16:33:54 +0900
Subject: [PATCH 2/6] Updates related tests

Updates the dashboard and visualization feature tests
and sets the default date range to 180d
---
 ee/app/models/product_analytics/dashboard.rb  |  4 +-
 .../models/product_analytics/visualization.rb |  4 +-
 .../analytics/dora_metrics/dashboard.yaml     |  1 +
 .../visualizations/change_failure_rate.yaml   |  2 +-
 .../change_failure_rate_over_time.yaml        |  2 +-
 .../deployment_frequency_average.yaml         |  2 +-
 .../deployment_frequency_over_time.yaml       |  2 +-
 .../lead_time_for_changes_median.yaml         |  2 +-
 .../lead_time_for_changes_over_time.yaml      |  2 +-
 .../time_to_restore_service_median.yaml       |  2 +-
 .../time_to_restore_service_over_time.yaml    |  2 +-
 .../groups/analytics/dashboards_spec.rb       |  2 +-
 .../product_analytics/dashboard_spec.rb       | 52 ++++++++++++++++---
 .../product_analytics/visualization_spec.rb   | 16 +++++-
 14 files changed, 73 insertions(+), 22 deletions(-)

diff --git a/ee/app/models/product_analytics/dashboard.rb b/ee/app/models/product_analytics/dashboard.rb
index b92e7d20878914..f2bbf953d93a26 100644
--- a/ee/app/models/product_analytics/dashboard.rb
+++ b/ee/app/models/product_analytics/dashboard.rb
@@ -155,7 +155,7 @@ def self.contributions_dashboard(container, config_project)
       )
     end
 
-    def self.dora_metrics(container, config_project, _user)
+    def self.dora_metrics_dashboard(container, config_project)
       config = load_yaml_dashboard_config('dashboard', 'ee/lib/gitlab/analytics/dora_metrics')
 
       new(
@@ -174,7 +174,7 @@ def self.builtin_dashboards(container, config_project, user)
       builtin << value_stream_dashboard(container, config_project)
       builtin << ai_impact_dashboard(container, config_project, user)
       builtin << contributions_dashboard(container, config_project)
-      builtin << dora_metrics(container, config_project, user)
+      builtin << dora_metrics_dashboard(container, config_project)
 
       builtin.flatten
     end
diff --git a/ee/app/models/product_analytics/visualization.rb b/ee/app/models/product_analytics/visualization.rb
index 5745b33abfc75e..d916245802a698 100644
--- a/ee/app/models/product_analytics/visualization.rb
+++ b/ee/app/models/product_analytics/visualization.rb
@@ -168,7 +168,7 @@ def self.ai_impact_dashboard_visualizations(is_project = false)
     def self.dora_metrics_visualizations(is_project = false)
       # TODO: include license check
 
-      load_visualizations(DORA_METRICS_VISUALIZATIONS, DORA_METRICS_VISUALIZATIONS_PATH, is_project)
+      unsafe_load_builtin_visualizations(DORA_METRICS_VISUALIZATIONS, DORA_METRICS_VISUALIZATIONS_PATH, is_project)
     end
 
     def self.builtin_visualizations(container, user)
@@ -181,7 +181,7 @@ def self.builtin_visualizations(container, user)
       end
 
       visualizations << value_stream_dashboard_visualizations(is_project) if container.vsd_dashboard_editor_enabled?
-      visualizations << dora_metrics_visualizations(is_project)
+      visualizations << dora_metrics_visualizations(is_project) if container.value_streams_dashboard_available?
 
       if container.ai_impact_dashboard_available_for?(user)
         visualizations << ai_impact_dashboard_visualizations(is_project)
diff --git a/ee/lib/gitlab/analytics/dora_metrics/dashboard.yaml b/ee/lib/gitlab/analytics/dora_metrics/dashboard.yaml
index 21ec63c7e2dfc8..bea7bca55adcf6 100644
--- a/ee/lib/gitlab/analytics/dora_metrics/dashboard.yaml
+++ b/ee/lib/gitlab/analytics/dora_metrics/dashboard.yaml
@@ -5,6 +5,7 @@ status: experiment
 filters:
   dateRange:
     enabled: true
+    defaultOption: 180d
     options:
       - 7d
       - 30d
diff --git a/ee/lib/gitlab/analytics/dora_metrics/visualizations/change_failure_rate.yaml b/ee/lib/gitlab/analytics/dora_metrics/visualizations/change_failure_rate.yaml
index 6c489b4e463404..d3f37d217a81b2 100644
--- a/ee/lib/gitlab/analytics/dora_metrics/visualizations/change_failure_rate.yaml
+++ b/ee/lib/gitlab/analytics/dora_metrics/visualizations/change_failure_rate.yaml
@@ -5,7 +5,7 @@ data:
   type: dora_metrics
   query:
     metric: change_failure_rate
-    dateRange: 90d
+    dateRange: 180d
     interval: ALL
 options:
   unit: percent
diff --git a/ee/lib/gitlab/analytics/dora_metrics/visualizations/change_failure_rate_over_time.yaml b/ee/lib/gitlab/analytics/dora_metrics/visualizations/change_failure_rate_over_time.yaml
index c183033d9d1b9f..d3bbf2e7d32e41 100644
--- a/ee/lib/gitlab/analytics/dora_metrics/visualizations/change_failure_rate_over_time.yaml
+++ b/ee/lib/gitlab/analytics/dora_metrics/visualizations/change_failure_rate_over_time.yaml
@@ -5,7 +5,7 @@ data:
   type: dora_metrics
   query:
     metric: change_failure_rate
-    dateRange: 90d
+    dateRange: 180d
     interval: DAILY
 options:
   xAxis:
diff --git a/ee/lib/gitlab/analytics/dora_metrics/visualizations/deployment_frequency_average.yaml b/ee/lib/gitlab/analytics/dora_metrics/visualizations/deployment_frequency_average.yaml
index 2c43dd3a241c1f..0fa610cdcbf50f 100644
--- a/ee/lib/gitlab/analytics/dora_metrics/visualizations/deployment_frequency_average.yaml
+++ b/ee/lib/gitlab/analytics/dora_metrics/visualizations/deployment_frequency_average.yaml
@@ -5,7 +5,7 @@ data:
   type: dora_metrics
   query:
     metric: deployment_frequency
-    dateRange: 90d
+    dateRange: 180d
     interval: ALL
 options:
   unit: per_day
diff --git a/ee/lib/gitlab/analytics/dora_metrics/visualizations/deployment_frequency_over_time.yaml b/ee/lib/gitlab/analytics/dora_metrics/visualizations/deployment_frequency_over_time.yaml
index b236b0d36e61fb..ef8453aa96e501 100644
--- a/ee/lib/gitlab/analytics/dora_metrics/visualizations/deployment_frequency_over_time.yaml
+++ b/ee/lib/gitlab/analytics/dora_metrics/visualizations/deployment_frequency_over_time.yaml
@@ -5,7 +5,7 @@ data:
   type: dora_metrics
   query:
     metric: deployment_frequency
-    dateRange: 90d
+    dateRange: 180d
     interval: DAILY
 options:
   xAxis:
diff --git a/ee/lib/gitlab/analytics/dora_metrics/visualizations/lead_time_for_changes_median.yaml b/ee/lib/gitlab/analytics/dora_metrics/visualizations/lead_time_for_changes_median.yaml
index f73d6de4d118b1..ea1df2e10c36cf 100644
--- a/ee/lib/gitlab/analytics/dora_metrics/visualizations/lead_time_for_changes_median.yaml
+++ b/ee/lib/gitlab/analytics/dora_metrics/visualizations/lead_time_for_changes_median.yaml
@@ -5,7 +5,7 @@ data:
   type: dora_metrics
   query:
     metric: lead_time_for_changes
-    dateRange: 90d
+    dateRange: 180d
     interval: ALL
 options:
   unit: days
diff --git a/ee/lib/gitlab/analytics/dora_metrics/visualizations/lead_time_for_changes_over_time.yaml b/ee/lib/gitlab/analytics/dora_metrics/visualizations/lead_time_for_changes_over_time.yaml
index 612aef0e36713a..f392d26031927d 100644
--- a/ee/lib/gitlab/analytics/dora_metrics/visualizations/lead_time_for_changes_over_time.yaml
+++ b/ee/lib/gitlab/analytics/dora_metrics/visualizations/lead_time_for_changes_over_time.yaml
@@ -5,7 +5,7 @@ data:
   type: dora_metrics
   query:
     metric: lead_time_for_changes
-    dateRange: 90d
+    dateRange: 180d
     interval: DAILY
 options:
   xAxis:
diff --git a/ee/lib/gitlab/analytics/dora_metrics/visualizations/time_to_restore_service_median.yaml b/ee/lib/gitlab/analytics/dora_metrics/visualizations/time_to_restore_service_median.yaml
index f41b8d0037c6ca..b7c53c4825494f 100644
--- a/ee/lib/gitlab/analytics/dora_metrics/visualizations/time_to_restore_service_median.yaml
+++ b/ee/lib/gitlab/analytics/dora_metrics/visualizations/time_to_restore_service_median.yaml
@@ -5,7 +5,7 @@ data:
   type: dora_metrics
   query:
     metric: time_to_restore_service
-    dateRange: 90d
+    dateRange: 180d
     interval: ALL
 options:
   unit: days
diff --git a/ee/lib/gitlab/analytics/dora_metrics/visualizations/time_to_restore_service_over_time.yaml b/ee/lib/gitlab/analytics/dora_metrics/visualizations/time_to_restore_service_over_time.yaml
index 12e2f4cd17e0f8..150ad0d81b57a4 100644
--- a/ee/lib/gitlab/analytics/dora_metrics/visualizations/time_to_restore_service_over_time.yaml
+++ b/ee/lib/gitlab/analytics/dora_metrics/visualizations/time_to_restore_service_over_time.yaml
@@ -5,7 +5,7 @@ data:
   type: dora_metrics
   query:
     metric: time_to_restore_service
-    dateRange: 90d
+    dateRange: 180d
     interval: DAILY
 options:
   xAxis:
diff --git a/ee/spec/features/groups/analytics/dashboards_spec.rb b/ee/spec/features/groups/analytics/dashboards_spec.rb
index 175fe760aa43f9..b86943e82e8808 100644
--- a/ee/spec/features/groups/analytics/dashboards_spec.rb
+++ b/ee/spec/features/groups/analytics/dashboards_spec.rb
@@ -65,7 +65,7 @@
             expect(contribution_dashboard).to have_content _('Contributions Dashboard')
             expect(contribution_dashboard).to have_selector(dashboard_by_gitlab_testid)
 
-            expect(dora_metrics_dashboard).to have_content _('Dora Metrics')
+            expect(dora_metrics_dashboard).to have_content _('DORA Metrics')
             expect(dora_metrics_dashboard).to have_selector(dashboard_by_gitlab_testid)
 
             expect(custom_dashboard).to have_content _('Custom VSD')
diff --git a/ee/spec/models/product_analytics/dashboard_spec.rb b/ee/spec/models/product_analytics/dashboard_spec.rb
index db035062bf49b8..dcaf653cf46bde 100644
--- a/ee/spec/models/product_analytics/dashboard_spec.rb
+++ b/ee/spec/models/product_analytics/dashboard_spec.rb
@@ -44,6 +44,19 @@
     end
   end
 
+  shared_examples 'returns the DORA Metrics dashboard' do
+    it 'returns the value streams dashboard' do
+      expect(dashboard).to be_a(described_class)
+      expect(dashboard.title).to eq('DORA Metrics')
+      expect(dashboard.slug).to eq('dora_metrics')
+      expect(dashboard.description).to eq('DORA metrics stats and trends')
+    end
+
+    it 'returns the correct panels' do
+      expect(dashboard.panels.size).to eq(8)
+    end
+  end
+
   describe '#errors' do
     let(:dashboard) do
       described_class.new(
@@ -98,7 +111,13 @@
 
       it 'returns a collection of builtin dashboards' do
         expect(subject.map(&:title)).to match_array(
-          ['Audience', 'Behavior', 'Value Streams Dashboard', 'AI impact analytics']
+          [
+            'Audience',
+            'Behavior',
+            'Value Streams Dashboard',
+            'DORA Metrics',
+            'AI impact analytics'
+          ]
         )
       end
 
@@ -109,7 +128,7 @@
 
         it 'returns custom and builtin dashboards' do
           expect(subject).to be_a(Array)
-          expect(subject.size).to eq(5)
+          expect(subject.size).to eq(6)
           expect(subject.last).to be_a(described_class)
           expect(subject.last.title).to eq('Dashboard Example 1')
           expect(subject.last.slug).to eq('dashboard_example_1')
@@ -144,7 +163,8 @@
 
         it 'excludes the dashboard from the list' do
           expected_dashboards =
-            ["Audience", "Behavior", "Value Streams Dashboard", "AI impact analytics", "Dashboard Example 1"]
+            ["Audience", "Behavior", "Value Streams Dashboard", "AI impact analytics",
+              "DORA Metrics", "Dashboard Example 1"]
 
           expect(subject.map(&:title)).to eq(expected_dashboards)
         end
@@ -156,7 +176,7 @@
         end
 
         it 'excludes product analytics dashboards' do
-          expect(subject.size).to eq(3)
+          expect(subject.size).to eq(4)
         end
       end
     end
@@ -167,8 +187,8 @@
       subject { described_class.for(container: resource_parent, user: user) }
 
       it 'returns a collection of builtin dashboards' do
-        expect(subject.map(&:title)).to match_array(['Value Streams Dashboard', 'AI impact analytics',
-          'Contributions Dashboard'])
+        expect(subject.map(&:title)).to match_array(['Value Streams Dashboard', 'DORA Metrics',
+          'AI impact analytics', 'Contributions Dashboard'])
       end
 
       context 'when configuration project is set' do
@@ -179,7 +199,8 @@
         it 'returns custom and builtin dashboards' do
           expect(subject).to be_a(Array)
           expect(subject.map(&:title)).to match_array(
-            ['Value Streams Dashboard', 'AI impact analytics', 'Dashboard Example 1', 'Contributions Dashboard']
+            ['Value Streams Dashboard', 'AI impact analytics', 'DORA Metrics',
+             'Dashboard Example 1', 'Contributions Dashboard']
           )
         end
       end
@@ -197,7 +218,8 @@
 
         it 'excludes the dashboard from the list' do
           expect(subject.map(&:title)).to match_array(
-            ['Value Streams Dashboard', 'AI impact analytics', 'Dashboard Example 1', 'Contributions Dashboard']
+            ['Value Streams Dashboard', 'AI impact analytics', 'DORA Metrics',
+              "Dashboard Example 1", 'Contributions Dashboard']
           )
         end
       end
@@ -296,6 +318,20 @@
     end
   end
 
+  describe '.dora_metrics_dashboard' do
+    context 'for groups' do
+      let(:dashboard) { described_class.dora_metrics_dashboard(group, config_project) }
+
+      it_behaves_like 'returns the DORA Metrics dashboard'
+    end
+
+    context 'for projects' do
+      let(:dashboard) { described_class.dora_metrics_dashboard(project, config_project) }
+
+      it_behaves_like 'returns the DORA Metrics dashboard'
+    end
+  end
+
   describe '.ai_impact_dashboard' do
     context 'for groups' do
       subject { described_class.ai_impact_dashboard(group, config_project, user) }
diff --git a/ee/spec/models/product_analytics/visualization_spec.rb b/ee/spec/models/product_analytics/visualization_spec.rb
index 004c07b511a1ea..2c8808540682c9 100644
--- a/ee/spec/models/product_analytics/visualization_spec.rb
+++ b/ee/spec/models/product_analytics/visualization_spec.rb
@@ -28,6 +28,10 @@
       vsd_dora_metrics_table
       vsd_lifecycle_metrics_table
       vsd_security_metrics_table
+      change_failure_rate
+      deployment_frequency_average
+      lead_time_for_changes_median
+      time_to_restore_service_median
     ]
   end
 
@@ -290,13 +294,23 @@
   describe '.value_stream_dashboard_visualizations' do
     subject { described_class.value_stream_dashboard_visualizations }
 
-    num_builtin_visualizations = 11
+    num_builtin_visualizations = 7
 
     it 'returns the value stream dashboard builtin visualizations' do
       expect(subject.count).to eq(num_builtin_visualizations)
     end
   end
 
+  describe '.dora_metrics_visualizations' do
+    subject { described_class.dora_metrics_visualizations }
+
+    num_builtin_visualizations = 8
+
+    it 'returns the dora metrics dashboard builtin visualizations' do
+      expect(subject.count).to eq(num_builtin_visualizations)
+    end
+  end
+
   context 'when dashboard is a built-in dashboard' do
     let(:dashboard) { dashboards.find { |d| d.title == 'Audience' } }
 
-- 
GitLab


From aee1fa01abc99ce1aa46328359514c70483bf8ac Mon Sep 17 00:00:00 2001
From: Ezekiel Kigbo <ekigbo@gitlab.com>
Date: Thu, 20 Feb 2025 21:53:12 +0900
Subject: [PATCH 3/6] Added `dora_metrics_dashboard.yml` feature flag

Updates the dashboard list and related tests
to ensure the dashboard is not available when
the feature flag is off.
---
 .../models/concerns/product_analytics_helpers.rb   |  4 ++++
 ee/app/models/product_analytics/dashboard.rb       |  2 +-
 ee/app/models/product_analytics/visualization.rb   |  4 +---
 .../feature_flags/wip/dora_metrics_dashboard.yml   |  9 +++++++++
 .../features/groups/analytics/dashboards_spec.rb   | 14 ++++++++++++++
 ee/spec/models/product_analytics/dashboard_spec.rb |  2 +-
 .../helpers/value_streams_dashboard_helpers.rb     |  4 ++++
 7 files changed, 34 insertions(+), 5 deletions(-)
 create mode 100644 ee/config/feature_flags/wip/dora_metrics_dashboard.yml

diff --git a/ee/app/models/concerns/product_analytics_helpers.rb b/ee/app/models/concerns/product_analytics_helpers.rb
index cc1b51ca7e6ffd..93ea967c1943e6 100644
--- a/ee/app/models/concerns/product_analytics_helpers.rb
+++ b/ee/app/models/concerns/product_analytics_helpers.rb
@@ -52,6 +52,10 @@ def ai_impact_dashboard_available_for?(user)
     Ability.allowed?(user, :read_enterprise_ai_analytics, self)
   end
 
+  def dora_metrics_dashboard_enabled?
+    Feature.enabled?(:dora_metrics_dashboard, self)
+  end
+
   def contributions_dashboard_available?
     is_a?(Group) && Feature.enabled?(:contributions_analytics_dashboard, self)
   end
diff --git a/ee/app/models/product_analytics/dashboard.rb b/ee/app/models/product_analytics/dashboard.rb
index f2bbf953d93a26..29230fc0eac2b7 100644
--- a/ee/app/models/product_analytics/dashboard.rb
+++ b/ee/app/models/product_analytics/dashboard.rb
@@ -174,7 +174,7 @@ def self.builtin_dashboards(container, config_project, user)
       builtin << value_stream_dashboard(container, config_project)
       builtin << ai_impact_dashboard(container, config_project, user)
       builtin << contributions_dashboard(container, config_project)
-      builtin << dora_metrics_dashboard(container, config_project)
+      builtin << dora_metrics_dashboard(container, config_project) if container.dora_metrics_dashboard_enabled?
 
       builtin.flatten
     end
diff --git a/ee/app/models/product_analytics/visualization.rb b/ee/app/models/product_analytics/visualization.rb
index d916245802a698..a6569943a564a2 100644
--- a/ee/app/models/product_analytics/visualization.rb
+++ b/ee/app/models/product_analytics/visualization.rb
@@ -166,8 +166,6 @@ def self.ai_impact_dashboard_visualizations(is_project = false)
     end
 
     def self.dora_metrics_visualizations(is_project = false)
-      # TODO: include license check
-
       unsafe_load_builtin_visualizations(DORA_METRICS_VISUALIZATIONS, DORA_METRICS_VISUALIZATIONS_PATH, is_project)
     end
 
@@ -181,7 +179,7 @@ def self.builtin_visualizations(container, user)
       end
 
       visualizations << value_stream_dashboard_visualizations(is_project) if container.vsd_dashboard_editor_enabled?
-      visualizations << dora_metrics_visualizations(is_project) if container.value_streams_dashboard_available?
+      visualizations << dora_metrics_visualizations(is_project) if container.vsd_dashboard_editor_enabled? && container.dora_metrics_dashboard_enabled?
 
       if container.ai_impact_dashboard_available_for?(user)
         visualizations << ai_impact_dashboard_visualizations(is_project)
diff --git a/ee/config/feature_flags/wip/dora_metrics_dashboard.yml b/ee/config/feature_flags/wip/dora_metrics_dashboard.yml
new file mode 100644
index 00000000000000..084d60b5f21fd3
--- /dev/null
+++ b/ee/config/feature_flags/wip/dora_metrics_dashboard.yml
@@ -0,0 +1,9 @@
+---
+name: dora_metrics_dashboard
+feature_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/514864
+introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/178515
+rollout_issue_url:
+milestone: '17.10'
+group: group::optimize
+type: wip
+default_enabled: false
diff --git a/ee/spec/features/groups/analytics/dashboards_spec.rb b/ee/spec/features/groups/analytics/dashboards_spec.rb
index b86943e82e8808..d62579156541c6 100644
--- a/ee/spec/features/groups/analytics/dashboards_spec.rb
+++ b/ee/spec/features/groups/analytics/dashboards_spec.rb
@@ -75,6 +75,20 @@
         end
 
         it_behaves_like 'has value streams dashboard link'
+
+        context 'with dora_metrics_dashboard disabled' do
+          before do
+            stub_feature_flags(dora_metrics_dashboard: false)
+
+            visit_group_analytics_dashboards_list(group)
+          end
+
+          it 'does not render DORA metrics dashboard link' do
+            dashboard_items_arr = page.all(dashboard_list_item_title).map(&:text)
+
+            expect(dashboard_items_arr).not_to include('DORA Metrics')
+          end
+        end
       end
 
       context 'with default configuration' do
diff --git a/ee/spec/models/product_analytics/dashboard_spec.rb b/ee/spec/models/product_analytics/dashboard_spec.rb
index dcaf653cf46bde..a47d317df963de 100644
--- a/ee/spec/models/product_analytics/dashboard_spec.rb
+++ b/ee/spec/models/product_analytics/dashboard_spec.rb
@@ -200,7 +200,7 @@
           expect(subject).to be_a(Array)
           expect(subject.map(&:title)).to match_array(
             ['Value Streams Dashboard', 'AI impact analytics', 'DORA Metrics',
-             'Dashboard Example 1', 'Contributions Dashboard']
+              'Dashboard Example 1', 'Contributions Dashboard']
           )
         end
       end
diff --git a/spec/support/helpers/value_streams_dashboard_helpers.rb b/spec/support/helpers/value_streams_dashboard_helpers.rb
index 9a4336c0219696..23e904e1725804 100644
--- a/spec/support/helpers/value_streams_dashboard_helpers.rb
+++ b/spec/support/helpers/value_streams_dashboard_helpers.rb
@@ -31,6 +31,10 @@ def dashboard_list_item_testid
     "[data-testid='dashboard-list-item']"
   end
 
+  def dashboard_list_item_title
+    "[data-testid='dashboard-router-link']"
+  end
+
   def create_custom_yaml_config(user, pointer_project, yaml_fixture_path)
     repository_file_path = '.gitlab/analytics/dashboards/value_streams/value_streams.yaml'
 
-- 
GitLab


From 87dc6ef1a71bc49a6347271ff2b0f26085d98d3d Mon Sep 17 00:00:00 2001
From: Ezekiel Kigbo <ekigbo@gitlab.com>
Date: Thu, 20 Feb 2025 22:42:54 +0900
Subject: [PATCH 4/6] Fix minor lint

---
 ee/app/models/product_analytics/visualization.rb             | 5 ++++-
 .../product_analytics/product_analytics_helpers_spec.rb      | 5 ++++-
 2 files changed, 8 insertions(+), 2 deletions(-)

diff --git a/ee/app/models/product_analytics/visualization.rb b/ee/app/models/product_analytics/visualization.rb
index a6569943a564a2..790b8f4891ec33 100644
--- a/ee/app/models/product_analytics/visualization.rb
+++ b/ee/app/models/product_analytics/visualization.rb
@@ -179,7 +179,10 @@ def self.builtin_visualizations(container, user)
       end
 
       visualizations << value_stream_dashboard_visualizations(is_project) if container.vsd_dashboard_editor_enabled?
-      visualizations << dora_metrics_visualizations(is_project) if container.vsd_dashboard_editor_enabled? && container.dora_metrics_dashboard_enabled?
+
+      if container.vsd_dashboard_editor_enabled? && container.dora_metrics_dashboard_enabled?
+        visualizations << dora_metrics_visualizations(is_project)
+      end
 
       if container.ai_impact_dashboard_available_for?(user)
         visualizations << ai_impact_dashboard_visualizations(is_project)
diff --git a/ee/spec/models/concerns/product_analytics/product_analytics_helpers_spec.rb b/ee/spec/models/concerns/product_analytics/product_analytics_helpers_spec.rb
index 60faf32015d98d..3d7b74b8c22b93 100644
--- a/ee/spec/models/concerns/product_analytics/product_analytics_helpers_spec.rb
+++ b/ee/spec/models/concerns/product_analytics/product_analytics_helpers_spec.rb
@@ -98,11 +98,13 @@
   describe '#product_analytics_dashboards' do
     it 'returns nothing if product analytics disabled' do
       stub_licensed_features(product_analytics: false)
+      stub_feature_flags(dora_metrics_dashboard: false)
       expect(project.product_analytics_dashboards(user)).to be_empty
     end
 
     it 'returns nothing if feature flag is disabled' do
       stub_licensed_features(product_analytics: false)
+      stub_feature_flags(dora_metrics_dashboard: false)
       stub_feature_flags(product_analytics_features: false)
       expect(project.product_analytics_dashboards(user)).to be_empty
     end
@@ -164,11 +166,12 @@
 
       it 'includes built in dashboards' do
         expect(project.product_analytics_dashboards(user).map(&:title))
-          .to match_array(%w[Audience Behavior])
+          .to match_array(['Audience', 'Behavior', 'DORA Metrics'])
       end
 
       context 'when product analytics onboarding is incomplete' do
         before do
+          stub_feature_flags(dora_metrics_dashboard: false)
           project.project_setting.update!(product_analytics_instrumentation_key: nil)
         end
 
-- 
GitLab


From ae7893b5a222205bef5fb64a59a2dd94c24a5540 Mon Sep 17 00:00:00 2001
From: Ezekiel Kigbo <ekigbo@gitlab.com>
Date: Mon, 24 Feb 2025 13:12:42 +0900
Subject: [PATCH 5/6] Add DORA metrics license check

---
 .../concerns/product_analytics_helpers.rb     |  5 ++-
 ee/app/models/product_analytics/dashboard.rb  |  2 +-
 .../models/product_analytics/visualization.rb |  2 +-
 .../product_analytics_helpers_spec.rb         |  5 +--
 .../product_analytics/dashboard_spec.rb       | 27 +++++++++++--
 .../product_analytics/visualization_spec.rb   | 40 +++++++++++++++----
 6 files changed, 62 insertions(+), 19 deletions(-)

diff --git a/ee/app/models/concerns/product_analytics_helpers.rb b/ee/app/models/concerns/product_analytics_helpers.rb
index 93ea967c1943e6..7ed67391ebc08a 100644
--- a/ee/app/models/concerns/product_analytics_helpers.rb
+++ b/ee/app/models/concerns/product_analytics_helpers.rb
@@ -52,8 +52,9 @@ def ai_impact_dashboard_available_for?(user)
     Ability.allowed?(user, :read_enterprise_ai_analytics, self)
   end
 
-  def dora_metrics_dashboard_enabled?
-    Feature.enabled?(:dora_metrics_dashboard, self)
+  def dora_metrics_dashboard_enabled?(user)
+    Feature.enabled?(:dora_metrics_dashboard, self) &&
+      Ability.allowed?(user, :read_dora4_analytics, self)
   end
 
   def contributions_dashboard_available?
diff --git a/ee/app/models/product_analytics/dashboard.rb b/ee/app/models/product_analytics/dashboard.rb
index 29230fc0eac2b7..a7f8fc7864f8ca 100644
--- a/ee/app/models/product_analytics/dashboard.rb
+++ b/ee/app/models/product_analytics/dashboard.rb
@@ -174,7 +174,7 @@ def self.builtin_dashboards(container, config_project, user)
       builtin << value_stream_dashboard(container, config_project)
       builtin << ai_impact_dashboard(container, config_project, user)
       builtin << contributions_dashboard(container, config_project)
-      builtin << dora_metrics_dashboard(container, config_project) if container.dora_metrics_dashboard_enabled?
+      builtin << dora_metrics_dashboard(container, config_project) if container.dora_metrics_dashboard_enabled?(user)
 
       builtin.flatten
     end
diff --git a/ee/app/models/product_analytics/visualization.rb b/ee/app/models/product_analytics/visualization.rb
index 790b8f4891ec33..30741c952ec200 100644
--- a/ee/app/models/product_analytics/visualization.rb
+++ b/ee/app/models/product_analytics/visualization.rb
@@ -180,7 +180,7 @@ def self.builtin_visualizations(container, user)
 
       visualizations << value_stream_dashboard_visualizations(is_project) if container.vsd_dashboard_editor_enabled?
 
-      if container.vsd_dashboard_editor_enabled? && container.dora_metrics_dashboard_enabled?
+      if container.vsd_dashboard_editor_enabled? && container.dora_metrics_dashboard_enabled?(user)
         visualizations << dora_metrics_visualizations(is_project)
       end
 
diff --git a/ee/spec/models/concerns/product_analytics/product_analytics_helpers_spec.rb b/ee/spec/models/concerns/product_analytics/product_analytics_helpers_spec.rb
index 3d7b74b8c22b93..420a7d5327b184 100644
--- a/ee/spec/models/concerns/product_analytics/product_analytics_helpers_spec.rb
+++ b/ee/spec/models/concerns/product_analytics/product_analytics_helpers_spec.rb
@@ -154,7 +154,7 @@
     context 'without configuration project' do
       before do
         allow(::Gitlab::CurrentSettings).to receive(:product_analytics_enabled?).and_return true
-        stub_licensed_features(product_analytics: true)
+        stub_licensed_features(product_analytics: true, dora4_analytics: true)
         stub_feature_flags(product_analytics_features: true)
         project.project_setting.update!(product_analytics_instrumentation_key: "key")
         allow_next_instance_of(::ProductAnalytics::CubeDataQueryService) do |instance|
@@ -166,12 +166,11 @@
 
       it 'includes built in dashboards' do
         expect(project.product_analytics_dashboards(user).map(&:title))
-          .to match_array(['Audience', 'Behavior', 'DORA Metrics'])
+          .to match_array(['Audience', 'Behavior'])
       end
 
       context 'when product analytics onboarding is incomplete' do
         before do
-          stub_feature_flags(dora_metrics_dashboard: false)
           project.project_setting.update!(product_analytics_instrumentation_key: nil)
         end
 
diff --git a/ee/spec/models/product_analytics/dashboard_spec.rb b/ee/spec/models/product_analytics/dashboard_spec.rb
index a47d317df963de..afe65500c4f48b 100644
--- a/ee/spec/models/product_analytics/dashboard_spec.rb
+++ b/ee/spec/models/product_analytics/dashboard_spec.rb
@@ -20,16 +20,20 @@
   end
 
   before do
-    allow(Ability).to receive(:allowed?)
-                  .with(user, :read_enterprise_ai_analytics, anything)
-                  .and_return(true)
+      allow(Ability).to receive(:allowed?)
+                    .with(user, :read_enterprise_ai_analytics, anything)
+                    .and_return(true)
+      allow(Ability).to receive(:allowed?)
+                    .with(user, :read_dora4_analytics, anything)
+                    .and_return(true)
 
     allow(Gitlab::ClickHouse).to receive(:globally_enabled_for_analytics?).and_return(true)
 
     stub_licensed_features(
       product_analytics: true,
       project_level_analytics_dashboard: true,
-      group_level_analytics_dashboard: true
+      group_level_analytics_dashboard: true,
+      dora4_analytics: true
     )
   end
 
@@ -223,6 +227,21 @@
           )
         end
       end
+
+      context 'when DORA metrics are not licensed' do
+        before do
+          allow(Ability).to receive(:allowed?)
+                        .with(user, :read_enterprise_ai_analytics, anything)
+                        .and_return(true)
+          allow(Ability).to receive(:allowed?)
+                        .with(user, :read_dora4_analytics, anything)
+                        .and_return(false)
+        end
+
+        it 'excludes the dashboard from the list' do
+          expect(subject.map(&:title)).not_to include('DORA Metrics')
+        end
+      end
     end
 
     context 'when resource is not a project or a group' do
diff --git a/ee/spec/models/product_analytics/visualization_spec.rb b/ee/spec/models/product_analytics/visualization_spec.rb
index 2c8808540682c9..87ae0147867cb7 100644
--- a/ee/spec/models/product_analytics/visualization_spec.rb
+++ b/ee/spec/models/product_analytics/visualization_spec.rb
@@ -21,17 +21,9 @@
     %w[
       dora_chart
       usage_overview
-      change_failure_rate_over_time
-      deployment_frequency_over_time
-      lead_time_for_changes_over_time
-      time_to_restore_service_over_time
       vsd_dora_metrics_table
       vsd_lifecycle_metrics_table
       vsd_security_metrics_table
-      change_failure_rate
-      deployment_frequency_average
-      lead_time_for_changes_median
-      time_to_restore_service_median
     ]
   end
 
@@ -52,6 +44,19 @@
     ]
   end
 
+  let(:dora_metrics_available_visualizations) do
+    %w[
+      change_failure_rate_over_time
+      deployment_frequency_over_time
+      lead_time_for_changes_over_time
+      time_to_restore_service_over_time
+      change_failure_rate
+      deployment_frequency_average
+      lead_time_for_changes_median
+      time_to_restore_service_median
+    ]
+  end
+
   before do
     allow(Gitlab::CurrentSettings).to receive(:product_analytics_enabled?).and_return(true)
     stub_licensed_features(
@@ -65,6 +70,10 @@
         'results' => [{ "data" => [{ "TrackedEvents.count" => "1" }] }]
       }))
     end
+
+    allow(Ability).to receive(:allowed?)
+                  .with(user, :read_dora4_analytics, anything)
+                  .and_return(false)
   end
 
   shared_examples_for 'a valid visualization' do
@@ -86,6 +95,20 @@
     end
   end
 
+  shared_examples_for 'shows DORA Metrics visualizations when available' do
+    before do
+      allow(Ability).to receive(:allowed?)
+                    .with(user, :read_enterprise_ai_analytics, anything)
+                    .and_return(true)
+                    .with(user, :read_dora4_analytics, anything)
+                    .and_return(true)
+    end
+
+    it 'includes built in visualizations for DORA metrics dashboard' do
+      expect(subject.map(&:slug)).to include(*dora_metrics_available_visualizations)
+    end
+  end
+
   describe '#slug' do
     subject { described_class.for(container: project, user: user) }
 
@@ -220,6 +243,7 @@
       end
 
       it_behaves_like 'shows AI impact visualizations when available'
+      it_behaves_like 'shows DORA Metrics visualizations when available'
     end
   end
 
-- 
GitLab


From 61546ad43be7cbd90013b02cc3ca6505cf3fcda7 Mon Sep 17 00:00:00 2001
From: Ezekiel Kigbo <ekigbo@gitlab.com>
Date: Tue, 25 Feb 2025 00:33:20 +0900
Subject: [PATCH 6/6] Fix minor rubocop warnings

---
 .../product_analytics_helpers_spec.rb                |  2 +-
 ee/spec/models/product_analytics/dashboard_spec.rb   | 12 ++++++------
 2 files changed, 7 insertions(+), 7 deletions(-)

diff --git a/ee/spec/models/concerns/product_analytics/product_analytics_helpers_spec.rb b/ee/spec/models/concerns/product_analytics/product_analytics_helpers_spec.rb
index 420a7d5327b184..22ce75c84242a3 100644
--- a/ee/spec/models/concerns/product_analytics/product_analytics_helpers_spec.rb
+++ b/ee/spec/models/concerns/product_analytics/product_analytics_helpers_spec.rb
@@ -166,7 +166,7 @@
 
       it 'includes built in dashboards' do
         expect(project.product_analytics_dashboards(user).map(&:title))
-          .to match_array(['Audience', 'Behavior'])
+          .to match_array(%w[Audience Behavior])
       end
 
       context 'when product analytics onboarding is incomplete' do
diff --git a/ee/spec/models/product_analytics/dashboard_spec.rb b/ee/spec/models/product_analytics/dashboard_spec.rb
index afe65500c4f48b..21f9ad65819017 100644
--- a/ee/spec/models/product_analytics/dashboard_spec.rb
+++ b/ee/spec/models/product_analytics/dashboard_spec.rb
@@ -20,12 +20,12 @@
   end
 
   before do
-      allow(Ability).to receive(:allowed?)
-                    .with(user, :read_enterprise_ai_analytics, anything)
-                    .and_return(true)
-      allow(Ability).to receive(:allowed?)
-                    .with(user, :read_dora4_analytics, anything)
-                    .and_return(true)
+    allow(Ability).to receive(:allowed?)
+      .with(user, :read_enterprise_ai_analytics, anything)
+      .and_return(true)
+    allow(Ability).to receive(:allowed?)
+      .with(user, :read_dora4_analytics, anything)
+      .and_return(true)
 
     allow(Gitlab::ClickHouse).to receive(:globally_enabled_for_analytics?).and_return(true)
 
-- 
GitLab