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

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

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

61
      def recorded_at
62
        Time.current
63 64
      end

65
      # rubocop: disable Metrics/AbcSize
66
      # rubocop: disable CodeReuse/ActiveRecord
67
      def system_usage_data
68
        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)
69

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

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

192
      def cycle_analytics_usage_data
193
        Gitlab::CycleAnalytics::UsageData.new.to_json
194 195
      rescue ActiveRecord::StatementInvalid
        { avg_cycle_analytics: {} }
196 197
      end

198 199 200 201 202 203 204 205
      # 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

206 207 208 209 210 211
      def features_usage_data
        features_usage_data_ce
      end

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

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

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

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

309 310 311 312
      def topology_usage_data
        Gitlab::UsageData::Topology.new.topology_usage_data
      end

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

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

348
        # rubocop: disable UsageData/LargeTable
349 350 351 352 353
        %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
354
        # rubocop: enable UsageData/LargeTable
355 356 357 358 359 360 361

        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

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

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

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

399
      def jira_import_usage
400
        # rubocop: disable UsageData/LargeTable
401 402 403 404 405 406 407
        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 }
        }
408
        # rubocop: enable UsageData/LargeTable
409 410
      end

411 412 413 414 415
      # rubocop: disable CodeReuse/ActiveRecord
      # rubocop: disable UsageData/LargeTable
      def successful_deployments_with_cluster(scope)
        scope
          .joins(cluster: :deployments)
416
          .merge(::Clusters::Cluster.enabled)
417 418 419 420 421
          .merge(Deployment.success)
      end
      # rubocop: enable UsageData/LargeTable
      # rubocop: enable CodeReuse/ActiveRecord

422 423 424 425
      def user_preferences_usage
        {} # augmented in EE
      end

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

437 438 439 440 441 442 443
      def installation_type
        if Rails.env.production?
          Gitlab::INSTALLATION_TYPE
        else
          "gitlab-development-kit"
        end
      end
444

445
      def last_28_days_time_period
446 447
        { created_at: 28.days.ago..Time.current }
      end
448

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

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

      # Omitted because no user, creator or author associated: `campaigns_imported_from_github`, `ldap_group_links`
511
      # rubocop: disable CodeReuse/ActiveRecord
512
      def usage_activity_by_stage_manage(time_period)
513 514 515 516
        {
          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),
517 518 519 520 521 522 523 524 525 526 527
          omniauth_providers: filtered_omniauth_provider_names.reject { |name| name == 'group_saml' },
          projects_imported: {
            gitlab_project: projects_imported_count('gitlab_project', time_period),
            gitlab: projects_imported_count('gitlab', time_period),
            github: projects_imported_count('github', time_period),
            bitbucket: projects_imported_count('bitbucket', time_period),
            bitbucket_server: projects_imported_count('bitbucket_server', time_period),
            gitea: projects_imported_count('gitea', time_period),
            git: projects_imported_count('git', time_period),
            manifest: projects_imported_count('manifest', time_period)
          }
528
        }
529
      end
530
      # rubocop: enable CodeReuse/ActiveRecord
531

532
      # rubocop: disable CodeReuse/ActiveRecord
533
      def usage_activity_by_stage_monitor(time_period)
534 535 536 537 538 539 540
        {
          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)
        }
541
      end
542
      # rubocop: enable CodeReuse/ActiveRecord
543

544
      # rubocop: disable CodeReuse/ActiveRecord
545
      def usage_activity_by_stage_package(time_period)
546 547 548
        {
          projects_with_packages: distinct_count(::Project.with_packages.where(time_period), :creator_id)
        }
549
      end
550
      # rubocop: enable CodeReuse/ActiveRecord
551 552 553 554

      # 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`
555
      # rubocop: disable CodeReuse/ActiveRecord
556
      def usage_activity_by_stage_plan(time_period)
557 558 559 560
        {
          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),
561 562 563
          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))
564
        }
565
      end
566
      # rubocop: enable CodeReuse/ActiveRecord
567 568

      # Omitted because no user, creator or author associated: `environments`, `feature_flags`, `in_review_folder`, `pages_domains`
569
      # rubocop: disable CodeReuse/ActiveRecord
570
      def usage_activity_by_stage_release(time_period)
571 572 573 574 575 576
        {
          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)
        }
577
      end
578
      # rubocop: enable CodeReuse/ActiveRecord
579 580

      # Omitted because no user, creator or author associated: `ci_runners`
581
      # rubocop: disable CodeReuse/ActiveRecord
582
      def usage_activity_by_stage_verify(time_period)
583 584 585 586 587 588 589 590 591 592 593
        {
          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)
        }
594
      end
595
      # rubocop: enable CodeReuse/ActiveRecord
596 597 598 599 600 601 602 603

      # 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

604
      def analytics_unique_visits_data
605 606
        results = ::Gitlab::Analytics::UniqueVisits.analytics_events.each_with_object({}) do |target, hash|
          hash[target] = redis_usage_data { unique_visit_service.unique_visits_for(targets: target) }
607
        end
608
        results['analytics_unique_visits_for_any_target'] = redis_usage_data { unique_visit_service.unique_visits_for(targets: :analytics) }
609
        results['analytics_unique_visits_for_any_target_monthly'] = redis_usage_data { unique_visit_service.unique_visits_for(targets: :analytics, start_date: 4.weeks.ago.to_date, end_date: Date.current) }
610 611 612 613

        { analytics_unique_visits: results }
      end

614
      def compliance_unique_visits_data
615 616
        results = ::Gitlab::Analytics::UniqueVisits.compliance_events.each_with_object({}) do |target, hash|
          hash[target] = redis_usage_data { unique_visit_service.unique_visits_for(targets: target) }
617 618
        end
        results['compliance_unique_visits_for_any_target'] = redis_usage_data { unique_visit_service.unique_visits_for(targets: :compliance) }
619
        results['compliance_unique_visits_for_any_target_monthly'] = redis_usage_data { unique_visit_service.unique_visits_for(targets: :compliance, start_date: 4.weeks.ago.to_date, end_date: Date.current) }
620 621 622 623

        { compliance_unique_visits: results }
      end

624 625 626 627 628 629 630 631 632 633 634 635
      def search_unique_visits_data
        events = ::Gitlab::UsageDataCounters::HLLRedisCounter.events_for_category('search')
        results = events.each_with_object({}) do |event, hash|
          hash[event] = redis_usage_data { ::Gitlab::UsageDataCounters::HLLRedisCounter.unique_events(event_names: event, start_date: 7.days.ago.to_date, end_date: Date.current) }
        end

        results['search_unique_visits_for_any_target_weekly'] = redis_usage_data { ::Gitlab::UsageDataCounters::HLLRedisCounter.unique_events(event_names: events, start_date: 7.days.ago.to_date, end_date: Date.current) }
        results['search_unique_visits_for_any_target_monthly'] = redis_usage_data { ::Gitlab::UsageDataCounters::HLLRedisCounter.unique_events(event_names: events, start_date: 4.weeks.ago.to_date, end_date: Date.current) }

        { search_unique_visits: results }
      end

636
      def action_monthly_active_users(time_period)
637
        counter = Gitlab::UsageDataCounters::TrackUniqueEvents
638 639

        project_count = redis_usage_data do
640 641
          counter.count_unique_events(
            event_action: Gitlab::UsageDataCounters::TrackUniqueEvents::PUSH_ACTION,
642 643 644 645 646 647
            date_from: time_period[:created_at].first,
            date_to: time_period[:created_at].last
          )
        end

        design_count = redis_usage_data do
648 649
          counter.count_unique_events(
            event_action: Gitlab::UsageDataCounters::TrackUniqueEvents::DESIGN_ACTION,
650 651 652 653 654 655
            date_from: time_period[:created_at].first,
            date_to: time_period[:created_at].last
          )
        end

        wiki_count = redis_usage_data do
656 657
          counter.count_unique_events(
            event_action: Gitlab::UsageDataCounters::TrackUniqueEvents::WIKI_ACTION,
658 659 660 661 662 663 664 665 666 667 668 669
            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

670 671
      private

672 673 674 675 676 677 678 679 680
      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
681
        # rubocop: disable UsageData/LargeTable:
682
        projects_with_service_desk = ::Project.where(service_desk_enabled: true)
683
        # rubocop: enable UsageData/LargeTable:
684 685 686 687 688 689 690 691 692 693 694 695 696
        {
          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

697 698 699 700 701 702
      def unique_visit_service
        strong_memoize(:unique_visit_service) do
          ::Gitlab::Analytics::UniqueVisits.new
        end
      end

703 704 705 706
      def total_alert_issues
        # Remove prometheus table queries once they are deprecated
        # To be removed with https://gitlab.com/gitlab-org/gitlab/-/issues/217407.
        [
707 708 709
          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)
710 711 712
        ].reduce(:+)
      end

713 714 715 716 717 718 719 720 721 722 723 724
      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

725 726 727 728 729 730 731 732 733 734 735 736
      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

737 738 739 740 741 742 743 744 745 746 747 748
      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

749 750 751 752 753 754 755 756 757 758 759 760
      def project_minimum_id
        strong_memoize(:project_minimum_id) do
          ::Project.minimum(:id)
        end
      end

      def project_maximum_id
        strong_memoize(:project_maximum_id) do
          ::Project.maximum(:id)
        end
      end

761
      def clear_memoized
762 763
        clear_memoization(:issue_minimum_id)
        clear_memoization(:issue_maximum_id)
764 765
        clear_memoization(:user_minimum_id)
        clear_memoization(:user_maximum_id)
766
        clear_memoization(:unique_visit_service)
767 768
        clear_memoization(:deployment_minimum_id)
        clear_memoization(:deployment_maximum_id)
769 770
        clear_memoization(:approval_merge_request_rule_minimum_id)
        clear_memoization(:approval_merge_request_rule_maximum_id)
771 772
        clear_memoization(:project_minimum_id)
        clear_memoization(:project_maximum_id)
773
      end
774 775

      # rubocop: disable CodeReuse/ActiveRecord
776
      # rubocop: disable UsageData/DistinctCountByLargeForeignKey
777 778 779
      def cluster_applications_user_distinct_count(applications, time_period)
        distinct_count(applications.where(time_period).available.joins(:cluster), 'clusters.user_id')
      end
780
      # rubocop: enable UsageData/DistinctCountByLargeForeignKey
781 782 783 784 785

      def clusters_user_distinct_count(clusters, time_period)
        distinct_count(clusters.where(time_period), :user_id)
      end
      # rubocop: enable CodeReuse/ActiveRecord
786 787 788 789 790 791 792 793 794 795 796 797

      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
798 799 800 801

      def deployment_count(relation)
        count relation, start: deployment_minimum_id, finish: deployment_maximum_id
      end
802 803 804 805

      def projects_imported_count(from, time_period)
        distinct_count(::Project.imported_from(from).where(time_period), :creator_id) # rubocop: disable CodeReuse/ActiveRecord
      end
806 807 808
    end
  end
end
809 810

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