diff options
author | Małgorzata Ksionek <mksionek@gitlab.com> | 2019-06-19 22:51:06 +0200 |
---|---|---|
committer | Małgorzata Ksionek <mksionek@gitlab.com> | 2019-07-03 16:45:52 +0200 |
commit | e8e7881daeaa7083be871701b1b123a7a4e88740 (patch) | |
tree | 7eaaa14a06ec71a79f651abb74d76ff064188d20 | |
parent | 406b67ca824790a5965b44ac76f87db68037248f (diff) | |
download | gitlab-ce-e8e7881daeaa7083be871701b1b123a7a4e88740.tar.gz |
First batch of changes regarding cycle analytics fior groups
Changing cycle analytics classes to accept multiple projects at once
21 files changed, 123 insertions, 72 deletions
diff --git a/app/controllers/projects/cycle_analytics/events_controller.rb b/app/controllers/projects/cycle_analytics/events_controller.rb index fb43356ff10..ae55bbcb60f 100644 --- a/app/controllers/projects/cycle_analytics/events_controller.rb +++ b/app/controllers/projects/cycle_analytics/events_controller.rb @@ -50,7 +50,7 @@ module Projects end def cycle_analytics - @cycle_analytics ||= ::CycleAnalytics.new(project, options(events_params)) + @cycle_analytics ||= ::CycleAnalytics::ProjectLevel.new(project: project, options: options(events_params)) end def events_params diff --git a/app/controllers/projects/cycle_analytics_controller.rb b/app/controllers/projects/cycle_analytics_controller.rb index 8c071496ba9..fe89b519a3f 100644 --- a/app/controllers/projects/cycle_analytics_controller.rb +++ b/app/controllers/projects/cycle_analytics_controller.rb @@ -9,7 +9,7 @@ class Projects::CycleAnalyticsController < Projects::ApplicationController before_action :authorize_read_cycle_analytics! def show - @cycle_analytics = ::CycleAnalytics.new(@project, options(cycle_analytics_params)) + @cycle_analytics = ::CycleAnalytics::ProjectLevel.new(project: @project, options: options(cycle_analytics_params)) @cycle_analytics_no_data = @cycle_analytics.no_stats? diff --git a/app/models/cycle_analytics.rb b/app/models/cycle_analytics.rb deleted file mode 100644 index d0f5b6970b1..00000000000 --- a/app/models/cycle_analytics.rb +++ /dev/null @@ -1,46 +0,0 @@ -# frozen_string_literal: true - -class CycleAnalytics - STAGES = %i[issue plan code test review staging production].freeze - - def initialize(project, options) - @project = project - @options = options - end - - def all_medians_per_stage - STAGES.each_with_object({}) do |stage_name, medians_per_stage| - medians_per_stage[stage_name] = self[stage_name].median - end - end - - def summary - @summary ||= ::Gitlab::CycleAnalytics::StageSummary.new(@project, - from: @options[:from], - current_user: @options[:current_user]).data - end - - def stats - @stats ||= stats_per_stage - end - - def no_stats? - stats.all? { |hash| hash[:value].nil? } - end - - def permissions(user:) - Gitlab::CycleAnalytics::Permissions.get(user: user, project: @project) - end - - def [](stage_name) - Gitlab::CycleAnalytics::Stage[stage_name].new(project: @project, options: @options) - end - - private - - def stats_per_stage - STAGES.map do |stage_name| - self[stage_name].as_json - end - end -end diff --git a/app/models/cycle_analytics/base.rb b/app/models/cycle_analytics/base.rb new file mode 100644 index 00000000000..24f6701d47b --- /dev/null +++ b/app/models/cycle_analytics/base.rb @@ -0,0 +1,33 @@ +# frozen_string_literal: true + +module CycleAnalytics + class Base + STAGES = %i[issue plan code test review staging production].freeze + + def all_medians_per_stage + STAGES.each_with_object({}) do |stage_name, medians_per_stage| + medians_per_stage[stage_name] = self[stage_name].median + end + end + + def stats + @stats ||= stats_per_stage + end + + def no_stats? + stats.all? { |hash| hash[:value].nil? } + end + + def [](stage_name) + Gitlab::CycleAnalytics::Stage[stage_name].new(projects: @projects, options: @options) + end + + private + + def stats_per_stage + STAGES.map do |stage_name| + self[stage_name].as_json + end + end + end +end diff --git a/app/models/cycle_analytics/group_level.rb b/app/models/cycle_analytics/group_level.rb new file mode 100644 index 00000000000..c48919a61ac --- /dev/null +++ b/app/models/cycle_analytics/group_level.rb @@ -0,0 +1,22 @@ +# frozen_string_literal: true + +module CycleAnalytics + class GroupLevel < Base + def initialize(project: nil, projects:, options:) + @projects = projects + @options = options + end + + def summary + @summary ||= ::Gitlab::CycleAnalytics::GroupStageSummary.new(@project, + from: @options[:from], + current_user: @options[:current_user]).data + end + + def permissions(user:) + STAGES.each_with_object({}) do |stage, obj| + obj[stage] = true + end + end + end +end diff --git a/app/models/cycle_analytics/project_level.rb b/app/models/cycle_analytics/project_level.rb new file mode 100644 index 00000000000..64e4352cb64 --- /dev/null +++ b/app/models/cycle_analytics/project_level.rb @@ -0,0 +1,21 @@ +# frozen_string_literal: true + +module CycleAnalytics + class ProjectLevel < Base + def initialize(project:, projects: nil, options:) + @projects = [project] + @project = project + @options = options + end + + def summary + @summary ||= ::Gitlab::CycleAnalytics::StageSummary.new(@project, + from: @options[:from], + current_user: @options[:current_user]).data + end + + def permissions(user:) + Gitlab::CycleAnalytics::Permissions.get(user: user, project: @project) + end + end +end diff --git a/app/serializers/analytics_issue_entity.rb b/app/serializers/analytics_issue_entity.rb index ab15bd0ac7a..6d17b9af3c5 100644 --- a/app/serializers/analytics_issue_entity.rb +++ b/app/serializers/analytics_issue_entity.rb @@ -20,12 +20,12 @@ class AnalyticsIssueEntity < Grape::Entity end expose :url do |object| - url_to(:namespace_project_issue, id: object[:iid].to_s) + url_to(:namespace_project_issue, object) end private - def url_to(route, id) - public_send("#{route}_url", request.project.namespace, request.project, id) # rubocop:disable GitlabSecurity/PublicSend + def url_to(route, object) + public_send("#{route}_url", request.namespace, object[:project_name], object[:iid].to_s) # rubocop:disable GitlabSecurity/PublicSend end end diff --git a/app/serializers/analytics_merge_request_entity.rb b/app/serializers/analytics_merge_request_entity.rb index b7134da9461..21d7eeb81b0 100644 --- a/app/serializers/analytics_merge_request_entity.rb +++ b/app/serializers/analytics_merge_request_entity.rb @@ -4,6 +4,6 @@ class AnalyticsMergeRequestEntity < AnalyticsIssueEntity expose :state expose :url do |object| - url_to(:namespace_project_merge_request, id: object[:iid].to_s) + url_to(:namespace_project_merge_request, object) end end diff --git a/lib/gitlab/cycle_analytics/base_event_fetcher.rb b/lib/gitlab/cycle_analytics/base_event_fetcher.rb index 304d60996a6..21f15fb4965 100644 --- a/lib/gitlab/cycle_analytics/base_event_fetcher.rb +++ b/lib/gitlab/cycle_analytics/base_event_fetcher.rb @@ -9,8 +9,9 @@ module Gitlab MAX_EVENTS = 50 - def initialize(project:, stage:, options:) - @project = project + def initialize(projects:, stage:, options:) + @projects = projects + @project = @projects.first @stage = stage @options = options end @@ -59,13 +60,22 @@ 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 + + def serialization_context + namespace = @group ? @group : @project.namespace + { namespace: namespace } + end end end end diff --git a/lib/gitlab/cycle_analytics/base_query.rb b/lib/gitlab/cycle_analytics/base_query.rb index 36231b187cd..653786b85a7 100644 --- a/lib/gitlab/cycle_analytics/base_query.rb +++ b/lib/gitlab/cycle_analytics/base_query.rb @@ -10,12 +10,14 @@ 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) query = mr_closing_issues_table.join(issue_table).on(issue_table[:id].eq(mr_closing_issues_table[:issue_id])) .join(issue_metrics_table).on(issue_table[:id].eq(issue_metrics_table[:issue_id])) + .join(projects_table).on(issue_table[:project_id].eq(projects_table[:id])) + .project(projects_table[:name].as("project_name")) .project(issue_table[:project_id].as("project_id")) .where(issue_table[:project_id].in(project_ids)) .where(issue_table[:created_at].gteq(@options[:from])) # rubocop:disable Gitlab/ModuleWithInstanceVariables diff --git a/lib/gitlab/cycle_analytics/base_stage.rb b/lib/gitlab/cycle_analytics/base_stage.rb index e2d6a301734..0095a7b344d 100644 --- a/lib/gitlab/cycle_analytics/base_stage.rb +++ b/lib/gitlab/cycle_analytics/base_stage.rb @@ -5,8 +5,8 @@ module Gitlab class BaseStage include BaseQuery - def initialize(project:, options:) - @project = project + def initialize(projects:, options:) + @projects = projects @options = options end @@ -23,18 +23,18 @@ module Gitlab end def median - BatchLoader.for(@project.id).batch(key: name) do |project_ids, loader| + ids = @projects.map(&:id) + BatchLoader.for(@projects.first.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)) + subtract_datetimes(stage_query(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(@projects.first.id, median_datetime(cte_table, interval_query, name)) else begin median_datetimes(cte_table, interval_query, name, :project_id)&.each do |project_id, median| @@ -54,7 +54,7 @@ module Gitlab private def event_fetcher - @event_fetcher ||= Gitlab::CycleAnalytics::EventFetcher[name].new(project: @project, + @event_fetcher ||= Gitlab::CycleAnalytics::EventFetcher[name].new(projects: @projects, stage: name, options: event_options) end diff --git a/lib/gitlab/cycle_analytics/code_event_fetcher.rb b/lib/gitlab/cycle_analytics/code_event_fetcher.rb index 6c348f1862d..fcc282bf7a6 100644 --- a/lib/gitlab/cycle_analytics/code_event_fetcher.rb +++ b/lib/gitlab/cycle_analytics/code_event_fetcher.rb @@ -20,7 +20,7 @@ module Gitlab private def serialize(event) - AnalyticsMergeRequestSerializer.new(project: @project).represent(event) + AnalyticsMergeRequestSerializer.new(serialization_context).represent(event) end def allowed_ids_finder_class diff --git a/lib/gitlab/cycle_analytics/issue_event_fetcher.rb b/lib/gitlab/cycle_analytics/issue_event_fetcher.rb index 8a870f2e2a3..1465e71fd74 100644 --- a/lib/gitlab/cycle_analytics/issue_event_fetcher.rb +++ b/lib/gitlab/cycle_analytics/issue_event_fetcher.rb @@ -10,7 +10,8 @@ module Gitlab issue_table[:iid], issue_table[:id], issue_table[:created_at], - issue_table[:author_id]] + issue_table[:author_id], + projects_table[:name]] super(*args) end @@ -18,7 +19,7 @@ module Gitlab private def serialize(event) - AnalyticsIssueSerializer.new(project: @project).represent(event) + AnalyticsIssueSerializer.new(serialization_context).represent(event) end def allowed_ids_finder_class diff --git a/lib/gitlab/cycle_analytics/issue_helper.rb b/lib/gitlab/cycle_analytics/issue_helper.rb index c9266341378..bef2854777a 100644 --- a/lib/gitlab/cycle_analytics/issue_helper.rb +++ b/lib/gitlab/cycle_analytics/issue_helper.rb @@ -5,6 +5,8 @@ module Gitlab module IssueHelper def stage_query(project_ids) query = issue_table.join(issue_metrics_table).on(issue_table[:id].eq(issue_metrics_table[:issue_id])) + .join(projects_table).on(issue_table[:project_id].eq(projects_table[:id])) + .project(projects_table[:name].as("project_name")) .project(issue_table[:project_id].as("project_id")) .where(issue_table[:project_id].in(project_ids)) .where(issue_table[:created_at].gteq(@options[:from])) # rubocop:disable Gitlab/ModuleWithInstanceVariables diff --git a/lib/gitlab/cycle_analytics/metrics_tables.rb b/lib/gitlab/cycle_analytics/metrics_tables.rb index 3e0302d308d..bccb5542f1a 100644 --- a/lib/gitlab/cycle_analytics/metrics_tables.rb +++ b/lib/gitlab/cycle_analytics/metrics_tables.rb @@ -31,6 +31,10 @@ module Gitlab Issue::Metrics.arel_table end + def projects_table + Project.arel_table + end + def user_table User.arel_table end diff --git a/lib/gitlab/cycle_analytics/permissions.rb b/lib/gitlab/cycle_analytics/permissions.rb index afefd09b614..03ba98b4dfb 100644 --- a/lib/gitlab/cycle_analytics/permissions.rb +++ b/lib/gitlab/cycle_analytics/permissions.rb @@ -23,7 +23,7 @@ module Gitlab end def get - ::CycleAnalytics::STAGES.each do |stage| + ::CycleAnalytics::Base::STAGES.each do |stage| @stage_permission_hash[stage] = authorized_stage?(stage) end diff --git a/lib/gitlab/cycle_analytics/plan_event_fetcher.rb b/lib/gitlab/cycle_analytics/plan_event_fetcher.rb index d924f956dcd..bad02e00a13 100644 --- a/lib/gitlab/cycle_analytics/plan_event_fetcher.rb +++ b/lib/gitlab/cycle_analytics/plan_event_fetcher.rb @@ -18,7 +18,7 @@ module Gitlab private def serialize(event) - AnalyticsIssueSerializer.new(project: @project).represent(event) + AnalyticsIssueSerializer.new(serialization_context).represent(event) end def allowed_ids_finder_class diff --git a/lib/gitlab/cycle_analytics/plan_helper.rb b/lib/gitlab/cycle_analytics/plan_helper.rb index 30fc2ce6d40..6858cf0afe8 100644 --- a/lib/gitlab/cycle_analytics/plan_helper.rb +++ b/lib/gitlab/cycle_analytics/plan_helper.rb @@ -5,6 +5,8 @@ module Gitlab module PlanHelper def stage_query(project_ids) query = issue_table.join(issue_metrics_table).on(issue_table[:id].eq(issue_metrics_table[:issue_id])) + .join(projects_table).on(issue_table[:project_id].eq(projects_table[:id])) + .project(projects_table[:name].as("project_name")) .project(issue_table[:project_id].as("project_id")) .where(issue_table[:project_id].in(project_ids)) .where(issue_table[:created_at].gteq(@options[:from])) # rubocop:disable Gitlab/ModuleWithInstanceVariables diff --git a/lib/gitlab/cycle_analytics/production_event_fetcher.rb b/lib/gitlab/cycle_analytics/production_event_fetcher.rb index 6bcbe0412a9..c4b54d06d6e 100644 --- a/lib/gitlab/cycle_analytics/production_event_fetcher.rb +++ b/lib/gitlab/cycle_analytics/production_event_fetcher.rb @@ -18,7 +18,7 @@ module Gitlab private def serialize(event) - AnalyticsIssueSerializer.new(project: @project).represent(event) + AnalyticsIssueSerializer.new(serialization_context).represent(event) end def allowed_ids_finder_class diff --git a/lib/gitlab/cycle_analytics/review_event_fetcher.rb b/lib/gitlab/cycle_analytics/review_event_fetcher.rb index b6354b5ffad..4b5d79097b7 100644 --- a/lib/gitlab/cycle_analytics/review_event_fetcher.rb +++ b/lib/gitlab/cycle_analytics/review_event_fetcher.rb @@ -19,7 +19,7 @@ module Gitlab private def serialize(event) - AnalyticsMergeRequestSerializer.new(project: @project).represent(event) + AnalyticsMergeRequestSerializer.new(serialization_context).represent(event) end def allowed_ids_finder_class diff --git a/spec/models/cycle_analytics_spec.rb b/spec/models/cycle_analytics/project_level_spec.rb index 5d8b5b573cf..196306fd52a 100644 --- a/spec/models/cycle_analytics_spec.rb +++ b/spec/models/cycle_analytics/project_level_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -describe CycleAnalytics do +describe CycleAnalytics::ProjectLevel do let(:project) { create(:project, :repository) } let(:from_date) { 10.days.ago } let(:user) { create(:user, :admin) } @@ -11,7 +11,7 @@ describe CycleAnalytics do let(:mr) { create_merge_request_closing_issue(user, project, issue, commit_message: "References #{issue.to_reference}") } let(:pipeline) { create(:ci_empty_pipeline, status: 'created', project: project, ref: mr.source_branch, sha: mr.source_branch_sha, head_pipeline_of: mr) } - subject { described_class.new(project, from: from_date) } + subject { described_class.new(project: project, options: { from: from_date }) } describe '#all_medians_per_stage' do before do |