summaryrefslogtreecommitdiff
path: root/lib/gitlab/cycle_analytics/base_stage.rb
blob: e641bed704bd2510ed873b786429bf9947720901 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
# frozen_string_literal: true

module Gitlab
  module CycleAnalytics
    class BaseStage
      include BaseQuery
      include BaseDataExtraction

      attr_reader :options

      def initialize(options:)
        @options = options
      end

      def events
        event_fetcher.fetch
      end

      def as_json(serializer: AnalyticsStageSerializer)
        serializer.new.represent(self)
      end

      def title
        raise NotImplementedError.new("Expected #{self.name} to implement title")
      end

      def project_median
        return if project.nil?

        BatchLoader.for(project.id).batch(key: name) do |project_ids, loader|
          if project_ids.one?
            loader.call(project.id, median_query(project_ids))
          else
            begin
              median_datetimes(cte_table, interval_query(project_ids), name, :project_id)&.each do |project_id, median|
                loader.call(project_id, median)
              end
            rescue NotSupportedError
              {}
            end
          end
        end
      end

      def group_median
        median_query(projects.map(&:id))
      end

      def median_query(project_ids)
        # Build a `SELECT` query. We find the first of the `end_time_attrs` that isn't `NULL` (call this end_time).
        # Next, we find the first of the start_time_attrs that isn't `NULL` (call this start_time).
        # We compute the (end_time - start_time) interval, and give it an alias based on the current
        # cycle analytics stage.

        median_datetime(cte_table, interval_query(project_ids), name)
      end

      def name
        raise NotImplementedError.new("Expected #{self.name} to implement name")
      end

      def cte_table
        Arel::Table.new("cte_table_for_#{name}")
      end

      def interval_query(project_ids)
        Arel::Nodes::As.new(cte_table,
          subtract_datetimes(stage_query(project_ids), start_time_attrs, end_time_attrs, name.to_s))
      end

      private

      def event_fetcher
        @event_fetcher ||= Gitlab::CycleAnalytics::EventFetcher[name].new(stage: name,
                                                                          options: event_options)
      end

      def event_options
        options.merge(start_time_attrs: start_time_attrs, end_time_attrs: end_time_attrs)
      end
    end
  end
end