Commit b214be49 authored by James Lopez's avatar James Lopez

big refactor based on MR feedback

parent daa4f3de
...@@ -32,7 +32,7 @@ class CycleAnalytics ...@@ -32,7 +32,7 @@ class CycleAnalytics
def stats_per_stage def stats_per_stage
STAGES.map do |stage_name| STAGES.map do |stage_name|
Gitlab::CycleAnalytics::Stage[stage_name].new(project: @project, options: @options).median_data self[stage_name].new(project: @project, options: @options).median_data
end end
end end
end end
module Gitlab module Gitlab
module CycleAnalytics module CycleAnalytics
class BaseEvent class BaseEventFetcher
include MetricsTables include MetricsTables
include ClassNameUtil
attr_reader :start_time_attrs, :end_time_attrs, :projections, :query attr_reader :projections, :query, :stage
def initialize(fetcher:, options:) def initialize(fetcher:, options:, stage:)
@fetcher = fetcher @fetcher = fetcher
@project = fetcher.project @project = fetcher.project
@options = options @options = options
@stage = stage
end end
def fetch def fetch
...@@ -26,10 +26,6 @@ module Gitlab ...@@ -26,10 +26,6 @@ module Gitlab
@order || @start_time_attrs @order || @start_time_attrs
end end
def stage
class_name_for('Event')
end
private private
def update_author! def update_author!
......
module Gitlab module Gitlab
module CycleAnalytics module CycleAnalytics
class BaseStage class BaseStage
include ClassNameUtil attr_accessor :start_time_attrs, :end_time_attrs
def initialize(project:, options:) def initialize(project:, options:)
@project = project @project = project
@options = options @options = options
@fetcher = Gitlab::CycleAnalytics::MetricsFetcher.new(project: project, @fetcher = Gitlab::CycleAnalytics::MetricsFetcher.new(project: project,
from: options[:from], from: options[:from],
branch: options[:branch]) branch: options[:branch],
stage: self)
end
def event
@event ||= Gitlab::CycleAnalytics::Event[stage].new(fetcher: @fetcher, options: @options)
end end
def events def events
Gitlab::CycleAnalytics::Event[stage].new(fetcher: @fetcher, options: @options).fetch event.fetch
end end
def median_data def median_data
...@@ -24,7 +29,7 @@ module Gitlab ...@@ -24,7 +29,7 @@ module Gitlab
end end
def median def median
raise NotImplementedError.new("Expected #{self.name} to implement median") @fetcher.median
end end
private private
......
module Gitlab
module CycleAnalytics
module ClassNameUtil
def class_name_for(type)
class_name.split(type).first.to_sym
end
def class_name
self.class.name.demodulize
end
end
end
end
module Gitlab module Gitlab
module CycleAnalytics module CycleAnalytics
class ReviewEvent < BaseEvent class CodeEventFetcher < BaseEventFetcher
include MergeRequestAllowed include MergeRequestAllowed
def initialize(*args) def initialize(*args)
@start_time_attrs = mr_table[:created_at]
@end_time_attrs = mr_metrics_table[:merged_at]
@projections = [mr_table[:title], @projections = [mr_table[:title],
mr_table[:iid], mr_table[:iid],
mr_table[:id], mr_table[:id],
mr_table[:created_at], mr_table[:created_at],
mr_table[:state], mr_table[:state],
mr_table[:author_id]] mr_table[:author_id]]
@order = mr_table[:created_at]
super(*args) super(*args)
end end
private
def serialize(event) def serialize(event)
AnalyticsMergeRequestSerializer.new(project: @project).represent(event).as_json AnalyticsMergeRequestSerializer.new(project: @project).represent(event).as_json
end end
......
module Gitlab module Gitlab
module CycleAnalytics module CycleAnalytics
class CodeStage < BaseStage class CodeStage < BaseStage
def description def initialize(*args)
"Time until first merge request" @start_time_attrs = issue_metrics_table[:first_mentioned_in_commit_at]
@end_time_attrs = mr_table[:created_at]
super(*args)
end end
def median def stage
@fetcher.median(:code, :code
Issue::Metrics.arel_table[:first_mentioned_in_commit_at], end
MergeRequest.arel_table[:created_at])
def description
"Time until first merge request"
end end
end end
end end
......
...@@ -2,7 +2,7 @@ module Gitlab ...@@ -2,7 +2,7 @@ module Gitlab
module CycleAnalytics module CycleAnalytics
module Event module Event
def self.[](stage_name) def self.[](stage_name)
const_get("::Gitlab::CycleAnalytics::#{stage_name.to_s.camelize}Event") CycleAnalytics.const_get("#{stage_name.to_s.camelize}Event")
end end
end end
end end
......
module Gitlab module Gitlab
module CycleAnalytics module CycleAnalytics
class ProductionEvent < BaseEvent class IssueEventFetcher < BaseEventFetcher
include IssueAllowed include IssueAllowed
def initialize(*args) def initialize(*args)
@start_time_attrs = issue_table[:created_at]
@end_time_attrs = mr_metrics_table[:first_deployed_to_production_at]
@projections = [issue_table[:title], @projections = [issue_table[:title],
issue_table[:iid], issue_table[:iid],
issue_table[:id], issue_table[:id],
......
module Gitlab module Gitlab
module CycleAnalytics module CycleAnalytics
class IssueStage < BaseStage class IssueStage < BaseStage
def description def initialize(*args)
"Time before an issue gets scheduled" @start_time_attrs = issue_table[:created_at]
@end_time_attrs = [issue_metrics_table[:first_associated_with_milestone_at],
issue_metrics_table[:first_added_to_board_at]]
super(*args)
end end
def median def stage
@fetcher.median(:issue, :issue
Issue.arel_table[:created_at], end
[Issue::Metrics.arel_table[:first_associated_with_milestone_at],
Issue::Metrics.arel_table[:first_added_to_board_at]]) def description
"Time before an issue gets scheduled"
end end
end end
end end
......
...@@ -9,14 +9,15 @@ module Gitlab ...@@ -9,14 +9,15 @@ module Gitlab
DEPLOYMENT_METRIC_STAGES = %i[production staging] DEPLOYMENT_METRIC_STAGES = %i[production staging]
def initialize(project:, from:, branch:) def initialize(project:, from:, branch:, stage:)
@project = project @project = project
@from = from @from = from
@branch = branch @branch = branch
@stage = stage
end end
def median(name, start_time_attrs, end_time_attrs) def median
cte_table = Arel::Table.new("cte_table_for_#{name}") cte_table = Arel::Table.new("cte_table_for_#{@stage.stage}")
# Build a `SELECT` query. We find the first of the `end_time_attrs` that isn't `NULL` (call this end_time). # 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). # Next, we find the first of the start_time_attrs that isn't `NULL` (call this start_time).
...@@ -24,24 +25,26 @@ module Gitlab ...@@ -24,24 +25,26 @@ module Gitlab
# cycle analytics stage. # cycle analytics stage.
interval_query = Arel::Nodes::As.new( interval_query = Arel::Nodes::As.new(
cte_table, cte_table,
subtract_datetimes(base_query_for(name), start_time_attrs, end_time_attrs, name.to_s)) subtract_datetimes(base_query_for(name), @stage.start_time_attrs, @stage.end_time_attrs, @stage.stage.to_s))
median_datetime(cte_table, interval_query, name) median_datetime(cte_table, interval_query, name)
end end
def events(stage_class) def events
ActiveRecord::Base.connection.exec_query(events_query(stage_class).to_sql) ActiveRecord::Base.connection.exec_query(events_query.to_sql)
end end
private private
def events_query(stage_class) def events_query
base_query = base_query_for(stage_class.stage) base_query = base_query_for(@stage.stage)
diff_fn = subtract_datetimes_diff(base_query, stage_class.start_time_attrs, stage_class.end_time_attrs) event = @stage.event
stage_class.custom_query(base_query) diff_fn = subtract_datetimes_diff(base_query, @stage.start_time_attrs, @stage.end_time_attrs)
base_query.project(extract_diff_epoch(diff_fn).as('total_time'), *stage_class.projections).order(stage_class.order.desc) event_instance.custom_query(base_query)
base_query.project(extract_diff_epoch(diff_fn).as('total_time'), *event.projections).order(event.order.desc)
end end
# Join table with a row for every <issue,merge_request> pair (where the merge request # Join table with a row for every <issue,merge_request> pair (where the merge request
......
module Gitlab module Gitlab
module CycleAnalytics module CycleAnalytics
class PlanEvent < BaseEvent class PlanEventFetcher < BaseEventFetcher
def initialize(*args) def initialize(*args)
@start_time_attrs = issue_metrics_table[:first_associated_with_milestone_at]
@end_time_attrs = [issue_metrics_table[:first_added_to_board_at],
issue_metrics_table[:first_mentioned_in_commit_at]]
@projections = [mr_diff_table[:st_commits].as('commits'), @projections = [mr_diff_table[:st_commits].as('commits'),
issue_metrics_table[:first_mentioned_in_commit_at]] issue_metrics_table[:first_mentioned_in_commit_at]]
......
module Gitlab module Gitlab
module CycleAnalytics module CycleAnalytics
class PlanStage < BaseStage class PlanStage < BaseStage
def description def initialize(*args)
"Time before an issue starts implementation" @start_time_attrs = [issue_metrics_table[:first_associated_with_milestone_at],
issue_metrics_table[:first_added_to_board_at]]
@end_time_attrs = issue_metrics_table[:first_mentioned_in_commit_at]
super(*args)
end end
def median def stage
@fetcher.median(:plan, :code
[Issue::Metrics.arel_table[:first_associated_with_milestone_at], end
Issue::Metrics.arel_table[:first_added_to_board_at]],
Issue::Metrics.arel_table[:first_mentioned_in_commit_at]) def description
"Time before an issue starts implementation"
end end
end end
end end
......
module Gitlab module Gitlab
module CycleAnalytics module CycleAnalytics
class IssueEvent < BaseEvent class ProductionEventFetcher < BaseEventFetcher
include IssueAllowed include IssueAllowed
def initialize(*args) def initialize(*args)
@start_time_attrs = issue_table[:created_at]
@end_time_attrs = [issue_metrics_table[:first_associated_with_milestone_at],
issue_metrics_table[:first_added_to_board_at]]
@projections = [issue_table[:title], @projections = [issue_table[:title],
issue_table[:iid], issue_table[:iid],
issue_table[:id], issue_table[:id],
......
module Gitlab module Gitlab
module CycleAnalytics module CycleAnalytics
class ProductionStage < BaseStage class ProductionStage < BaseStage
def description def initialize(*args)
"From issue creation until deploy to production" @start_time_attrs = issue_table[:created_at]
@end_time_attrs = mr_metrics_table[:first_deployed_to_production_at]
super(*args)
end end
def median def stage
@fetcher.median(:production, :production
Issue.arel_table[:created_at], end
MergeRequest::Metrics.arel_table[:first_deployed_to_production_at])
def description
"From issue creation until deploy to production"
end end
end end
end end
......
module Gitlab module Gitlab
module CycleAnalytics module CycleAnalytics
class CodeEvent < BaseEvent class ReviewEventFetcher < BaseEventFetcher
include MergeRequestAllowed include MergeRequestAllowed
def initialize(*args) def initialize(*args)
@start_time_attrs = issue_metrics_table[:first_mentioned_in_commit_at]
@end_time_attrs = mr_table[:created_at]
@projections = [mr_table[:title], @projections = [mr_table[:title],
mr_table[:iid], mr_table[:iid],
mr_table[:id], mr_table[:id],
mr_table[:created_at], mr_table[:created_at],
mr_table[:state], mr_table[:state],
mr_table[:author_id]] mr_table[:author_id]]
@order = mr_table[:created_at]
super(*args) super(*args)
end end
private
def serialize(event) def serialize(event)
AnalyticsMergeRequestSerializer.new(project: @project).represent(event).as_json AnalyticsMergeRequestSerializer.new(project: @project).represent(event).as_json
end end
......
module Gitlab module Gitlab
module CycleAnalytics module CycleAnalytics
class ReviewStage < BaseStage class ReviewStage < BaseStage
def description def initialize(*args)
"Time between merge request creation and merge/close" @start_time_attrs = mr_table[:created_at]
@end_time_attrs = mr_metrics_table[:merged_at]
super(*args)
end end
def median def stage
@fetcher.median(:review, :review
MergeRequest.arel_table[:created_at], end
MergeRequest::Metrics.arel_table[:merged_at])
def description
"Time between merge request creation and merge/close"
end end
end end
end end
......
...@@ -2,7 +2,7 @@ module Gitlab ...@@ -2,7 +2,7 @@ module Gitlab
module CycleAnalytics module CycleAnalytics
module Stage module Stage
def self.[](stage_name) def self.[](stage_name)
const_get("::Gitlab::CycleAnalytics::#{stage_name.to_s.camelize}Stage") CycleAnalytics.const_get("#{stage_name.to_s.camelize}Stage")
end end
end end
end end
......
module Gitlab module Gitlab
module CycleAnalytics module CycleAnalytics
class StagingEvent < BaseEvent class StagingEventFetcher < BaseEventFetcher
def initialize(*args) def initialize(*args)
@start_time_attrs = mr_metrics_table[:merged_at]
@end_time_attrs = mr_metrics_table[:first_deployed_to_production_at]
@projections = [build_table[:id]] @projections = [build_table[:id]]
@order = build_table[:created_at] @order = build_table[:created_at]
......
module Gitlab module Gitlab
module CycleAnalytics module CycleAnalytics
class StagingStage < BaseStage class StagingStage < BaseStage
def description def initialize(*args)
"From merge request merge until deploy to production" @start_time_attrs = mr_metrics_table[:merged_at]
@end_time_attrs = mr_metrics_table[:first_deployed_to_production_at]
super(*args)
end end
def median def stage
@fetcher.median(:staging, :staging
MergeRequest::Metrics.arel_table[:merged_at], end
MergeRequest::Metrics.arel_table[:first_deployed_to_production_at])
def description
"From merge request merge until deploy to production"
end end
end end
end end
......
module Gitlab
module CycleAnalytics
class TestEvent < StagingEvent
def initialize(*args)
super(*args)
@start_time_attrs = mr_metrics_table[:latest_build_started_at]
@end_time_attrs = mr_metrics_table[:latest_build_finished_at]
end
end
end
end
module Gitlab
module CycleAnalytics
class TestEventFetcher < StagingEventFetcher
end
end
end
module Gitlab module Gitlab
module CycleAnalytics module CycleAnalytics
class TestStage < BaseStage class TestStage < BaseStage
def description def initialize(*args)
"Total test time for all commits/merges" @start_time_attrs = mr_metrics_table[:latest_build_started_at]
@end_time_attrs = mr_metrics_table[:latest_build_finished_at]
super(*args)
end end
def median def stage
@fetcher.median(:test, :test
MergeRequest::Metrics.arel_table[:latest_build_started_at], end
MergeRequest::Metrics.arel_table[:latest_build_finished_at])
def description
"Total test time for all commits/merges"
end end
end end
end end
......
require 'spec_helper' require 'spec_helper'
require 'lib/gitlab/cycle_analytics/shared_event_spec' require 'lib/gitlab/cycle_analytics/shared_event_spec'
describe Gitlab::CycleAnalytics::CodeEvent do describe Gitlab::CycleAnalytics::CodeEventFetcher do
let(:stage_name) { :code } let(:stage_name) { :code }
it_behaves_like 'default query config' do it_behaves_like 'default query config' do
......
require 'spec_helper' require 'spec_helper'
require 'lib/gitlab/cycle_analytics/shared_event_spec' require 'lib/gitlab/cycle_analytics/shared_event_spec'
describe Gitlab::CycleAnalytics::IssueEvent do describe Gitlab::CycleAnalytics::IssueEventFetcher do
let(:stage_name) { :issue } let(:stage_name) { :issue }
it_behaves_like 'default query config' do it_behaves_like 'default query config' do
......
require 'spec_helper' require 'spec_helper'
require 'lib/gitlab/cycle_analytics/shared_event_spec' require 'lib/gitlab/cycle_analytics/shared_event_spec'
describe Gitlab::CycleAnalytics::PlanEvent do describe Gitlab::CycleAnalytics::PlanEventFetcher do
let(:stage_name) { :plan } let(:stage_name) { :plan }
it_behaves_like 'default query config' do it_behaves_like 'default query config' do
......
require 'spec_helper' require 'spec_helper'
require 'lib/gitlab/cycle_analytics/shared_event_spec' require 'lib/gitlab/cycle_analytics/shared_event_spec'
describe Gitlab::CycleAnalytics::ProductionEvent do describe Gitlab::CycleAnalytics::ProductionEventFetcher do
let(:stage_name) { :production } let(:stage_name) { :production }
it_behaves_like 'default query config' do it_behaves_like 'default query config' do
......
require 'spec_helper' require 'spec_helper'
require 'lib/gitlab/cycle_analytics/shared_event_spec' require 'lib/gitlab/cycle_analytics/shared_event_spec'
describe Gitlab::CycleAnalytics::ReviewEvent do describe Gitlab::CycleAnalytics::ReviewEventFetcher do
let(:stage_name) { :review } let(:stage_name) { :review }
it_behaves_like 'default query config' do it_behaves_like 'default query config' do
......
...@@ -4,10 +4,11 @@ shared_examples 'default query config' do ...@@ -4,10 +4,11 @@ shared_examples 'default query config' do
let(:fetcher) do let(:fetcher) do
Gitlab::CycleAnalytics::MetricsFetcher.new(project: create(:empty_project), Gitlab::CycleAnalytics::MetricsFetcher.new(project: create(:empty_project),
from: 1.day.ago, from: 1.day.ago,
branch: nil) branch: nil,
stage: stage_name)
end end
let(:event) { described_class.new(fetcher: fetcher, options: {}) } let(:event) { described_class.new(fetcher: fetcher, options: {}, stage: stage_name) }
it 'has the start attributes' do it 'has the start attributes' do
expect(event.start_time_attrs).not_to be_nil expect(event.start_time_attrs).not_to be_nil
......
...@@ -5,7 +5,7 @@ shared_examples 'base stage' do ...@@ -5,7 +5,7 @@ shared_examples 'base stage' do
before do before do
allow_any_instance_of(Gitlab::CycleAnalytics::MetricsFetcher).to receive(:median).and_return(1.12) allow_any_instance_of(Gitlab::CycleAnalytics::MetricsFetcher).to receive(:median).and_return(1.12)
allow_any_instance_of(Gitlab::CycleAnalytics::BaseEvent).to receive(:event_result).and_return({}) allow_any_instance_of(Gitlab::CycleAnalytics::BaseEventFetcher).to receive(:event_result).and_return({})
end end
it 'has the median data value' do it 'has the median data value' do
......
require 'spec_helper' require 'spec_helper'
require 'lib/gitlab/cycle_analytics/shared_event_spec' require 'lib/gitlab/cycle_analytics/shared_event_spec'
describe Gitlab::CycleAnalytics::StagingEvent do describe Gitlab::CycleAnalytics::StagingEventFetcher do
let(:stage_name) { :staging } let(:stage_name) { :staging }
it_behaves_like 'default query config' do it_behaves_like 'default query config' do
......
require 'spec_helper' require 'spec_helper'
require 'lib/gitlab/cycle_analytics/shared_event_spec' require 'lib/gitlab/cycle_analytics/shared_event_spec'
describe Gitlab::CycleAnalytics::TestEvent do describe Gitlab::CycleAnalytics::TestEventFetcher do
let(:stage_name) { :test } let(:stage_name) { :test }
it_behaves_like 'default query config' do it_behaves_like 'default query config' do
......
...@@ -11,7 +11,7 @@ describe AnalyticsStageSerializer do ...@@ -11,7 +11,7 @@ describe AnalyticsStageSerializer do
before do before do
allow_any_instance_of(Gitlab::CycleAnalytics::MetricsFetcher).to receive(:median).and_return(1.12) allow_any_instance_of(Gitlab::CycleAnalytics::MetricsFetcher).to receive(:median).and_return(1.12)
allow_any_instance_of(Gitlab::CycleAnalytics::BaseEvent).to receive(:event_result).and_return({}) allow_any_instance_of(Gitlab::CycleAnalytics::BaseEventFetcher).to receive(:event_result).and_return({})
end end
it 'it generates payload for single object' do it 'it generates payload for single object' do
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment