Commit 69ecd951 authored by James Lopez's avatar James Lopez

refactor fetcher and fixed specs

parent b8056669
...@@ -48,7 +48,7 @@ module Projects ...@@ -48,7 +48,7 @@ module Projects
end end
def cycle_analytics def cycle_analytics
@cycle_analytics ||= ::CycleAnalytics.new(project, options: options(events_params)) @cycle_analytics ||= ::CycleAnalytics.new(project, options(events_params))
end end
def events_params def events_params
......
...@@ -7,7 +7,7 @@ module Gitlab ...@@ -7,7 +7,7 @@ module Gitlab
attr_reader :start_time_attrs, :end_time_attrs, :projections, :query attr_reader :start_time_attrs, :end_time_attrs, :projections, :query
def initialize(fetcher:, options:) def initialize(fetcher:, options:)
@query = EventsQuery.new(fetcher: fetcher) @fetcher = fetcher
@project = fetcher.project @project = fetcher.project
@options = options @options = options
end end
...@@ -39,7 +39,7 @@ module Gitlab ...@@ -39,7 +39,7 @@ module Gitlab
end end
def event_result def event_result
@event_result ||= @query.execute(self).to_a @event_result ||= @fetcher.events(self).to_a
end end
def serialize(_event) def serialize(_event)
......
module Gitlab
module CycleAnalytics
class EventsQuery
def initialize(fetcher:)
@fetcher = fetcher
end
def execute(stage_class)
@stage_class = stage_class
ActiveRecord::Base.connection.exec_query(query.to_sql)
end
private
def query
base_query = @fetcher.base_query_for(@stage_class.stage)
diff_fn = @fetcher.subtract_datetimes_diff(base_query, @stage_class.start_time_attrs, @stage_class.end_time_attrs)
@stage_class.custom_query(base_query)
base_query.project(extract_epoch(diff_fn).as('total_time'), *@stage_class.projections).order(@stage_class.order.desc)
end
def extract_epoch(arel_attribute)
return arel_attribute unless Gitlab::Database.postgresql?
Arel.sql(%Q{EXTRACT(EPOCH FROM (#{arel_attribute.to_sql}))})
end
end
end
end
...@@ -29,6 +29,21 @@ module Gitlab ...@@ -29,6 +29,21 @@ module Gitlab
median_datetime(cte_table, interval_query, name) median_datetime(cte_table, interval_query, name)
end end
def events(stage_class)
ActiveRecord::Base.connection.exec_query(events_query(stage_class).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)
stage_class.custom_query(base_query)
base_query.project(extract_diff_epoch(diff_fn).as('total_time'), *stage_class.projections).order(stage_class.order.desc)
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
# closes the given issue) with issue and merge request metrics included. The metrics # 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 # are loaded with an inner join, so issues / merge requests without metrics are
......
...@@ -103,6 +103,11 @@ module Gitlab ...@@ -103,6 +103,11 @@ module Gitlab
Arel.sql(%Q{EXTRACT(EPOCH FROM "#{arel_attribute.relation.name}"."#{arel_attribute.name}")}) Arel.sql(%Q{EXTRACT(EPOCH FROM "#{arel_attribute.relation.name}"."#{arel_attribute.name}")})
end end
def extract_diff_epoch(diff)
return diff unless Gitlab::Database.postgresql?
Arel.sql(%Q{EXTRACT(EPOCH FROM (#{diff.to_sql}))})
end
# Need to cast '0' to an INTERVAL before we can check if the interval is positive # Need to cast '0' to an INTERVAL before we can check if the interval is positive
def zero_interval def zero_interval
Arel::Nodes::NamedFunction.new("CAST", [Arel.sql("'0' AS INTERVAL")]) Arel::Nodes::NamedFunction.new("CAST", [Arel.sql("'0' AS INTERVAL")])
......
...@@ -6,7 +6,7 @@ describe 'CycleAnalytics#code', feature: true do ...@@ -6,7 +6,7 @@ describe 'CycleAnalytics#code', feature: true do
let(:project) { create(:project) } let(:project) { create(:project) }
let(:from_date) { 10.days.ago } let(:from_date) { 10.days.ago }
let(:user) { create(:user, :admin) } let(:user) { create(:user, :admin) }
subject { CycleAnalyticsTest.new(project, options: { from: from_date }) } subject { CycleAnalytics.new(project, from: from_date) }
context 'with deployment' do context 'with deployment' do
generate_cycle_analytics_spec( generate_cycle_analytics_spec(
...@@ -37,7 +37,7 @@ describe 'CycleAnalytics#code', feature: true do ...@@ -37,7 +37,7 @@ describe 'CycleAnalytics#code', feature: true do
deploy_master deploy_master
end end
expect(subject.code).to be_nil expect(subject[:code].median).to be_nil
end end
end end
end end
...@@ -69,7 +69,7 @@ describe 'CycleAnalytics#code', feature: true do ...@@ -69,7 +69,7 @@ describe 'CycleAnalytics#code', feature: true do
merge_merge_requests_closing_issue(issue) merge_merge_requests_closing_issue(issue)
end end
expect(subject.code).to be_nil expect(subject[:code].median).to be_nil
end end
end end
end end
......
...@@ -6,7 +6,7 @@ describe 'CycleAnalytics#issue', models: true do ...@@ -6,7 +6,7 @@ describe 'CycleAnalytics#issue', models: true do
let(:project) { create(:project) } let(:project) { create(:project) }
let(:from_date) { 10.days.ago } let(:from_date) { 10.days.ago }
let(:user) { create(:user, :admin) } let(:user) { create(:user, :admin) }
subject { CycleAnalyticsTest.new(project, options: { from: from_date }) } subject { CycleAnalytics.new(project, from: from_date) }
generate_cycle_analytics_spec( generate_cycle_analytics_spec(
phase: :issue, phase: :issue,
...@@ -42,7 +42,7 @@ describe 'CycleAnalytics#issue', models: true do ...@@ -42,7 +42,7 @@ describe 'CycleAnalytics#issue', models: true do
merge_merge_requests_closing_issue(issue) merge_merge_requests_closing_issue(issue)
end end
expect(subject.issue).to be_nil expect(subject[:issue].median).to be_nil
end end
end end
end end
...@@ -6,7 +6,7 @@ describe 'CycleAnalytics#plan', feature: true do ...@@ -6,7 +6,7 @@ describe 'CycleAnalytics#plan', feature: true do
let(:project) { create(:project) } let(:project) { create(:project) }
let(:from_date) { 10.days.ago } let(:from_date) { 10.days.ago }
let(:user) { create(:user, :admin) } let(:user) { create(:user, :admin) }
subject { CycleAnalyticsTest.new(project, options: { from: from_date }) } subject { CycleAnalytics.new(project, from: from_date) }
generate_cycle_analytics_spec( generate_cycle_analytics_spec(
phase: :plan, phase: :plan,
...@@ -44,7 +44,7 @@ describe 'CycleAnalytics#plan', feature: true do ...@@ -44,7 +44,7 @@ describe 'CycleAnalytics#plan', feature: true do
create_merge_request_closing_issue(issue, source_branch: branch_name) create_merge_request_closing_issue(issue, source_branch: branch_name)
merge_merge_requests_closing_issue(issue) merge_merge_requests_closing_issue(issue)
expect(subject.issue).to be_nil expect(subject[:issue].median).to be_nil
end end
end end
end end
...@@ -6,7 +6,7 @@ describe 'CycleAnalytics#production', feature: true do ...@@ -6,7 +6,7 @@ describe 'CycleAnalytics#production', feature: true do
let(:project) { create(:project) } let(:project) { create(:project) }
let(:from_date) { 10.days.ago } let(:from_date) { 10.days.ago }
let(:user) { create(:user, :admin) } let(:user) { create(:user, :admin) }
subject { CycleAnalyticsTest.new(project, options: { from: from_date }) } subject { CycleAnalytics.new(project, from: from_date) }
generate_cycle_analytics_spec( generate_cycle_analytics_spec(
phase: :production, phase: :production,
...@@ -35,7 +35,7 @@ describe 'CycleAnalytics#production', feature: true do ...@@ -35,7 +35,7 @@ describe 'CycleAnalytics#production', feature: true do
deploy_master deploy_master
end end
expect(subject.production).to be_nil expect(subject[:production].median).to be_nil
end end
end end
...@@ -48,7 +48,7 @@ describe 'CycleAnalytics#production', feature: true do ...@@ -48,7 +48,7 @@ describe 'CycleAnalytics#production', feature: true do
deploy_master(environment: 'staging') deploy_master(environment: 'staging')
end end
expect(subject.production).to be_nil expect(subject[:production].median).to be_nil
end end
end end
end end
...@@ -6,7 +6,7 @@ describe 'CycleAnalytics#review', feature: true do ...@@ -6,7 +6,7 @@ describe 'CycleAnalytics#review', feature: true do
let(:project) { create(:project) } let(:project) { create(:project) }
let(:from_date) { 10.days.ago } let(:from_date) { 10.days.ago }
let(:user) { create(:user, :admin) } let(:user) { create(:user, :admin) }
subject { CycleAnalyticsTest.new(project, options: { from: from_date }) } subject { CycleAnalytics.new(project, from: from_date) }
generate_cycle_analytics_spec( generate_cycle_analytics_spec(
phase: :review, phase: :review,
...@@ -27,7 +27,7 @@ describe 'CycleAnalytics#review', feature: true do ...@@ -27,7 +27,7 @@ describe 'CycleAnalytics#review', feature: true do
MergeRequests::MergeService.new(project, user).execute(create(:merge_request)) MergeRequests::MergeService.new(project, user).execute(create(:merge_request))
end end
expect(subject.review).to be_nil expect(subject[:review].median).to be_nil
end end
end end
end end
...@@ -6,7 +6,7 @@ describe 'CycleAnalytics#staging', feature: true do ...@@ -6,7 +6,7 @@ describe 'CycleAnalytics#staging', feature: true do
let(:project) { create(:project) } let(:project) { create(:project) }
let(:from_date) { 10.days.ago } let(:from_date) { 10.days.ago }
let(:user) { create(:user, :admin) } let(:user) { create(:user, :admin) }
subject { CycleAnalyticsTest.new(project, options: { from: from_date }) } subject { CycleAnalytics.new(project, from: from_date) }
generate_cycle_analytics_spec( generate_cycle_analytics_spec(
phase: :staging, phase: :staging,
...@@ -45,7 +45,7 @@ describe 'CycleAnalytics#staging', feature: true do ...@@ -45,7 +45,7 @@ describe 'CycleAnalytics#staging', feature: true do
deploy_master deploy_master
end end
expect(subject.staging).to be_nil expect(subject[:staging].median).to be_nil
end end
end end
...@@ -58,7 +58,7 @@ describe 'CycleAnalytics#staging', feature: true do ...@@ -58,7 +58,7 @@ describe 'CycleAnalytics#staging', feature: true do
deploy_master(environment: 'staging') deploy_master(environment: 'staging')
end end
expect(subject.staging).to be_nil expect(subject[:staging].median).to be_nil
end end
end end
end end
...@@ -6,7 +6,7 @@ describe 'CycleAnalytics#test', feature: true do ...@@ -6,7 +6,7 @@ describe 'CycleAnalytics#test', feature: true do
let(:project) { create(:project) } let(:project) { create(:project) }
let(:from_date) { 10.days.ago } let(:from_date) { 10.days.ago }
let(:user) { create(:user, :admin) } let(:user) { create(:user, :admin) }
subject { CycleAnalyticsTest.new(project, options: { from: from_date }) } subject { CycleAnalytics.new(project, from: from_date) }
generate_cycle_analytics_spec( generate_cycle_analytics_spec(
phase: :test, phase: :test,
...@@ -35,7 +35,7 @@ describe 'CycleAnalytics#test', feature: true do ...@@ -35,7 +35,7 @@ describe 'CycleAnalytics#test', feature: true do
merge_merge_requests_closing_issue(issue) merge_merge_requests_closing_issue(issue)
end end
expect(subject.test).to be_nil expect(subject[:test].median).to be_nil
end end
end end
...@@ -48,7 +48,7 @@ describe 'CycleAnalytics#test', feature: true do ...@@ -48,7 +48,7 @@ describe 'CycleAnalytics#test', feature: true do
pipeline.succeed! pipeline.succeed!
end end
expect(subject.test).to be_nil expect(subject[:test].median).to be_nil
end end
end end
...@@ -65,7 +65,7 @@ describe 'CycleAnalytics#test', feature: true do ...@@ -65,7 +65,7 @@ describe 'CycleAnalytics#test', feature: true do
merge_merge_requests_closing_issue(issue) merge_merge_requests_closing_issue(issue)
end end
expect(subject.test).to be_nil expect(subject[:test].median).to be_nil
end end
end end
...@@ -82,7 +82,7 @@ describe 'CycleAnalytics#test', feature: true do ...@@ -82,7 +82,7 @@ describe 'CycleAnalytics#test', feature: true do
merge_merge_requests_closing_issue(issue) merge_merge_requests_closing_issue(issue)
end end
expect(subject.test).to be_nil expect(subject[:test].median).to be_nil
end end
end end
end end
class CycleAnalyticsTest < CycleAnalytics
def method_missing(method_sym, *arguments, &block)
classify_stage(method_sym).new(project: @project, options: @options, stage: method_sym).median
end
end
# rubocop:disable Metrics/AbcSize # rubocop:disable Metrics/AbcSize
# Note: The ABC size is large here because we have a method generating test cases with # Note: The ABC size is large here because we have a method generating test cases with
...@@ -56,7 +50,7 @@ module CycleAnalyticsHelpers ...@@ -56,7 +50,7 @@ module CycleAnalyticsHelpers
end end
median_time_difference = time_differences.sort[2] median_time_difference = time_differences.sort[2]
expect(subject.public_send(phase)).to be_within(5).of(median_time_difference) expect(subject[phase].median).to be_within(5).of(median_time_difference)
end end
context "when the data belongs to another project" do context "when the data belongs to another project" do
...@@ -88,7 +82,7 @@ module CycleAnalyticsHelpers ...@@ -88,7 +82,7 @@ module CycleAnalyticsHelpers
# Turn off the stub before checking assertions # Turn off the stub before checking assertions
allow(self).to receive(:project).and_call_original allow(self).to receive(:project).and_call_original
expect(subject.public_send(phase)).to be_nil expect(subject[phase].median).to be_nil
end end
end end
...@@ -111,7 +105,7 @@ module CycleAnalyticsHelpers ...@@ -111,7 +105,7 @@ module CycleAnalyticsHelpers
Timecop.freeze(end_time + 1.day) { post_fn[self, data] } if post_fn Timecop.freeze(end_time + 1.day) { post_fn[self, data] } if post_fn
expect(subject.public_send(phase)).to be_nil expect(subject[phase].median).to be_nil
end end
end end
end end
...@@ -131,7 +125,7 @@ module CycleAnalyticsHelpers ...@@ -131,7 +125,7 @@ module CycleAnalyticsHelpers
Timecop.freeze(end_time + 1.day) { post_fn[self, data] } if post_fn Timecop.freeze(end_time + 1.day) { post_fn[self, data] } if post_fn
end end
expect(subject.public_send(phase)).to be_nil expect(subject[phase].median).to be_nil
end end
end end
end end
...@@ -150,7 +144,7 @@ module CycleAnalyticsHelpers ...@@ -150,7 +144,7 @@ module CycleAnalyticsHelpers
post_fn[self, data] if post_fn post_fn[self, data] if post_fn
end end
expect(subject.public_send(phase)).to be_nil expect(subject[phase].median).to be_nil
end end
end end
end end
...@@ -158,7 +152,7 @@ module CycleAnalyticsHelpers ...@@ -158,7 +152,7 @@ module CycleAnalyticsHelpers
context "when none of the start / end conditions are matched" do context "when none of the start / end conditions are matched" do
it "returns nil" do it "returns nil" do
expect(subject.public_send(phase)).to be_nil expect(subject[phase].median).to be_nil
end end
end end
end end
......
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