usage_data.rb 36.2 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
        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)
67

68 69
        {
          counts: {
70 71 72 73 74 75 76 77 78 79 80 81 82
            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),
83
            # rubocop: disable UsageData/LargeTable:
84 85 86
            deployments: deployment_count(Deployment),
            successful_deployments: deployment_count(Deployment.success),
            failed_deployments: deployment_count(Deployment.failed),
87
            # rubocop: enable UsageData/LargeTable:
88 89 90
            environments: count(::Environment),
            clusters: count(::Clusters::Cluster),
            clusters_enabled: count(::Clusters::Cluster.enabled),
91 92
            project_clusters_enabled: count(::Clusters::Cluster.enabled.project_type),
            group_clusters_enabled: count(::Clusters::Cluster.enabled.group_type),
93
            instance_clusters_enabled: count(::Clusters::Cluster.enabled.instance_type),
94
            clusters_disabled: count(::Clusters::Cluster.disabled),
95 96
            project_clusters_disabled: count(::Clusters::Cluster.disabled.project_type),
            group_clusters_disabled: count(::Clusters::Cluster.disabled.group_type),
97
            instance_clusters_disabled: count(::Clusters::Cluster.disabled.instance_type),
98 99
            clusters_platforms_eks: count(::Clusters::Cluster.aws_installed.enabled),
            clusters_platforms_gke: count(::Clusters::Cluster.gcp_installed.enabled),
100
            clusters_platforms_user: count(::Clusters::Cluster.user_provided.enabled),
101 102 103
            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),
104
            clusters_applications_crossplane: count(::Clusters::Applications::Crossplane.available),
105 106 107
            clusters_applications_prometheus: count(::Clusters::Applications::Prometheus.available),
            clusters_applications_runner: count(::Clusters::Applications::Runner.available),
            clusters_applications_knative: count(::Clusters::Applications::Knative.available),
108
            clusters_applications_elastic_stack: count(::Clusters::Applications::ElasticStack.available),
109
            clusters_applications_jupyter: count(::Clusters::Applications::Jupyter.available),
110
            clusters_applications_cilium: count(::Clusters::Applications::Cilium.available),
111
            clusters_management_project: count(::Clusters::Cluster.with_management_project),
112
            in_review_folder: count(::Environment.in_review_folder),
113
            grafana_integrated_projects: count(GrafanaIntegration.enabled),
114
            groups: count(Group),
115
            issues: count(Issue, start: issue_minimum_id, finish: issue_maximum_id),
116
            issues_created_from_gitlab_error_tracking_ui: count(SentryIssue),
117
            issues_with_associated_zoom_link: count(ZoomMeeting.added_to_issue),
118
            issues_using_zoom_quick_actions: distinct_count(ZoomMeeting, :issue_id),
119
            issues_with_embedded_grafana_charts_approx: grafana_embed_usage_data,
120 121 122
            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,
123 124
            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),
125
            incident_labeled_issues: count(::Issue.with_label_attributes(::IncidentManagement::CreateIncidentLabelService::LABEL_PROPERTIES), start: issue_minimum_id, finish: issue_maximum_id),
126 127 128 129 130 131
            keys: count(Key),
            label_lists: count(List.label),
            lfs_objects: count(LfsObject),
            milestone_lists: count(List.milestone),
            milestones: count(Milestone),
            pages_domains: count(PagesDomain),
132
            pool_repositories: count(PoolRepository),
133 134
            projects: count(Project),
            projects_imported_from_github: count(Project.where(import_type: 'github')),
135
            projects_with_repositories_enabled: count(ProjectFeature.where('repository_access_level > ?', ProjectFeature::DISABLED)),
L
Logan King 已提交
136
            projects_with_error_tracking_enabled: count(::ErrorTracking::ProjectErrorTrackingSetting.where(enabled: true)),
137
            projects_with_alerts_service_enabled: count(AlertsService.active),
138
            projects_with_prometheus_alerts: distinct_count(PrometheusAlert, :project_id),
139
            projects_with_terraform_reports: distinct_count(::Ci::JobArtifact.terraform_reports, :project_id),
140
            projects_with_terraform_states: distinct_count(::Terraform::State, :project_id),
141
            protected_branches: count(ProtectedBranch),
142
            protected_branches_except_default: count(ProtectedBranch.where.not(name: ['main', 'master', Gitlab::CurrentSettings.default_branch_name])),
143 144
            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 360
          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))
361
          response["projects_inheriting_instance_#{service_name}_active".to_sym] = count(Service.active.where.not(inherit_from_id: nil).where(type: "#{service_name}_service".camelize))
362
        end.merge(jira_usage, jira_import_usage)
363
        # rubocop: enable UsageData/LargeTable:
364 365 366 367 368
      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
369 370
        results = {
          projects_jira_server_active: 0,
371
          projects_jira_cloud_active: 0
372
        }
373

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

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

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

404 405 406 407 408 409 410 411 412 413 414
      # 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

415 416 417 418
      def user_preferences_usage
        {} # augmented in EE
      end

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

430 431 432 433 434 435 436
      def installation_type
        if Rails.env.production?
          Gitlab::INSTALLATION_TYPE
        else
          "gitlab-development-kit"
        end
      end
437

438
      def last_28_days_time_period
439 440
        { created_at: 28.days.ago..Time.current }
      end
441

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

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

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

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

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

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

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

      # 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

583 584
      def analytics_unique_visits_data
        results = ::Gitlab::Analytics::UniqueVisits::TARGET_IDS.each_with_object({}) do |target_id, hash|
585
          hash[target_id] = redis_usage_data { unique_visit_service.unique_visits_for(targets: target_id) }
586
        end
587 588
        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) }
589 590 591 592

        { analytics_unique_visits: results }
      end

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 626 627 628
      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

629 630
      private

631 632 633 634 635 636 637 638 639
      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
640
        # rubocop: disable UsageData/LargeTable:
641
        projects_with_service_desk = ::Project.where(service_desk_enabled: true)
642
        # rubocop: enable UsageData/LargeTable:
643 644 645 646 647 648 649 650 651 652 653 654 655
        {
          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

656 657 658 659 660 661
      def unique_visit_service
        strong_memoize(:unique_visit_service) do
          ::Gitlab::Analytics::UniqueVisits.new
        end
      end

662 663 664 665
      def total_alert_issues
        # Remove prometheus table queries once they are deprecated
        # To be removed with https://gitlab.com/gitlab-org/gitlab/-/issues/217407.
        [
666 667 668
          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)
669 670 671
        ].reduce(:+)
      end

672 673 674 675 676 677 678 679 680 681 682 683
      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

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

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

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

      # 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
729 730 731 732 733 734 735 736 737 738 739 740

      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
741 742 743 744

      def deployment_count(relation)
        count relation, start: deployment_minimum_id, finish: deployment_maximum_id
      end
745 746 747
    end
  end
end
748 749

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