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