Commit b214be49 authored by James Lopez's avatar James Lopez

big refactor based on MR feedback

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