usage_data.rb 37.0 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
            .merge(compliance_unique_visits_data)
41
        end
42 43
      end

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

48
      def license_usage_data
49
        {
50
          recorded_at: recorded_at,
51 52 53 54
          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 },
55
          active_user_count: count(User.active),
56 57 58 59
          edition: 'CE'
        }
      end

60
      def recorded_at
61
        Time.current
62 63
      end

64
      # rubocop: disable Metrics/AbcSize
65
      # rubocop: disable CodeReuse/ActiveRecord
66
      def system_usage_data
67
        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: count(::Issue.incident, start: issue_minimum_id, finish: issue_maximum_id),
            alert_bot_incident_issues: count(::Issue.authored(::User.alert_bot), start: issue_minimum_id, finish: issue_maximum_id),
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
            protected_branches: count(ProtectedBranch),
143
            protected_branches_except_default: count(ProtectedBranch.where.not(name: ['main', 'master', Gitlab::CurrentSettings.default_branch_name])),
144 145
            releases: count(Release),
            remote_mirrors: count(RemoteMirror),
146 147
            personal_snippets: count(PersonalSnippet),
            project_snippets: count(ProjectSnippet),
148
            suggestions: count(Suggestion),
149
            terraform_reports: count(::Ci::JobArtifact.terraform_reports),
150
            terraform_states: count(::Terraform::State),
151
            todos: count(Todo),
152
            uploads: count(Upload),
153 154 155 156
            web_hooks: count(WebHook),
            labels: count(Label),
            merge_requests: count(MergeRequest),
            notes: count(Note)
157 158 159
          }.merge(
            services_usage,
            usage_counters,
160
            user_preferences_usage,
161
            ingress_modsecurity_usage,
162 163
            container_expiration_policies_usage,
            service_desk_counts
164 165 166
          ).tap do |data|
            data[:snippets] = data[:personal_snippets] + data[:project_snippets]
          end
167
        }
168
      end
169
      # rubocop: enable Metrics/AbcSize
170

171 172 173
      def system_usage_data_monthly
        {
          counts_monthly: {
174
            # rubocop: disable UsageData/LargeTable:
175 176 177
            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)),
178
            # rubocop: enable UsageData/LargeTable:
179 180
            personal_snippets: count(PersonalSnippet.where(last_28_days_time_period)),
            project_snippets: count(ProjectSnippet.where(last_28_days_time_period))
181 182 183
          }.tap do |data|
            data[:snippets] = data[:personal_snippets] + data[:project_snippets]
          end
184 185 186 187
        }
      end
      # rubocop: enable CodeReuse/ActiveRecord

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

194 195 196 197 198 199 200 201
      # 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

202 203 204 205 206 207
      def features_usage_data
        features_usage_data_ce
      end

      def features_usage_data_ce
        {
208
          instance_auto_devops_enabled: alt_usage_data(fallback: nil) { Gitlab::CurrentSettings.auto_devops_enabled? },
209
          container_registry_enabled: alt_usage_data(fallback: nil) { Gitlab.config.registry.enabled },
210
          dependency_proxy_enabled: Gitlab.config.try(:dependency_proxy)&.enabled,
211 212 213 214 215
          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? },
216
          prometheus_enabled: alt_usage_data(fallback: nil) { Gitlab::Prometheus::Internal.prometheus_enabled? },
217 218 219 220
          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? },
221
          ingress_modsecurity_enabled: Feature.enabled?(:ingress_modsecurity),
222
          grafana_link_enabled: alt_usage_data(fallback: nil) { Gitlab::CurrentSettings.grafana_enabled? }
223
        }
224
      end
225

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

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

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

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

309
      # rubocop: disable UsageData/DistinctCountByLargeForeignKey
310
      def ingress_modsecurity_usage
311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328
        ##
        # 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)
        }
329
      end
330
      # rubocop: enable UsageData/DistinctCountByLargeForeignKey
331

332 333 334 335 336 337 338
      # 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)
339
        # rubocop: disable UsageData/LargeTable
340
        base = ::ContainerExpirationPolicy.active
341
        # rubocop: enable UsageData/LargeTable
342 343
        results[:projects_with_expiration_policy_enabled] = distinct_count(base, :project_id, start: start, finish: finish)

344
        # rubocop: disable UsageData/LargeTable
345 346 347 348 349
        %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
350
        # rubocop: enable UsageData/LargeTable
351 352 353 354 355 356 357

        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

358
      def services_usage
359
        # rubocop: disable UsageData/LargeTable:
360
        Service.available_services_names.each_with_object({}) do |service_name, response|
361
          response["projects_#{service_name}_active".to_sym] = count(Service.active.where(template: false, instance: false, type: "#{service_name}_service".camelize))
362 363
          response["templates_#{service_name}_active".to_sym] = count(Service.active.where(template: true, type: "#{service_name}_service".camelize))
          response["instances_#{service_name}_active".to_sym] = count(Service.active.where(instance: true, type: "#{service_name}_service".camelize))
364
          response["projects_inheriting_instance_#{service_name}_active".to_sym] = count(Service.active.where.not(inherit_from_id: nil).where(type: "#{service_name}_service".camelize))
365
        end.merge(jira_usage, jira_import_usage)
366
        # rubocop: enable UsageData/LargeTable:
367 368 369 370 371
      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
372 373
        results = {
          projects_jira_server_active: 0,
374
          projects_jira_cloud_active: 0
375
        }
376

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

385 386
          results[:projects_jira_server_active] += counts[:server].size if counts[:server]
          results[:projects_jira_cloud_active] += counts[:cloud].size if counts[:cloud]
387
        end
388
        # rubocop: enable UsageData/LargeTable:
389
        results
390
      rescue ActiveRecord::StatementInvalid
391
        { projects_jira_server_active: FALLBACK, projects_jira_cloud_active: FALLBACK }
392
      end
393
      # rubocop: enable CodeReuse/ActiveRecord
394

395
      def jira_import_usage
396
        # rubocop: disable UsageData/LargeTable
397 398 399 400 401 402 403
        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 }
        }
404
        # rubocop: enable UsageData/LargeTable
405 406
      end

407 408 409 410 411 412 413 414 415 416 417
      # 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

418 419 420 421
      def user_preferences_usage
        {} # augmented in EE
      end

422
      # rubocop: disable CodeReuse/ActiveRecord
423 424
      def merge_requests_users(time_period)
        distinct_count(
425
          Event.where(target_type: Event::TARGET_TYPES[:merge_request].to_s).where(time_period),
426
          :author_id,
427 428
          start: user_minimum_id,
          finish: user_maximum_id
429 430 431 432
        )
      end
      # rubocop: enable CodeReuse/ActiveRecord

433 434 435 436 437 438 439
      def installation_type
        if Rails.env.production?
          Gitlab::INSTALLATION_TYPE
        else
          "gitlab-development-kit"
        end
      end
440

441
      def last_28_days_time_period
442 443
        { created_at: 28.days.ago..Time.current }
      end
444

445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462
      # 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
463
      # rubocop: disable UsageData/LargeTable
464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483
      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
484
      # rubocop: enable UsageData/LargeTable
485 486
      # rubocop: enable CodeReuse/ActiveRecord

487
      # rubocop: disable CodeReuse/ActiveRecord
488
      def usage_activity_by_stage_create(time_period)
489 490 491 492 493 494 495 496 497
        {
          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|
498 499 500 501
          if time_period.present?
            h[:merge_requests_users] = merge_requests_users(time_period)
            h.merge!(action_monthly_active_users(time_period))
          end
502
        end
503
      end
504
      # rubocop: enable CodeReuse/ActiveRecord
505 506

      # Omitted because no user, creator or author associated: `campaigns_imported_from_github`, `ldap_group_links`
507
      # rubocop: disable CodeReuse/ActiveRecord
508
      def usage_activity_by_stage_manage(time_period)
509 510 511 512 513 514
        {
          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' }
        }
515
      end
516
      # rubocop: enable CodeReuse/ActiveRecord
517

518
      # rubocop: disable CodeReuse/ActiveRecord
519
      def usage_activity_by_stage_monitor(time_period)
520 521 522 523 524 525 526
        {
          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)
        }
527
      end
528
      # rubocop: enable CodeReuse/ActiveRecord
529 530 531 532 533 534 535 536

      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`
537
      # rubocop: disable CodeReuse/ActiveRecord
538
      def usage_activity_by_stage_plan(time_period)
539 540 541 542
        {
          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),
543 544 545
          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))
546
        }
547
      end
548
      # rubocop: enable CodeReuse/ActiveRecord
549 550

      # Omitted because no user, creator or author associated: `environments`, `feature_flags`, `in_review_folder`, `pages_domains`
551
      # rubocop: disable CodeReuse/ActiveRecord
552
      def usage_activity_by_stage_release(time_period)
553 554 555 556 557 558
        {
          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)
        }
559
      end
560
      # rubocop: enable CodeReuse/ActiveRecord
561 562

      # Omitted because no user, creator or author associated: `ci_runners`
563
      # rubocop: disable CodeReuse/ActiveRecord
564
      def usage_activity_by_stage_verify(time_period)
565 566 567 568 569 570 571 572 573 574 575
        {
          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)
        }
576
      end
577
      # rubocop: enable CodeReuse/ActiveRecord
578 579 580 581 582 583 584 585

      # 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

586
      def analytics_unique_visits_data
587
        results = ::Gitlab::Analytics::UniqueVisits::ANALYTICS_IDS.each_with_object({}) do |target_id, hash|
588
          hash[target_id] = redis_usage_data { unique_visit_service.unique_visits_for(targets: target_id) }
589
        end
590 591
        results['analytics_unique_visits_for_any_target'] = redis_usage_data { unique_visit_service.unique_visits_for(targets: :analytics) }
        results['analytics_unique_visits_for_any_target_monthly'] = redis_usage_data { unique_visit_service.unique_visits_for(targets: :analytics, weeks: 4) }
592 593 594 595

        { analytics_unique_visits: results }
      end

596 597 598 599 600 601 602 603 604 605
      def compliance_unique_visits_data
        results = ::Gitlab::Analytics::UniqueVisits::COMPLIANCE_IDS.each_with_object({}) do |target_id, hash|
          hash[target_id] = redis_usage_data { unique_visit_service.unique_visits_for(targets: target_id) }
        end
        results['compliance_unique_visits_for_any_target'] = redis_usage_data { unique_visit_service.unique_visits_for(targets: :compliance) }
        results['compliance_unique_visits_for_any_target_monthly'] = redis_usage_data { unique_visit_service.unique_visits_for(targets: :compliance, weeks: 4) }

        { compliance_unique_visits: results }
      end

606 607 608 609
      def action_monthly_active_users(time_period)
        counter = Gitlab::UsageDataCounters::TrackUniqueActions

        project_count = redis_usage_data do
610
          counter.count_unique(
611 612 613 614 615 616 617
            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
618
          counter.count_unique(
619 620 621 622 623 624 625
            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
626
          counter.count_unique(
627 628 629 630 631 632 633 634 635 636 637 638 639
            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

640 641
      private

642 643 644 645 646 647 648 649 650
      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
651
        # rubocop: disable UsageData/LargeTable:
652
        projects_with_service_desk = ::Project.where(service_desk_enabled: true)
653
        # rubocop: enable UsageData/LargeTable:
654 655 656 657 658 659 660 661 662 663 664 665 666
        {
          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

667 668 669 670 671 672
      def unique_visit_service
        strong_memoize(:unique_visit_service) do
          ::Gitlab::Analytics::UniqueVisits.new
        end
      end

673 674 675 676
      def total_alert_issues
        # Remove prometheus table queries once they are deprecated
        # To be removed with https://gitlab.com/gitlab-org/gitlab/-/issues/217407.
        [
677 678 679
          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)
680 681 682
        ].reduce(:+)
      end

683 684 685 686 687 688 689 690 691 692 693 694
      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

695 696 697 698 699 700 701 702 703 704 705 706
      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

707 708 709 710 711 712 713 714 715 716 717 718
      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

719
      def clear_memoized
720 721
        clear_memoization(:issue_minimum_id)
        clear_memoization(:issue_maximum_id)
722 723
        clear_memoization(:user_minimum_id)
        clear_memoization(:user_maximum_id)
724
        clear_memoization(:unique_visit_service)
725 726
        clear_memoization(:deployment_minimum_id)
        clear_memoization(:deployment_maximum_id)
727 728
        clear_memoization(:approval_merge_request_rule_minimum_id)
        clear_memoization(:approval_merge_request_rule_maximum_id)
729
      end
730 731

      # rubocop: disable CodeReuse/ActiveRecord
732
      # rubocop: disable UsageData/DistinctCountByLargeForeignKey
733 734 735
      def cluster_applications_user_distinct_count(applications, time_period)
        distinct_count(applications.where(time_period).available.joins(:cluster), 'clusters.user_id')
      end
736
      # rubocop: enable UsageData/DistinctCountByLargeForeignKey
737 738 739 740 741

      def clusters_user_distinct_count(clusters, time_period)
        distinct_count(clusters.where(time_period), :user_id)
      end
      # rubocop: enable CodeReuse/ActiveRecord
742 743 744 745 746 747 748 749 750 751 752 753

      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
754 755 756 757

      def deployment_count(relation)
        count relation, start: deployment_minimum_id, finish: deployment_maximum_id
      end
758 759 760
    end
  end
end
761 762

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