Commit 9ed8c8ca authored by Adam Hegyi's avatar Adam Hegyi Committed by Peter Leitzen

Add analytics pages to Project Analytics sidebar

- Add `unless` option (lambda) to `nav_link` to have a flexible way
to skip matching the current controller and action.
- Move analytics specific pages to Analytics sidebar.
- Add the change behind a feature flag:
`analytics_pages_under_project_analytics_sidebar`
parent e3c73bc6
# 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
private
def navbar_sub_item(args)
NavbarSubItem.new(args)
end
def cycle_analytics_navbar_link(project, current_user)
return unless Feature.enabled?(:analytics_pages_under_project_analytics_sidebar, project)
return unless project_nav_tab?(:cycle_analytics)
navbar_sub_item(
title: _('Cycle Analytics'),
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 Feature.disabled?(:analytics_pages_under_project_analytics_sidebar, project)
return if project.empty_repo?
navbar_sub_item(
title: _('Repository Analytics'),
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 Feature.enabled?(:analytics_pages_under_project_analytics_sidebar, project)
return unless project_nav_tab?(:pipelines)
return unless project.feature_available?(:builds, current_user) || !project.empty_repo?
navbar_sub_item(
title: _('CI / CD Analytics'),
path: 'pipelines#charts',
link: charts_project_pipelines_path(project)
)
end
end
AnalyticsNavbarHelper.prepend_if_ee('EE::AnalyticsNavbarHelper')
...@@ -403,6 +403,10 @@ module ProjectsHelper ...@@ -403,6 +403,10 @@ module ProjectsHelper
nav_tabs << :operations nav_tabs << :operations
end end
if can?(current_user, :read_cycle_analytics, project)
nav_tabs << :cycle_analytics
end
tab_ability_map.each do |tab, ability| tab_ability_map.each do |tab, ability|
if can?(current_user, ability, project) if can?(current_user, ability, project)
nav_tabs << tab nav_tabs << tab
...@@ -643,7 +647,6 @@ module ProjectsHelper ...@@ -643,7 +647,6 @@ module ProjectsHelper
projects#show projects#show
projects#activity projects#activity
releases#index releases#index
cycle_analytics#show
] ]
end end
......
...@@ -12,6 +12,7 @@ module TabHelper ...@@ -12,6 +12,7 @@ module TabHelper
# :action - One or more action names to check (optional). # :action - One or more action names to check (optional).
# :path - A shorthand path, such as 'dashboard#index', to check (optional). # :path - A shorthand path, such as 'dashboard#index', to check (optional).
# :html_options - Extra options to be passed to the list element (optional). # :html_options - Extra options to be passed to the list element (optional).
# :unless - Callable object to skip rendering the 'active' class on `li` element (optional).
# block - An optional block that will become the contents of the returned # block - An optional block that will become the contents of the returned
# `li` element. # `li` element.
# #
...@@ -56,6 +57,14 @@ module TabHelper ...@@ -56,6 +57,14 @@ module TabHelper
# nav_link(path: 'admin/appearances#show') { "Hello"} # nav_link(path: 'admin/appearances#show') { "Hello"}
# # => '<li class="active">Hello</li>' # # => '<li class="active">Hello</li>'
# #
# # Shorthand path + unless
# # Add `active` class when TreeController is requested, except the `index` action.
# nav_link(controller: 'tree', unless: -> { action_name?('index') }) { "Hello" }
# # => '<li class="active">Hello</li>'
#
# # When `TreeController#index` is requested
# # => '<li>Hello</li>'
#
# Returns a list item element String # Returns a list item element String
def nav_link(options = {}, &block) def nav_link(options = {}, &block)
klass = active_nav_link?(options) ? 'active' : '' klass = active_nav_link?(options) ? 'active' : ''
...@@ -73,6 +82,8 @@ module TabHelper ...@@ -73,6 +82,8 @@ module TabHelper
end end
def active_nav_link?(options) def active_nav_link?(options)
return false if options[:unless]&.call
if path = options.delete(:path) if path = options.delete(:path)
unless path.respond_to?(:each) unless path.respond_to?(:each)
path = [path] path = [path]
......
- should_display_analytics_pages_in_sidebar = Feature.enabled?(:analytics_pages_under_project_analytics_sidebar, @project)
.nav-sidebar{ class: ("sidebar-collapsed-desktop" if collapsed_sidebar?) } .nav-sidebar{ class: ("sidebar-collapsed-desktop" if collapsed_sidebar?) }
.nav-sidebar-inner-scroll .nav-sidebar-inner-scroll
- can_edit = can?(current_user, :admin_project, @project) - can_edit = can?(current_user, :admin_project, @project)
...@@ -8,7 +10,9 @@ ...@@ -8,7 +10,9 @@
.sidebar-context-title .sidebar-context-title
= @project.name = @project.name
%ul.sidebar-top-level-items %ul.sidebar-top-level-items
= nav_link(path: sidebar_projects_paths, html_options: { class: 'home' }) do - paths = sidebar_projects_paths
- paths << 'cycle_analytics#show' unless should_display_analytics_pages_in_sidebar
= nav_link(path: paths, html_options: { class: 'home' }) do
= link_to project_path(@project), class: 'shortcuts-project rspec-project-link', data: { qa_selector: 'project_link' } do = link_to project_path(@project), class: 'shortcuts-project rspec-project-link', data: { qa_selector: 'project_link' } do
.nav-icon-container .nav-icon-container
= sprite_icon('home') = sprite_icon('home')
...@@ -34,6 +38,8 @@ ...@@ -34,6 +38,8 @@
= link_to project_releases_path(@project), title: _('Releases'), class: 'shortcuts-project-releases' do = link_to project_releases_path(@project), title: _('Releases'), class: 'shortcuts-project-releases' do
%span= _('Releases') %span= _('Releases')
- unless should_display_analytics_pages_in_sidebar
- if can?(current_user, :read_cycle_analytics, @project) - if can?(current_user, :read_cycle_analytics, @project)
= nav_link(path: 'cycle_analytics#show') do = nav_link(path: 'cycle_analytics#show') do
= link_to project_cycle_analytics_path(@project), title: _('Cycle Analytics'), class: 'shortcuts-project-cycle-analytics' do = link_to project_cycle_analytics_path(@project), title: _('Cycle Analytics'), class: 'shortcuts-project-cycle-analytics' do
...@@ -41,8 +47,9 @@ ...@@ -41,8 +47,9 @@
= render_if_exists 'layouts/nav/project_insights_link' = render_if_exists 'layouts/nav/project_insights_link'
- if project_nav_tab? :files - if project_nav_tab? :files
= nav_link(controller: sidebar_repository_paths) do = nav_link(controller: sidebar_repository_paths, unless: -> { should_display_analytics_pages_in_sidebar && current_path?('projects/graphs#charts') }) do
= link_to project_tree_path(@project), class: 'shortcuts-tree qa-project-menu-repo' do = link_to project_tree_path(@project), class: 'shortcuts-tree qa-project-menu-repo' do
.nav-icon-container .nav-icon-container
= sprite_icon('doc-text') = sprite_icon('doc-text')
...@@ -83,6 +90,7 @@ ...@@ -83,6 +90,7 @@
= link_to project_compare_index_path(@project, from: @repository.root_ref, to: current_ref) do = link_to project_compare_index_path(@project, from: @repository.root_ref, to: current_ref) do
= _('Compare') = _('Compare')
- unless should_display_analytics_pages_in_sidebar
= nav_link(path: 'graphs#charts') do = nav_link(path: 'graphs#charts') do
= link_to charts_project_graph_path(@project, current_ref) do = link_to charts_project_graph_path(@project, current_ref) do
= _('Charts') = _('Charts')
...@@ -170,7 +178,7 @@ ...@@ -170,7 +178,7 @@
= number_with_delimiter(@project.open_merge_requests_count) = number_with_delimiter(@project.open_merge_requests_count)
- if project_nav_tab? :pipelines - if project_nav_tab? :pipelines
= nav_link(controller: [:pipelines, :builds, :jobs, :pipeline_schedules, :artifacts]) do = nav_link(controller: [:pipelines, :builds, :jobs, :pipeline_schedules, :artifacts], unless: -> { should_display_analytics_pages_in_sidebar && current_path?('projects/pipelines#charts') }) do
= link_to project_pipelines_path(@project), class: 'shortcuts-pipelines qa-link-pipelines rspec-link-pipelines', data: { qa_selector: 'ci_cd_link' } do = link_to project_pipelines_path(@project), class: 'shortcuts-pipelines qa-link-pipelines rspec-link-pipelines', data: { qa_selector: 'ci_cd_link' } do
.nav-icon-container .nav-icon-container
= sprite_icon('rocket') = sprite_icon('rocket')
...@@ -201,13 +209,13 @@ ...@@ -201,13 +209,13 @@
%span %span
= _('Artifacts') = _('Artifacts')
- if project_nav_tab? :pipelines - if !should_display_analytics_pages_in_sidebar && project_nav_tab?(:pipelines)
= nav_link(controller: :pipeline_schedules) do = nav_link(controller: :pipeline_schedules) do
= link_to pipeline_schedules_path(@project), title: _('Schedules'), class: 'shortcuts-builds' do = link_to pipeline_schedules_path(@project), title: _('Schedules'), class: 'shortcuts-builds' do
%span %span
= _('Schedules') = _('Schedules')
- if @project.feature_available?(:builds, current_user) && !@project.empty_repo? - if !should_display_analytics_pages_in_sidebar && @project.feature_available?(:builds, current_user) && !@project.empty_repo?
= nav_link(path: 'pipelines#charts') do = nav_link(path: 'pipelines#charts') do
= link_to charts_project_pipelines_path(@project), title: _('Charts'), class: 'shortcuts-pipelines-charts' do = link_to charts_project_pipelines_path(@project), title: _('Charts'), class: 'shortcuts-pipelines-charts' do
%span %span
...@@ -290,7 +298,7 @@ ...@@ -290,7 +298,7 @@
= render_if_exists 'layouts/nav/sidebar/project_packages_link' = render_if_exists 'layouts/nav/sidebar/project_packages_link'
= render_if_exists 'layouts/nav/sidebar/project_analytics_link' # EE-specific = render 'layouts/nav/sidebar/project_analytics_link'
- if project_nav_tab? :wiki - if project_nav_tab? :wiki
- wiki_url = project_wiki_path(@project, :home) - wiki_url = project_wiki_path(@project, :home)
...@@ -410,6 +418,7 @@ ...@@ -410,6 +418,7 @@
= link_to project_network_path(@project, current_ref), title: _('Network'), class: 'shortcuts-network' do = link_to project_network_path(@project, current_ref), title: _('Network'), class: 'shortcuts-network' do
= _('Graph') = _('Graph')
- unless should_display_analytics_pages_in_sidebar
-# Shortcut to Repository > Charts (formerly, top-nav item "Graphs") -# Shortcut to Repository > Charts (formerly, top-nav item "Graphs")
- unless @project.empty_repo? - unless @project.empty_repo?
%li.hidden %li.hidden
......
- navbar_sub_item = project_analytics_navbar_links(@project, current_user).sort_by(&:title)
- all_paths = navbar_sub_item.map(&:path)
- if navbar_sub_item.any?
= nav_link(path: all_paths) do
= link_to navbar_sub_item.first.link, data: { qa_selector: 'project_analytics_link' } do
.nav-icon-container
= sprite_icon('chart')
%span.nav-item-name
= _('Analytics')
%ul.sidebar-sub-level-items
- navbar_sub_item.each do |menu_item|
= nav_link(path: menu_item.path) do
= link_to(menu_item.link, menu_item.link_to_options) do
%span= menu_item.title
# 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)
].compact
end
private
def insights_navbar_link(project, current_user)
return unless ::Feature.enabled?(:analytics_pages_under_project_analytics_sidebar, project)
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
...@@ -6,10 +6,14 @@ module EE ...@@ -6,10 +6,14 @@ module EE
override :sidebar_projects_paths override :sidebar_projects_paths
def sidebar_projects_paths def sidebar_projects_paths
if ::Feature.enabled?(:analytics_pages_under_project_analytics_sidebar, @project)
super
else
super + %w[ super + %w[
projects/insights#show projects/insights#show
] ]
end end
end
override :sidebar_settings_paths override :sidebar_settings_paths
def sidebar_settings_paths def sidebar_settings_paths
......
- if can?(current_user, :read_code_review_analytics, @project)
- project_analytics_link = project_analytics_code_reviews_path(@project)
= nav_link controller: :code_review do
= link_to project_analytics_link, data: { qa_selector: 'project_analytics_link' } do
.nav-icon-container
= sprite_icon('chart')
%span.nav-item-name
= _('Project Analytics')
%ul.sidebar-sub-level-items
= nav_link(controller: :code_review, html_options: { class: "fly-out-top-item" } ) do
= link_to project_analytics_link do
%strong.fly-out-top-item-name
= _('Project Analytics')
%li.divider.fly-out-top-item
- if project_nav_tab?(:code_review)
= nav_link controller: :code_review do
= link_to project_analytics_link, title: _('Code Review') do
%span= _('Code Review')
# frozen_string_literal: true
require 'spec_helper'
describe 'Project active tab' do
let(:user) { create :user }
let(:project) { create(:project, :repository) }
def click_tab(title)
page.within '.sidebar-top-level-items > .active' do
click_link(title)
end
end
context 'when `analytics_pages_under_project_analytics_sidebar` feature flag is enabled' do
before do
stub_feature_flags(analytics_pages_under_project_analytics_sidebar: { enabled: true, thing: project })
project.add_maintainer(user)
sign_in(user)
end
context 'on project Analytics/Insights' do
before do
stub_licensed_features(insights: true)
visit project_insights_path(project)
end
it_behaves_like 'page has active tab', _('Analytics')
it_behaves_like 'page has active sub tab', _('Insights')
end
context 'on project Analytics/Code Review' do
before do
stub_licensed_features(code_review_analytics: true)
visit project_analytics_code_reviews_path(project)
end
it_behaves_like 'page has active tab', _('Analytics')
it_behaves_like 'page has active sub tab', _('Code Review')
end
end
end
...@@ -3014,6 +3014,9 @@ msgstr "" ...@@ -3014,6 +3014,9 @@ msgstr ""
msgid "CI / CD" msgid "CI / CD"
msgstr "" msgstr ""
msgid "CI / CD Analytics"
msgstr ""
msgid "CI / CD Charts" msgid "CI / CD Charts"
msgstr "" msgstr ""
...@@ -14310,9 +14313,6 @@ msgstr "" ...@@ -14310,9 +14313,6 @@ msgstr ""
msgid "Project '%{project_name}' will be deleted on %{date}" msgid "Project '%{project_name}' will be deleted on %{date}"
msgstr "" msgstr ""
msgid "Project Analytics"
msgstr ""
msgid "Project Badges" msgid "Project Badges"
msgstr "" msgstr ""
...@@ -15769,6 +15769,9 @@ msgstr "" ...@@ -15769,6 +15769,9 @@ msgstr ""
msgid "Repository" msgid "Repository"
msgstr "" msgstr ""
msgid "Repository Analytics"
msgstr ""
msgid "Repository Graph" msgid "Repository Graph"
msgstr "" msgstr ""
......
...@@ -7,6 +7,8 @@ describe 'Project active tab' do ...@@ -7,6 +7,8 @@ describe 'Project active tab' do
let(:project) { create(:project, :repository) } let(:project) { create(:project, :repository) }
before do before do
stub_feature_flags(analytics_pages_under_project_analytics_sidebar: { enabled: false, thing: project })
project.add_maintainer(user) project.add_maintainer(user)
sign_in(user) sign_in(user)
end end
...@@ -17,21 +19,6 @@ describe 'Project active tab' do ...@@ -17,21 +19,6 @@ describe 'Project active tab' do
end end
end end
shared_examples 'page has active tab' do |title|
it "activates #{title} tab" do
expect(page).to have_selector('.sidebar-top-level-items > li.active', count: 1)
expect(find('.sidebar-top-level-items > li.active')).to have_content(title)
end
end
shared_examples 'page has active sub tab' do |title|
it "activates #{title} sub tab" do
expect(page).to have_selector('.sidebar-sub-level-items > li.active:not(.fly-out-top-item)', count: 1)
expect(find('.sidebar-sub-level-items > li.active:not(.fly-out-top-item)'))
.to have_content(title)
end
end
context 'on project Home' do context 'on project Home' do
before do before do
visit project_path(project) visit project_path(project)
...@@ -136,4 +123,35 @@ describe 'Project active tab' do ...@@ -136,4 +123,35 @@ describe 'Project active tab' do
it_behaves_like 'page has active sub tab', 'Repository' it_behaves_like 'page has active sub tab', 'Repository'
end end
end end
context 'when `analytics_pages_under_project_analytics_sidebar` feature flag is enabled' do
before do
stub_feature_flags(analytics_pages_under_project_analytics_sidebar: { enabled: true, thing: project })
end
context 'on project Analytics' do
before do
visit charts_project_graph_path(project, 'master')
end
context 'on project Analytics/Repository Analytics' do
it_behaves_like 'page has active tab', _('Analytics')
it_behaves_like 'page has active sub tab', _('Repository Analytics')
end
context 'on project Analytics/Repository Analytics' do
it_behaves_like 'page has active tab', _('Analytics')
it_behaves_like 'page has active sub tab', _('Repository Analytics')
end
context 'on project Analytics/Cycle Analytics' do
before do
click_tab(_('CI / CD Analytics'))
end
it_behaves_like 'page has active tab', _('Analytics')
it_behaves_like 'page has active sub tab', _('CI / CD Analytics')
end
end
end
end end
...@@ -7,6 +7,8 @@ describe 'User uses shortcuts', :js do ...@@ -7,6 +7,8 @@ describe 'User uses shortcuts', :js do
let(:user) { create(:user) } let(:user) { create(:user) }
before do before do
stub_feature_flags(analytics_pages_under_project_analytics_sidebar: { enabled: false, thing: project })
project.add_maintainer(user) project.add_maintainer(user)
sign_in(user) sign_in(user)
...@@ -156,4 +158,18 @@ describe 'User uses shortcuts', :js do ...@@ -156,4 +158,18 @@ describe 'User uses shortcuts', :js do
expect(page).to have_active_navigation('Wiki') expect(page).to have_active_navigation('Wiki')
end end
end end
context 'when `analytics_pages_under_project_analytics_sidebar` feature flag is enabled' do
before do
stub_feature_flags(analytics_pages_under_project_analytics_sidebar: { enabled: true, thing: project })
end
it 'redirects to the repository charts page' do
find('body').native.send_key('g')
find('body').native.send_key('d')
expect(page).to have_active_navigation(_('Analytics'))
expect(page).to have_active_sub_navigation(_('Repository Analytics'))
end
end
end end
...@@ -9,3 +9,18 @@ RSpec.shared_examples 'has nav sidebar' do ...@@ -9,3 +9,18 @@ RSpec.shared_examples 'has nav sidebar' do
expect(rendered).not_to have_selector('.sidebar-expanded-mobile') expect(rendered).not_to have_selector('.sidebar-expanded-mobile')
end end
end end
RSpec.shared_examples 'page has active tab' do |title|
it "activates #{title} tab" do
expect(page).to have_selector('.sidebar-top-level-items > li.active', count: 1)
expect(find('.sidebar-top-level-items > li.active')).to have_content(title)
end
end
RSpec.shared_examples 'page has active sub tab' do |title|
it "activates #{title} sub tab" do
expect(page).to have_selector('.sidebar-sub-level-items > li.active:not(.fly-out-top-item)', count: 1)
expect(find('.sidebar-sub-level-items > li.active:not(.fly-out-top-item)'))
.to have_content(title)
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