big refactor based on MR feedback
This commit is contained in:
parent
daa4f3ded7
commit
b214be493d
32 changed files with 136 additions and 129 deletions
|
@ -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
|
||||
|
|
|
@ -1,15 +1,15 @@
|
|||
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!
|
|
@ -1,18 +1,23 @@
|
|||
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
|
||||
|
|
|
@ -1,13 +0,0 @@
|
|||
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
|
|
@ -1,21 +1,22 @@
|
|||
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
|
|
@ -1,14 +1,19 @@
|
|||
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
|
||||
|
|
|
@ -1,11 +1,9 @@
|
|||
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],
|
|
@ -1,15 +1,20 @@
|
|||
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
|
||||
|
|
|
@ -1,10 +1,7 @@
|
|||
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]]
|
||||
|
|
@ -1,15 +1,20 @@
|
|||
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
|
||||
|
|
|
@ -1,12 +1,9 @@
|
|||
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],
|
|
@ -1,14 +1,19 @@
|
|||
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
|
||||
|
|
|
@ -1,24 +1,19 @@
|
|||
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
|
|
@ -1,14 +1,19 @@
|
|||
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
|
||||
|
|
|
@ -1,9 +1,7 @@
|
|||
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]
|
||||
|
|
@ -1,14 +1,19 @@
|
|||
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
|
||||
|
|
|
@ -1,12 +0,0 @@
|
|||
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
|
6
lib/gitlab/cycle_analytics/test_event_fetcher.rb
Normal file
6
lib/gitlab/cycle_analytics/test_event_fetcher.rb
Normal file
|
@ -0,0 +1,6 @@
|
|||
module Gitlab
|
||||
module CycleAnalytics
|
||||
class TestEventFetcher < StagingEventFetcher
|
||||
end
|
||||
end
|
||||
end
|
|
@ -1,14 +1,19 @@
|
|||
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
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
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
|
|
@ -1,7 +1,7 @@
|
|||
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
|
|
@ -1,7 +1,7 @@
|
|||
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
|
|
@ -1,7 +1,7 @@
|
|||
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
|
|
@ -1,7 +1,7 @@
|
|||
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
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
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
|
|
@ -1,7 +1,7 @@
|
|||
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
|
||||
|
|
Loading…
Reference in a new issue