usage_data.rb 35.5 KB
Newer Older
G
gfyoung 已提交
1 2
# frozen_string_literal: true

3 4 5
# When developing usage data metrics use the below usage data interface methods
# unless you have good reasons to implement custom usage data
# See `lib/gitlab/utils/usage_data.rb`
6
#
7 8 9 10 11 12
# Examples
#   issues_using_zoom_quick_actions: distinct_count(ZoomMeeting, :issue_id),
#   active_user_count: count(User.active)
#   alt_usage_data { Gitlab::VERSION }
#   redis_usage_data(Gitlab::UsageDataCounters::WikiPageCounter)
#   redis_usage_data { ::Gitlab::UsageCounters::PodLogs.usage_totals[:total] }
13 14 15
module Gitlab
  class UsageData
    class << self
16
      include Gitlab::Utils::UsageData
17
      include Gitlab::Utils::StrongMemoize
18

19
      def data(force_refresh: false)
A
Alex Kalderimis 已提交
20 21 22
        Rails.cache.fetch('usage_data', force: force_refresh, expires_in: 2.weeks) do
          uncached_data
        end
23 24 25
      end

      def uncached_data
26
        clear_memoized
27

28 29 30
        with_finished_at(:recording_ce_finished_at) do
          license_usage_data
            .merge(system_usage_data)
31
            .merge(system_usage_data_monthly)
32 33 34 35 36
            .merge(features_usage_data)
            .merge(components_usage_data)
            .merge(cycle_analytics_usage_data)
            .merge(object_store_usage_data)
            .merge(topology_usage_data)
37
            .merge(usage_activity_by_stage)
38
            .merge(usage_activity_by_stage(:usage_activity_by_stage_monthly, last_28_days_time_period))
39
            .merge(analytics_unique_visits_data)
40
        end
41 42
      end

43 44
      def to_json(force_refresh: false)
        data(force_refresh: force_refresh).to_json
45 46
      end

47
      def license_usage_data
48
        {
49
          recorded_at: recorded_at,
50 51 52 53
          uuid: alt_usage_data { Gitlab::CurrentSettings.uuid },
          hostname: alt_usage_data { Gitlab.config.gitlab.host },
          version: alt_usage_data { Gitlab::VERSION },
          installation_type: alt_usage_data { installation_type },
54
          active_user_count: count(User.active),
55 56 57 58
          edition: 'CE'
        }
      end

59 60 61 62
      def recorded_at
        Time.now
      end

63
      # rubocop: disable Metrics/AbcSize
64
      # rubocop: disable CodeReuse/ActiveRecord
65
      def system_usage_data
66 67
        alert_bot_incident_count = count(::Issue.authored(::User.alert_bot), start: issue_minimum_id, finish: issue_maximum_id)
        issues_created_manually_from_alerts = count(Issue.with_alert_management_alerts.not_authored_by(::User.alert_bot), start: issue_minimum_id, finish: issue_maximum_id)
68

69 70
        {
          counts: {
71 72 73 74 75 76 77 78 79 80 81 82 83
            assignee_lists: count(List.assignee),
            boards: count(Board),
            ci_builds: count(::Ci::Build),
            ci_internal_pipelines: count(::Ci::Pipeline.internal),
            ci_external_pipelines: count(::Ci::Pipeline.external),
            ci_pipeline_config_auto_devops: count(::Ci::Pipeline.auto_devops_source),
            ci_pipeline_config_repository: count(::Ci::Pipeline.repository_source),
            ci_runners: count(::Ci::Runner),
            ci_triggers: count(::Ci::Trigger),
            ci_pipeline_schedules: count(::Ci::PipelineSchedule),
            auto_devops_enabled: count(::ProjectAutoDevops.enabled),
            auto_devops_disabled: count(::ProjectAutoDevops.disabled),
            deploy_keys: count(DeployKey),
84
            # rubocop: disable UsageData/LargeTable:
85 86 87
            deployments: deployment_count(Deployment),
            successful_deployments: deployment_count(Deployment.success),
            failed_deployments: deployment_count(Deployment.failed),
88
            # rubocop: enable UsageData/LargeTable:
89 90 91
            environments: count(::Environment),
            clusters: count(::Clusters::Cluster),
            clusters_enabled: count(::Clusters::Cluster.enabled),
92 93
            project_clusters_enabled: count(::Clusters::Cluster.enabled.project_type),
            group_clusters_enabled: count(::Clusters::Cluster.enabled.group_type),
94
            instance_clusters_enabled: count(::Clusters::Cluster.enabled.instance_type),
95
            clusters_disabled: count(::Clusters::Cluster.disabled),
96 97
            project_clusters_disabled: count(::Clusters::Cluster.disabled.project_type),
            group_clusters_disabled: count(::Clusters::Cluster.disabled.group_type),
98
            instance_clusters_disabled: count(::Clusters::Cluster.disabled.instance_type),
99 100
            clusters_platforms_eks: count(::Clusters::Cluster.aws_installed.enabled),
            clusters_platforms_gke: count(::Clusters::Cluster.gcp_installed.enabled),
101
            clusters_platforms_user: count(::Clusters::Cluster.user_provided.enabled),
102 103 104
            clusters_applications_helm: count(::Clusters::Applications::Helm.available),
            clusters_applications_ingress: count(::Clusters::Applications::Ingress.available),
            clusters_applications_cert_managers: count(::Clusters::Applications::CertManager.available),
105
            clusters_applications_crossplane: count(::Clusters::Applications::Crossplane.available),
106 107 108
            clusters_applications_prometheus: count(::Clusters::Applications::Prometheus.available),
            clusters_applications_runner: count(::Clusters::Applications::Runner.available),
            clusters_applications_knative: count(::Clusters::Applications::Knative.available),
109
            clusters_applications_elastic_stack: count(::Clusters::Applications::ElasticStack.available),
110
            clusters_applications_jupyter: count(::Clusters::Applications::Jupyter.available),
111
            clusters_applications_cilium: count(::Clusters::Applications::Cilium.available),
112
            clusters_management_project: count(::Clusters::Cluster.with_management_project),
113
            in_review_folder: count(::Environment.in_review_folder),
114
            grafana_integrated_projects: count(GrafanaIntegration.enabled),
115
            groups: count(Group),
116
            issues: count(Issue, start: issue_minimum_id, finish: issue_maximum_id),
117
            issues_created_from_gitlab_error_tracking_ui: count(SentryIssue),
118
            issues_with_associated_zoom_link: count(ZoomMeeting.added_to_issue),
119
            issues_using_zoom_quick_actions: distinct_count(ZoomMeeting, :issue_id),
120
            issues_with_embedded_grafana_charts_approx: grafana_embed_usage_data,
121 122 123
            issues_created_from_alerts: total_alert_issues,
            issues_created_gitlab_alerts: issues_created_manually_from_alerts,
            issues_created_manually_from_alerts: issues_created_manually_from_alerts,
124 125
            incident_issues: alert_bot_incident_count,
            alert_bot_incident_issues: alert_bot_incident_count,
126
            incident_labeled_issues: count(::Issue.with_label_attributes(::IncidentManagement::CreateIncidentLabelService::LABEL_PROPERTIES), start: issue_minimum_id, finish: issue_maximum_id),
127 128 129 130 131 132
            keys: count(Key),
            label_lists: count(List.label),
            lfs_objects: count(LfsObject),
            milestone_lists: count(List.milestone),
            milestones: count(Milestone),
            pages_domains: count(PagesDomain),
133
            pool_repositories: count(PoolRepository),
134 135
            projects: count(Project),
            projects_imported_from_github: count(Project.where(import_type: 'github')),
136
            projects_with_repositories_enabled: count(ProjectFeature.where('repository_access_level > ?', ProjectFeature::DISABLED)),
L
Logan King 已提交
137
            projects_with_error_tracking_enabled: count(::ErrorTracking::ProjectErrorTrackingSetting.where(enabled: true)),
138
            projects_with_alerts_service_enabled: count(AlertsService.active),
139
            projects_with_prometheus_alerts: distinct_count(PrometheusAlert, :project_id),
140
            projects_with_terraform_reports: distinct_count(::Ci::JobArtifact.terraform_reports, :project_id),
141
            projects_with_terraform_states: distinct_count(::Terraform::State, :project_id),
142 143 144
            protected_branches: count(ProtectedBranch),
            releases: count(Release),
            remote_mirrors: count(RemoteMirror),
145 146
            personal_snippets: count(PersonalSnippet),
            project_snippets: count(ProjectSnippet),
147
            suggestions: count(Suggestion),
148
            terraform_reports: count(::Ci::JobArtifact.terraform_reports),
149
            terraform_states: count(::Terraform::State),
150
            todos: count(Todo),
151
            uploads: count(Upload),
152 153 154 155
            web_hooks: count(WebHook),
            labels: count(Label),
            merge_requests: count(MergeRequest),
            notes: count(Note)
156 157 158
          }.merge(
            services_usage,
            usage_counters,
159
            user_preferences_usage,
160
            ingress_modsecurity_usage,
161 162
            container_expiration_policies_usage,
            service_desk_counts
163 164 165
          ).tap do |data|
            data[:snippets] = data[:personal_snippets] + data[:project_snippets]
          end
166
        }
167
      end
168
      # rubocop: enable Metrics/AbcSize
169

170 171 172
      def system_usage_data_monthly
        {
          counts_monthly: {
173
            # rubocop: disable UsageData/LargeTable:
174 175 176
            deployments: deployment_count(Deployment.where(last_28_days_time_period)),
            successful_deployments: deployment_count(Deployment.success.where(last_28_days_time_period)),
            failed_deployments: deployment_count(Deployment.failed.where(last_28_days_time_period)),
177
            # rubocop: enable UsageData/LargeTable:
178 179
            personal_snippets: count(PersonalSnippet.where(last_28_days_time_period)),
            project_snippets: count(ProjectSnippet.where(last_28_days_time_period))
180 181 182
          }.tap do |data|
            data[:snippets] = data[:personal_snippets] + data[:project_snippets]
          end
183 184 185 186
        }
      end
      # rubocop: enable CodeReuse/ActiveRecord

187
      def cycle_analytics_usage_data
188
        Gitlab::CycleAnalytics::UsageData.new.to_json
189 190
      rescue ActiveRecord::StatementInvalid
        { avg_cycle_analytics: {} }
191 192
      end

193 194 195 196 197 198 199 200
      # rubocop:disable CodeReuse/ActiveRecord
      def grafana_embed_usage_data
        count(Issue.joins('JOIN grafana_integrations USING (project_id)')
          .where("issues.description LIKE '%' || grafana_integrations.grafana_url || '%'")
          .where(grafana_integrations: { enabled: true }))
      end
      # rubocop: enable CodeReuse/ActiveRecord

201 202 203 204 205 206
      def features_usage_data
        features_usage_data_ce
      end

      def features_usage_data_ce
        {
207
          instance_auto_devops_enabled: alt_usage_data(fallback: nil) { Gitlab::CurrentSettings.auto_devops_enabled? },
208
          container_registry_enabled: alt_usage_data(fallback: nil) { Gitlab.config.registry.enabled },
209
          dependency_proxy_enabled: Gitlab.config.try(:dependency_proxy)&.enabled,
210 211 212 213 214
          gitlab_shared_runners_enabled: alt_usage_data(fallback: nil) { Gitlab.config.gitlab_ci.shared_runners_enabled },
          gravatar_enabled: alt_usage_data(fallback: nil) { Gitlab::CurrentSettings.gravatar_enabled? },
          ldap_enabled: alt_usage_data(fallback: nil) { Gitlab.config.ldap.enabled },
          mattermost_enabled: alt_usage_data(fallback: nil) { Gitlab.config.mattermost.enabled },
          omniauth_enabled: alt_usage_data(fallback: nil) { Gitlab::Auth.omniauth_enabled? },
215
          prometheus_enabled: alt_usage_data(fallback: nil) { Gitlab::Prometheus::Internal.prometheus_enabled? },
216 217 218 219
          prometheus_metrics_enabled: alt_usage_data(fallback: nil) { Gitlab::Metrics.prometheus_metrics_enabled? },
          reply_by_email_enabled: alt_usage_data(fallback: nil) { Gitlab::IncomingEmail.enabled? },
          signup_enabled: alt_usage_data(fallback: nil) { Gitlab::CurrentSettings.allow_signup? },
          web_ide_clientside_preview_enabled: alt_usage_data(fallback: nil) { Gitlab::CurrentSettings.web_ide_clientside_preview_enabled? },
220
          ingress_modsecurity_enabled: Feature.enabled?(:ingress_modsecurity),
221
          grafana_link_enabled: alt_usage_data(fallback: nil) { Gitlab::CurrentSettings.grafana_enabled? }
222
        }
223
      end
224

A
Alex Kalderimis 已提交
225
      # @return [Hash<Symbol, Integer>]
T
Tiago Botelho 已提交
226
      def usage_counters
227
        usage_data_counters.map { |counter| redis_usage_data(counter) }.reduce({}, :merge)
A
Alex Kalderimis 已提交
228 229 230 231
      end

      # @return [Array<#totals>] An array of objects that respond to `#totals`
      def usage_data_counters
232
        [
233 234 235 236 237 238 239 240
          Gitlab::UsageDataCounters::WikiPageCounter,
          Gitlab::UsageDataCounters::WebIdeCounter,
          Gitlab::UsageDataCounters::NoteCounter,
          Gitlab::UsageDataCounters::SnippetCounter,
          Gitlab::UsageDataCounters::SearchCounter,
          Gitlab::UsageDataCounters::CycleAnalyticsCounter,
          Gitlab::UsageDataCounters::ProductivityAnalyticsCounter,
          Gitlab::UsageDataCounters::SourceCodeCounter,
241 242
          Gitlab::UsageDataCounters::MergeRequestCounter,
          Gitlab::UsageDataCounters::DesignsCounter
243
        ]
T
Tiago Botelho 已提交
244 245
      end

246 247
      def components_usage_data
        {
248
          git: { version: alt_usage_data(fallback: { major: -1 }) { Gitlab::Git.version } },
249 250 251
          gitaly: {
            version: alt_usage_data { Gitaly::Server.all.first.server_version },
            servers: alt_usage_data { Gitaly::Server.count },
252
            clusters: alt_usage_data { Gitaly::Server.gitaly_clusters },
253
            filesystems: alt_usage_data(fallback: ["-1"]) { Gitaly::Server.filesystems }
254 255
          },
          gitlab_pages: {
256
            enabled: alt_usage_data(fallback: nil) { Gitlab.config.pages.enabled },
257 258
            version: alt_usage_data { Gitlab::Pages::VERSION }
          },
259
          container_registry_server: {
260 261 262
            vendor: alt_usage_data(fallback: nil) { Gitlab::CurrentSettings.container_registry_vendor },
            version: alt_usage_data(fallback: nil) { Gitlab::CurrentSettings.container_registry_version }
          },
263 264 265
          database: {
            adapter: alt_usage_data { Gitlab::Database.adapter_name },
            version: alt_usage_data { Gitlab::Database.version }
266
          }
267
        }
268
      end
269

270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303
      def object_store_config(component)
        config = alt_usage_data(fallback: nil) do
          Settings[component]['object_store']
        end

        if config
          {
            enabled: alt_usage_data { Settings[component]['enabled'] },
            object_store: {
              enabled: alt_usage_data { config['enabled'] },
              direct_upload: alt_usage_data { config['direct_upload'] },
              background_upload: alt_usage_data { config['background_upload'] },
              provider: alt_usage_data { config['connection']['provider'] }
            }
          }
        else
          {
            enabled: alt_usage_data { Settings[component]['enabled'] }
          }
        end
      end

      def object_store_usage_data
        {
          object_store: {
            artifacts: object_store_config('artifacts'),
            external_diffs: object_store_config('external_diffs'),
            lfs: object_store_config('lfs'),
            uploads: object_store_config('uploads'),
            packages: object_store_config('packages')
          }
        }
      end

304 305 306 307
      def topology_usage_data
        Gitlab::UsageData::Topology.new.topology_usage_data
      end

308
      def ingress_modsecurity_usage
309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326
        ##
        # This method measures usage of the Modsecurity Web Application Firewall across the entire
        # instance's deployed environments.
        #
        # NOTE: this service is an approximation as it does not yet take into account if environment
        # is enabled and only measures applications installed using GitLab Managed Apps (disregards
        # CI-based managed apps).
        #
        # More details: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/28331#note_318621786
        ##

        column = ::Deployment.arel_table[:environment_id]
        {
          ingress_modsecurity_logging: distinct_count(successful_deployments_with_cluster(::Clusters::Applications::Ingress.modsecurity_enabled.logging), column),
          ingress_modsecurity_blocking: distinct_count(successful_deployments_with_cluster(::Clusters::Applications::Ingress.modsecurity_enabled.blocking), column),
          ingress_modsecurity_disabled: distinct_count(successful_deployments_with_cluster(::Clusters::Applications::Ingress.modsecurity_disabled), column),
          ingress_modsecurity_not_installed: distinct_count(successful_deployments_with_cluster(::Clusters::Applications::Ingress.modsecurity_not_installed), column)
        }
327 328
      end

329 330 331 332 333 334 335
      # rubocop: disable CodeReuse/ActiveRecord
      def container_expiration_policies_usage
        results = {}
        start = ::Project.minimum(:id)
        finish = ::Project.maximum(:id)

        results[:projects_with_expiration_policy_disabled] = distinct_count(::ContainerExpirationPolicy.where(enabled: false), :project_id, start: start, finish: finish)
336
        # rubocop: disable UsageData/LargeTable
337
        base = ::ContainerExpirationPolicy.active
338
        # rubocop: enable UsageData/LargeTable
339 340
        results[:projects_with_expiration_policy_enabled] = distinct_count(base, :project_id, start: start, finish: finish)

341
        # rubocop: disable UsageData/LargeTable
342 343 344 345 346
        %i[keep_n cadence older_than].each do |option|
          ::ContainerExpirationPolicy.public_send("#{option}_options").keys.each do |value| # rubocop: disable GitlabSecurity/PublicSend
            results["projects_with_expiration_policy_enabled_with_#{option}_set_to_#{value}".to_sym] = distinct_count(base.where(option => value), :project_id, start: start, finish: finish)
          end
        end
347
        # rubocop: enable UsageData/LargeTable
348 349 350 351 352 353 354

        results[:projects_with_expiration_policy_enabled_with_keep_n_unset] = distinct_count(base.where(keep_n: nil), :project_id, start: start, finish: finish)
        results[:projects_with_expiration_policy_enabled_with_older_than_unset] = distinct_count(base.where(older_than: nil), :project_id, start: start, finish: finish)

        results
      end

355
      def services_usage
356
        # rubocop: disable UsageData/LargeTable:
357
        Service.available_services_names.each_with_object({}) do |service_name, response|
358
          response["projects_#{service_name}_active".to_sym] = count(Service.active.where(template: false, instance: false, type: "#{service_name}_service".camelize))
359
        end.merge(jira_usage, jira_import_usage)
360
        # rubocop: enable UsageData/LargeTable:
361 362 363 364 365
      end

      def jira_usage
        # Jira Cloud does not support custom domains as per https://jira.atlassian.com/browse/CLOUD-6999
        # so we can just check for subdomains of atlassian.net
366 367
        results = {
          projects_jira_server_active: 0,
368
          projects_jira_cloud_active: 0
369
        }
370

371
        # rubocop: disable UsageData/LargeTable:
372
        JiraService.active.includes(:jira_tracker_data).find_in_batches(batch_size: 100) do |services|
373
          counts = services.group_by do |service|
374
            # TODO: Simplify as part of https://gitlab.com/gitlab-org/gitlab/issues/29404
375 376 377 378
            service_url = service.data_fields&.url || (service.properties && service.properties['url'])
            service_url&.include?('.atlassian.net') ? :cloud : :server
          end

379 380
          results[:projects_jira_server_active] += counts[:server].size if counts[:server]
          results[:projects_jira_cloud_active] += counts[:cloud].size if counts[:cloud]
381
        end
382
        # rubocop: enable UsageData/LargeTable:
383
        results
384
      rescue ActiveRecord::StatementInvalid
385
        { projects_jira_server_active: FALLBACK, projects_jira_cloud_active: FALLBACK }
386
      end
387
      # rubocop: enable CodeReuse/ActiveRecord
388

389
      def jira_import_usage
390
        # rubocop: disable UsageData/LargeTable
391 392 393 394 395 396 397
        finished_jira_imports = JiraImportState.finished

        {
          jira_imports_total_imported_count: count(finished_jira_imports),
          jira_imports_projects_count: distinct_count(finished_jira_imports, :project_id),
          jira_imports_total_imported_issues_count: alt_usage_data { JiraImportState.finished_imports_count }
        }
398
        # rubocop: enable UsageData/LargeTable
399 400
      end

401 402 403 404 405 406 407 408 409 410 411
      # rubocop: disable CodeReuse/ActiveRecord
      # rubocop: disable UsageData/LargeTable
      def successful_deployments_with_cluster(scope)
        scope
          .joins(cluster: :deployments)
          .merge(Clusters::Cluster.enabled)
          .merge(Deployment.success)
      end
      # rubocop: enable UsageData/LargeTable
      # rubocop: enable CodeReuse/ActiveRecord

412 413 414 415
      def user_preferences_usage
        {} # augmented in EE
      end

416
      # rubocop: disable CodeReuse/ActiveRecord
417 418
      def merge_requests_users(time_period)
        distinct_count(
419
          Event.where(target_type: Event::TARGET_TYPES[:merge_request].to_s).where(time_period),
420
          :author_id,
421 422
          start: user_minimum_id,
          finish: user_maximum_id
423 424 425 426
        )
      end
      # rubocop: enable CodeReuse/ActiveRecord

427 428 429 430 431 432 433
      def installation_type
        if Rails.env.production?
          Gitlab::INSTALLATION_TYPE
        else
          "gitlab-development-kit"
        end
      end
434

435
      def last_28_days_time_period
436 437
        { created_at: 28.days.ago..Time.current }
      end
438

439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456
      # Source: https://gitlab.com/gitlab-data/analytics/blob/master/transform/snowflake-dbt/data/ping_metrics_to_stage_mapping_data.csv
      def usage_activity_by_stage(key = :usage_activity_by_stage, time_period = {})
        {
          key => {
            configure: usage_activity_by_stage_configure(time_period),
            create: usage_activity_by_stage_create(time_period),
            manage: usage_activity_by_stage_manage(time_period),
            monitor: usage_activity_by_stage_monitor(time_period),
            package: usage_activity_by_stage_package(time_period),
            plan: usage_activity_by_stage_plan(time_period),
            release: usage_activity_by_stage_release(time_period),
            secure: usage_activity_by_stage_secure(time_period),
            verify: usage_activity_by_stage_verify(time_period)
          }
        }
      end

      # rubocop: disable CodeReuse/ActiveRecord
457
      # rubocop: disable UsageData/LargeTable
458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477
      def usage_activity_by_stage_configure(time_period)
        {
          clusters_applications_cert_managers: cluster_applications_user_distinct_count(::Clusters::Applications::CertManager, time_period),
          clusters_applications_helm: cluster_applications_user_distinct_count(::Clusters::Applications::Helm, time_period),
          clusters_applications_ingress: cluster_applications_user_distinct_count(::Clusters::Applications::Ingress, time_period),
          clusters_applications_knative: cluster_applications_user_distinct_count(::Clusters::Applications::Knative, time_period),
          clusters_management_project: clusters_user_distinct_count(::Clusters::Cluster.with_management_project, time_period),
          clusters_disabled: clusters_user_distinct_count(::Clusters::Cluster.disabled, time_period),
          clusters_enabled: clusters_user_distinct_count(::Clusters::Cluster.enabled, time_period),
          clusters_platforms_gke: clusters_user_distinct_count(::Clusters::Cluster.gcp_installed.enabled, time_period),
          clusters_platforms_eks: clusters_user_distinct_count(::Clusters::Cluster.aws_installed.enabled, time_period),
          clusters_platforms_user: clusters_user_distinct_count(::Clusters::Cluster.user_provided.enabled, time_period),
          instance_clusters_disabled: clusters_user_distinct_count(::Clusters::Cluster.disabled.instance_type, time_period),
          instance_clusters_enabled: clusters_user_distinct_count(::Clusters::Cluster.enabled.instance_type, time_period),
          group_clusters_disabled: clusters_user_distinct_count(::Clusters::Cluster.disabled.group_type, time_period),
          group_clusters_enabled: clusters_user_distinct_count(::Clusters::Cluster.enabled.group_type, time_period),
          project_clusters_disabled: clusters_user_distinct_count(::Clusters::Cluster.disabled.project_type, time_period),
          project_clusters_enabled: clusters_user_distinct_count(::Clusters::Cluster.enabled.project_type, time_period)
        }
      end
478
      # rubocop: enable UsageData/LargeTable
479 480
      # rubocop: enable CodeReuse/ActiveRecord

481
      # rubocop: disable CodeReuse/ActiveRecord
482
      def usage_activity_by_stage_create(time_period)
483 484 485 486 487 488 489 490 491
        {
          deploy_keys: distinct_count(::DeployKey.where(time_period), :user_id),
          keys: distinct_count(::Key.regular_keys.where(time_period), :user_id),
          merge_requests: distinct_count(::MergeRequest.where(time_period), :author_id),
          projects_with_disable_overriding_approvers_per_merge_request: count(::Project.where(time_period.merge(disable_overriding_approvers_per_merge_request: true))),
          projects_without_disable_overriding_approvers_per_merge_request: count(::Project.where(time_period.merge(disable_overriding_approvers_per_merge_request: [false, nil]))),
          remote_mirrors: distinct_count(::Project.with_remote_mirrors.where(time_period), :creator_id),
          snippets: distinct_count(::Snippet.where(time_period), :author_id)
        }.tap do |h|
492 493 494 495
          if time_period.present?
            h[:merge_requests_users] = merge_requests_users(time_period)
            h.merge!(action_monthly_active_users(time_period))
          end
496
        end
497
      end
498
      # rubocop: enable CodeReuse/ActiveRecord
499 500

      # Omitted because no user, creator or author associated: `campaigns_imported_from_github`, `ldap_group_links`
501
      # rubocop: disable CodeReuse/ActiveRecord
502
      def usage_activity_by_stage_manage(time_period)
503 504 505 506 507 508
        {
          events: distinct_count(::Event.where(time_period), :author_id),
          groups: distinct_count(::GroupMember.where(time_period), :user_id),
          users_created: count(::User.where(time_period), start: user_minimum_id, finish: user_maximum_id),
          omniauth_providers: filtered_omniauth_provider_names.reject { |name| name == 'group_saml' }
        }
509
      end
510
      # rubocop: enable CodeReuse/ActiveRecord
511

512
      # rubocop: disable CodeReuse/ActiveRecord
513
      def usage_activity_by_stage_monitor(time_period)
514 515 516 517 518 519 520
        {
          clusters: distinct_count(::Clusters::Cluster.where(time_period), :user_id),
          clusters_applications_prometheus: cluster_applications_user_distinct_count(::Clusters::Applications::Prometheus, time_period),
          operations_dashboard_default_dashboard: count(::User.active.with_dashboard('operations').where(time_period),
                                                        start: user_minimum_id,
                                                        finish: user_maximum_id)
        }
521
      end
522
      # rubocop: enable CodeReuse/ActiveRecord
523 524 525 526 527 528 529 530

      def usage_activity_by_stage_package(time_period)
        {}
      end

      # Omitted because no user, creator or author associated: `boards`, `labels`, `milestones`, `uploads`
      # Omitted because too expensive: `epics_deepest_relationship_level`
      # Omitted because of encrypted properties: `projects_jira_cloud_active`, `projects_jira_server_active`
531
      # rubocop: disable CodeReuse/ActiveRecord
532
      def usage_activity_by_stage_plan(time_period)
533 534 535 536
        {
          issues: distinct_count(::Issue.where(time_period), :author_id),
          notes: distinct_count(::Note.where(time_period), :author_id),
          projects: distinct_count(::Project.where(time_period), :creator_id),
537 538 539
          todos: distinct_count(::Todo.where(time_period), :author_id),
          service_desk_enabled_projects: distinct_count_service_desk_enabled_projects(time_period),
          service_desk_issues: count(::Issue.service_desk.where(time_period))
540
        }
541
      end
542
      # rubocop: enable CodeReuse/ActiveRecord
543 544

      # Omitted because no user, creator or author associated: `environments`, `feature_flags`, `in_review_folder`, `pages_domains`
545
      # rubocop: disable CodeReuse/ActiveRecord
546
      def usage_activity_by_stage_release(time_period)
547 548 549 550 551 552
        {
          deployments: distinct_count(::Deployment.where(time_period), :user_id),
          failed_deployments: distinct_count(::Deployment.failed.where(time_period), :user_id),
          releases: distinct_count(::Release.where(time_period), :author_id),
          successful_deployments: distinct_count(::Deployment.success.where(time_period), :user_id)
        }
553
      end
554
      # rubocop: enable CodeReuse/ActiveRecord
555 556

      # Omitted because no user, creator or author associated: `ci_runners`
557
      # rubocop: disable CodeReuse/ActiveRecord
558
      def usage_activity_by_stage_verify(time_period)
559 560 561 562 563 564 565 566 567 568 569
        {
          ci_builds: distinct_count(::Ci::Build.where(time_period), :user_id),
          ci_external_pipelines: distinct_count(::Ci::Pipeline.external.where(time_period), :user_id, start: user_minimum_id, finish: user_maximum_id),
          ci_internal_pipelines: distinct_count(::Ci::Pipeline.internal.where(time_period), :user_id, start: user_minimum_id, finish: user_maximum_id),
          ci_pipeline_config_auto_devops: distinct_count(::Ci::Pipeline.auto_devops_source.where(time_period), :user_id, start: user_minimum_id, finish: user_maximum_id),
          ci_pipeline_config_repository: distinct_count(::Ci::Pipeline.repository_source.where(time_period), :user_id, start: user_minimum_id, finish: user_maximum_id),
          ci_pipeline_schedules: distinct_count(::Ci::PipelineSchedule.where(time_period), :owner_id),
          ci_pipelines: distinct_count(::Ci::Pipeline.where(time_period), :user_id, start: user_minimum_id, finish: user_maximum_id),
          ci_triggers: distinct_count(::Ci::Trigger.where(time_period), :owner_id),
          clusters_applications_runner: cluster_applications_user_distinct_count(::Clusters::Applications::Runner, time_period)
        }
570
      end
571
      # rubocop: enable CodeReuse/ActiveRecord
572 573 574 575 576 577 578 579

      # Currently too complicated and to get reliable counts for these stats:
      # container_scanning_jobs, dast_jobs, dependency_scanning_jobs, license_management_jobs, sast_jobs, secret_detection_jobs
      # Once https://gitlab.com/gitlab-org/gitlab/merge_requests/17568 is merged, this might be doable
      def usage_activity_by_stage_secure(time_period)
        {}
      end

580 581
      def analytics_unique_visits_data
        results = ::Gitlab::Analytics::UniqueVisits::TARGET_IDS.each_with_object({}) do |target_id, hash|
582
          hash[target_id] = redis_usage_data { unique_visit_service.unique_visits_for(targets: target_id) }
583
        end
584 585
        results['analytics_unique_visits_for_any_target'] = redis_usage_data { unique_visit_service.unique_visits_for(targets: :any) }
        results['analytics_unique_visits_for_any_target_monthly'] = redis_usage_data { unique_visit_service.unique_visits_for(targets: :any, weeks: 4) }
586 587 588 589

        { analytics_unique_visits: results }
      end

590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612 613 614 615 616 617 618 619 620 621 622 623 624 625
      def action_monthly_active_users(time_period)
        return {} unless Feature.enabled?(Gitlab::UsageDataCounters::TrackUniqueActions::FEATURE_FLAG)

        counter = Gitlab::UsageDataCounters::TrackUniqueActions

        project_count = redis_usage_data do
          counter.count_unique_events(
            event_action: Gitlab::UsageDataCounters::TrackUniqueActions::PUSH_ACTION,
            date_from: time_period[:created_at].first,
            date_to: time_period[:created_at].last
          )
        end

        design_count = redis_usage_data do
          counter.count_unique_events(
            event_action: Gitlab::UsageDataCounters::TrackUniqueActions::DESIGN_ACTION,
            date_from: time_period[:created_at].first,
            date_to: time_period[:created_at].last
          )
        end

        wiki_count = redis_usage_data do
          counter.count_unique_events(
            event_action: Gitlab::UsageDataCounters::TrackUniqueActions::WIKI_ACTION,
            date_from: time_period[:created_at].first,
            date_to: time_period[:created_at].last
          )
        end

        {
          action_monthly_active_users_project_repo: project_count,
          action_monthly_active_users_design_management: design_count,
          action_monthly_active_users_wiki_repo: wiki_count
        }
      end

626 627
      private

628 629 630 631 632 633 634 635 636
      def distinct_count_service_desk_enabled_projects(time_period)
        project_creator_id_start = user_minimum_id
        project_creator_id_finish = user_maximum_id

        distinct_count(::Project.service_desk_enabled.where(time_period), :creator_id, start: project_creator_id_start, finish: project_creator_id_finish) # rubocop: disable CodeReuse/ActiveRecord
      end

      # rubocop: disable CodeReuse/ActiveRecord
      def service_desk_counts
637
        # rubocop: disable UsageData/LargeTable:
638
        projects_with_service_desk = ::Project.where(service_desk_enabled: true)
639
        # rubocop: enable UsageData/LargeTable:
640 641 642 643 644 645 646 647 648 649 650 651 652
        {
          service_desk_enabled_projects: count(projects_with_service_desk),
          service_desk_issues: count(
            ::Issue.where(
              project: projects_with_service_desk,
              author: ::User.support_bot,
              confidential: true
            )
          )
        }
      end
      # rubocop: enable CodeReuse/ActiveRecord

653 654 655 656 657 658
      def unique_visit_service
        strong_memoize(:unique_visit_service) do
          ::Gitlab::Analytics::UniqueVisits.new
        end
      end

659 660 661 662
      def total_alert_issues
        # Remove prometheus table queries once they are deprecated
        # To be removed with https://gitlab.com/gitlab-org/gitlab/-/issues/217407.
        [
663 664 665
          count(Issue.with_alert_management_alerts, start: issue_minimum_id, finish: issue_maximum_id),
          count(::Issue.with_self_managed_prometheus_alert_events, start: issue_minimum_id, finish: issue_maximum_id),
          count(::Issue.with_prometheus_alert_events, start: issue_minimum_id, finish: issue_maximum_id)
666 667 668
        ].reduce(:+)
      end

669 670 671 672 673 674 675 676 677 678 679 680
      def user_minimum_id
        strong_memoize(:user_minimum_id) do
          ::User.minimum(:id)
        end
      end

      def user_maximum_id
        strong_memoize(:user_maximum_id) do
          ::User.maximum(:id)
        end
      end

681 682 683 684 685 686 687 688 689 690 691 692
      def issue_minimum_id
        strong_memoize(:issue_minimum_id) do
          ::Issue.minimum(:id)
        end
      end

      def issue_maximum_id
        strong_memoize(:issue_maximum_id) do
          ::Issue.maximum(:id)
        end
      end

693 694 695 696 697 698 699 700 701 702 703 704
      def deployment_minimum_id
        strong_memoize(:deployment_minimum_id) do
          ::Deployment.minimum(:id)
        end
      end

      def deployment_maximum_id
        strong_memoize(:deployment_maximum_id) do
          ::Deployment.maximum(:id)
        end
      end

705
      def clear_memoized
706 707
        clear_memoization(:issue_minimum_id)
        clear_memoization(:issue_maximum_id)
708 709
        clear_memoization(:user_minimum_id)
        clear_memoization(:user_maximum_id)
710
        clear_memoization(:unique_visit_service)
711 712
        clear_memoization(:deployment_minimum_id)
        clear_memoization(:deployment_maximum_id)
713 714
        clear_memoization(:approval_merge_request_rule_minimum_id)
        clear_memoization(:approval_merge_request_rule_maximum_id)
715
      end
716 717 718 719 720 721 722 723 724 725

      # rubocop: disable CodeReuse/ActiveRecord
      def cluster_applications_user_distinct_count(applications, time_period)
        distinct_count(applications.where(time_period).available.joins(:cluster), 'clusters.user_id')
      end

      def clusters_user_distinct_count(clusters, time_period)
        distinct_count(clusters.where(time_period), :user_id)
      end
      # rubocop: enable CodeReuse/ActiveRecord
726 727 728 729 730 731 732 733 734 735 736 737

      def omniauth_provider_names
        ::Gitlab.config.omniauth.providers.map(&:name)
      end

      # LDAP provider names are set by customers and could include
      # sensitive info (server names, etc). LDAP providers normally
      # don't appear in omniauth providers but filter to ensure
      # no internal details leak via usage ping.
      def filtered_omniauth_provider_names
        omniauth_provider_names.reject { |name| name.starts_with?('ldap') }
      end
738 739 740 741

      def deployment_count(relation)
        count relation, start: deployment_minimum_id, finish: deployment_maximum_id
      end
742 743 744
    end
  end
end
745 746

Gitlab::UsageData.prepend_if_ee('EE::Gitlab::UsageData')