Commit cf05dc9b authored by Mayra Cabrera's avatar Mayra Cabrera

Merge branch '217973-use-standard-finders-in-vsa-base-query' into 'master'

Always use the finders in Value Stream Analytics

See merge request gitlab-org/gitlab!33033
parents 33a63ea4 91f36e73
...@@ -10,33 +10,25 @@ module EE::Gitlab::Analytics::CycleAnalytics::BaseQueryBuilder ...@@ -10,33 +10,25 @@ module EE::Gitlab::Analytics::CycleAnalytics::BaseQueryBuilder
private private
# rubocop: disable CodeReuse/ActiveRecord override :build_finder_params
def filter_by_project_ids(query) def build_finder_params(params)
project_ids = Array(params[:project_ids]) super.tap do |finder_params|
finder_params[:project_ids] = Array(params[:project_ids])
query = query.where(project_id: project_ids) if project_ids.any? end
query
end end
# rubocop: enable CodeReuse/ActiveRecord
override :filter_by_parent_model override :add_parent_model_params!
# rubocop: disable CodeReuse/ActiveRecord def add_parent_model_params!(finder_params)
def filter_by_parent_model(query)
return super unless parent_class.eql?(Group) return super unless parent_class.eql?(Group)
if subject_class.eql?(Issue) finder_params.merge!(group_id: stage.parent_id, include_subgroups: true)
join_groups(query.joins(:project))
elsif subject_class.eql?(MergeRequest)
join_groups(query.joins(:target_project))
else
raise ArgumentError, "unknown subject_class: #{subject_class}"
end
end end
# rubocop: enable CodeReuse/ActiveRecord
# rubocop: disable CodeReuse/ActiveRecord # rubocop: disable CodeReuse/ActiveRecord
def join_groups(query) def filter_by_project_ids(query)
query.joins(Arel.sql("INNER JOIN (#{stage.parent.self_and_descendants.to_sql}) namespaces ON namespaces.id=projects.namespace_id")) return query if params[:project_ids].empty?
query.where(project_id: params[:project_ids])
end end
# rubocop: enable CodeReuse/ActiveRecord # rubocop: enable CodeReuse/ActiveRecord
end end
...@@ -2,9 +2,4 @@ ...@@ -2,9 +2,4 @@
module EE::Gitlab::Analytics::CycleAnalytics::RecordsFetcher module EE::Gitlab::Analytics::CycleAnalytics::RecordsFetcher
extend ::Gitlab::Utils::Override extend ::Gitlab::Utils::Override
override :finder_params
def finder_params
super.merge({ ::Group => { group_id: stage.parent_id } })
end
end end
...@@ -43,7 +43,8 @@ module Gitlab ...@@ -43,7 +43,8 @@ module Gitlab
params: { params: {
from: @options[:from], from: @options[:from],
to: @options[:to] || DateTime.now, to: @options[:to] || DateTime.now,
project_ids: @options[:projects] project_ids: @options[:projects],
current_user: @current_user
} }
) )
end end
......
...@@ -8,6 +8,12 @@ describe Gitlab::Analytics::CycleAnalytics::BaseQueryBuilder do ...@@ -8,6 +8,12 @@ describe Gitlab::Analytics::CycleAnalytics::BaseQueryBuilder do
let_it_be(:project_in_group) { create(:project, :repository, group: group) } let_it_be(:project_in_group) { create(:project, :repository, group: group) }
let_it_be(:project_in_subgroup) { create(:project, :repository, group: subgroup) } let_it_be(:project_in_subgroup) { create(:project, :repository, group: subgroup) }
let_it_be(:project_outside_group) { create(:project, :repository, group: create(:group)) } let_it_be(:project_outside_group) { create(:project, :repository, group: create(:group)) }
let_it_be(:user) { create(:user) }
before do
group.add_maintainer(user)
project_outside_group.add_maintainer(user)
end
context 'when the subject is `Issue`' do context 'when the subject is `Issue`' do
let(:issue_in_project) { create(:issue, project: project_in_group, created_at: 5.days.ago) } let(:issue_in_project) { create(:issue, project: project_in_group, created_at: 5.days.ago) }
...@@ -27,7 +33,7 @@ describe Gitlab::Analytics::CycleAnalytics::BaseQueryBuilder do ...@@ -27,7 +33,7 @@ describe Gitlab::Analytics::CycleAnalytics::BaseQueryBuilder do
group: group group: group
}) })
result = described_class.new(stage: stage).build result = described_class.new(stage: stage, params: { current_user: user }).build
expect(result).to contain_exactly(issue_in_project, issue_in_subgroup_project) expect(result).to contain_exactly(issue_in_project, issue_in_subgroup_project)
end end
...@@ -51,7 +57,7 @@ describe Gitlab::Analytics::CycleAnalytics::BaseQueryBuilder do ...@@ -51,7 +57,7 @@ describe Gitlab::Analytics::CycleAnalytics::BaseQueryBuilder do
group: group group: group
}) })
result = described_class.new(stage: stage).build result = described_class.new(stage: stage, params: { current_user: user }).build
expect(result).to contain_exactly(mr_in_project, mr_in_subgroup_project) expect(result).to contain_exactly(mr_in_project, mr_in_subgroup_project)
end end
......
...@@ -8,21 +8,24 @@ module Gitlab ...@@ -8,21 +8,24 @@ module Gitlab
delegate :subject_class, to: :stage delegate :subject_class, to: :stage
# rubocop: disable CodeReuse/ActiveRecord FINDER_CLASSES = {
MergeRequest.to_s => MergeRequestsFinder,
Issue.to_s => IssuesFinder
}.freeze
def initialize(stage:, params: {}) def initialize(stage:, params: {})
@stage = stage @stage = stage
@params = params @params = build_finder_params(params)
end end
# rubocop: disable CodeReuse/ActiveRecord
def build def build
query = subject_class query = finder.execute
query = filter_by_parent_model(query)
query = filter_by_time_range(query)
query = stage.start_event.apply_query_customization(query) query = stage.start_event.apply_query_customization(query)
query = stage.end_event.apply_query_customization(query) query = stage.end_event.apply_query_customization(query)
query.where(duration_condition) query.where(duration_condition)
end end
# rubocop: enable CodeReuse/ActiveRecord
private private
...@@ -32,38 +35,33 @@ module Gitlab ...@@ -32,38 +35,33 @@ module Gitlab
stage.end_event.timestamp_projection.gteq(stage.start_event.timestamp_projection) stage.end_event.timestamp_projection.gteq(stage.start_event.timestamp_projection)
end end
def filter_by_parent_model(query) def finder
if parent_class.eql?(Project) FINDER_CLASSES.fetch(subject_class.to_s).new(params[:current_user], params)
if subject_class.eql?(Issue)
query.where(project_id: stage.parent_id)
elsif subject_class.eql?(MergeRequest)
query.where(target_project_id: stage.parent_id)
else
raise ArgumentError, "unknown subject_class: #{subject_class}"
end
else
raise ArgumentError, "unknown parent_class: #{parent_class}"
end
end end
def filter_by_time_range(query) def parent_class
from = params.fetch(:from, 30.days.ago) stage.parent.class
to = params[:to]
query = query.where(subject_table[:created_at].gteq(from))
query = query.where(subject_table[:created_at].lteq(to)) if to
query
end end
def subject_table def build_finder_params(params)
subject_class.arel_table {}.tap do |finder_params|
finder_params[:current_user] = params[:current_user]
add_parent_model_params!(finder_params)
add_time_range_params!(finder_params, params[:from], params[:to])
end
end end
def parent_class def add_parent_model_params!(finder_params)
stage.parent.class raise(ArgumentError, "unknown parent_class: #{parent_class}") unless parent_class.eql?(Project)
finder_params[:project_id] = stage.parent_id
end end
# rubocop: enable CodeReuse/ActiveRecord def add_time_range_params!(finder_params, from, to)
finder_params[:created_after] = from || 30.days.ago
finder_params[:created_before] = to if to
end
end end
end end
end end
......
...@@ -11,12 +11,14 @@ module Gitlab ...@@ -11,12 +11,14 @@ module Gitlab
@query = query @query = query
end end
# rubocop: disable CodeReuse/ActiveRecord
def seconds def seconds
@query = @query.select(median_duration_in_seconds.as('median')) @query = @query.select(median_duration_in_seconds.as('median')).reorder(nil)
result = execute_query(@query).first || {} result = execute_query(@query).first || {}
result['median'] || nil result['median'] || nil
end end
# rubocop: enable CodeReuse/ActiveRecord
def days def days
seconds ? seconds.fdiv(1.day) : nil seconds ? seconds.fdiv(1.day) : nil
......
...@@ -12,13 +12,11 @@ module Gitlab ...@@ -12,13 +12,11 @@ module Gitlab
MAPPINGS = { MAPPINGS = {
Issue => { Issue => {
finder_class: IssuesFinder,
serializer_class: AnalyticsIssueSerializer, serializer_class: AnalyticsIssueSerializer,
includes_for_query: { project: [:namespace], author: [] }, includes_for_query: { project: [:namespace], author: [] },
columns_for_select: %I[title iid id created_at author_id project_id] columns_for_select: %I[title iid id created_at author_id project_id]
}, },
MergeRequest => { MergeRequest => {
finder_class: MergeRequestsFinder,
serializer_class: AnalyticsMergeRequestSerializer, serializer_class: AnalyticsMergeRequestSerializer,
includes_for_query: { target_project: [:namespace], author: [] }, includes_for_query: { target_project: [:namespace], author: [] },
columns_for_select: %I[title iid id created_at author_id state_id target_project_id] columns_for_select: %I[title iid id created_at author_id state_id target_project_id]
...@@ -56,27 +54,12 @@ module Gitlab ...@@ -56,27 +54,12 @@ module Gitlab
attr_reader :stage, :query, :params attr_reader :stage, :query, :params
def finder_query
MAPPINGS
.fetch(subject_class)
.fetch(:finder_class)
.new(params.fetch(:current_user), finder_params.fetch(stage.parent.class))
.execute
end
def columns def columns
MAPPINGS.fetch(subject_class).fetch(:columns_for_select).map do |column_name| MAPPINGS.fetch(subject_class).fetch(:columns_for_select).map do |column_name|
subject_class.arel_table[column_name] subject_class.arel_table[column_name]
end end
end end
# EE will override this to include Group rules
def finder_params
{
Project => { project_id: stage.parent_id }
}
end
def default_test_stage? def default_test_stage?
stage.matches_with_stage_params?(Gitlab::Analytics::CycleAnalytics::DefaultStages.params_for_test_stage) stage.matches_with_stage_params?(Gitlab::Analytics::CycleAnalytics::DefaultStages.params_for_test_stage)
end end
...@@ -113,8 +96,7 @@ module Gitlab ...@@ -113,8 +96,7 @@ module Gitlab
end end
def records def records
results = finder_query results = ordered_and_limited_query
.merge(ordered_and_limited_query)
.select(*columns, round_duration_to_seconds.as('total_time')) .select(*columns, round_duration_to_seconds.as('total_time'))
# using preloader instead of includes to avoid AR generating a large column list # using preloader instead of includes to avoid AR generating a large column list
......
...@@ -6,7 +6,8 @@ describe Gitlab::Analytics::CycleAnalytics::BaseQueryBuilder do ...@@ -6,7 +6,8 @@ describe Gitlab::Analytics::CycleAnalytics::BaseQueryBuilder do
let_it_be(:project) { create(:project, :empty_repo) } let_it_be(:project) { create(:project, :empty_repo) }
let_it_be(:mr1) { create(:merge_request, target_project: project, source_project: project, allow_broken: true, created_at: 3.months.ago) } let_it_be(:mr1) { create(:merge_request, target_project: project, source_project: project, allow_broken: true, created_at: 3.months.ago) }
let_it_be(:mr2) { create(:merge_request, target_project: project, source_project: project, allow_broken: true, created_at: 1.month.ago) } let_it_be(:mr2) { create(:merge_request, target_project: project, source_project: project, allow_broken: true, created_at: 1.month.ago) }
let(:params) { {} } let_it_be(:user) { create(:user) }
let(:params) { { current_user: user } }
let(:records) do let(:records) do
stage = build(:cycle_analytics_project_stage, { stage = build(:cycle_analytics_project_stage, {
start_event_identifier: :merge_request_created, start_event_identifier: :merge_request_created,
...@@ -17,6 +18,7 @@ describe Gitlab::Analytics::CycleAnalytics::BaseQueryBuilder do ...@@ -17,6 +18,7 @@ describe Gitlab::Analytics::CycleAnalytics::BaseQueryBuilder do
end end
before do before do
project.add_maintainer(user)
mr1.metrics.update!(merged_at: 1.month.ago) mr1.metrics.update!(merged_at: 1.month.ago)
mr2.metrics.update!(merged_at: Time.now) mr2.metrics.update!(merged_at: Time.now)
end end
......
...@@ -23,7 +23,7 @@ describe Gitlab::Analytics::CycleAnalytics::RecordsFetcher do ...@@ -23,7 +23,7 @@ describe Gitlab::Analytics::CycleAnalytics::RecordsFetcher do
describe '#serialized_records' do describe '#serialized_records' do
shared_context 'when records are loaded by maintainer' do shared_context 'when records are loaded by maintainer' do
before do before do
project.add_user(user, Gitlab::Access::MAINTAINER) project.add_user(user, Gitlab::Access::DEVELOPER)
end end
it 'returns all records' do it 'returns all records' do
...@@ -103,6 +103,8 @@ describe Gitlab::Analytics::CycleAnalytics::RecordsFetcher do ...@@ -103,6 +103,8 @@ describe Gitlab::Analytics::CycleAnalytics::RecordsFetcher do
latest_build_finished_at: 7.days.ago, latest_build_finished_at: 7.days.ago,
pipeline: ci_build2.pipeline pipeline: ci_build2.pipeline
}) })
project.add_user(user, Gitlab::Access::MAINTAINER)
end end
context 'returns build records' do context 'returns build records' 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