Commit 0f31a593 authored by Francisco Javier López's avatar Francisco Javier López Committed by Chad Woolley

Add and refactor Analytics menu

In this commit we're refactoring the Analytics
menu in the group sidebar.

We're moving away from partial defined logic to object
contained one. Now menus are defined in objects that
will contain all the information instead of in partials
parent d2990167
# 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 group_analytics_navbar_links(group, current_user)
[]
end
private
def navbar_sub_item(args)
NavbarSubItem.new(**args)
end
end
end
Analytics::NavbarHelper.prepend_mod_with('Analytics::NavbarHelper')
- navbar_links = links.sort_by(&:title)
- all_paths = navbar_links.map(&:path)
- analytics_link = navbar_links.find { |link| link.title == _('Value stream') } || navbar_links.first
- if navbar_links.any?
= nav_link(path: all_paths) do
= link_to analytics_link.link, {class: 'shortcuts-analytics has-sub-items', data: { qa_selector: 'analytics_anchor' } } do
.nav-icon-container
= sprite_icon('chart')
%span.nav-item-name{ data: { qa_selector: 'analytics_link' } }
= _('Analytics')
%ul.sidebar-sub-level-items{ data: { qa_selector: 'analytics_sidebar_submenu' } }
= nav_link(path: analytics_link.path, html_options: { class: "fly-out-top-item" } ) do
= link_to analytics_link.link do
%strong.fly-out-top-item-name
= _('Analytics')
%li.divider.fly-out-top-item
- navbar_links.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
= render 'layouts/nav/sidebar/analytics_links', links: group_analytics_navbar_links(@group, current_user)
- if group_sidebar_link?(:wiki)
= render 'layouts/nav/sidebar/wiki_link', wiki_url: @group.wiki.web_url
......
# frozen_string_literal: true
module EE
module Analytics
module NavbarHelper
extend ::Gitlab::Utils::Override
override :group_analytics_navbar_links
def group_analytics_navbar_links(group, current_user)
super + [
group_ci_cd_analytics_navbar_link(group, current_user),
group_devops_adoption_navbar_link(group, current_user),
group_repository_analytics_navbar_link(group, current_user),
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),
group_merge_request_analytics_navbar_link(group, current_user)
].compact
end
private
# Currently an empty page, so don't show it on the navbar for now
def group_merge_request_analytics_navbar_link(group, current_user)
return
return unless group_sidebar_link?(:merge_request_analytics) # rubocop: disable Lint/UnreachableCode
navbar_sub_item(
title: _('Merge request'),
path: 'groups/analytics/merge_request_analytics#show',
link: group_analytics_merge_request_analytics_path(group)
)
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 group_devops_adoption_navbar_link(group, current_user)
return unless group_sidebar_link?(:group_devops_adoption)
navbar_sub_item(
title: _('DevOps adoption'),
path: 'groups/analytics/devops_adoption#show',
link: group_analytics_devops_adoption_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: _('Issue'),
path: 'issues_analytics#show',
link: group_issues_analytics_path(group)
)
end
def group_ci_cd_analytics_navbar_link(group, current_user)
return unless group.licensed_feature_available?(:group_ci_cd_analytics)
return unless group_sidebar_link?(:group_ci_cd_analytics)
navbar_sub_item(
title: _('CI/CD'),
path: 'groups/analytics/ci_cd_analytics#show',
link: group_analytics_ci_cd_analytics_path(group)
)
end
def group_repository_analytics_navbar_link(group, current_user)
return unless group.licensed_feature_available?(:group_coverage_reports)
return unless group_sidebar_link?(:repository_analytics)
navbar_sub_item(
title: _('Repositories'),
path: 'groups/analytics/repository_analytics#show',
link: group_analytics_repository_analytics_path(group)
)
end
end
end
end
- return unless group_sidebar_link?(:group_insights)
= nav_link(path: 'groups/insights#show') do
= link_to group_insights_path(@group), title: _('Insights'), class: 'shortcuts-group-insights', data: { qa_selector: 'group_insights_link' } do
%span= _('Insights')
......@@ -14,6 +14,7 @@ module EE
insert_menu_after(::Sidebars::Groups::Menus::GroupInformationMenu, ::Sidebars::Groups::Menus::EpicsMenu.new(context))
insert_menu_after(::Sidebars::Groups::Menus::MergeRequestsMenu, ::Sidebars::Groups::Menus::SecurityComplianceMenu.new(context))
insert_menu_after(::Sidebars::Groups::Menus::SecurityComplianceMenu, ::Sidebars::Groups::Menus::PushRulesMenu.new(context))
insert_menu_after(::Sidebars::Groups::Menus::PackagesRegistriesMenu, ::Sidebars::Groups::Menus::AnalyticsMenu.new(context))
end
end
end
......
# frozen_string_literal: true
module Sidebars
module Groups
module Menus
class AnalyticsMenu < ::Sidebars::Menu
include Gitlab::Utils::StrongMemoize
override :configure_menu_items
def configure_menu_items
add_item(ci_cd_analytics_menu_item)
add_item(contribution_analytics_menu_item)
add_item(devops_adoption_menu_item)
add_item(insights_analytics_menu_item)
add_item(issues_analytics_menu_item)
add_item(merge_request_analytics_menu_item)
add_item(productivity_analytics_menu_item)
add_item(repository_analytics_menu_item)
add_item(cycle_analytics_menu_item)
true
end
override :link
def link
return cycle_analytics_menu_item.link if cycle_analytics_menu_item.render?
renderable_items.first.link
end
override :extra_container_html_options
def extra_container_html_options
{
class: 'shortcuts-analytics'
}
end
override :title
def title
_('Analytics')
end
override :sprite_icon
def sprite_icon
'chart'
end
private
def ci_cd_analytics_menu_item
unless show_ci_cd_analytics?
return ::Sidebars::NilMenuItem.new(item_id: :ci_cd_analytics)
end
::Sidebars::MenuItem.new(
title: _('CI/CD'),
link: group_analytics_ci_cd_analytics_path(context.group),
active_routes: { path: 'groups/analytics/ci_cd_analytics#show' },
item_id: :ci_cd_analytics
)
end
def show_ci_cd_analytics?
context.group.licensed_feature_available?(:group_ci_cd_analytics) &&
::Feature.enabled?(:group_ci_cd_analytics_page, context.group, default_enabled: true) &&
can?(context.current_user, :view_group_ci_cd_analytics, context.group)
end
def contribution_analytics_menu_item
unless show_contribution_analytics?
return ::Sidebars::NilMenuItem.new(item_id: :contribution_analytics)
end
::Sidebars::MenuItem.new(
title: _('Contribution'),
link: group_contribution_analytics_path(context.group),
active_routes: { path: 'groups/contribution_analytics#show' },
container_html_options: { data: { placement: 'right' } },
item_id: :contribution_analytics
)
end
def show_contribution_analytics?
can?(context.current_user, :read_group_contribution_analytics, context.group) ||
LicenseHelper.show_promotions?(context.current_user)
end
def devops_adoption_menu_item
unless can?(context.current_user, :view_group_devops_adoption, context.group)
return ::Sidebars::NilMenuItem.new(item_id: :devops_adoption)
end
::Sidebars::MenuItem.new(
title: _('DevOps adoption'),
link: group_analytics_devops_adoption_path(context.group),
active_routes: { path: 'groups/analytics/devops_adoption#show' },
item_id: :devops_adoption
)
end
def insights_analytics_menu_item
unless context.group.insights_available?
return ::Sidebars::NilMenuItem.new(item_id: :insights)
end
::Sidebars::MenuItem.new(
title: _('Insights'),
link: group_insights_path(context.group),
active_routes: { path: 'groups/insights#show' },
container_html_options: { class: 'shortcuts-group-insights' },
item_id: :insights
)
end
def issues_analytics_menu_item
unless context.group.licensed_feature_available?(:issues_analytics)
return ::Sidebars::NilMenuItem.new(item_id: :issues_analytics)
end
::Sidebars::MenuItem.new(
title: _('Issue'),
link: group_issues_analytics_path(context.group),
active_routes: { path: 'issues_analytics#show' },
item_id: :issues_analytics
)
end
def merge_request_analytics_menu_item
unless show_merge_requests_analytics?
return ::Sidebars::NilMenuItem.new(item_id: :merge_requests_analytics)
end
::Sidebars::MenuItem.new(
title: _('Merge request'),
link: group_analytics_merge_request_analytics_path(context.group),
active_routes: { path: 'groups/analytics/merge_request_analytics#show' },
item_id: :merge_requests_analytics
)
end
# Currently an empty page, so don't show it on the navbar for now
def show_merge_requests_analytics?
return false
can?(context.current_user, :read_group_merge_request_analytics, context.group) # rubocop:disable Lint/UnreachableCode
end
def productivity_analytics_menu_item
unless show_productivity_analytics?
return ::Sidebars::NilMenuItem.new(item_id: :productivity_analytics)
end
::Sidebars::MenuItem.new(
title: _('Productivity'),
link: group_analytics_productivity_analytics_path(context.group),
active_routes: { path: 'groups/analytics/productivity_analytics#show' },
item_id: :productivity_analytics
)
end
def show_productivity_analytics?
context.group.licensed_feature_available?(:productivity_analytics) &&
can?(context.current_user, :view_productivity_analytics, context.group)
end
def repository_analytics_menu_item
unless show_repository_analytics?
return ::Sidebars::NilMenuItem.new(item_id: :repository_analytics)
end
::Sidebars::MenuItem.new(
title: _('Repository'),
link: group_analytics_repository_analytics_path(context.group),
active_routes: { path: 'groups/analytics/repository_analytics#show' },
item_id: :repository_analytics
)
end
def show_repository_analytics?
context.group.licensed_feature_available?(:group_coverage_reports) &&
can?(context.current_user, :read_group_repository_analytics, context.group)
end
def cycle_analytics_menu_item
strong_memoize(:cycle_analytics_menu_item) do
unless can?(context.current_user, :read_group_cycle_analytics, context.group)
next ::Sidebars::NilMenuItem.new(item_id: :cycle_analytics)
end
::Sidebars::MenuItem.new(
title: _('Value stream'),
link: group_analytics_cycle_analytics_path(context.group),
active_routes: { path: 'groups/analytics/cycle_analytics#show' },
item_id: :cycle_analytics
)
end
end
end
end
end
end
......@@ -70,7 +70,7 @@ RSpec.describe 'Group navbar' do
it 'redirects to value stream when Analytics item is clicked' do
page.within('.sidebar-top-level-items') do
find('[data-qa-selector=analytics_anchor]').click # rubocop:disable QA/SelectorUsage
find('.shortcuts-analytics').click
end
wait_for_requests
......
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe Sidebars::Groups::Menus::AnalyticsMenu do
let_it_be(:owner) { create(:user) }
let_it_be_with_refind(:group) do
create(:group, :private).tap do |g|
g.add_owner(owner)
end
end
let(:user) { owner }
let(:context) { Sidebars::Groups::Context.new(current_user: user, container: group) }
let(:menu) { described_class.new(context) }
describe '#link' do
before do
stub_licensed_features(cycle_analytics_for_groups: true, group_ci_cd_analytics: true)
end
it 'returns link to the value stream page' do
expect(menu.link).to include("/groups/#{group.full_path}/-/analytics/value_stream_analytics")
end
context 'when Value Stream is not visible' do
it 'returns link to the the first visible menu item' do
allow(menu).to receive(:cycle_analytics_menu_item).and_return(double(render?: false))
expect(menu.link).not_to include("/groups/#{group.full_path}/-/analytics/value_stream_analytics")
expect(menu.link).to eq menu.renderable_items.first.link
end
end
end
describe 'Menu items' do
subject { described_class.new(context).renderable_items.index { |e| e.item_id == item_id } }
describe 'CI/CD' do
let(:item_id) { :ci_cd_analytics }
before do
stub_licensed_features(group_ci_cd_analytics: true)
end
specify { is_expected.not_to be_nil }
describe 'when licensed feature :group_ci_cd_analytics is disabled' do
specify do
stub_licensed_features(group_ci_cd_analytics: false)
is_expected.to be_nil
end
end
describe 'when feature flag :group_ci_cd_analytics_page is disabled' do
specify do
stub_feature_flags(group_ci_cd_analytics_page: false)
is_expected.to be_nil
end
end
describe 'when the user does not have access' do
let(:user) { nil }
specify { is_expected.to be_nil }
end
end
describe 'Devops adoptions' do
let(:item_id) { :devops_adoption }
before do
stub_licensed_features(group_level_devops_adoption: true)
end
specify { is_expected.not_to be_nil }
describe 'when the user does not have access' do
let(:user) { nil }
specify { is_expected.to be_nil }
end
end
describe 'Repository' do
let(:item_id) { :repository_analytics }
before do
stub_licensed_features(group_coverage_reports: true, group_repository_analytics: true)
end
specify { is_expected.not_to be_nil }
describe 'when licensed feature :group_coverage_reports is disabled' do
specify do
stub_licensed_features(group_coverage_reports: false)
is_expected.to be_nil
end
end
describe 'when licensed feature :group_repository_analytics is disabled' do
specify do
stub_licensed_features(group_repository_analytics: false)
is_expected.to be_nil
end
end
describe 'when the user does not have access' do
let(:user) { nil }
specify { is_expected.to be_nil }
end
end
describe 'Contribution analytics' do
let(:item_id) { :contribution_analytics }
before do
stub_licensed_features(contribution_analytics: true)
end
specify { is_expected.not_to be_nil }
describe 'when licensed feature :group_coverage_reports is disabled' do
specify do
stub_licensed_features(contribution_analytics: false)
is_expected.to be_nil
end
end
describe 'when the user does not have access' do
let(:user) { nil }
specify { is_expected.to be_nil }
describe 'when show_promotions? is true' do
specify do
allow(LicenseHelper).to receive(:show_promotions?).and_return(true)
is_expected.not_to be_nil
end
end
end
end
describe 'Insights' do
let(:item_id) { :insights }
let(:insights_available) { true }
before do
allow(group).to receive(:insights_available?).and_return(insights_available)
end
specify { is_expected.not_to be_nil }
describe 'when insights are not available' do
let(:insights_available) { false }
specify { is_expected.to be_nil }
end
end
describe 'Issue analytics' do
let(:item_id) { :issues_analytics }
let(:issues_analytics_enabled) { true }
before do
stub_licensed_features(issues_analytics: issues_analytics_enabled)
end
specify { is_expected.not_to be_nil }
describe 'when licensed feature :issues_analytics is disabled' do
let(:issues_analytics_enabled) { false }
specify { is_expected.to be_nil }
end
end
describe 'Productivity analytics' do
let(:item_id) { :productivity_analytics }
let(:productivity_analytics_enabled) { true }
before do
stub_licensed_features(productivity_analytics: productivity_analytics_enabled)
end
specify { is_expected.not_to be_nil }
describe 'when licensed feature :productivity_analytics is disabled' do
let(:productivity_analytics_enabled) { false }
specify { is_expected.to be_nil }
end
describe 'when the user does not have access' do
let(:user) { nil }
specify { is_expected.to be_nil }
end
end
describe 'Value Stream' do
let(:item_id) { :cycle_analytics }
let(:cycle_analytics_enabled) { true }
before do
stub_licensed_features(cycle_analytics_for_groups: cycle_analytics_enabled)
end
specify { is_expected.not_to be_nil }
describe 'when licensed feature :cycle_analytics_for_groups is disabled' do
let(:cycle_analytics_enabled) { false }
specify { is_expected.to be_nil }
end
describe 'when the user does not have access' do
let(:user) { nil }
specify { is_expected.to be_nil }
end
end
end
end
......@@ -323,15 +323,46 @@ RSpec.describe 'layouts/nav/sidebar/_group' do
end
end
describe 'DevOps adoption link' do
let!(:current_user) { create(:user) }
describe 'Analytics menu' do
let_it_be(:owner) { create(:user) }
let_it_be(:guest) { create(:user) }
let_it_be_with_refind(:group) do
create(:group).tap do |g|
g.add_maintainer(owner)
g.add_guest(guest)
end
end
before do
group.add_maintainer(current_user)
allow(view).to receive(:current_user).and_return(owner)
end
allow(view).to receive(:current_user).and_return(current_user)
describe 'CI/CD analytics' do
let(:ci_cd_analytics_enabled) { true }
before do
stub_licensed_features(group_ci_cd_analytics: ci_cd_analytics_enabled)
end
it 'has a link to the CI/CD analytics page' do
render
expect(rendered).to have_link('CI/CD', href: group_analytics_ci_cd_analytics_path(group))
end
describe 'feature is disabled' do
let(:ci_cd_analytics_enabled) { false }
specify do
render
expect(rendered).not_to have_link('CI/CD')
end
end
end
describe 'DevOps' do
context 'DevOps adoption feature is available' do
before do
stub_licensed_features(group_level_devops_adoption: true)
......@@ -357,13 +388,31 @@ RSpec.describe 'layouts/nav/sidebar/_group' do
end
end
describe 'contribution analytics tab' do
let!(:current_user) { create(:user) }
describe 'Repository analytics' do
before do
group.add_guest(current_user)
stub_licensed_features(group_coverage_reports: true, group_repository_analytics: true)
end
allow(view).to receive(:current_user).and_return(current_user)
it 'has a link to the Repository analytics page' do
render
expect(rendered).to have_link('Repository', href: group_analytics_repository_analytics_path(group))
end
describe 'feature is not available' do
specify do
stub_licensed_features(group_coverage_reports: false)
render
expect(rendered).not_to have_link('Repository')
end
end
end
describe 'contribution analytics tab' do
before do
allow(view).to receive(:current_user).and_return(guest)
end
context 'contribution analytics feature is available' do
......@@ -444,6 +493,97 @@ RSpec.describe 'layouts/nav/sidebar/_group' do
end
end
describe 'Insights analytics' do
it 'has a link to the insights analytics page' do
allow(group).to receive(:insights_available?).and_return(true)
render
expect(rendered).to have_link('Insights', href: group_insights_path(group))
end
describe 'feature is disabled' do
specify do
render
expect(rendered).not_to have_link('Insights')
end
end
end
describe 'Issue analytics' do
let(:issues_analytics_enabled) { true }
before do
stub_licensed_features(issues_analytics: issues_analytics_enabled)
end
it 'has a link to the Issue analytics page' do
render
expect(rendered).to have_link('Issue', href: group_issues_analytics_path(group))
end
describe 'feature is disabled' do
let(:issues_analytics_enabled) { false }
specify do
render
expect(rendered).not_to have_link(exact_text: 'Issue')
end
end
end
describe 'Productivity analytics' do
let(:productivity_analytics_enabled) { true }
before do
stub_licensed_features(productivity_analytics: productivity_analytics_enabled)
end
it 'has a link to the Productivity analytics page' do
render
expect(rendered).to have_link('Productivity', href: group_analytics_productivity_analytics_path(group))
end
describe 'feature is disabled' do
let(:productivity_analytics_enabled) { false }
specify do
render
expect(rendered).not_to have_link('Productivity', href: group_analytics_productivity_analytics_path(group))
end
end
end
describe 'Cycle analytics' do
let(:cycle_analytics_enabled) { true }
before do
stub_licensed_features(cycle_analytics_for_groups: cycle_analytics_enabled)
end
it 'has a link to the Cycle analytics page' do
render
expect(rendered).to have_link('Value stream', href: group_analytics_cycle_analytics_path(group))
end
describe 'feature is disabled' do
let(:cycle_analytics_enabled) { false }
specify do
render
expect(rendered).not_to have_link('Value stream')
end
end
end
end
describe 'wiki tab' do
let(:can_read_wiki) { true }
......
......@@ -32,10 +32,6 @@ module QA
element :ldap_synchronization_link
element :billing_link
end
view 'ee/app/views/layouts/nav/_group_insights_link.html.haml' do
element :group_insights_link
end
end
end
......@@ -71,10 +67,18 @@ module QA
end
end
def click_contribution_analytics_item
hover_group_analytics do
within_submenu do
click_element(:sidebar_menu_item_link, menu_item: 'Contribution')
end
end
end
def click_group_insights_link
hover_element(:analytics_link) do
within_submenu(:analytics_sidebar_submenu) do
click_element(:group_insights_link)
hover_group_analytics do
within_submenu do
click_element(:sidebar_menu_item_link, menu_item: 'Insights')
end
end
end
......@@ -141,6 +145,15 @@ module QA
yield
end
end
def hover_group_analytics
within_sidebar do
scroll_to_element(:sidebar_menu_link, menu_item: 'Analytics')
find_element(:sidebar_menu_link, menu_item: 'Analytics').hover
yield
end
end
end
end
end
......
......@@ -15,11 +15,6 @@ module QA
element :group_package_settings_link
end
view 'app/views/layouts/nav/sidebar/_analytics_links.html.haml' do
element :analytics_link
element :analytics_sidebar_submenu
end
def click_group_members_item
hover_group_information do
within_submenu do
......@@ -42,14 +37,6 @@ module QA
end
end
def click_contribution_analytics_item
hover_element(:analytics_link) do
within_submenu(:analytics_sidebar_submenu) do
click_element(:contribution_analytics_link)
end
end
end
def click_group_general_settings_item
hover_element(:group_settings) do
within_submenu(:group_sidebar_submenu) do
......
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