Commit 04182e40 authored by Robert Speicher's avatar Robert Speicher

Merge branch '198578-analytics-namespace-fix-in-foss' into 'master'

Introduce Analytics namespace in FOSS

Closes #198578

See merge request gitlab-org/gitlab!29493
parents 8a7fe44e 06b7f084
# 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
class Analytics::ApplicationController < ApplicationController
include RoutableActions
module Analytics
class ApplicationController < ApplicationController
include RoutableActions
layout 'analytics'
layout 'analytics'
private
private
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)) }
end
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)) }
end
def self.increment_usage_counter(counter_klass, counter, *args)
before_action(*args) { counter_klass.count(counter) }
end
def self.increment_usage_counter(counter_klass, counter, *args)
before_action(*args) { counter_klass.count(counter) }
end
def authorize_view_by_action!(action)
return render_403 unless can?(current_user, action, @group || :global)
end
def authorize_view_by_action!(action)
return render_403 unless can?(current_user, action, @group || :global)
end
def check_feature_availability!(feature)
return render_403 unless ::License.feature_available?(feature)
return render_403 unless @group && @group.root_ancestor.feature_available?(feature)
end
def check_feature_availability!(feature)
return render_403 unless ::License.feature_available?(feature)
return render_403 unless @group && @group.root_ancestor.feature_available?(feature)
end
def load_group
return unless params['group_id']
def load_group
return unless params['group_id']
@group = find_routable!(Group, params['group_id'])
end
@group = find_routable!(Group, params['group_id'])
end
def load_project
return unless @group && params['project_id']
def load_project
return unless @group && params['project_id']
@project = find_routable!(@group.projects, params['project_id'])
end
@project = find_routable!(@group.projects, params['project_id'])
end
private_class_method :check_feature_flag, :increment_usage_counter
private_class_method :check_feature_flag, :increment_usage_counter
end
end
# frozen_string_literal: true
class Analytics::ProductivityAnalyticsController < Analytics::ApplicationController
check_feature_flag Gitlab::Analytics::PRODUCTIVITY_ANALYTICS_FEATURE_FLAG
increment_usage_counter Gitlab::UsageDataCounters::ProductivityAnalyticsCounter,
:views, only: :show, if: -> { request.format.html? }
before_action :load_group
before_action :load_project
before_action :build_request_params
before_action -> {
check_feature_availability!(:productivity_analytics)
}, if: -> { request.format.json? }
before_action -> {
authorize_view_by_action!(:view_productivity_analytics)
}
before_action -> {
push_frontend_feature_flag(:productivity_analytics_scatterplot_enabled, default_enabled: true)
}
before_action :validate_params, only: :show, if: -> { request.format.json? }
include IssuableCollections
def show
respond_to do |format|
format.html
format.json do
metric = params.fetch('metric_type', ProductivityAnalytics::DEFAULT_TYPE)
data = case params['chart_type']
when 'scatterplot'
productivity_analytics.scatterplot_data(type: metric)
when 'histogram'
productivity_analytics.histogram_data(type: metric)
else
include_relations(paginate(productivity_analytics.merge_requests_extended)).map do |merge_request|
serializer.represent(merge_request, {}, ProductivityAnalyticsMergeRequestEntity)
module Analytics
class ProductivityAnalyticsController < ::Analytics::ApplicationController
check_feature_flag Gitlab::Analytics::PRODUCTIVITY_ANALYTICS_FEATURE_FLAG
increment_usage_counter Gitlab::UsageDataCounters::ProductivityAnalyticsCounter,
:views, only: :show, if: -> { request.format.html? }
before_action :load_group
before_action :load_project
before_action :build_request_params
before_action -> {
check_feature_availability!(:productivity_analytics)
}, if: -> { request.format.json? }
before_action -> {
authorize_view_by_action!(:view_productivity_analytics)
}
before_action -> {
push_frontend_feature_flag(:productivity_analytics_scatterplot_enabled, default_enabled: true)
}
before_action :validate_params, only: :show, if: -> { request.format.json? }
include IssuableCollections
def show
respond_to do |format|
format.html
format.json do
metric = params.fetch('metric_type', ProductivityAnalytics::DEFAULT_TYPE)
data = case params['chart_type']
when 'scatterplot'
productivity_analytics.scatterplot_data(type: metric)
when 'histogram'
productivity_analytics.histogram_data(type: metric)
else
include_relations(paginate(productivity_analytics.merge_requests_extended)).map do |merge_request|
serializer.represent(merge_request, {}, ProductivityAnalyticsMergeRequestEntity)
end
end
end
render json: data, status: :ok
render json: data, status: :ok
end
end
end
end
private
private
def paginate(merge_requests)
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-Page', paginated_data.current_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-Total', paginated_data.total_count.to_s)
response.set_header('X-Total-Pages', paginated_data.total_pages.to_s)
def paginate(merge_requests)
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-Page', paginated_data.current_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-Total', paginated_data.total_count.to_s)
response.set_header('X-Total-Pages', paginated_data.total_pages.to_s)
end
end
end
def serializer
@serializer ||= BaseSerializer.new(current_user: current_user)
end
def serializer
@serializer ||= BaseSerializer.new(current_user: current_user)
end
def finder_type
ProductivityAnalyticsFinder
end
def finder_type
ProductivityAnalyticsFinder
end
def default_state
'merged'
end
def default_state
'merged'
end
def validate_params
if @request_params.invalid?
render(
json: { message: 'Invalid parameters', errors: @request_params.errors },
status: :unprocessable_entity
)
def validate_params
if @request_params.invalid?
render(
json: { message: 'Invalid parameters', errors: @request_params.errors },
status: :unprocessable_entity
)
end
end
end
def build_request_params
@request_params ||= Analytics::ProductivityAnalyticsRequestParams.new(allowed_request_params.merge(group: @group, project: @project))
end
def build_request_params
@request_params ||= ::Analytics::ProductivityAnalyticsRequestParams.new(allowed_request_params.merge(group: @group, project: @project))
end
def allowed_request_params
params.permit(
:merged_after,
:merged_before,
:author_username,
:milestone_title,
label_name: []
)
end
def allowed_request_params
params.permit(
:merged_after,
:merged_before,
:author_username,
:milestone_title,
label_name: []
)
end
def productivity_analytics
@productivity_analytics ||= ProductivityAnalytics.new(merge_requests: finder.execute, sort: params[:sort])
end
def productivity_analytics
@productivity_analytics ||= ProductivityAnalytics.new(merge_requests: finder.execute, sort: params[:sort])
end
# rubocop: disable CodeReuse/ActiveRecord
def include_relations(paginated_mrs)
# 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.
# So we load relations manually here.
preloader = ActiveRecord::Associations::Preloader.new
preloader.preload(paginated_mrs, { author: [], target_project: { namespace: :route } })
paginated_mrs
# rubocop: disable CodeReuse/ActiveRecord
def include_relations(paginated_mrs)
# 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.
# So we load relations manually here.
preloader = ActiveRecord::Associations::Preloader.new
preloader.preload(paginated_mrs, { author: [], target_project: { namespace: :route } })
paginated_mrs
end
# rubocop: enable CodeReuse/ActiveRecord
end
# rubocop: enable CodeReuse/ActiveRecord
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
private
def metrics_calculator
@metrics_calculator ||= Analytics::MergeRequestMetricsCalculator.new(merge_request)
@metrics_calculator ||= ::Analytics::MergeRequestMetricsCalculator.new(merge_request)
end
end
end
......@@ -53,7 +53,7 @@ module EE
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
......
......@@ -9,7 +9,7 @@ module EE
def execute(note)
super
Analytics::RefreshCommentsData.for_note(note)&.execute(force: true)
::Analytics::RefreshCommentsData.for_note(note)&.execute(force: true)
StatusPage.trigger_publish(project, current_user, note)
end
end
......
......@@ -10,7 +10,7 @@ module EE
def execute
super
Analytics::RefreshCommentsData.for_note(note)&.execute
::Analytics::RefreshCommentsData.for_note(note)&.execute
::SystemNoteService.design_discussion_added(note) if create_design_discussion_system_note?
end
......
......@@ -51,7 +51,7 @@ module MergeRequests
def calculate_approvals_metrics(merge_request)
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
......@@ -78,9 +78,9 @@ class Gitlab::Seeder::CustomizableCycleAnalytics
]
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
......
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