usage_data.rb 26.7 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 13
# 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] }

14 15
module Gitlab
  class UsageData
16
    BATCH_SIZE = 100
17

18
    class << self
19
      include Gitlab::Utils::UsageData
20
      include Gitlab::Utils::StrongMemoize
21

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

      def uncached_data
29
        clear_memoized
30

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

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

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

62 63 64 65
      def recorded_at
        Time.now
      end

66
      # rubocop: disable Metrics/AbcSize
67
      # rubocop: disable CodeReuse/ActiveRecord
68
      def system_usage_data
69
        alert_bot_incident_count = count(::Issue.authored(::User.alert_bot))
70
        issues_created_manually_from_alerts = count(Issue.with_alert_management_alerts.not_authored_by(::User.alert_bot))
71

72 73
        {
          counts: {
74 75 76 77 78 79 80 81 82 83 84 85 86 87
            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),
            deployments: count(Deployment),
88 89
            successful_deployments: count(Deployment.success),
            failed_deployments: count(Deployment.failed),
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_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 116
            groups: count(Group),
            issues: count(Issue),
117
            issues_created_from_gitlab_error_tracking_ui: count(SentryIssue),
118
            issues_with_associated_zoom_link: count(ZoomMeeting.added_to_issue),
119
            issues_using_zoom_quick_actions: distinct_count(ZoomMeeting, :issue_id),
120
            issues_with_embedded_grafana_charts_approx: grafana_embed_usage_data,
121 122 123
            issues_created_from_alerts: total_alert_issues,
            issues_created_gitlab_alerts: issues_created_manually_from_alerts,
            issues_created_manually_from_alerts: issues_created_manually_from_alerts,
124 125
            incident_issues: alert_bot_incident_count,
            alert_bot_incident_issues: alert_bot_incident_count,
126
            incident_labeled_issues: count(::Issue.with_label_attributes(IncidentManagement::CreateIncidentLabelService::LABEL_PROPERTIES)),
127 128 129 130 131 132
            keys: count(Key),
            label_lists: count(List.label),
            lfs_objects: count(LfsObject),
            milestone_lists: count(List.milestone),
            milestones: count(Milestone),
            pages_domains: count(PagesDomain),
133
            pool_repositories: count(PoolRepository),
134 135
            projects: count(Project),
            projects_imported_from_github: count(Project.where(import_type: 'github')),
136
            projects_with_repositories_enabled: count(ProjectFeature.where('repository_access_level > ?', ProjectFeature::DISABLED)),
L
Logan King 已提交
137
            projects_with_error_tracking_enabled: count(::ErrorTracking::ProjectErrorTrackingSetting.where(enabled: true)),
138
            projects_with_alerts_service_enabled: count(AlertsService.active),
139
            projects_with_prometheus_alerts: distinct_count(PrometheusAlert, :project_id),
140
            projects_with_terraform_reports: distinct_count(::Ci::JobArtifact.terraform_reports, :project_id),
141
            projects_with_terraform_states: distinct_count(::Terraform::State, :project_id),
142 143 144
            protected_branches: count(ProtectedBranch),
            releases: count(Release),
            remote_mirrors: count(RemoteMirror),
145 146
            personal_snippets: count(PersonalSnippet),
            project_snippets: count(ProjectSnippet),
147
            suggestions: count(Suggestion),
148
            terraform_reports: count(::Ci::JobArtifact.terraform_reports),
149
            terraform_states: count(::Terraform::State),
150
            todos: count(Todo),
151
            uploads: count(Upload),
152 153 154 155
            web_hooks: count(WebHook),
            labels: count(Label),
            merge_requests: count(MergeRequest),
            notes: count(Note)
156 157 158
          }.merge(
            services_usage,
            usage_counters,
159
            user_preferences_usage,
160
            ingress_modsecurity_usage,
161
            container_expiration_policies_usage
162 163 164
          ).tap do |data|
            data[:snippets] = data[:personal_snippets] + data[:project_snippets]
          end
165
        }
166
      end
167
      # rubocop: enable Metrics/AbcSize
168

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

184
      def cycle_analytics_usage_data
185
        Gitlab::CycleAnalytics::UsageData.new.to_json
186 187
      rescue ActiveRecord::StatementInvalid
        { avg_cycle_analytics: {} }
188 189
      end

190 191 192 193 194 195 196 197
      # 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

198 199 200 201 202 203
      def features_usage_data
        features_usage_data_ce
      end

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

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

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

243 244
      def components_usage_data
        {
245
          git: { version: alt_usage_data(fallback: { major: -1 }) { Gitlab::Git.version } },
246 247 248
          gitaly: {
            version: alt_usage_data { Gitaly::Server.all.first.server_version },
            servers: alt_usage_data { Gitaly::Server.count },
249
            clusters: alt_usage_data { Gitaly::Server.gitaly_clusters },
250
            filesystems: alt_usage_data(fallback: ["-1"]) { Gitaly::Server.filesystems }
251 252
          },
          gitlab_pages: {
253
            enabled: alt_usage_data(fallback: nil) { Gitlab.config.pages.enabled },
254 255 256 257 258 259
            version: alt_usage_data { Gitlab::Pages::VERSION }
          },
          database: {
            adapter: alt_usage_data { Gitlab::Database.adapter_name },
            version: alt_usage_data { Gitlab::Database.version }
          },
260
          app_server: { type: app_server_type }
261
        }
262
      end
263

264 265 266 267 268 269 270 271
      def app_server_type
        Gitlab::Runtime.identify.to_s
      rescue Gitlab::Runtime::IdentificationError => e
        Gitlab::AppLogger.error(e.message)
        Gitlab::ErrorTracking.track_exception(e)
        'unknown_app_server_type'
      end

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 305
      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

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

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 330
      end

331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353
      # 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)
        base = ::ContainerExpirationPolicy.active
        results[:projects_with_expiration_policy_enabled] = distinct_count(base, :project_id, start: start, finish: finish)

        %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

        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
      # rubocop: enable CodeReuse/ActiveRecord

354
      # rubocop: disable CodeReuse/ActiveRecord
355
      def services_usage
356
        Service.available_services_names.without('jira').each_with_object({}) do |service_name, response|
357
          response["projects_#{service_name}_active".to_sym] = count(Service.active.where(template: false, type: "#{service_name}_service".camelize))
358
        end.merge(jira_usage).merge(jira_import_usage)
359 360 361 362 363 364
      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

365 366 367
        results = {
          projects_jira_server_active: 0,
          projects_jira_cloud_active: 0,
368
          projects_jira_active: 0
369
        }
370

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

          results[:projects_jira_server_active] += counts[:server].count if counts[:server]
          results[:projects_jira_cloud_active] += counts[:cloud].count if counts[:cloud]
383
          results[:projects_jira_active] += services.size
384 385 386
        end

        results
387
      rescue ActiveRecord::StatementInvalid
388
        { projects_jira_server_active: FALLBACK, projects_jira_cloud_active: FALLBACK, projects_jira_active: FALLBACK }
389
      end
390 391 392 393 394 395 396

      def successful_deployments_with_cluster(scope)
        scope
          .joins(cluster: :deployments)
          .merge(Clusters::Cluster.enabled)
          .merge(Deployment.success)
      end
397
      # rubocop: enable CodeReuse/ActiveRecord
398

399 400 401 402 403 404 405 406 407 408
      def jira_import_usage
        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 }
        }
      end

409 410 411 412
      def user_preferences_usage
        {} # augmented in EE
      end

413
      # rubocop: disable CodeReuse/ActiveRecord
414
      def merge_requests_users(time_period)
415 416 417 418 419
        query =
          Event
            .where(target_type: Event::TARGET_TYPES[:merge_request].to_s)
            .where(time_period)

420
        distinct_count(
421 422 423 424 425 426 427 428 429
          query,
          :author_id,
          batch_size: 5_000, # Based on query performance, this is the optimal batch size.
          start: User.minimum(:id),
          finish: User.maximum(:id)
        )
      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 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482
      # 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
      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
      # rubocop: enable CodeReuse/ActiveRecord

      def usage_activity_by_stage_create(time_period)
483 484 485
        {}.tap do |h|
          h[:merge_requests_users] = merge_requests_users(time_period) if time_period.present?
        end
486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524
      end

      # Omitted because no user, creator or author associated: `campaigns_imported_from_github`, `ldap_group_links`
      def usage_activity_by_stage_manage(time_period)
        {}
      end

      def usage_activity_by_stage_monitor(time_period)
        {}
      end

      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`
      def usage_activity_by_stage_plan(time_period)
        {}
      end

      # Omitted because no user, creator or author associated: `environments`, `feature_flags`, `in_review_folder`, `pages_domains`
      def usage_activity_by_stage_release(time_period)
        {}
      end

      # Omitted because no user, creator or author associated: `ci_runners`
      def usage_activity_by_stage_verify(time_period)
        {}
      end

      # 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

525 526 527 528 529 530 531 532 533
      def analytics_unique_visits_data
        results = ::Gitlab::Analytics::UniqueVisits::TARGET_IDS.each_with_object({}) do |target_id, hash|
          hash[target_id] = redis_usage_data { unique_visit_service.weekly_unique_visits_for_target(target_id) }
        end
        results['analytics_unique_visits_for_any_target'] = redis_usage_data { unique_visit_service.weekly_unique_visits_for_any_target }

        { analytics_unique_visits: results }
      end

534 535
      private

536 537 538 539 540 541
      def unique_visit_service
        strong_memoize(:unique_visit_service) do
          ::Gitlab::Analytics::UniqueVisits.new
        end
      end

542 543 544 545 546 547 548 549 550 551
      def total_alert_issues
        # Remove prometheus table queries once they are deprecated
        # To be removed with https://gitlab.com/gitlab-org/gitlab/-/issues/217407.
        [
          count(Issue.with_alert_management_alerts),
          count(::Issue.with_self_managed_prometheus_alert_events),
          count(::Issue.with_prometheus_alert_events)
        ].reduce(:+)
      end

552 553 554 555 556 557 558 559 560 561 562 563
      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

564 565 566 567 568 569 570 571 572 573 574 575
      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

576
      def clear_memoized
577 578
        clear_memoization(:user_minimum_id)
        clear_memoization(:user_maximum_id)
579
        clear_memoization(:unique_visit_service)
580
      end
581 582 583 584 585 586 587 588 589 590

      # 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
591 592 593
    end
  end
end
594 595

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