cycle_analytics.rb 3.65 KB
Newer Older
1
class CycleAnalytics
2
  include Gitlab::Database::Median
3
  include Gitlab::Database::DateTime
4 5

  def initialize(project, from:)
6
    @project = project
7
    @from = from
8 9 10
  end

  def summary
11
    @summary ||= Summary.new(@project, from: @from)
12 13
  end

14
  def issue
15
    calculate_metric(:issue,
16 17 18
                     Issue.arel_table[:created_at],
                     [Issue::Metrics.arel_table[:first_associated_with_milestone_at],
                      Issue::Metrics.arel_table[:first_added_to_board_at]])
19 20 21
  end

  def plan
22
    calculate_metric(:plan,
23 24 25
                     [Issue::Metrics.arel_table[:first_associated_with_milestone_at],
                      Issue::Metrics.arel_table[:first_added_to_board_at]],
                     Issue::Metrics.arel_table[:first_mentioned_in_commit_at])
26
  end
27

28
  def code
29
    calculate_metric(:code,
30 31
                     Issue::Metrics.arel_table[:first_mentioned_in_commit_at],
                     MergeRequest.arel_table[:created_at])
32 33
  end

34
  def test
35
    calculate_metric(:test,
36 37
                     MergeRequest::Metrics.arel_table[:latest_build_started_at],
                     MergeRequest::Metrics.arel_table[:latest_build_finished_at])
38 39
  end

40
  def review
41
    calculate_metric(:review,
42 43
                     MergeRequest.arel_table[:created_at],
                     MergeRequest::Metrics.arel_table[:merged_at])
44 45
  end

46
  def staging
47
    calculate_metric(:staging,
48 49
                     MergeRequest::Metrics.arel_table[:merged_at],
                     MergeRequest::Metrics.arel_table[:first_deployed_to_production_at])
50 51
  end

52
  def production
53
    calculate_metric(:production,
54 55
                     Issue.arel_table[:created_at],
                     MergeRequest::Metrics.arel_table[:first_deployed_to_production_at])
56 57
  end

58 59
  private

60
  def calculate_metric(name, start_time_attrs, end_time_attrs)
61
    cte_table = Arel::Table.new("cte_table_for_#{name}")
62

63 64 65 66 67 68 69 70
    # 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.
    interval_query = Arel::Nodes::As.new(
      cte_table,
      subtract_datetimes(base_query, end_time_attrs, start_time_attrs, name.to_s))

71
    median_datetime(cte_table, interval_query, name)
72 73
  end

74 75 76 77
  # Join table with a row for every <issue,merge_request> pair (where the merge request
  # closes the given issue) with issue and merge request metrics included. The metrics
  # are loaded with an inner join, so issues / merge requests without metrics are
  # automatically excluded.
78
  def base_query
79
    arel_table = MergeRequestsClosingIssues.arel_table
80

81
    # Load issues
82 83 84 85 86
    query = arel_table.join(Issue.arel_table).on(Issue.arel_table[:id].eq(arel_table[:issue_id])).
            join(Issue::Metrics.arel_table).on(Issue.arel_table[:id].eq(Issue::Metrics.arel_table[:issue_id])).
            where(Issue.arel_table[:project_id].eq(@project.id)).
            where(Issue.arel_table[:deleted_at].eq(nil)).
            where(Issue.arel_table[:created_at].gteq(@from))
87

88
    # Load merge_requests
89 90 91 92
    query = query.join(MergeRequest.arel_table, Arel::Nodes::OuterJoin).
            on(MergeRequest.arel_table[:id].eq(arel_table[:merge_request_id])).
            join(MergeRequest::Metrics.arel_table).
            on(MergeRequest.arel_table[:id].eq(MergeRequest::Metrics.arel_table[:merge_request_id]))
93 94

    # Limit to merge requests that have been deployed to production after `@from`
95
    query.where(MergeRequest::Metrics.arel_table[:first_deployed_to_production_at].gteq(@from))
96
  end
97
end