From 158680cd1fa6cb28d158aefb086ad4947b3d637d Mon Sep 17 00:00:00 2001
From: Piotr Skorupa <pskorupa@gitlab.com>
Date: Thu, 29 Jun 2023 15:02:37 +0000
Subject: [PATCH] Add instrumentation classes for Jira usage metrics

This migrates two Jira usage metrics in Service Ping to instrumentation
classes framework.

They have been subsequently removed from `Gitlab::UsageData`, as they
are generated directly from instrumentation classes.
---
 ...180232_projects_jira_dvcs_cloud_active.yml |  3 ++
 ...80234_projects_jira_dvcs_server_active.yml |  3 ++
 ...6180236_projects_jira_issuelist_active.yml |  1 +
 ee/lib/ee/gitlab/usage_data.rb                | 18 -------
 ...jects_with_jira_issuelist_active_metric.rb | 28 +++++++++++
 ee/spec/lib/ee/gitlab/usage_data_spec.rb      |  2 -
 ..._with_jira_issuelist_active_metric_spec.rb | 26 ++++++++++
 ...jects_with_jira_dvcs_integration_metric.rb | 23 +++++++++
 lib/gitlab/usage_data.rb                      |  9 +---
 rubocop/rubocop-code_reuse.yml                |  1 +
 ..._with_jira_dvcs_integration_metric_spec.rb | 50 +++++++++++++++++++
 spec/support/helpers/usage_data_helpers.rb    |  2 -
 12 files changed, 136 insertions(+), 30 deletions(-)
 create mode 100644 ee/lib/gitlab/usage/metrics/instrumentations/count_projects_with_jira_issuelist_active_metric.rb
 create mode 100644 ee/spec/lib/gitlab/usage/metrics/instrumentations/count_projects_with_jira_issuelist_active_metric_spec.rb
 create mode 100644 lib/gitlab/usage/metrics/instrumentations/count_projects_with_jira_dvcs_integration_metric.rb
 create mode 100644 spec/lib/gitlab/usage/metrics/instrumentations/count_projects_with_jira_dvcs_integration_metric_spec.rb

diff --git a/config/metrics/counts_all/20210216180232_projects_jira_dvcs_cloud_active.yml b/config/metrics/counts_all/20210216180232_projects_jira_dvcs_cloud_active.yml
index 29edaa6ab3b41..5bf8e1d6e7820 100644
--- a/config/metrics/counts_all/20210216180232_projects_jira_dvcs_cloud_active.yml
+++ b/config/metrics/counts_all/20210216180232_projects_jira_dvcs_cloud_active.yml
@@ -9,6 +9,9 @@ value_type: number
 status: active
 time_frame: all
 data_source: database
+instrumentation_class: CountProjectsWithJiraDvcsIntegrationMetric
+options:
+  cloud: true
 distribution:
 - ce
 - ee
diff --git a/config/metrics/counts_all/20210216180234_projects_jira_dvcs_server_active.yml b/config/metrics/counts_all/20210216180234_projects_jira_dvcs_server_active.yml
index 9673956c7e142..bfb402c257ea9 100644
--- a/config/metrics/counts_all/20210216180234_projects_jira_dvcs_server_active.yml
+++ b/config/metrics/counts_all/20210216180234_projects_jira_dvcs_server_active.yml
@@ -9,6 +9,9 @@ value_type: number
 status: active
 time_frame: all
 data_source: database
+instrumentation_class: CountProjectsWithJiraDvcsIntegrationMetric
+options:
+  cloud: false
 distribution:
 - ce
 - ee
diff --git a/ee/config/metrics/counts_all/20210216180236_projects_jira_issuelist_active.yml b/ee/config/metrics/counts_all/20210216180236_projects_jira_issuelist_active.yml
index 7384333f8485f..bbee0595a21ec 100644
--- a/ee/config/metrics/counts_all/20210216180236_projects_jira_issuelist_active.yml
+++ b/ee/config/metrics/counts_all/20210216180236_projects_jira_issuelist_active.yml
@@ -9,6 +9,7 @@ value_type: number
 status: active
 time_frame: all
 data_source: database
+instrumentation_class: CountProjectsWithJiraIssuelistActiveMetric
 distribution:
 - ee
 tier:
diff --git a/ee/lib/ee/gitlab/usage_data.rb b/ee/lib/ee/gitlab/usage_data.rb
index aa72901a71955..86b00724094e5 100644
--- a/ee/lib/ee/gitlab/usage_data.rb
+++ b/ee/lib/ee/gitlab/usage_data.rb
@@ -139,13 +139,6 @@ def system_usage_data
           end
         end
 
-        override :jira_usage
-        def jira_usage
-          super.merge(
-            projects_jira_issuelist_active: projects_jira_issuelist_active
-          )
-        end
-
         # Omitted because no user, creator or author associated: `auto_devops_disabled`, `auto_devops_enabled`
         # Omitted because not in use anymore: `gcp_clusters`, `gcp_clusters_disabled`, `gcp_clusters_enabled`
         # rubocop:disable CodeReuse/ActiveRecord
@@ -335,17 +328,6 @@ def merge_requests_with_overridden_project_rules(time_period = nil)
           )
         end
 
-        def projects_jira_issuelist_active
-          # rubocop: disable UsageData/LargeTable:
-          min_id = minimum_id(::Integrations::JiraTrackerData.where(issues_enabled: true), :integration_id)
-          max_id = maximum_id(::Integrations::JiraTrackerData.where(issues_enabled: true), :integration_id)
-          # rubocop: enable UsageData/LargeTable:
-          # rubocop: disable UsageData/DistinctCountByLargeForeignKey
-          distinct_count(::Integrations::Jira.active.left_outer_joins(:jira_tracker_data).where(jira_tracker_data: { issues_enabled: true }), start: min_id, finish: max_id)
-          # rubocop: enable UsageData/DistinctCountByLargeForeignKey
-        end
-        # rubocop:enable CodeReuse/ActiveRecord
-
         # rubocop:disable CodeReuse/ActiveRecord
         def projects_with_sectional_code_owner_rules(time_period)
           distinct_count(
diff --git a/ee/lib/gitlab/usage/metrics/instrumentations/count_projects_with_jira_issuelist_active_metric.rb b/ee/lib/gitlab/usage/metrics/instrumentations/count_projects_with_jira_issuelist_active_metric.rb
new file mode 100644
index 0000000000000..df8f1dc1bec34
--- /dev/null
+++ b/ee/lib/gitlab/usage/metrics/instrumentations/count_projects_with_jira_issuelist_active_metric.rb
@@ -0,0 +1,28 @@
+# frozen_string_literal: true
+
+module Gitlab
+  module Usage
+    module Metrics
+      module Instrumentations
+        class CountProjectsWithJiraIssuelistActiveMetric < DatabaseMetric
+          operation :distinct_count
+
+          start do
+            ::Integrations::JiraTrackerData.where(issues_enabled: true).minimum(:integration_id)
+          end
+
+          finish do
+            ::Integrations::JiraTrackerData.where(issues_enabled: true).maximum(:integration_id)
+          end
+
+          relation do
+            ::Integrations::Jira
+              .active
+              .left_outer_joins(:jira_tracker_data)
+              .where(jira_tracker_data: { issues_enabled: true })
+          end
+        end
+      end
+    end
+  end
+end
diff --git a/ee/spec/lib/ee/gitlab/usage_data_spec.rb b/ee/spec/lib/ee/gitlab/usage_data_spec.rb
index bfd41f42c5e62..4d01f72628012 100644
--- a/ee/spec/lib/ee/gitlab/usage_data_spec.rb
+++ b/ee/spec/lib/ee/gitlab/usage_data_spec.rb
@@ -96,7 +96,6 @@
           ldap_users
           operations_dashboard_default_dashboard
           operations_dashboard_users_with_projects_added
-          projects_jira_issuelist_active
           projects_mirrored_with_pipelines_enabled
           projects_reporting_ci_cd_back_to_github
           status_page_issues
@@ -108,7 +107,6 @@
       expect(count_data[:status_page_projects]).to eq(1)
       expect(count_data[:status_page_issues]).to eq(1)
       expect(count_data[:issues_with_health_status]).to eq(2)
-      expect(count_data[:projects_jira_issuelist_active]).to eq(1)
       expect(count_data[:epic_issues]).to eq(2)
     end
 
diff --git a/ee/spec/lib/gitlab/usage/metrics/instrumentations/count_projects_with_jira_issuelist_active_metric_spec.rb b/ee/spec/lib/gitlab/usage/metrics/instrumentations/count_projects_with_jira_issuelist_active_metric_spec.rb
new file mode 100644
index 0000000000000..e0420c91012c9
--- /dev/null
+++ b/ee/spec/lib/gitlab/usage/metrics/instrumentations/count_projects_with_jira_issuelist_active_metric_spec.rb
@@ -0,0 +1,26 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Gitlab::Usage::Metrics::Instrumentations::CountProjectsWithJiraIssuelistActiveMetric,
+  feature_category: :integrations do
+  let_it_be(:project_1) { create(:project) }
+  let_it_be(:project_2) { create(:project) }
+  let_it_be(:jira_project_with_issuelist) do
+    create(:jira_integration, project: project_1, issues_enabled: true, project_key: 'foo')
+  end
+
+  let_it_be(:jira_project_without_issuelist) do
+    create(:jira_integration, project: project_2, issues_enabled: false, project_key: 'bar')
+  end
+
+  let(:expected_value) { 1 }
+  let(:expected_query) do
+    'SELECT COUNT(DISTINCT "integrations"."id") FROM "integrations" ' \
+      'LEFT OUTER JOIN "jira_tracker_data" ON "jira_tracker_data"."integration_id" = "integrations"."id" ' \
+      'WHERE "integrations"."type_new" = \'Integrations::Jira\' AND "integrations"."active" = TRUE ' \
+      'AND "jira_tracker_data"."issues_enabled" = TRUE'
+  end
+
+  it_behaves_like 'a correct instrumented metric value and query', { time_frame: 'all', data_source: 'database' }
+end
diff --git a/lib/gitlab/usage/metrics/instrumentations/count_projects_with_jira_dvcs_integration_metric.rb b/lib/gitlab/usage/metrics/instrumentations/count_projects_with_jira_dvcs_integration_metric.rb
new file mode 100644
index 0000000000000..25a45a259e287
--- /dev/null
+++ b/lib/gitlab/usage/metrics/instrumentations/count_projects_with_jira_dvcs_integration_metric.rb
@@ -0,0 +1,23 @@
+# frozen_string_literal: true
+
+module Gitlab
+  module Usage
+    module Metrics
+      module Instrumentations
+        class CountProjectsWithJiraDvcsIntegrationMetric < DatabaseMetric
+          operation :count
+
+          def initialize(metric_definition)
+            super
+
+            raise ArgumentError, "option 'cloud' must be a boolean" unless [true, false].include?(options[:cloud])
+          end
+
+          relation do |options|
+            ProjectFeatureUsage.with_jira_dvcs_integration_enabled(cloud: options[:cloud])
+          end
+        end
+      end
+    end
+  end
+end
diff --git a/lib/gitlab/usage_data.rb b/lib/gitlab/usage_data.rb
index 72168bce782f8..2da16cf872515 100644
--- a/lib/gitlab/usage_data.rb
+++ b/lib/gitlab/usage_data.rb
@@ -286,16 +286,9 @@ def integrations_usage
           response[:"instances_#{name}_active"] = count(Integration.active.where(instance: true, type: type))
           response[:"projects_inheriting_#{name}_active"] = count(Integration.active.where.not(project: nil).where.not(inherit_from_id: nil).where(type: type))
           response[:"groups_inheriting_#{name}_active"] = count(Integration.active.where.not(group: nil).where.not(inherit_from_id: nil).where(type: type))
-        end.merge(jira_usage, jira_import_usage)
+        end.merge(jira_import_usage)
         # rubocop: enable UsageData/LargeTable:
       end
-
-      def jira_usage
-        {
-          projects_jira_dvcs_cloud_active: count(ProjectFeatureUsage.with_jira_dvcs_integration_enabled),
-          projects_jira_dvcs_server_active: count(ProjectFeatureUsage.with_jira_dvcs_integration_enabled(cloud: false))
-        }
-      end
       # rubocop: enable CodeReuse/ActiveRecord
 
       def jira_import_usage
diff --git a/rubocop/rubocop-code_reuse.yml b/rubocop/rubocop-code_reuse.yml
index c1babff4b12ea..f96de5caf994d 100644
--- a/rubocop/rubocop-code_reuse.yml
+++ b/rubocop/rubocop-code_reuse.yml
@@ -42,3 +42,4 @@ CodeReuse/ActiveRecord:
     - ee/lib/tasks/**/*.rake
     - ee/lib/ee/gitlab/background_migration/**/*.rb
     - ee/lib/gitlab/llm/open_ai/response_modifiers/tanuki_bot.rb
+    - ee/lib/gitlab/usage/metrics/instrumentations/**/*.rb
diff --git a/spec/lib/gitlab/usage/metrics/instrumentations/count_projects_with_jira_dvcs_integration_metric_spec.rb b/spec/lib/gitlab/usage/metrics/instrumentations/count_projects_with_jira_dvcs_integration_metric_spec.rb
new file mode 100644
index 0000000000000..a2d86fc504442
--- /dev/null
+++ b/spec/lib/gitlab/usage/metrics/instrumentations/count_projects_with_jira_dvcs_integration_metric_spec.rb
@@ -0,0 +1,50 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Gitlab::Usage::Metrics::Instrumentations::CountProjectsWithJiraDvcsIntegrationMetric,
+  feature_category: :integrations do
+  describe 'metric value and query' do
+    let_it_be_with_reload(:project_1) { create(:project) }
+    let_it_be_with_reload(:project_2) { create(:project) }
+    let_it_be_with_reload(:project_3) { create(:project) }
+
+    before do
+      project_1.feature_usage.log_jira_dvcs_integration_usage(cloud: false)
+      project_2.feature_usage.log_jira_dvcs_integration_usage(cloud: false)
+      project_3.feature_usage.log_jira_dvcs_integration_usage(cloud: true)
+    end
+
+    context 'when counting cloud integrations' do
+      let(:expected_value) { 1 }
+      let(:expected_query) do
+        'SELECT COUNT("project_feature_usages"."project_id") FROM "project_feature_usages" ' \
+          'WHERE "project_feature_usages"."jira_dvcs_cloud_last_sync_at" IS NOT NULL'
+      end
+
+      it_behaves_like 'a correct instrumented metric value and query', { time_frame: 'all', options: { cloud: true } }
+    end
+
+    context 'when counting non-cloud integrations' do
+      let(:expected_value) { 2 }
+      let(:expected_query) do
+        'SELECT COUNT("project_feature_usages"."project_id") FROM "project_feature_usages" ' \
+          'WHERE "project_feature_usages"."jira_dvcs_server_last_sync_at" IS NOT NULL'
+      end
+
+      it_behaves_like 'a correct instrumented metric value and query', { time_frame: 'all', options: { cloud: false } }
+    end
+  end
+
+  it "raises an exception if option is not present" do
+    expect do
+      described_class.new(options: {}, time_frame: 'all')
+    end.to raise_error(ArgumentError, %r{must be a boolean})
+  end
+
+  it "raises an exception if option has invalid value" do
+    expect do
+      described_class.new(options: { cloud: 'yes' }, time_frame: 'all')
+    end.to raise_error(ArgumentError, %r{must be a boolean})
+  end
+end
diff --git a/spec/support/helpers/usage_data_helpers.rb b/spec/support/helpers/usage_data_helpers.rb
index 73f7a79dd5be5..8ac3b0c134b81 100644
--- a/spec/support/helpers/usage_data_helpers.rb
+++ b/spec/support/helpers/usage_data_helpers.rb
@@ -50,8 +50,6 @@ module UsageDataHelpers
     projects_asana_active
     projects_jenkins_active
     projects_jira_active
-    projects_jira_dvcs_cloud_active
-    projects_jira_dvcs_server_active
     projects_slack_active
     projects_slack_slash_commands_active
     projects_custom_issue_tracker_active
-- 
GitLab