Commit 06b7f084 authored by Adam Hegyi's avatar Adam Hegyi

Introduce Analytics namespace in FOSS

parent 2fea2997
# frozen_string_literal: true
module Analytics
module NavbarHelper
class NavbarSubItem
attr_reader :title, :path, :link, :link_to_options
def initialize(title:, path:, link:, link_to_options: {})
@title = title
@path = path
@link = link
@link_to_options = link_to_options.merge(title: title)
end
end
def project_analytics_navbar_links(project, current_user)
[
cycle_analytics_navbar_link(project, current_user),
repository_analytics_navbar_link(project, current_user),
ci_cd_analytics_navbar_link(project, current_user)
].compact
end
def group_analytics_navbar_links(group, current_user)
[]
end
private
def navbar_sub_item(args)
NavbarSubItem.new(args)
end
def cycle_analytics_navbar_link(project, current_user)
return unless project_nav_tab?(:cycle_analytics)
navbar_sub_item(
title: _('Value Stream'),
path: 'cycle_analytics#show',
link: project_cycle_analytics_path(project),
link_to_options: { class: 'shortcuts-project-cycle-analytics' }
)
end
def repository_analytics_navbar_link(project, current_user)
return if project.empty_repo?
navbar_sub_item(
title: _('Repository'),
path: 'graphs#charts',
link: charts_project_graph_path(project, current_ref),
link_to_options: { class: 'shortcuts-repository-charts' }
)
end
def ci_cd_analytics_navbar_link(project, current_user)
return unless project_nav_tab?(:pipelines)
return unless project.feature_available?(:builds, current_user) || !project.empty_repo?
navbar_sub_item(
title: _('CI / CD'),
path: 'pipelines#charts',
link: charts_project_pipelines_path(project)
)
end
end
end
Analytics::NavbarHelper.prepend_if_ee('EE::Analytics::NavbarHelper')
# frozen_string_literal: true
module AnalyticsNavbarHelper
class NavbarSubItem
attr_reader :title, :path, :link, :link_to_options
def initialize(title:, path:, link:, link_to_options: {})
@title = title
@path = path
@link = link
@link_to_options = link_to_options.merge(title: title)
end
end
def project_analytics_navbar_links(project, current_user)
[
cycle_analytics_navbar_link(project, current_user),
repository_analytics_navbar_link(project, current_user),
ci_cd_analytics_navbar_link(project, current_user)
].compact
end
def group_analytics_navbar_links(group, current_user)
[]
end
private
def navbar_sub_item(args)
NavbarSubItem.new(args)
end
def cycle_analytics_navbar_link(project, current_user)
return unless project_nav_tab?(:cycle_analytics)
navbar_sub_item(
title: _('Value Stream'),
path: 'cycle_analytics#show',
link: project_cycle_analytics_path(project),
link_to_options: { class: 'shortcuts-project-cycle-analytics' }
)
end
def repository_analytics_navbar_link(project, current_user)
return if project.empty_repo?
navbar_sub_item(
title: _('Repository'),
path: 'graphs#charts',
link: charts_project_graph_path(project, current_ref),
link_to_options: { class: 'shortcuts-repository-charts' }
)
end
def ci_cd_analytics_navbar_link(project, current_user)
return unless project_nav_tab?(:pipelines)
return unless project.feature_available?(:builds, current_user) || !project.empty_repo?
navbar_sub_item(
title: _('CI / CD'),
path: 'pipelines#charts',
link: charts_project_pipelines_path(project)
)
end
end
AnalyticsNavbarHelper.prepend_if_ee('EE::AnalyticsNavbarHelper')
# frozen_string_literal: true # frozen_string_literal: true
class Analytics::ApplicationController < ApplicationController module Analytics
include RoutableActions class ApplicationController < ApplicationController
include RoutableActions
layout 'analytics' layout 'analytics'
private private
def self.check_feature_flag(flag, *args) def self.check_feature_flag(flag, *args)
before_action(*args) { render_404 unless Feature.enabled?(flag, default_enabled: Gitlab::Analytics.feature_enabled_by_default?(flag)) } before_action(*args) { render_404 unless Feature.enabled?(flag, default_enabled: Gitlab::Analytics.feature_enabled_by_default?(flag)) }
end end
def self.increment_usage_counter(counter_klass, counter, *args) def self.increment_usage_counter(counter_klass, counter, *args)
before_action(*args) { counter_klass.count(counter) } before_action(*args) { counter_klass.count(counter) }
end end
def authorize_view_by_action!(action) def authorize_view_by_action!(action)
return render_403 unless can?(current_user, action, @group || :global) return render_403 unless can?(current_user, action, @group || :global)
end end
def check_feature_availability!(feature) def check_feature_availability!(feature)
return render_403 unless ::License.feature_available?(feature) return render_403 unless ::License.feature_available?(feature)
return render_403 unless @group && @group.root_ancestor.feature_available?(feature) return render_403 unless @group && @group.root_ancestor.feature_available?(feature)
end end
def load_group def load_group
return unless params['group_id'] return unless params['group_id']
@group = find_routable!(Group, params['group_id']) @group = find_routable!(Group, params['group_id'])
end end
def load_project def load_project
return unless @group && params['project_id'] return unless @group && params['project_id']
@project = find_routable!(@group.projects, params['project_id']) @project = find_routable!(@group.projects, params['project_id'])
end end
private_class_method :check_feature_flag, :increment_usage_counter private_class_method :check_feature_flag, :increment_usage_counter
end
end end
# frozen_string_literal: true # frozen_string_literal: true
class Analytics::ProductivityAnalyticsController < Analytics::ApplicationController module Analytics
check_feature_flag Gitlab::Analytics::PRODUCTIVITY_ANALYTICS_FEATURE_FLAG class ProductivityAnalyticsController < ::Analytics::ApplicationController
increment_usage_counter Gitlab::UsageDataCounters::ProductivityAnalyticsCounter, check_feature_flag Gitlab::Analytics::PRODUCTIVITY_ANALYTICS_FEATURE_FLAG
:views, only: :show, if: -> { request.format.html? } increment_usage_counter Gitlab::UsageDataCounters::ProductivityAnalyticsCounter,
:views, only: :show, if: -> { request.format.html? }
before_action :load_group
before_action :load_project before_action :load_group
before_action :build_request_params before_action :load_project
before_action -> { before_action :build_request_params
check_feature_availability!(:productivity_analytics) before_action -> {
}, if: -> { request.format.json? } check_feature_availability!(:productivity_analytics)
}, if: -> { request.format.json? }
before_action -> {
authorize_view_by_action!(:view_productivity_analytics) before_action -> {
} authorize_view_by_action!(:view_productivity_analytics)
}
before_action -> {
push_frontend_feature_flag(:productivity_analytics_scatterplot_enabled, default_enabled: true) before_action -> {
} push_frontend_feature_flag(:productivity_analytics_scatterplot_enabled, default_enabled: true)
}
before_action :validate_params, only: :show, if: -> { request.format.json? }
before_action :validate_params, only: :show, if: -> { request.format.json? }
include IssuableCollections
include IssuableCollections
def show
respond_to do |format| def show
format.html respond_to do |format|
format.json do format.html
metric = params.fetch('metric_type', ProductivityAnalytics::DEFAULT_TYPE) format.json do
metric = params.fetch('metric_type', ProductivityAnalytics::DEFAULT_TYPE)
data = case params['chart_type']
when 'scatterplot' data = case params['chart_type']
productivity_analytics.scatterplot_data(type: metric) when 'scatterplot'
when 'histogram' productivity_analytics.scatterplot_data(type: metric)
productivity_analytics.histogram_data(type: metric) when 'histogram'
else productivity_analytics.histogram_data(type: metric)
include_relations(paginate(productivity_analytics.merge_requests_extended)).map do |merge_request| else
serializer.represent(merge_request, {}, ProductivityAnalyticsMergeRequestEntity) include_relations(paginate(productivity_analytics.merge_requests_extended)).map do |merge_request|
serializer.represent(merge_request, {}, ProductivityAnalyticsMergeRequestEntity)
end
end end
end
render json: data, status: :ok render json: data, status: :ok
end
end end
end end
end
private private
def paginate(merge_requests) def paginate(merge_requests)
merge_requests.page(params[:page]).per(params[:per_page]).tap do |paginated_data| merge_requests.page(params[:page]).per(params[:per_page]).tap do |paginated_data|
response.set_header('X-Per-Page', paginated_data.limit_value.to_s) response.set_header('X-Per-Page', paginated_data.limit_value.to_s)
response.set_header('X-Page', paginated_data.current_page.to_s) response.set_header('X-Page', paginated_data.current_page.to_s)
response.set_header('X-Next-Page', paginated_data.next_page.to_s) response.set_header('X-Next-Page', paginated_data.next_page.to_s)
response.set_header('X-Prev-Page', paginated_data.prev_page.to_s) response.set_header('X-Prev-Page', paginated_data.prev_page.to_s)
response.set_header('X-Total', paginated_data.total_count.to_s) response.set_header('X-Total', paginated_data.total_count.to_s)
response.set_header('X-Total-Pages', paginated_data.total_pages.to_s) response.set_header('X-Total-Pages', paginated_data.total_pages.to_s)
end
end end
end
def serializer def serializer
@serializer ||= BaseSerializer.new(current_user: current_user) @serializer ||= BaseSerializer.new(current_user: current_user)
end end
def finder_type def finder_type
ProductivityAnalyticsFinder ProductivityAnalyticsFinder
end end
def default_state def default_state
'merged' 'merged'
end end
def validate_params def validate_params
if @request_params.invalid? if @request_params.invalid?
render( render(
json: { message: 'Invalid parameters', errors: @request_params.errors }, json: { message: 'Invalid parameters', errors: @request_params.errors },
status: :unprocessable_entity status: :unprocessable_entity
) )
end
end end
end
def build_request_params def build_request_params
@request_params ||= Analytics::ProductivityAnalyticsRequestParams.new(allowed_request_params.merge(group: @group, project: @project)) @request_params ||= ::Analytics::ProductivityAnalyticsRequestParams.new(allowed_request_params.merge(group: @group, project: @project))
end end
def allowed_request_params def allowed_request_params
params.permit( params.permit(
:merged_after, :merged_after,
:merged_before, :merged_before,
:author_username, :author_username,
:milestone_title, :milestone_title,
label_name: [] label_name: []
) )
end end
def productivity_analytics def productivity_analytics
@productivity_analytics ||= ProductivityAnalytics.new(merge_requests: finder.execute, sort: params[:sort]) @productivity_analytics ||= ProductivityAnalytics.new(merge_requests: finder.execute, sort: params[:sort])
end end
# rubocop: disable CodeReuse/ActiveRecord # rubocop: disable CodeReuse/ActiveRecord
def include_relations(paginated_mrs) def include_relations(paginated_mrs)
# Due to Rails bug: https://github.com/rails/rails/issues/34889 we can't use .includes statement # Due to Rails bug: https://github.com/rails/rails/issues/34889 we can't use .includes statement
# to avoid N+1 call when we load custom columns. # to avoid N+1 call when we load custom columns.
# So we load relations manually here. # So we load relations manually here.
preloader = ActiveRecord::Associations::Preloader.new preloader = ActiveRecord::Associations::Preloader.new
preloader.preload(paginated_mrs, { author: [], target_project: { namespace: :route } }) preloader.preload(paginated_mrs, { author: [], target_project: { namespace: :route } })
paginated_mrs paginated_mrs
end
# rubocop: enable CodeReuse/ActiveRecord
end end
# rubocop: enable CodeReuse/ActiveRecord
end end
# frozen_string_literal: true
module EE
module Analytics
module NavbarHelper
extend ::Gitlab::Utils::Override
override :project_analytics_navbar_links
def project_analytics_navbar_links(project, current_user)
super + [
insights_navbar_link(project, current_user),
code_review_analytics_navbar_link(project, current_user),
project_issues_analytics_navbar_link(project, current_user)
].compact
end
override :group_analytics_navbar_links
def group_analytics_navbar_links(group, current_user)
super + [
contribution_analytics_navbar_link(group, current_user),
group_insights_navbar_link(group, current_user),
issues_analytics_navbar_link(group, current_user),
productivity_analytics_navbar_link(group, current_user),
group_cycle_analytics_navbar_link(group, current_user)
].compact
end
private
def project_issues_analytics_navbar_link(project, current_user)
return unless ::Feature.enabled?(:project_level_issues_analytics, project, default_enabled: true)
return unless project_nav_tab?(:issues_analytics)
navbar_sub_item(
title: _('Issues'),
path: 'issues_analytics#show',
link: project_analytics_issues_analytics_path(project)
)
end
def group_cycle_analytics_navbar_link(group, current_user)
return unless group_sidebar_link?(:cycle_analytics)
navbar_sub_item(
title: _('Value Stream'),
path: 'groups/analytics/cycle_analytics#show',
link: group_analytics_cycle_analytics_path(group)
)
end
def productivity_analytics_navbar_link(group, current_user)
return unless group_sidebar_link?(:productivity_analytics)
navbar_sub_item(
title: _('Productivity'),
path: 'groups/analytics/productivity_analytics#show',
link: group_analytics_productivity_analytics_path(group)
)
end
def contribution_analytics_navbar_link(group, current_user)
return unless group_sidebar_link?(:contribution_analytics)
navbar_sub_item(
title: _('Contribution'),
path: 'groups/contribution_analytics#show',
link: group_contribution_analytics_path(group),
link_to_options: { data: { placement: 'right', qa_selector: 'contribution_analytics_link' } }
)
end
def group_insights_navbar_link(group, current_user)
return unless group_sidebar_link?(:group_insights)
navbar_sub_item(
title: _('Insights'),
path: 'groups/insights#show',
link: group_insights_path(group),
link_to_options: { class: 'shortcuts-group-insights', data: { qa_selector: 'group_insights_link' } }
)
end
def issues_analytics_navbar_link(group, current_user)
return unless group_sidebar_link?(:analytics)
navbar_sub_item(
title: _('Issues'),
path: 'issues_analytics#show',
link: group_issues_analytics_path(group)
)
end
def insights_navbar_link(project, current_user)
return unless project_nav_tab?(:project_insights)
navbar_sub_item(
title: _('Insights'),
path: 'insights#show',
link: project_insights_path(project),
link_to_options: { class: 'shortcuts-project-insights', data: { qa_selector: 'project_insights_link' } }
)
end
def code_review_analytics_navbar_link(project, current_user)
return unless project_nav_tab?(:code_review)
navbar_sub_item(
title: _('Code Review'),
path: 'projects/analytics/code_reviews#index',
link: project_analytics_code_reviews_path(project)
)
end
end
end
end
# frozen_string_literal: true
module EE
module AnalyticsNavbarHelper
extend ::Gitlab::Utils::Override
override :project_analytics_navbar_links
def project_analytics_navbar_links(project, current_user)
super + [
insights_navbar_link(project, current_user),
code_review_analytics_navbar_link(project, current_user),
project_issues_analytics_navbar_link(project, current_user)
].compact
end
override :group_analytics_navbar_links
def group_analytics_navbar_links(group, current_user)
super + [
contribution_analytics_navbar_link(group, current_user),
group_insights_navbar_link(group, current_user),
issues_analytics_navbar_link(group, current_user),
productivity_analytics_navbar_link(group, current_user),
group_cycle_analytics_navbar_link(group, current_user)
].compact
end
private
def project_issues_analytics_navbar_link(project, current_user)
return unless ::Feature.enabled?(:project_level_issues_analytics, project, default_enabled: true)
return unless project_nav_tab?(:issues_analytics)
navbar_sub_item(
title: _('Issues'),
path: 'issues_analytics#show',
link: project_analytics_issues_analytics_path(project)
)
end
def group_cycle_analytics_navbar_link(group, current_user)
return unless group_sidebar_link?(:cycle_analytics)
navbar_sub_item(
title: _('Value Stream'),
path: 'groups/analytics/cycle_analytics#show',
link: group_analytics_cycle_analytics_path(group)
)
end
def productivity_analytics_navbar_link(group, current_user)
return unless group_sidebar_link?(:productivity_analytics)
navbar_sub_item(
title: _('Productivity'),
path: 'groups/analytics/productivity_analytics#show',
link: group_analytics_productivity_analytics_path(group)
)
end
def contribution_analytics_navbar_link(group, current_user)
return unless group_sidebar_link?(:contribution_analytics)
navbar_sub_item(
title: _('Contribution'),
path: 'groups/contribution_analytics#show',
link: group_contribution_analytics_path(group),
link_to_options: { data: { placement: 'right', qa_selector: 'contribution_analytics_link' } }
)
end
def group_insights_navbar_link(group, current_user)
return unless group_sidebar_link?(:group_insights)
navbar_sub_item(
title: _('Insights'),
path: 'groups/insights#show',
link: group_insights_path(group),
link_to_options: { class: 'shortcuts-group-insights', data: { qa_selector: 'group_insights_link' } }
)
end
def issues_analytics_navbar_link(group, current_user)
return unless group_sidebar_link?(:analytics)
navbar_sub_item(
title: _('Issues'),
path: 'issues_analytics#show',
link: group_issues_analytics_path(group)
)
end
def insights_navbar_link(project, current_user)
return unless project_nav_tab?(:project_insights)
navbar_sub_item(
title: _('Insights'),
path: 'insights#show',
link: project_insights_path(project),
link_to_options: { class: 'shortcuts-project-insights', data: { qa_selector: 'project_insights_link' } }
)
end
def code_review_analytics_navbar_link(project, current_user)
return unless project_nav_tab?(:code_review)
navbar_sub_item(
title: _('Code Review'),
path: 'projects/analytics/code_reviews#index',
link: project_analytics_code_reviews_path(project)
)
end
end
end
...@@ -19,7 +19,7 @@ module EE ...@@ -19,7 +19,7 @@ module EE
private private
def metrics_calculator def metrics_calculator
@metrics_calculator ||= Analytics::MergeRequestMetricsCalculator.new(merge_request) @metrics_calculator ||= ::Analytics::MergeRequestMetricsCalculator.new(merge_request)
end end
end end
end end
...@@ -53,7 +53,7 @@ module EE ...@@ -53,7 +53,7 @@ module EE
return unless merge_request.project.feature_available?(:code_review_analytics) return unless merge_request.project.feature_available?(:code_review_analytics)
Analytics::RefreshReassignData.new(merge_request).execute_async ::Analytics::RefreshReassignData.new(merge_request).execute_async
end end
end end
end end
......
...@@ -9,7 +9,7 @@ module EE ...@@ -9,7 +9,7 @@ module EE
def execute(note) def execute(note)
super super
Analytics::RefreshCommentsData.for_note(note)&.execute(force: true) ::Analytics::RefreshCommentsData.for_note(note)&.execute(force: true)
StatusPage.trigger_publish(project, current_user, note) StatusPage.trigger_publish(project, current_user, note)
end end
end end
......
...@@ -10,7 +10,7 @@ module EE ...@@ -10,7 +10,7 @@ module EE
def execute def execute
super super
Analytics::RefreshCommentsData.for_note(note)&.execute ::Analytics::RefreshCommentsData.for_note(note)&.execute
::SystemNoteService.design_discussion_added(note) if create_design_discussion_system_note? ::SystemNoteService.design_discussion_added(note) if create_design_discussion_system_note?
end end
......
...@@ -51,7 +51,7 @@ module MergeRequests ...@@ -51,7 +51,7 @@ module MergeRequests
def calculate_approvals_metrics(merge_request) def calculate_approvals_metrics(merge_request)
return unless merge_request.project.feature_available?(:code_review_analytics) return unless merge_request.project.feature_available?(:code_review_analytics)
Analytics::RefreshApprovalsData.new(merge_request).execute_async ::Analytics::RefreshApprovalsData.new(merge_request).execute_async
end end
end end
end end
...@@ -78,9 +78,9 @@ class Gitlab::Seeder::CustomizableCycleAnalytics ...@@ -78,9 +78,9 @@ class Gitlab::Seeder::CustomizableCycleAnalytics
] ]
stages_params.each do |params| stages_params.each do |params|
next if Analytics::CycleAnalytics::GroupStage.where(group: group).find_by(name: params[:name]) next if ::Analytics::CycleAnalytics::GroupStage.where(group: group).find_by(name: params[:name])
Analytics::CycleAnalytics::Stages::CreateService.new(parent: group, current_user: user, params: params).execute ::Analytics::CycleAnalytics::Stages::CreateService.new(parent: group, current_user: user, params: params).execute
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