diff --git a/lib/gitlab/cycle_analytics/base_event_fetcher.rb b/lib/gitlab/cycle_analytics/base_event_fetcher.rb index 304d60996a6..0b8d5089a5b 100644 --- a/lib/gitlab/cycle_analytics/base_event_fetcher.rb +++ b/lib/gitlab/cycle_analytics/base_event_fetcher.rb @@ -9,7 +9,7 @@ module Gitlab MAX_EVENTS = 50 - def initialize(project:, stage:, options:) + def initialize(project: nil, stage:, options:) @project = project @stage = stage @options = options @@ -59,13 +59,17 @@ module Gitlab def allowed_ids @allowed_ids ||= allowed_ids_finder_class - .new(@options[:current_user], project_id: @project.id) + .new(@options[:current_user], allowed_ids_source) .execute.where(id: event_result_ids).pluck(:id) end def event_result_ids event_result.map { |event| event['id'] } end + + def allowed_ids_source + { project_id: @project.id } + end end end end diff --git a/lib/gitlab/cycle_analytics/base_query.rb b/lib/gitlab/cycle_analytics/base_query.rb index 36231b187cd..733cad827e9 100644 --- a/lib/gitlab/cycle_analytics/base_query.rb +++ b/lib/gitlab/cycle_analytics/base_query.rb @@ -10,7 +10,7 @@ module Gitlab private def base_query - @base_query ||= stage_query(@project.id) # rubocop:disable Gitlab/ModuleWithInstanceVariables + @base_query ||= stage_query(projects.map(&:id)) # rubocop:disable Gitlab/ModuleWithInstanceVariables end def stage_query(project_ids) diff --git a/lib/gitlab/cycle_analytics/base_stage.rb b/lib/gitlab/cycle_analytics/base_stage.rb index e2d6a301734..9e64dcb87e2 100644 --- a/lib/gitlab/cycle_analytics/base_stage.rb +++ b/lib/gitlab/cycle_analytics/base_stage.rb @@ -5,7 +5,7 @@ module Gitlab class BaseStage include BaseQuery - def initialize(project:, options:) + def initialize(project: nil, options:) @project = project @options = options end @@ -14,8 +14,8 @@ module Gitlab event_fetcher.fetch end - def as_json - AnalyticsStageSerializer.new.represent(self) + def as_json(serializer: AnalyticsStageSerializer) + serializer.new.represent(self) end def title @@ -24,20 +24,11 @@ module Gitlab def median BatchLoader.for(@project.id).batch(key: name) do |project_ids, loader| - cte_table = Arel::Table.new("cte_table_for_#{name}") - - # 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). - # We compute the (end_time - start_time) interval, and give it an alias based on the current - # cycle analytics stage. - interval_query = Arel::Nodes::As.new(cte_table, - subtract_datetimes(stage_query(project_ids), start_time_attrs, end_time_attrs, name.to_s)) - if project_ids.one? - loader.call(@project.id, median_datetime(cte_table, interval_query, name)) + loader.call(@project.id, median_query(project_ids)) else begin - median_datetimes(cte_table, interval_query, name, :project_id)&.each do |project_id, median| + median_datetimes(cte_table, interval_query(project_ids), name, :project_id)&.each do |project_id, median| loader.call(project_id, median) end rescue NotSupportedError @@ -47,10 +38,28 @@ module Gitlab end end + def median_query(project_ids) + # 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). + # We compute the (end_time - start_time) interval, and give it an alias based on the current + # cycle analytics stage. + + median_datetime(cte_table, interval_query(project_ids), name) + end + def name raise NotImplementedError.new("Expected #{self.name} to implement name") end + def cte_table + Arel::Table.new("cte_table_for_#{name}") + end + + def interval_query(project_ids) + Arel::Nodes::As.new(cte_table, + subtract_datetimes(stage_query(project_ids), start_time_attrs, end_time_attrs, name.to_s)) + end + private def event_fetcher @@ -62,6 +71,10 @@ module Gitlab def event_options @options.merge(start_time_attrs: start_time_attrs, end_time_attrs: end_time_attrs) end + + def projects + [@project] + end end end end