Commit 967cfa43 authored by Adam Hegyi's avatar Adam Hegyi

Show in progress items in VSA

This change implements query changes to the value stream analytics
feature to show and calulate metrics on in progress items.
parent 6edf99aa
...@@ -14,6 +14,7 @@ module EE ...@@ -14,6 +14,7 @@ module EE
options[:group] = params[:group_id] if params[:group_id] options[:group] = params[:group_id] if params[:group_id]
options[:from] = params[:from] if params[:from] options[:from] = params[:from] if params[:from]
options[:to] = params[:to] if params[:to] options[:to] = params[:to] if params[:to]
options[:end_event_filter] = params[:end_event_filter] if params[:end_event_filter]
options.merge!(params.slice(*::Gitlab::Analytics::CycleAnalytics::RequestParams::FINDER_PARAM_NAMES)) options.merge!(params.slice(*::Gitlab::Analytics::CycleAnalytics::RequestParams::FINDER_PARAM_NAMES))
end end
end end
......
...@@ -21,6 +21,7 @@ module Gitlab ...@@ -21,6 +21,7 @@ module Gitlab
:direction, :direction,
:page, :page,
:stage_id, :stage_id,
:end_event_filter,
label_name: [].freeze, label_name: [].freeze,
assignee_username: [].freeze, assignee_username: [].freeze,
project_ids: [].freeze project_ids: [].freeze
...@@ -44,6 +45,7 @@ module Gitlab ...@@ -44,6 +45,7 @@ module Gitlab
attribute :direction attribute :direction
attribute :page attribute :page
attribute :stage_id attribute :stage_id
attribute :end_event_filter
FINDER_PARAM_NAMES.each do |param_name| FINDER_PARAM_NAMES.each do |param_name|
attribute param_name attribute param_name
...@@ -60,6 +62,7 @@ module Gitlab ...@@ -60,6 +62,7 @@ module Gitlab
self.created_before = (self.created_before || Time.current).at_end_of_day self.created_before = (self.created_before || Time.current).at_end_of_day
self.created_after = (created_after || default_created_after).at_beginning_of_day self.created_after = (created_after || default_created_after).at_beginning_of_day
self.end_event_filter ||= Gitlab::Analytics::CycleAnalytics::BaseQueryBuilder::DEFAULT_END_EVENT_FILTER
end end
def project_ids def project_ids
...@@ -74,7 +77,8 @@ module Gitlab ...@@ -74,7 +77,8 @@ module Gitlab
project_ids: project_ids, project_ids: project_ids,
sort: sort&.to_sym, sort: sort&.to_sym,
direction: direction&.to_sym, direction: direction&.to_sym,
page: page page: page,
end_event_filter: end_event_filter.to_sym
}.merge(attributes.symbolize_keys.slice(*FINDER_PARAM_NAMES)) }.merge(attributes.symbolize_keys.slice(*FINDER_PARAM_NAMES))
end end
......
...@@ -32,6 +32,12 @@ module Gitlab ...@@ -32,6 +32,12 @@ module Gitlab
end end
# rubocop: enable CodeReuse/ActiveRecord # rubocop: enable CodeReuse/ActiveRecord
# rubocop: disable CodeReuse/ActiveRecord
def apply_negated_query_customization(query)
query.where('NOT EXISTS (?)', subquery)
end
# rubocop: enable CodeReuse/ActiveRecord
private private
def resource_label_events_table def resource_label_events_table
......
...@@ -44,6 +44,7 @@ module Gitlab ...@@ -44,6 +44,7 @@ module Gitlab
from: @options[:from], from: @options[:from],
to: @options[:to] || DateTime.now, to: @options[:to] || DateTime.now,
project_ids: @options[:projects], project_ids: @options[:projects],
end_event_filter: @options[:end_event_filter],
current_user: @current_user current_user: @current_user
}.merge(@options.slice(*::Gitlab::Analytics::CycleAnalytics::RequestParams::FINDER_PARAM_NAMES)) }.merge(@options.slice(*::Gitlab::Analytics::CycleAnalytics::RequestParams::FINDER_PARAM_NAMES))
) )
......
...@@ -5,8 +5,10 @@ require 'spec_helper' ...@@ -5,8 +5,10 @@ require 'spec_helper'
RSpec.describe Gitlab::Analytics::CycleAnalytics::DataCollector do RSpec.describe Gitlab::Analytics::CycleAnalytics::DataCollector do
let_it_be(:user) { create(:user) } let_it_be(:user) { create(:user) }
let(:current_time) { Time.new(2019, 6, 1) }
around do |example| around do |example|
Timecop.freeze { example.run } Timecop.freeze(current_time) { example.run }
end end
def round_to_days(seconds) def round_to_days(seconds)
...@@ -21,11 +23,11 @@ RSpec.describe Gitlab::Analytics::CycleAnalytics::DataCollector do ...@@ -21,11 +23,11 @@ RSpec.describe Gitlab::Analytics::CycleAnalytics::DataCollector do
shared_examples 'custom Value Stream Analytics Stage' do shared_examples 'custom Value Stream Analytics Stage' do
let(:params) { { from: Time.new(2019), to: Time.new(2020), current_user: user } } let(:params) { { from: Time.new(2019), to: Time.new(2020), current_user: user } }
let(:data_collector) { described_class.new(stage: stage, params: params) } let(:data_collector) { described_class.new(stage: stage, params: params) }
let(:resource_1_end_time) { Time.new(2019, 3, 15) } let_it_be(:resource_1_end_time) { Time.new(2019, 3, 15) }
let(:resource_2_end_time) { Time.new(2019, 3, 10) } let_it_be(:resource_2_end_time) { Time.new(2019, 3, 10) }
let(:resource_3_end_time) { Time.new(2019, 3, 20) } let_it_be(:resource_3_end_time) { Time.new(2019, 3, 20) }
let!(:resource1) do let_it_be(:resource1) do
# takes 10 days # takes 10 days
resource = travel_to(Time.new(2019, 3, 5)) do resource = travel_to(Time.new(2019, 3, 5)) do
create_data_for_start_event(self) create_data_for_start_event(self)
...@@ -38,7 +40,7 @@ RSpec.describe Gitlab::Analytics::CycleAnalytics::DataCollector do ...@@ -38,7 +40,7 @@ RSpec.describe Gitlab::Analytics::CycleAnalytics::DataCollector do
resource resource
end end
let!(:resource2) do let_it_be(:resource2) do
# takes 5 days # takes 5 days
resource = travel_to(Time.new(2019, 3, 5)) do resource = travel_to(Time.new(2019, 3, 5)) do
create_data_for_start_event(self) create_data_for_start_event(self)
...@@ -51,7 +53,7 @@ RSpec.describe Gitlab::Analytics::CycleAnalytics::DataCollector do ...@@ -51,7 +53,7 @@ RSpec.describe Gitlab::Analytics::CycleAnalytics::DataCollector do
resource resource
end end
let!(:resource3) do let_it_be(:resource3) do
# takes 15 days # takes 15 days
resource = travel_to(Time.new(2019, 3, 5)) do resource = travel_to(Time.new(2019, 3, 5)) do
create_data_for_start_event(self) create_data_for_start_event(self)
...@@ -64,6 +66,21 @@ RSpec.describe Gitlab::Analytics::CycleAnalytics::DataCollector do ...@@ -64,6 +66,21 @@ RSpec.describe Gitlab::Analytics::CycleAnalytics::DataCollector do
resource resource
end end
let_it_be(:unfinished_resource_1_start_time) { Time.new(2019, 3, 5) }
let_it_be(:unfinished_resource_2_start_time) { Time.new(2019, 5, 10) }
let_it_be(:unfinished_resource_1) do
travel_to(unfinished_resource_1_start_time) do
create_data_for_start_event(self)
end
end
let_it_be(:unfinished_resource_2) do
travel_to(unfinished_resource_2_start_time) do
create_data_for_start_event(self)
end
end
it 'loads serialized records' do it 'loads serialized records' do
items = data_collector.serialized_records items = data_collector.serialized_records
expect(items.size).to eq(3) expect(items.size).to eq(3)
...@@ -116,6 +133,32 @@ RSpec.describe Gitlab::Analytics::CycleAnalytics::DataCollector do ...@@ -116,6 +133,32 @@ RSpec.describe Gitlab::Analytics::CycleAnalytics::DataCollector do
it { is_expected.to eq(3) } it { is_expected.to eq(3) }
end end
context 'when filtering in progress items' do
before do
params[:end_event_filter] = :in_progress
end
describe '#count' do
subject(:count) { data_collector.count }
it { is_expected.to eq(2) }
end
it 'calculates median' do
duration_1 = current_time - unfinished_resource_1_start_time
duration_2 = current_time - unfinished_resource_2_start_time
expected_median = (duration_1 + duration_2).fdiv(2)
expect(round_to_days(data_collector.median.seconds)).to eq(round_to_days(expected_median))
end
it 'loads serialized records' do
items = data_collector.serialized_records
expect(items.size).to eq(2)
end
end
end end
shared_examples 'test various start and end event combinations' do shared_examples 'test various start and end event combinations' do
...@@ -209,11 +252,13 @@ RSpec.describe Gitlab::Analytics::CycleAnalytics::DataCollector do ...@@ -209,11 +252,13 @@ RSpec.describe Gitlab::Analytics::CycleAnalytics::DataCollector do
def create_data_for_start_event(example_class) def create_data_for_start_event(example_class)
issue = create(:issue, :opened, project: example_class.project) issue = create(:issue, :opened, project: example_class.project)
Sidekiq::Worker.skipping_transaction_check do
Issues::UpdateService.new( Issues::UpdateService.new(
example_class.project, example_class.project,
user, user,
label_ids: [example_class.label.id] label_ids: [example_class.label.id]
).execute(issue) ).execute(issue)
end
issue issue
end end
...@@ -241,22 +286,26 @@ RSpec.describe Gitlab::Analytics::CycleAnalytics::DataCollector do ...@@ -241,22 +286,26 @@ RSpec.describe Gitlab::Analytics::CycleAnalytics::DataCollector do
def create_data_for_start_event(example_class) def create_data_for_start_event(example_class)
issue = create(:issue, :opened, project: example_class.project) issue = create(:issue, :opened, project: example_class.project)
Sidekiq::Worker.skipping_transaction_check do
Issues::UpdateService.new( Issues::UpdateService.new(
example_class.project, example_class.project,
user, user,
label_ids: [example_class.label.id] label_ids: [example_class.label.id]
).execute(issue) ).execute(issue)
end
issue issue
end end
def create_data_for_end_event(issue, example_class) def create_data_for_end_event(issue, example_class)
Sidekiq::Worker.skipping_transaction_check do
Issues::UpdateService.new( Issues::UpdateService.new(
example_class.project, example_class.project,
user, user,
label_ids: [example_class.label.id, example_class.other_label.id] label_ids: [example_class.label.id, example_class.other_label.id]
).execute(issue) ).execute(issue)
end end
end
it_behaves_like 'custom Value Stream Analytics Stage' do it_behaves_like 'custom Value Stream Analytics Stage' do
context 'when filtering for two labels' do context 'when filtering for two labels' do
...@@ -291,12 +340,14 @@ RSpec.describe Gitlab::Analytics::CycleAnalytics::DataCollector do ...@@ -291,12 +340,14 @@ RSpec.describe Gitlab::Analytics::CycleAnalytics::DataCollector do
end end
def create_data_for_end_event(issue, example_class) def create_data_for_end_event(issue, example_class)
Sidekiq::Worker.skipping_transaction_check do
Issues::UpdateService.new( Issues::UpdateService.new(
example_class.project, example_class.project,
user, user,
label_ids: [example_class.label.id] label_ids: [example_class.label.id]
).execute(issue) ).execute(issue)
end end
end
it_behaves_like 'custom Value Stream Analytics Stage' it_behaves_like 'custom Value Stream Analytics Stage'
end end
...@@ -411,29 +462,33 @@ RSpec.describe Gitlab::Analytics::CycleAnalytics::DataCollector do ...@@ -411,29 +462,33 @@ RSpec.describe Gitlab::Analytics::CycleAnalytics::DataCollector do
def create_data_for_start_event(example_class) def create_data_for_start_event(example_class)
mr = create(:merge_request, source_project: example_class.project, allow_broken: true) mr = create(:merge_request, source_project: example_class.project, allow_broken: true)
Sidekiq::Worker.skipping_transaction_check do
MergeRequests::UpdateService.new( MergeRequests::UpdateService.new(
example_class.project, example_class.project,
user, user,
label_ids: [label.id] label_ids: [label.id]
).execute(mr) ).execute(mr)
end
mr mr
end end
def create_data_for_end_event(mr, example_class) def create_data_for_end_event(mr, example_class)
Sidekiq::Worker.skipping_transaction_check do
MergeRequests::UpdateService.new( MergeRequests::UpdateService.new(
example_class.project, example_class.project,
user, user,
label_ids: [] label_ids: []
).execute(mr) ).execute(mr)
end end
end
it_behaves_like 'custom Value Stream Analytics Stage' it_behaves_like 'custom Value Stream Analytics Stage'
end end
context 'between code stage start time and merge request created time' do context 'between code stage start time and merge request closed time' do
let(:start_event_identifier) { :code_stage_start } let(:start_event_identifier) { :code_stage_start }
let(:end_event_identifier) { :merge_request_created } let(:end_event_identifier) { :merge_request_closed }
context 'when issue is referenced in the commit message' do context 'when issue is referenced in the commit message' do
def create_data_for_start_event(example_class) def create_data_for_start_event(example_class)
...@@ -446,18 +501,20 @@ RSpec.describe Gitlab::Analytics::CycleAnalytics::DataCollector do ...@@ -446,18 +501,20 @@ RSpec.describe Gitlab::Analytics::CycleAnalytics::DataCollector do
allow_broken: true allow_broken: true
}) })
Sidekiq::Worker.skipping_transaction_check do
MergeRequests::UpdateService.new( MergeRequests::UpdateService.new(
example_class.project, example_class.project,
user, user,
assignees: [user] assignees: [user]
).execute(mr) ).execute(mr)
end
mr.metrics.update!(first_commit_at: Time.zone.now) mr.metrics.update!(first_commit_at: Time.zone.now)
mr mr
end end
def create_data_for_end_event(mr, example_class) def create_data_for_end_event(mr, example_class)
mr.update!(created_at: Time.zone.now) mr.metrics.update!(latest_closed_at: Time.zone.now)
end end
it_behaves_like 'custom Value Stream Analytics Stage' it_behaves_like 'custom Value Stream Analytics Stage'
...@@ -471,7 +528,7 @@ RSpec.describe Gitlab::Analytics::CycleAnalytics::DataCollector do ...@@ -471,7 +528,7 @@ RSpec.describe Gitlab::Analytics::CycleAnalytics::DataCollector do
end end
def create_data_for_end_event(mr, example_class) def create_data_for_end_event(mr, example_class)
mr.update!(created_at: Time.zone.now) mr.metrics.update!(latest_closed_at: Time.zone.now)
end end
it_behaves_like 'custom Value Stream Analytics Stage' it_behaves_like 'custom Value Stream Analytics Stage'
...@@ -493,17 +550,19 @@ RSpec.describe Gitlab::Analytics::CycleAnalytics::DataCollector do ...@@ -493,17 +550,19 @@ RSpec.describe Gitlab::Analytics::CycleAnalytics::DataCollector do
allow_broken: true allow_broken: true
}) })
Sidekiq::Worker.skipping_transaction_check do
MergeRequests::UpdateService.new( MergeRequests::UpdateService.new(
example_class.project, example_class.project,
user, user,
label_ids: [label.id, other_label.id] label_ids: [label.id, other_label.id]
).execute(mr) ).execute(mr)
end
mr mr
end end
def create_data_for_end_event(mr, example_class) def create_data_for_end_event(mr, example_class)
mr.update!(created_at: Time.zone.now) mr.metrics.update!(latest_closed_at: Time.zone.now)
end end
it_behaves_like 'custom Value Stream Analytics Stage' it_behaves_like 'custom Value Stream Analytics Stage'
......
...@@ -227,7 +227,9 @@ RSpec.shared_examples 'Value Stream Analytics Stages controller' do ...@@ -227,7 +227,9 @@ RSpec.shared_examples 'Value Stream Analytics Stages controller' do
end end
it 'accepts sort params' do it 'accepts sort params' do
expect(Gitlab::Analytics::CycleAnalytics::Sorting).to receive(:apply).with(kind_of(ActiveRecord::Relation), kind_of(Analytics::CycleAnalytics::GroupStage), :duration, :asc).and_call_original expect_next_instance_of(Gitlab::Analytics::CycleAnalytics::Sorting) do |sort|
expect(sort).to receive(:apply).with(:duration, :asc).and_call_original
end
subject subject
......
...@@ -7,9 +7,10 @@ module Gitlab ...@@ -7,9 +7,10 @@ module Gitlab
include Gitlab::Utils::StrongMemoize include Gitlab::Utils::StrongMemoize
include StageQueryHelpers include StageQueryHelpers
def initialize(stage:, query:) def initialize(stage:, query:, params: {})
@stage = stage @stage = stage
@query = query @query = query
@params = params
end end
def seconds def seconds
...@@ -22,7 +23,7 @@ module Gitlab ...@@ -22,7 +23,7 @@ module Gitlab
private private
attr_reader :stage attr_reader :stage, :params
# rubocop: disable CodeReuse/ActiveRecord # rubocop: disable CodeReuse/ActiveRecord
def select_average def select_average
......
...@@ -5,6 +5,7 @@ module Gitlab ...@@ -5,6 +5,7 @@ module Gitlab
module CycleAnalytics module CycleAnalytics
class BaseQueryBuilder class BaseQueryBuilder
include Gitlab::CycleAnalytics::MetricsTables include Gitlab::CycleAnalytics::MetricsTables
include StageQueryHelpers
delegate :subject_class, to: :stage delegate :subject_class, to: :stage
...@@ -13,6 +14,8 @@ module Gitlab ...@@ -13,6 +14,8 @@ module Gitlab
Issue.to_s => IssuesFinder Issue.to_s => IssuesFinder
}.freeze }.freeze
DEFAULT_END_EVENT_FILTER = :finished
def initialize(stage:, params: {}) def initialize(stage:, params: {})
@stage = stage @stage = stage
@params = build_finder_params(params) @params = build_finder_params(params)
...@@ -22,8 +25,7 @@ module Gitlab ...@@ -22,8 +25,7 @@ module Gitlab
def build def build
query = finder.execute query = finder.execute
query = stage.start_event.apply_query_customization(query) query = stage.start_event.apply_query_customization(query)
query = stage.end_event.apply_query_customization(query) apply_end_event_query_customization(query)
query.where(duration_condition)
end end
# rubocop: enable CodeReuse/ActiveRecord # rubocop: enable CodeReuse/ActiveRecord
...@@ -46,6 +48,7 @@ module Gitlab ...@@ -46,6 +48,7 @@ module Gitlab
def build_finder_params(params) def build_finder_params(params)
{}.tap do |finder_params| {}.tap do |finder_params|
finder_params[:current_user] = params[:current_user] finder_params[:current_user] = params[:current_user]
finder_params[:end_event_filter] = params[:end_event_filter] || DEFAULT_END_EVENT_FILTER
add_parent_model_params!(finder_params) add_parent_model_params!(finder_params)
add_time_range_params!(finder_params, params[:from], params[:to]) add_time_range_params!(finder_params, params[:from], params[:to])
...@@ -62,6 +65,17 @@ module Gitlab ...@@ -62,6 +65,17 @@ module Gitlab
finder_params[:created_after] = from || 30.days.ago finder_params[:created_after] = from || 30.days.ago
finder_params[:created_before] = to if to finder_params[:created_before] = to if to
end end
# rubocop: disable CodeReuse/ActiveRecord
def apply_end_event_query_customization(query)
if in_progress?
stage.end_event.apply_negated_query_customization(query)
else
query = stage.end_event.apply_query_customization(query)
query.where(duration_condition)
end
end
# rubocop: enable CodeReuse/ActiveRecord
end end
end end
end end
......
...@@ -29,13 +29,13 @@ module Gitlab ...@@ -29,13 +29,13 @@ module Gitlab
def median def median
strong_memoize(:median) do strong_memoize(:median) do
Median.new(stage: stage, query: query) Median.new(stage: stage, query: query, params: params)
end end
end end
def average def average
strong_memoize(:average) do strong_memoize(:average) do
Average.new(stage: stage, query: query) Average.new(stage: stage, query: query, params: params)
end end
end end
......
...@@ -6,9 +6,10 @@ module Gitlab ...@@ -6,9 +6,10 @@ module Gitlab
class Median class Median
include StageQueryHelpers include StageQueryHelpers
def initialize(stage:, query:) def initialize(stage:, query:, params: {})
@stage = stage @stage = stage
@query = query @query = query
@params = params
end end
# rubocop: disable CodeReuse/ActiveRecord # rubocop: disable CodeReuse/ActiveRecord
...@@ -26,7 +27,7 @@ module Gitlab ...@@ -26,7 +27,7 @@ module Gitlab
private private
attr_reader :stage attr_reader :stage, :params
def percentile_cont def percentile_cont
percentile_cont_ordering = Arel::Nodes::UnaryOperation.new(Arel::Nodes::SqlLiteral.new('ORDER BY'), duration) percentile_cont_ordering = Arel::Nodes::UnaryOperation.new(Arel::Nodes::SqlLiteral.new('ORDER BY'), duration)
......
...@@ -124,7 +124,7 @@ module Gitlab ...@@ -124,7 +124,7 @@ module Gitlab
def time_columns def time_columns
[ [
stage.start_event.timestamp_projection.as('start_event_timestamp'), stage.start_event.timestamp_projection.as('start_event_timestamp'),
stage.end_event.timestamp_projection.as('end_event_timestamp'), end_event_timestamp_projection.as('end_event_timestamp'),
round_duration_to_seconds.as('total_time') round_duration_to_seconds.as('total_time')
] ]
end end
......
...@@ -4,23 +4,35 @@ module Gitlab ...@@ -4,23 +4,35 @@ module Gitlab
module Analytics module Analytics
module CycleAnalytics module CycleAnalytics
class Sorting class Sorting
include StageQueryHelpers
def initialize(stage:, query:, params: {})
@stage = stage
@query = query
@params = params
end
# rubocop: disable CodeReuse/ActiveRecord # rubocop: disable CodeReuse/ActiveRecord
SORTING_OPTIONS = { def apply(sort, direction)
sorting_options = {
end_event: { end_event: {
asc: -> (query, stage) { query.reorder(stage.end_event.timestamp_projection.asc) }, asc: -> { query.reorder(end_event_timestamp_projection.asc) },
desc: -> (query, stage) { query.reorder(stage.end_event.timestamp_projection.desc) } desc: -> { query.reorder(end_event_timestamp_projection.desc) }
}.freeze, },
duration: { duration: {
asc: -> (query, stage) { query.reorder(Arel::Nodes::Subtraction.new(stage.end_event.timestamp_projection, stage.start_event.timestamp_projection).asc) }, asc: -> { query.reorder(duration.asc) },
desc: -> (query, stage) { query.reorder(Arel::Nodes::Subtraction.new(stage.end_event.timestamp_projection, stage.start_event.timestamp_projection).desc) } desc: -> { query.reorder(duration.desc) }
}.freeze }
}.freeze }
# rubocop: enable CodeReuse/ActiveRecord,
def self.apply(query, stage, sort, direction) sort_lambda = sorting_options.dig(sort, direction) || sorting_options.dig(:end_event, :desc)
sort_lambda = SORTING_OPTIONS.dig(sort, direction) || SORTING_OPTIONS.dig(:end_event, :desc) sort_lambda.call
sort_lambda.call(query, stage)
end end
# rubocop: enable CodeReuse/ActiveRecord
private
attr_reader :stage, :query, :params
end end
end end
end end
......
...@@ -11,6 +11,12 @@ module Gitlab ...@@ -11,6 +11,12 @@ module Gitlab
end end
# rubocop: enable CodeReuse/ActiveRecord # rubocop: enable CodeReuse/ActiveRecord
# rubocop: disable CodeReuse/ActiveRecord
def apply_negated_query_customization(query)
super.joins(:metrics)
end
# rubocop: enable CodeReuse/ActiveRecord
def column_list def column_list
[timestamp_projection] [timestamp_projection]
end end
......
...@@ -51,6 +51,12 @@ module Gitlab ...@@ -51,6 +51,12 @@ module Gitlab
query query
end end
# rubocop: disable CodeReuse/ActiveRecord
def apply_negated_query_customization(query)
query.where(timestamp_projection.eq(nil))
end
# rubocop: enable CodeReuse/ActiveRecord
def self.label_based? def self.label_based?
false false
end end
......
...@@ -18,22 +18,30 @@ module Gitlab ...@@ -18,22 +18,30 @@ module Gitlab
def duration def duration
Arel::Nodes::Subtraction.new( Arel::Nodes::Subtraction.new(
stage.end_event.timestamp_projection, end_event_timestamp_projection,
stage.start_event.timestamp_projection stage.start_event.timestamp_projection
) )
end end
def end_event_timestamp_projection
if in_progress?
Arel::Nodes::NamedFunction.new('TO_TIMESTAMP', [Time.current.to_i])
else
stage.end_event.timestamp_projection
end
end
# rubocop: disable CodeReuse/ActiveRecord # rubocop: disable CodeReuse/ActiveRecord
def order_by(query, sort, direction, extra_columns_to_select = [:id]) def order_by(query, sort, direction, extra_columns_to_select = [:id])
ordered_query = Gitlab::Analytics::CycleAnalytics::Sorting.apply(query, stage, sort, direction) ordered_query = Gitlab::Analytics::CycleAnalytics::Sorting.new(stage: stage, query: query, params: params).apply(sort, direction)
# When filtering for more than one label, postgres requires the columns in ORDER BY to be present in the GROUP BY clause # When filtering for more than one label, postgres requires the columns in ORDER BY to be present in the GROUP BY clause
if requires_grouping? if requires_grouping?
column_list = [ column_list = [].tap do |array|
*extra_columns_to_select, array.concat(extra_columns_to_select)
*stage.end_event.column_list, array.concat(stage.end_event.column_list) unless in_progress?
*stage.start_event.column_list array.concat(stage.start_event.column_list)
] end
ordered_query = ordered_query.group(column_list) ordered_query = ordered_query.group(column_list)
end end
...@@ -45,6 +53,10 @@ module Gitlab ...@@ -45,6 +53,10 @@ module Gitlab
def requires_grouping? def requires_grouping?
Array(params[:label_name]).size > 1 Array(params[:label_name]).size > 1
end end
def in_progress?
params[:end_event_filter] == :in_progress
end
end end
end end
end end
......
...@@ -5,7 +5,7 @@ require 'spec_helper' ...@@ -5,7 +5,7 @@ require 'spec_helper'
RSpec.describe Gitlab::Analytics::CycleAnalytics::Sorting do RSpec.describe Gitlab::Analytics::CycleAnalytics::Sorting do
let(:stage) { build(:cycle_analytics_project_stage, start_event_identifier: :merge_request_created, end_event_identifier: :merge_request_merged) } let(:stage) { build(:cycle_analytics_project_stage, start_event_identifier: :merge_request_created, end_event_identifier: :merge_request_merged) }
subject(:order_values) { described_class.apply(MergeRequest.joins(:metrics), stage, sort, direction).order_values } subject(:order_values) { described_class.new(query: MergeRequest.joins(:metrics), stage: stage).apply(sort, direction).order_values }
context 'when invalid sorting params are given' do context 'when invalid sorting params are given' do
let(:sort) { :unknown_sort } let(:sort) { :unknown_sort }
......
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