diff options
author | GitLab Bot <gitlab-bot@gitlab.com> | 2021-05-19 15:44:42 +0000 |
---|---|---|
committer | GitLab Bot <gitlab-bot@gitlab.com> | 2021-05-19 15:44:42 +0000 |
commit | 4555e1b21c365ed8303ffb7a3325d773c9b8bf31 (patch) | |
tree | 5423a1c7516cffe36384133ade12572cf709398d /lib/sidebars | |
parent | e570267f2f6b326480d284e0164a6464ba4081bc (diff) | |
download | gitlab-ce-4555e1b21c365ed8303ffb7a3325d773c9b8bf31.tar.gz |
Add latest changes from gitlab-org/gitlab@13-12-stable-eev13.12.0-rc42
Diffstat (limited to 'lib/sidebars')
36 files changed, 2439 insertions, 0 deletions
diff --git a/lib/sidebars/concerns/container_with_html_options.rb b/lib/sidebars/concerns/container_with_html_options.rb new file mode 100644 index 00000000000..873cb5b0de9 --- /dev/null +++ b/lib/sidebars/concerns/container_with_html_options.rb @@ -0,0 +1,60 @@ +# frozen_string_literal: true + +module Sidebars + module Concerns + module ContainerWithHtmlOptions + # The attributes returned from this method + # will be applied to helper methods like + # `link_to` or the div containing the container. + def container_html_options + { + aria: { label: title } + }.merge(extra_container_html_options) + end + + # Classes will override mostly this method + # and not `container_html_options`. + def extra_container_html_options + {} + end + + # The attributes returned from this method + # will be applied to helper methods like + # `link_to` or the div containing the container + # when it is collapsed. + def collapsed_container_html_options + { + aria: { label: title } + }.merge(extra_collapsed_container_html_options) + end + + # Classes should mostly override this method + # and not `collapsed_container_html_options`. + def extra_collapsed_container_html_options + {} + end + + # Attributes to pass to the html_options attribute + # in the helper method that sets the active class + # on each element. + def nav_link_html_options + {} + end + + def title + raise NotImplementedError + end + + # The attributes returned from this method + # will be applied right next to the title, + # for example in the span that renders the title. + def title_html_options + {} + end + + def link + raise NotImplementedError + end + end + end +end diff --git a/lib/sidebars/concerns/has_active_routes.rb b/lib/sidebars/concerns/has_active_routes.rb new file mode 100644 index 00000000000..50c9f8c85a1 --- /dev/null +++ b/lib/sidebars/concerns/has_active_routes.rb @@ -0,0 +1,18 @@ +# frozen_string_literal: true + +module Sidebars + module Concerns + module HasActiveRoutes + # This method will indicate for which paths or + # controllers, the menu or menu item should + # be set as active. + # + # The returned values are passed to the `nav_link` helper method, + # so the params can be either `path`, `page`, `controller`. + # Param 'action' is not supported. + def active_routes + {} + end + end + end +end diff --git a/lib/sidebars/concerns/has_hint.rb b/lib/sidebars/concerns/has_hint.rb new file mode 100644 index 00000000000..dc4f765e974 --- /dev/null +++ b/lib/sidebars/concerns/has_hint.rb @@ -0,0 +1,18 @@ +# frozen_string_literal: true + +# This module has the necessary methods to store +# hints for menus. Hints are elements displayed +# when the user hover the menu item. +module Sidebars + module Concerns + module HasHint + def show_hint? + false + end + + def hint_html_options + {} + end + end + end +end diff --git a/lib/sidebars/concerns/has_icon.rb b/lib/sidebars/concerns/has_icon.rb new file mode 100644 index 00000000000..afff466239d --- /dev/null +++ b/lib/sidebars/concerns/has_icon.rb @@ -0,0 +1,29 @@ +# frozen_string_literal: true + +# This module has the necessary methods to show +# sprites or images next to the menu item. +module Sidebars + module Concerns + module HasIcon + def sprite_icon + nil + end + + def sprite_icon_html_options + {} + end + + def image_path + nil + end + + def image_html_options + {} + end + + def icon_or_image? + sprite_icon || image_path + end + end + end +end diff --git a/lib/sidebars/concerns/has_pill.rb b/lib/sidebars/concerns/has_pill.rb new file mode 100644 index 00000000000..5082ed477e6 --- /dev/null +++ b/lib/sidebars/concerns/has_pill.rb @@ -0,0 +1,23 @@ +# frozen_string_literal: true + +# This module introduces the logic to show the "pill" element +# next to the menu item, indicating the a count. +module Sidebars + module Concerns + module HasPill + def has_pill? + false + end + + # In this method we will need to provide the query + # to retrieve the elements count + def pill_count + raise NotImplementedError + end + + def pill_html_options + {} + end + end + end +end diff --git a/lib/sidebars/concerns/positionable_list.rb b/lib/sidebars/concerns/positionable_list.rb new file mode 100644 index 00000000000..0bbe1d918e5 --- /dev/null +++ b/lib/sidebars/concerns/positionable_list.rb @@ -0,0 +1,56 @@ +# frozen_string_literal: true + +# This module handles element positions in a list. +module Sidebars + module Concerns + module PositionableList + def add_element(list, element) + return unless element + + list << element + end + + def insert_element_before(list, before_element, new_element) + return unless new_element + + index = index_of(list, before_element) + + if index + list.insert(index, new_element) + else + list.unshift(new_element) + end + end + + def insert_element_after(list, after_element, new_element) + return unless new_element + + index = index_of(list, after_element) + + if index + list.insert(index + 1, new_element) + else + add_element(list, new_element) + end + end + + def replace_element(list, element_to_replace, new_element) + return unless new_element + + index = index_of(list, element_to_replace) + + return unless index + + list[index] = new_element + end + + private + + # Classes including this method will have to define + # the way to identify elements through this method + def index_of(list, element) + raise NotImplementedError + end + end + end +end diff --git a/lib/sidebars/concerns/renderable.rb b/lib/sidebars/concerns/renderable.rb new file mode 100644 index 00000000000..750efa2fcb8 --- /dev/null +++ b/lib/sidebars/concerns/renderable.rb @@ -0,0 +1,14 @@ +# frozen_string_literal: true + +module Sidebars + module Concerns + module Renderable + # This method will control whether the menu or menu_item + # should be rendered. It will be overriden by specific + # classes. + def render? + true + end + end + end +end diff --git a/lib/sidebars/context.rb b/lib/sidebars/context.rb new file mode 100644 index 00000000000..d9ac2705aaf --- /dev/null +++ b/lib/sidebars/context.rb @@ -0,0 +1,21 @@ +# frozen_string_literal: true + +# This class stores all the information needed to display and +# render the sidebar and menus. +# It usually stores information regarding the context and calculated +# values where the logic is in helpers. +module Sidebars + class Context + attr_reader :current_user, :container + + def initialize(current_user:, container:, **args) + @current_user = current_user + @container = container + + args.each do |key, value| + singleton_class.public_send(:attr_reader, key) # rubocop:disable GitlabSecurity/PublicSend + instance_variable_set("@#{key}", value) + end + end + end +end diff --git a/lib/sidebars/menu.rb b/lib/sidebars/menu.rb new file mode 100644 index 00000000000..d81e413f4a9 --- /dev/null +++ b/lib/sidebars/menu.rb @@ -0,0 +1,93 @@ +# frozen_string_literal: true + +module Sidebars + class Menu + extend ::Gitlab::Utils::Override + include ::Gitlab::Routing + include GitlabRoutingHelper + include Gitlab::Allowable + include ::Sidebars::Concerns::HasPill + include ::Sidebars::Concerns::HasIcon + include ::Sidebars::Concerns::PositionableList + include ::Sidebars::Concerns::Renderable + include ::Sidebars::Concerns::ContainerWithHtmlOptions + include ::Sidebars::Concerns::HasActiveRoutes + + attr_reader :context + delegate :current_user, :container, to: :@context + + def initialize(context) + @context = context + @items = [] + + configure_menu_items + end + + def configure_menu_items + true + end + + override :render? + def render? + has_renderable_items? + end + + # Menus might have or not a link + override :link + def link + nil + end + + # This method normalizes the information retrieved from the submenus and this menu + # Value from menus is something like: [{ path: 'foo', path: 'bar', controller: :foo }] + # This method filters the information and returns: { path: ['foo', 'bar'], controller: :foo } + def all_active_routes + @all_active_routes ||= begin + ([active_routes] + renderable_items.map(&:active_routes)).flatten.each_with_object({}) do |pairs, hash| + pairs.each do |k, v| + hash[k] ||= [] + hash[k] += Array(v) + hash[k].uniq! + end + + hash + end + end + end + + # Returns whether the menu has any menu item, no + # matter whether it is renderable or not + def has_items? + @items.any? + end + + # Returns all renderable menu items + def renderable_items + @renderable_items ||= @items.select(&:render?) + end + + # Returns whether the menu has any renderable menu item + def has_renderable_items? + renderable_items.any? + end + + def add_item(item) + add_element(@items, item) + end + + def insert_item_before(before_item, new_item) + insert_element_before(@items, before_item, new_item) + end + + def insert_item_after(after_item, new_item) + insert_element_after(@items, after_item, new_item) + end + + private + + override :index_of + def index_of(list, element) + list.index { |e| e.item_id == element } + end + end +end diff --git a/lib/sidebars/menu_item.rb b/lib/sidebars/menu_item.rb new file mode 100644 index 00000000000..b0a12e769dc --- /dev/null +++ b/lib/sidebars/menu_item.rb @@ -0,0 +1,26 @@ +# frozen_string_literal: true + +module Sidebars + class MenuItem + attr_reader :title, :link, :active_routes, :item_id, :container_html_options, :sprite_icon, :sprite_icon_html_options, :hint_html_options + + def initialize(title:, link:, active_routes:, item_id: nil, container_html_options: {}, sprite_icon: nil, sprite_icon_html_options: {}, hint_html_options: {}) + @title = title + @link = link + @active_routes = active_routes + @item_id = item_id + @container_html_options = { aria: { label: title } }.merge(container_html_options) + @sprite_icon = sprite_icon + @sprite_icon_html_options = sprite_icon_html_options + @hint_html_options = hint_html_options + end + + def show_hint? + hint_html_options.present? + end + + def render? + true + end + end +end diff --git a/lib/sidebars/nil_menu_item.rb b/lib/sidebars/nil_menu_item.rb new file mode 100644 index 00000000000..9ff7fd0d6d6 --- /dev/null +++ b/lib/sidebars/nil_menu_item.rb @@ -0,0 +1,16 @@ +# frozen_string_literal: true + +module Sidebars + class NilMenuItem < MenuItem + extend ::Gitlab::Utils::Override + + def initialize(item_id:) + super(item_id: item_id, title: nil, link: nil, active_routes: {}) + end + + override :render? + def render? + false + end + end +end diff --git a/lib/sidebars/panel.rb b/lib/sidebars/panel.rb new file mode 100644 index 00000000000..75b3ba65729 --- /dev/null +++ b/lib/sidebars/panel.rb @@ -0,0 +1,86 @@ +# frozen_string_literal: true + +module Sidebars + class Panel + extend ::Gitlab::Utils::Override + include ::Sidebars::Concerns::PositionableList + + attr_reader :context, :scope_menu, :hidden_menu + + def initialize(context) + @context = context + @scope_menu = nil + @hidden_menu = nil + @menus = [] + + configure_menus + end + + def configure_menus + # No-op + end + + def add_menu(menu) + add_element(@menus, menu) + end + + def insert_menu_before(before_menu, new_menu) + insert_element_before(@menus, before_menu, new_menu) + end + + def insert_menu_after(after_menu, new_menu) + insert_element_after(@menus, after_menu, new_menu) + end + + def replace_menu(menu_to_replace, new_menu) + replace_element(@menus, menu_to_replace, new_menu) + end + + def set_scope_menu(scope_menu) + @scope_menu = scope_menu + end + + def set_hidden_menu(hidden_menu) + @hidden_menu = hidden_menu + end + + def aria_label + raise NotImplementedError + end + + def has_renderable_menus? + renderable_menus.any? + end + + def renderable_menus + @renderable_menus ||= @menus.select(&:render?) + end + + def container + context.container + end + + # Auxiliar method that helps with the migration from + # regular views to the new logic + def render_raw_scope_menu_partial + # No-op + end + + # Auxiliar method that helps with the migration from + # regular views to the new logic. + # + # Any menu inside this partial will be added after + # all the menus added in the `configure_menus` + # method. + def render_raw_menus_partial + # No-op + end + + private + + override :index_of + def index_of(list, element) + list.index { |e| e.is_a?(element) } + end + end +end diff --git a/lib/sidebars/projects/context.rb b/lib/sidebars/projects/context.rb new file mode 100644 index 00000000000..4c82309035d --- /dev/null +++ b/lib/sidebars/projects/context.rb @@ -0,0 +1,11 @@ +# frozen_string_literal: true + +module Sidebars + module Projects + class Context < ::Sidebars::Context + def initialize(current_user:, container:, **args) + super(current_user: current_user, container: container, project: container, **args) + end + end + end +end diff --git a/lib/sidebars/projects/menus/analytics_menu.rb b/lib/sidebars/projects/menus/analytics_menu.rb new file mode 100644 index 00000000000..660965005c3 --- /dev/null +++ b/lib/sidebars/projects/menus/analytics_menu.rb @@ -0,0 +1,95 @@ +# frozen_string_literal: true + +module Sidebars + module Projects + module Menus + class AnalyticsMenu < ::Sidebars::Menu + include Gitlab::Utils::StrongMemoize + + override :configure_menu_items + def configure_menu_items + return false unless can?(context.current_user, :read_analytics, context.project) + + add_item(ci_cd_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 + if !context.project.feature_available?(:builds, context.current_user) || + !can?(context.current_user, :read_build, context.project) || + context.project.empty_repo? + return ::Sidebars::NilMenuItem.new(item_id: :ci_cd_analytics) + end + + ::Sidebars::MenuItem.new( + title: _('CI/CD'), + link: charts_project_pipelines_path(context.project), + active_routes: { path: 'pipelines#charts' }, + item_id: :ci_cd_analytics + ) + end + + def repository_analytics_menu_item + if context.project.empty_repo? + return ::Sidebars::NilMenuItem.new(item_id: :repository_analytics) + end + + ::Sidebars::MenuItem.new( + title: _('Repository'), + link: charts_project_graph_path(context.project, context.current_ref), + container_html_options: { class: 'shortcuts-repository-charts' }, + active_routes: { path: 'graphs#charts' }, + item_id: :repository_analytics + ) + end + + def cycle_analytics_menu_item + strong_memoize(:cycle_analytics_menu_item) do + unless can?(context.current_user, :read_cycle_analytics, context.project) + next ::Sidebars::NilMenuItem.new(item_id: :cycle_analytics) + end + + ::Sidebars::MenuItem.new( + title: _('Value Stream'), + link: project_cycle_analytics_path(context.project), + container_html_options: { class: 'shortcuts-project-cycle-analytics' }, + active_routes: { path: 'cycle_analytics#show' }, + item_id: :cycle_analytics + ) + end + end + end + end + end +end + +Sidebars::Projects::Menus::AnalyticsMenu.prepend_mod_with('Sidebars::Projects::Menus::AnalyticsMenu') diff --git a/lib/sidebars/projects/menus/ci_cd_menu.rb b/lib/sidebars/projects/menus/ci_cd_menu.rb new file mode 100644 index 00000000000..042ad17fdfc --- /dev/null +++ b/lib/sidebars/projects/menus/ci_cd_menu.rb @@ -0,0 +1,118 @@ +# frozen_string_literal: true + +module Sidebars + module Projects + module Menus + class CiCdMenu < ::Sidebars::Menu + override :configure_menu_items + def configure_menu_items + return unless can?(context.current_user, :read_build, context.project) + + add_item(pipelines_menu_item) + add_item(pipelines_editor_menu_item) + add_item(jobs_menu_item) + add_item(artifacts_menu_item) + add_item(pipeline_schedules_menu_item) + end + + override :link + def link + project_pipelines_path(context.project) + end + + override :extra_container_html_options + def extra_container_html_options + { + class: 'shortcuts-pipelines rspec-link-pipelines' + } + end + + override :title + def title + _('CI/CD') + end + + override :title_html_options + def title_html_options + { + id: 'js-onboarding-pipelines-link' + } + end + + override :sprite_icon + def sprite_icon + 'rocket' + end + + private + + def pipelines_menu_item + ::Sidebars::MenuItem.new( + title: _('Pipelines'), + link: project_pipelines_path(context.project), + container_html_options: { class: 'shortcuts-pipelines' }, + active_routes: { path: pipelines_routes }, + item_id: :pipelines + ) + end + + def pipelines_routes + %w[ + pipelines#index + pipelines#show + pipelines#new + ] + end + + def pipelines_editor_menu_item + unless context.can_view_pipeline_editor + return ::Sidebars::NilMenuItem.new(item_id: :pipelines_editor) + end + + ::Sidebars::MenuItem.new( + title: s_('Pipelines|Editor'), + link: project_ci_pipeline_editor_path(context.project), + active_routes: { path: 'projects/ci/pipeline_editor#show' }, + item_id: :pipelines_editor + ) + end + + def jobs_menu_item + ::Sidebars::MenuItem.new( + title: _('Jobs'), + link: project_jobs_path(context.project), + container_html_options: { class: 'shortcuts-builds' }, + active_routes: { controller: :jobs }, + item_id: :jobs + ) + end + + def artifacts_menu_item + unless Feature.enabled?(:artifacts_management_page, context.project) + return ::Sidebars::NilMenuItem.new(item_id: :artifacts) + end + + ::Sidebars::MenuItem.new( + title: _('Artifacts'), + link: project_artifacts_path(context.project), + container_html_options: { class: 'shortcuts-builds' }, + active_routes: { path: 'artifacts#index' }, + item_id: :artifacts + ) + end + + def pipeline_schedules_menu_item + ::Sidebars::MenuItem.new( + title: _('Schedules'), + link: pipeline_schedules_path(context.project), + container_html_options: { class: 'shortcuts-builds' }, + active_routes: { controller: :pipeline_schedules }, + item_id: :pipeline_schedules + ) + end + end + end + end +end + +Sidebars::Projects::Menus::CiCdMenu.prepend_mod_with('Sidebars::Projects::Menus::CiCdMenu') diff --git a/lib/sidebars/projects/menus/confluence_menu.rb b/lib/sidebars/projects/menus/confluence_menu.rb new file mode 100644 index 00000000000..0d83238fa82 --- /dev/null +++ b/lib/sidebars/projects/menus/confluence_menu.rb @@ -0,0 +1,43 @@ +# frozen_string_literal: true + +module Sidebars + module Projects + module Menus + class ConfluenceMenu < ::Sidebars::Menu + override :link + def link + project_wikis_confluence_path(context.project) + end + + override :extra_container_html_options + def extra_container_html_options + { + class: 'shortcuts-confluence' + } + end + + override :title + def title + _('Confluence') + end + + override :image_path + def image_path + 'confluence.svg' + end + + override :image_html_options + def image_html_options + { + alt: title + } + end + + override :render? + def render? + context.project.has_confluence? + end + end + end + end +end diff --git a/lib/sidebars/projects/menus/deployments_menu.rb b/lib/sidebars/projects/menus/deployments_menu.rb new file mode 100644 index 00000000000..f3d13e12258 --- /dev/null +++ b/lib/sidebars/projects/menus/deployments_menu.rb @@ -0,0 +1,87 @@ +# frozen_string_literal: true + +module Sidebars + module Projects + module Menus + class DeploymentsMenu < ::Sidebars::Menu + override :configure_menu_items + def configure_menu_items + return false if Feature.disabled?(:sidebar_refactor, context.current_user, default_enabled: :yaml) + + add_item(feature_flags_menu_item) + add_item(environments_menu_item) + add_item(releases_menu_item) + + true + end + + override :link + def link + renderable_items.first.link + end + + override :extra_container_html_options + def extra_container_html_options + { + class: 'shortcuts-deployments' + } + end + + override :title + def title + _('Deployments') + end + + override :sprite_icon + def sprite_icon + 'environment' + end + + private + + def feature_flags_menu_item + unless can?(context.current_user, :read_feature_flag, context.project) + return ::Sidebars::NilMenuItem.new(item_id: :feature_flags) + end + + ::Sidebars::MenuItem.new( + title: _('Feature Flags'), + link: project_feature_flags_path(context.project), + active_routes: { controller: :feature_flags }, + container_html_options: { class: 'shortcuts-feature-flags' }, + item_id: :feature_flags + ) + end + + def environments_menu_item + unless can?(context.current_user, :read_environment, context.project) + return ::Sidebars::NilMenuItem.new(item_id: :environments) + end + + ::Sidebars::MenuItem.new( + title: _('Environments'), + link: project_environments_path(context.project), + active_routes: { controller: :environments }, + container_html_options: { class: 'shortcuts-environments' }, + item_id: :environments + ) + end + + def releases_menu_item + if !can?(context.current_user, :read_release, context.project) || + context.project.empty_repo? + return ::Sidebars::NilMenuItem.new(item_id: :releases) + end + + ::Sidebars::MenuItem.new( + title: _('Releases'), + link: project_releases_path(context.project), + item_id: :releases, + active_routes: { controller: :releases }, + container_html_options: { class: 'shortcuts-deployments-releases' } + ) + end + end + end + end +end diff --git a/lib/sidebars/projects/menus/external_issue_tracker_menu.rb b/lib/sidebars/projects/menus/external_issue_tracker_menu.rb new file mode 100644 index 00000000000..136d30f38c3 --- /dev/null +++ b/lib/sidebars/projects/menus/external_issue_tracker_menu.rb @@ -0,0 +1,59 @@ +# frozen_string_literal: true + +module Sidebars + module Projects + module Menus + class ExternalIssueTrackerMenu < ::Sidebars::Menu + override :link + def link + external_issue_tracker.issue_tracker_path + end + + override :extra_container_html_options + def extra_container_html_options + { + target: '_blank', + rel: 'noopener noreferrer', + class: 'shortcuts-external_tracker' + } + end + + override :extra_collapsed_container_html_options + def extra_collapsed_container_html_options + { + target: '_blank', + rel: 'noopener noreferrer' + } + end + + override :title + def title + external_issue_tracker.title + end + + override :title_html_options + def title_html_options + { + id: 'js-onboarding-issues-link' + } + end + + override :sprite_icon + def sprite_icon + 'external-link' + end + + override :render? + def render? + external_issue_tracker.present? + end + + private + + def external_issue_tracker + @external_issue_tracker ||= context.project.external_issue_tracker + end + end + end + end +end diff --git a/lib/sidebars/projects/menus/external_wiki_menu.rb b/lib/sidebars/projects/menus/external_wiki_menu.rb new file mode 100644 index 00000000000..825f0ca5e8b --- /dev/null +++ b/lib/sidebars/projects/menus/external_wiki_menu.rb @@ -0,0 +1,52 @@ +# frozen_string_literal: true + +module Sidebars + module Projects + module Menus + class ExternalWikiMenu < ::Sidebars::Menu + override :link + def link + external_wiki.external_wiki_url + end + + override :extra_container_html_options + def extra_container_html_options + { + target: '_blank', + rel: 'noopener noreferrer', + class: 'shortcuts-external_wiki' + } + end + + override :extra_collapsed_container_html_options + def extra_collapsed_container_html_options + { + target: '_blank', + rel: 'noopener noreferrer' + } + end + + override :title + def title + s_('ExternalWikiService|External wiki') + end + + override :sprite_icon + def sprite_icon + 'external-link' + end + + override :render? + def render? + external_wiki.present? + end + + private + + def external_wiki + @external_wiki ||= context.project.external_wiki + end + end + end + end +end diff --git a/lib/sidebars/projects/menus/hidden_menu.rb b/lib/sidebars/projects/menus/hidden_menu.rb new file mode 100644 index 00000000000..c273ee8b74f --- /dev/null +++ b/lib/sidebars/projects/menus/hidden_menu.rb @@ -0,0 +1,105 @@ +# frozen_string_literal: true + +module Sidebars + module Projects + module Menus + class HiddenMenu < ::Sidebars::Menu + override :configure_menu_items + def configure_menu_items + add_item(activity_menu_item) + add_item(graph_menu_item) + add_item(new_issue_menu_item) + add_item(jobs_menu_item) + add_item(commits_menu_item) + add_item(issue_boards_menu_item) + + true + end + + private + + def activity_menu_item + ::Sidebars::MenuItem.new( + title: _('Activity'), + link: activity_project_path(context.project), + active_routes: {}, + container_html_options: { class: 'shortcuts-project-activity' }, + item_id: :activity + ) + end + + def graph_menu_item + if !can?(context.current_user, :download_code, context.project) || + context.project.empty_repo? + return ::Sidebars::NilMenuItem.new(item_id: :graph) + end + + ::Sidebars::MenuItem.new( + title: _('Graph'), + link: project_network_path(context.project, context.current_ref), + active_routes: {}, + container_html_options: { class: 'shortcuts-network' }, + item_id: :graph + ) + end + + def new_issue_menu_item + unless can?(context.current_user, :read_issue, context.project) + return ::Sidebars::NilMenuItem.new(item_id: :new_issue) + end + + ::Sidebars::MenuItem.new( + title: _('Create a new issue'), + link: new_project_issue_path(context.project), + active_routes: {}, + container_html_options: { class: 'shortcuts-new-issue' }, + item_id: :new_issue + ) + end + + def jobs_menu_item + unless can?(context.current_user, :read_build, context.project) + return ::Sidebars::NilMenuItem.new(item_id: :jobs) + end + + ::Sidebars::MenuItem.new( + title: _('Jobs'), + link: project_jobs_path(context.project), + active_routes: {}, + container_html_options: { class: 'shortcuts-builds' }, + item_id: :jobs + ) + end + + def commits_menu_item + if !can?(context.current_user, :download_code, context.project) || + context.project.empty_repo? + return ::Sidebars::NilMenuItem.new(item_id: :commits) + end + + ::Sidebars::MenuItem.new( + title: _('Commits'), + link: project_commits_path(context.project), + active_routes: {}, + container_html_options: { class: 'shortcuts-commits' }, + item_id: :commits + ) + end + + def issue_boards_menu_item + unless can?(context.current_user, :read_issue, context.project) + return ::Sidebars::NilMenuItem.new(item_id: :issue_boards) + end + + ::Sidebars::MenuItem.new( + title: _('Issue Boards'), + link: project_boards_path(context.project), + active_routes: {}, + container_html_options: { class: 'shortcuts-issue-boards' }, + item_id: :issue_boards + ) + end + end + end + end +end diff --git a/lib/sidebars/projects/menus/infrastructure_menu.rb b/lib/sidebars/projects/menus/infrastructure_menu.rb new file mode 100644 index 00000000000..75b6cae295f --- /dev/null +++ b/lib/sidebars/projects/menus/infrastructure_menu.rb @@ -0,0 +1,99 @@ +# frozen_string_literal: true + +module Sidebars + module Projects + module Menus + class InfrastructureMenu < ::Sidebars::Menu + override :configure_menu_items + def configure_menu_items + return false if Feature.disabled?(:sidebar_refactor, context.current_user) + return false unless context.project.feature_available?(:operations, context.current_user) + + add_item(kubernetes_menu_item) + add_item(serverless_menu_item) + add_item(terraform_menu_item) + + true + end + + override :link + def link + project_clusters_path(context.project) + end + + override :extra_container_html_options + def extra_container_html_options + { + class: 'shortcuts-infrastructure' + } + end + + override :title + def title + _('Infrastructure') + end + + override :sprite_icon + def sprite_icon + 'cloud-gear' + end + + private + + def kubernetes_menu_item + unless can?(context.current_user, :read_cluster, context.project) + return ::Sidebars::NilMenuItem.new(item_id: :kubernetes) + end + + ::Sidebars::MenuItem.new( + title: _('Kubernetes clusters'), + link: project_clusters_path(context.project), + active_routes: { controller: [:cluster_agents, :clusters] }, + container_html_options: { class: 'shortcuts-kubernetes' }, + hint_html_options: kubernetes_hint_html_options, + item_id: :kubernetes + ) + end + + def kubernetes_hint_html_options + return {} unless context.show_cluster_hint + + { disabled: true, + data: { trigger: 'manual', + container: 'body', + placement: 'right', + highlight: UserCalloutsHelper::GKE_CLUSTER_INTEGRATION, + highlight_priority: UserCallout.feature_names[:GKE_CLUSTER_INTEGRATION], + dismiss_endpoint: user_callouts_path, + auto_devops_help_path: help_page_path('topics/autodevops/index.md') } } + end + + def serverless_menu_item + unless can?(context.current_user, :read_cluster, context.project) + return ::Sidebars::NilMenuItem.new(item_id: :serverless) + end + + ::Sidebars::MenuItem.new( + title: _('Serverless platform'), + link: project_serverless_functions_path(context.project), + active_routes: { controller: :functions }, + item_id: :serverless + ) + end + + def terraform_menu_item + unless can?(context.current_user, :read_terraform_state, context.project) + return ::Sidebars::NilMenuItem.new(item_id: :terraform) + end + + ::Sidebars::MenuItem.new( + title: _('Terraform'), + link: project_terraform_index_path(context.project), + active_routes: { controller: :terraform }, + item_id: :terraform + ) + end + end + end + end +end diff --git a/lib/sidebars/projects/menus/issues_menu.rb b/lib/sidebars/projects/menus/issues_menu.rb new file mode 100644 index 00000000000..9840f644179 --- /dev/null +++ b/lib/sidebars/projects/menus/issues_menu.rb @@ -0,0 +1,135 @@ +# frozen_string_literal: true + +module Sidebars + module Projects + module Menus + class IssuesMenu < ::Sidebars::Menu + include Gitlab::Utils::StrongMemoize + + override :configure_menu_items + def configure_menu_items + return unless can?(context.current_user, :read_issue, context.project) + + add_item(list_menu_item) + add_item(boards_menu_item) + add_item(labels_menu_item) + add_item(service_desk_menu_item) + add_item(milestones_menu_item) + + true + end + + override :link + def link + project_issues_path(context.project) + end + + override :extra_container_html_options + def extra_container_html_options + { + class: 'shortcuts-issues' + } + end + + override :title + def title + _('Issues') + end + + override :title_html_options + def title_html_options + { + id: 'js-onboarding-issues-link' + } + end + + override :sprite_icon + def sprite_icon + 'issues' + end + + override :active_routes + def active_routes + { controller: 'projects/issues' } + end + + override :has_pill? + def has_pill? + strong_memoize(:has_pill) do + context.project.issues_enabled? + end + end + + override :pill_count + def pill_count + strong_memoize(:pill_count) do + context.project.open_issues_count(context.current_user) + end + end + + override :pill_html_options + def pill_html_options + { + class: 'issue_counter' + } + end + + private + + def list_menu_item + ::Sidebars::MenuItem.new( + title: _('List'), + link: project_issues_path(context.project), + active_routes: { path: 'projects/issues#index' }, + container_html_options: { aria: { label: _('Issues') } }, + item_id: :issue_list + ) + end + + def boards_menu_item + title = context.project.multiple_issue_boards_available? ? s_('IssueBoards|Boards') : s_('IssueBoards|Board') + + ::Sidebars::MenuItem.new( + title: title, + link: project_boards_path(context.project), + active_routes: { controller: :boards }, + item_id: :boards + ) + end + + def labels_menu_item + if Feature.enabled?(:sidebar_refactor, context.current_user) + return ::Sidebars::NilMenuItem.new(item_id: :labels) + end + + ::Sidebars::MenuItem.new( + title: _('Labels'), + link: project_labels_path(context.project), + active_routes: { controller: :labels }, + item_id: :labels + ) + end + + def service_desk_menu_item + ::Sidebars::MenuItem.new( + title: _('Service Desk'), + link: service_desk_project_issues_path(context.project), + active_routes: { path: 'issues#service_desk' }, + item_id: :service_desk + ) + end + + def milestones_menu_item + ::Sidebars::MenuItem.new( + title: _('Milestones'), + link: project_milestones_path(context.project), + active_routes: { controller: :milestones }, + item_id: :milestones + ) + end + end + end + end +end + +Sidebars::Projects::Menus::IssuesMenu.prepend_mod_with('Sidebars::Projects::Menus::IssuesMenu') diff --git a/lib/sidebars/projects/menus/labels_menu.rb b/lib/sidebars/projects/menus/labels_menu.rb new file mode 100644 index 00000000000..12cf0444994 --- /dev/null +++ b/lib/sidebars/projects/menus/labels_menu.rb @@ -0,0 +1,50 @@ +# frozen_string_literal: true + +module Sidebars + module Projects + module Menus + class LabelsMenu < ::Sidebars::Menu + override :link + def link + project_labels_path(context.project) + end + + override :extra_container_html_options + def extra_container_html_options + { + class: 'shortcuts-labels' + } + end + + override :title + def title + _('Labels') + end + + override :title_html_options + def title_html_options + { + id: 'js-onboarding-labels-link' + } + end + + override :active_routes + def active_routes + { controller: :labels } + end + + override :sprite_icon + def sprite_icon + 'label' + end + + override :render? + def render? + return false if Feature.enabled?(:sidebar_refactor, context.current_user) + + can?(context.current_user, :read_label, context.project) && !context.project.issues_enabled? + end + end + end + end +end diff --git a/lib/sidebars/projects/menus/learn_gitlab_menu.rb b/lib/sidebars/projects/menus/learn_gitlab_menu.rb new file mode 100644 index 00000000000..e3fcd8f25d5 --- /dev/null +++ b/lib/sidebars/projects/menus/learn_gitlab_menu.rb @@ -0,0 +1,62 @@ +# frozen_string_literal: true + +module Sidebars + module Projects + module Menus + class LearnGitlabMenu < ::Sidebars::Menu + include Gitlab::Utils::StrongMemoize + + override :link + def link + project_learn_gitlab_path(context.project) + end + + override :active_routes + def active_routes + { controller: :learn_gitlab } + end + + override :title + def title + _('Learn GitLab') + end + + override :has_pill? + def has_pill? + context.learn_gitlab_experiment_enabled + end + + override :pill_count + def pill_count + strong_memoize(:pill_count) do + percentage = LearnGitlab::Onboarding.new(context.project.namespace).completed_percentage + + "#{percentage}%" + end + end + + override :extra_container_html_options + def nav_link_html_options + { + class: 'home', + data: { + track_action: 'click_menu', + track_property: context.learn_gitlab_experiment_tracking_category, + track_label: 'learn_gitlab' + } + } + end + + override :image_path + def image_path + 'learn_gitlab/graduation_hat.svg' + end + + override :render? + def render? + context.learn_gitlab_experiment_enabled + end + end + end + end +end diff --git a/lib/sidebars/projects/menus/members_menu.rb b/lib/sidebars/projects/menus/members_menu.rb new file mode 100644 index 00000000000..498bfa74261 --- /dev/null +++ b/lib/sidebars/projects/menus/members_menu.rb @@ -0,0 +1,43 @@ +# frozen_string_literal: true + +module Sidebars + module Projects + module Menus + class MembersMenu < ::Sidebars::Menu + override :link + def link + project_project_members_path(context.project) + end + + override :extra_container_html_options + def extra_container_html_options + { + id: 'js-onboarding-members-link' + } + end + + override :title + def title + _('Members') + end + + override :sprite_icon + def sprite_icon + 'users' + end + + override :render? + def render? + return false if Feature.enabled?(:sidebar_refactor, context.current_user, default_enabled: :yaml) + + can?(context.current_user, :read_project_member, context.project) + end + + override :active_routes + def active_routes + { controller: :project_members } + end + end + end + end +end diff --git a/lib/sidebars/projects/menus/merge_requests_menu.rb b/lib/sidebars/projects/menus/merge_requests_menu.rb new file mode 100644 index 00000000000..fe501667d37 --- /dev/null +++ b/lib/sidebars/projects/menus/merge_requests_menu.rb @@ -0,0 +1,70 @@ +# frozen_string_literal: true + +module Sidebars + module Projects + module Menus + class MergeRequestsMenu < ::Sidebars::Menu + override :link + def link + project_merge_requests_path(context.project) + end + + override :extra_container_html_options + def extra_container_html_options + { + class: 'shortcuts-merge_requests' + } + end + + override :title + def title + _('Merge requests') + end + + override :title_html_options + def title_html_options + { + id: 'js-onboarding-mr-link' + } + end + + override :sprite_icon + def sprite_icon + 'git-merge' + end + + override :render? + def render? + can?(context.current_user, :read_merge_request, context.project) && + context.project.repo_exists? + end + + override :has_pill? + def has_pill? + true + end + + override :pill_count + def pill_count + @pill_count ||= context.project.open_merge_requests_count + end + + override :pill_html_options + def pill_html_options + { + class: 'merge_counter js-merge-counter' + } + end + + override :active_routes + def active_routes + if context.project.issues_enabled? + { controller: :merge_requests } + else + { controller: [:merge_requests, :milestones] } + end + end + end + end + end +end diff --git a/lib/sidebars/projects/menus/monitor_menu.rb b/lib/sidebars/projects/menus/monitor_menu.rb new file mode 100644 index 00000000000..18c990d0e1f --- /dev/null +++ b/lib/sidebars/projects/menus/monitor_menu.rb @@ -0,0 +1,246 @@ +# frozen_string_literal: true + +module Sidebars + module Projects + module Menus + class MonitorMenu < ::Sidebars::Menu + override :configure_menu_items + def configure_menu_items + return false unless context.project.feature_available?(:operations, context.current_user) + + add_item(metrics_dashboard_menu_item) + add_item(logs_menu_item) + add_item(tracing_menu_item) + add_item(error_tracking_menu_item) + add_item(alert_management_menu_item) + add_item(incidents_menu_item) + add_item(serverless_menu_item) + add_item(terraform_menu_item) + add_item(kubernetes_menu_item) + add_item(environments_menu_item) + add_item(feature_flags_menu_item) + add_item(product_analytics_menu_item) + + true + end + + override :link + def link + if can?(context.current_user, :read_environment, context.project) + metrics_project_environments_path(context.project) + else + project_feature_flags_path(context.project) + end + end + + override :extra_container_html_options + def extra_container_html_options + { + class: Feature.enabled?(:sidebar_refactor, context.current_user, default_enabled: :yaml) ? 'shortcuts-monitor' : 'shortcuts-operations' + } + end + + override :title + def title + Feature.enabled?(:sidebar_refactor, context.current_user, default_enabled: :yaml) ? _('Monitor') : _('Operations') + end + + override :sprite_icon + def sprite_icon + Feature.enabled?(:sidebar_refactor, context.current_user, default_enabled: :yaml) ? 'monitor' : 'cloud-gear' + end + + override :active_routes + def active_routes + { controller: [:user, :gcp] } + end + + private + + def metrics_dashboard_menu_item + unless can?(context.current_user, :metrics_dashboard, context.project) + return ::Sidebars::NilMenuItem.new(item_id: :metrics) + end + + ::Sidebars::MenuItem.new( + title: _('Metrics'), + link: project_metrics_dashboard_path(context.project), + active_routes: { path: 'metrics_dashboard#show' }, + container_html_options: { class: 'shortcuts-metrics' }, + item_id: :metrics + ) + end + + def logs_menu_item + if !can?(context.current_user, :read_environment, context.project) || + !can?(context.current_user, :read_pod_logs, context.project) + return ::Sidebars::NilMenuItem.new(item_id: :logs) + end + + ::Sidebars::MenuItem.new( + title: _('Logs'), + link: project_logs_path(context.project), + active_routes: { path: 'logs#index' }, + item_id: :logs + ) + end + + def tracing_menu_item + if !can?(context.current_user, :read_environment, context.project) || + !can?(context.current_user, :admin_project, context.project) + return ::Sidebars::NilMenuItem.new(item_id: :tracing) + end + + ::Sidebars::MenuItem.new( + title: _('Tracing'), + link: project_tracing_path(context.project), + active_routes: { path: 'tracings#show' }, + item_id: :tracing + ) + end + + def error_tracking_menu_item + unless can?(context.current_user, :read_sentry_issue, context.project) + return ::Sidebars::NilMenuItem.new(item_id: :error_tracking) + end + + ::Sidebars::MenuItem.new( + title: _('Error Tracking'), + link: project_error_tracking_index_path(context.project), + active_routes: { controller: :error_tracking }, + item_id: :error_tracking + ) + end + + def alert_management_menu_item + unless can?(context.current_user, :read_alert_management_alert, context.project) + return ::Sidebars::NilMenuItem.new(item_id: :alert_management) + end + + ::Sidebars::MenuItem.new( + title: _('Alerts'), + link: project_alert_management_index_path(context.project), + active_routes: { controller: :alert_management }, + item_id: :alert_management + ) + end + + def incidents_menu_item + unless can?(context.current_user, :read_issue, context.project) + return ::Sidebars::NilMenuItem.new(item_id: :incidents) + end + + ::Sidebars::MenuItem.new( + title: _('Incidents'), + link: project_incidents_path(context.project), + active_routes: { controller: [:incidents, :incident_management] }, + item_id: :incidents + ) + end + + def serverless_menu_item + if Feature.enabled?(:sidebar_refactor, context.current_user) || + !can?(context.current_user, :read_cluster, context.project) + return ::Sidebars::NilMenuItem.new(item_id: :serverless) + end + + ::Sidebars::MenuItem.new( + title: _('Serverless'), + link: project_serverless_functions_path(context.project), + active_routes: { controller: :functions }, + item_id: :serverless + ) + end + + def terraform_menu_item + if Feature.enabled?(:sidebar_refactor, context.current_user) || + !can?(context.current_user, :read_terraform_state, context.project) + return ::Sidebars::NilMenuItem.new(item_id: :terraform) + end + + ::Sidebars::MenuItem.new( + title: _('Terraform'), + link: project_terraform_index_path(context.project), + active_routes: { controller: :terraform }, + item_id: :terraform + ) + end + + def kubernetes_menu_item + if Feature.enabled?(:sidebar_refactor, context.current_user) || + !can?(context.current_user, :read_cluster, context.project) + return ::Sidebars::NilMenuItem.new(item_id: :kubernetes) + end + + ::Sidebars::MenuItem.new( + title: _('Kubernetes'), + link: project_clusters_path(context.project), + active_routes: { controller: [:cluster_agents, :clusters] }, + container_html_options: { class: 'shortcuts-kubernetes' }, + hint_html_options: kubernetes_hint_html_options, + item_id: :kubernetes + ) + end + + def kubernetes_hint_html_options + return {} unless context.show_cluster_hint + + { disabled: true, + data: { trigger: 'manual', + container: 'body', + placement: 'right', + highlight: UserCalloutsHelper::GKE_CLUSTER_INTEGRATION, + highlight_priority: UserCallout.feature_names[:GKE_CLUSTER_INTEGRATION], + dismiss_endpoint: user_callouts_path, + auto_devops_help_path: help_page_path('topics/autodevops/index.md') } } + end + + def environments_menu_item + if Feature.enabled?(:sidebar_refactor, context.current_user, default_enabled: :yaml) || + !can?(context.current_user, :read_environment, context.project) + return ::Sidebars::NilMenuItem.new(item_id: :environments) + end + + ::Sidebars::MenuItem.new( + title: _('Environments'), + link: project_environments_path(context.project), + active_routes: { controller: :environments }, + container_html_options: { class: 'shortcuts-environments' }, + item_id: :environments + ) + end + + def feature_flags_menu_item + if Feature.enabled?(:sidebar_refactor, context.current_user, default_enabled: :yaml) || + !can?(context.current_user, :read_feature_flag, context.project) + return ::Sidebars::NilMenuItem.new(item_id: :feature_flags) + end + + ::Sidebars::MenuItem.new( + title: _('Feature Flags'), + link: project_feature_flags_path(context.project), + active_routes: { controller: :feature_flags }, + container_html_options: { class: 'shortcuts-feature-flags' }, + item_id: :feature_flags + ) + end + + def product_analytics_menu_item + if Feature.disabled?(:product_analytics, context.project) || + !can?(context.current_user, :read_product_analytics, context.project) + return ::Sidebars::NilMenuItem.new(item_id: :product_analytics) + end + + ::Sidebars::MenuItem.new( + title: _('Product Analytics'), + link: project_product_analytics_path(context.project), + active_routes: { controller: :product_analytics }, + item_id: :product_analytics + ) + end + end + end + end +end + +Sidebars::Projects::Menus::MonitorMenu.prepend_mod_with('Sidebars::Projects::Menus::MonitorMenu') diff --git a/lib/sidebars/projects/menus/packages_registries_menu.rb b/lib/sidebars/projects/menus/packages_registries_menu.rb new file mode 100644 index 00000000000..7087916bb04 --- /dev/null +++ b/lib/sidebars/projects/menus/packages_registries_menu.rb @@ -0,0 +1,75 @@ +# frozen_string_literal: true + +module Sidebars + module Projects + module Menus + class PackagesRegistriesMenu < ::Sidebars::Menu + override :configure_menu_items + def configure_menu_items + add_item(packages_registry_menu_item) + add_item(container_registry_menu_item) + add_item(infrastructure_registry_menu_item) + + true + end + + override :link + def link + renderable_items.first.link + end + + override :title + def title + _('Packages & Registries') + end + + override :sprite_icon + def sprite_icon + 'package' + end + + private + + def packages_registry_menu_item + if !::Gitlab.config.packages.enabled || !can?(context.current_user, :read_package, context.project) + return ::Sidebars::NilMenuItem.new(item_id: :packages_registry) + end + + ::Sidebars::MenuItem.new( + title: _('Package Registry'), + link: project_packages_path(context.project), + active_routes: { controller: :packages }, + item_id: :packages_registry, + container_html_options: { class: 'shortcuts-container-registry' } + ) + end + + def container_registry_menu_item + if !::Gitlab.config.registry.enabled || !can?(context.current_user, :read_container_image, context.project) + return ::Sidebars::NilMenuItem.new(item_id: :container_registry) + end + + ::Sidebars::MenuItem.new( + title: _('Container Registry'), + link: project_container_registry_index_path(context.project), + active_routes: { controller: :repositories }, + item_id: :container_registry + ) + end + + def infrastructure_registry_menu_item + if Feature.disabled?(:infrastructure_registry_page, context.current_user) + return ::Sidebars::NilMenuItem.new(item_id: :infrastructure_registry) + end + + ::Sidebars::MenuItem.new( + title: _('Infrastructure Registry'), + link: project_infrastructure_registry_index_path(context.project), + active_routes: { controller: :infrastructure_registry }, + item_id: :infrastructure_registry + ) + end + end + end + end +end diff --git a/lib/sidebars/projects/menus/project_information_menu.rb b/lib/sidebars/projects/menus/project_information_menu.rb new file mode 100644 index 00000000000..cbb34714087 --- /dev/null +++ b/lib/sidebars/projects/menus/project_information_menu.rb @@ -0,0 +1,136 @@ +# frozen_string_literal: true + +module Sidebars + module Projects + module Menus + class ProjectInformationMenu < ::Sidebars::Menu + override :configure_menu_items + def configure_menu_items + add_item(details_menu_item) + add_item(activity_menu_item) + add_item(releases_menu_item) + add_item(labels_menu_item) + add_item(members_menu_item) + + true + end + + override :link + def link + project_path(context.project) + end + + override :extra_container_html_options + def extra_container_html_options + { + class: 'shortcuts-project rspec-project-link' + } + end + + override :nav_link_html_options + def nav_link_html_options + { class: 'home' } + end + + override :title + def title + if Feature.enabled?(:sidebar_refactor, context.current_user) + _('Project information') + else + _('Project overview') + end + end + + override :sprite_icon + def sprite_icon + if Feature.enabled?(:sidebar_refactor, context.current_user) + 'project' + else + 'home' + end + end + + override :active_routes + def active_routes + return {} if Feature.disabled?(:sidebar_refactor, context.current_user) + + { path: 'projects#show' } + end + + private + + def details_menu_item + return if Feature.enabled?(:sidebar_refactor, context.current_user) + + ::Sidebars::MenuItem.new( + title: _('Details'), + link: project_path(context.project), + active_routes: { path: 'projects#show' }, + item_id: :project_overview, + container_html_options: { + aria: { label: _('Project details') }, + class: 'shortcuts-project' + } + ) + end + + def activity_menu_item + ::Sidebars::MenuItem.new( + title: _('Activity'), + link: activity_project_path(context.project), + active_routes: { path: 'projects#activity' }, + item_id: :activity, + container_html_options: { class: 'shortcuts-project-activity' } + ) + end + + def releases_menu_item + return ::Sidebars::NilMenuItem.new(item_id: :releases) unless show_releases? + + ::Sidebars::MenuItem.new( + title: _('Releases'), + link: project_releases_path(context.project), + item_id: :releases, + active_routes: { controller: :releases }, + container_html_options: { class: 'shortcuts-project-releases' } + ) + end + + def show_releases? + Feature.disabled?(:sidebar_refactor, context.current_user, default_enabled: :yaml) && + can?(context.current_user, :read_release, context.project) && + !context.project.empty_repo? + end + + def labels_menu_item + if Feature.disabled?(:sidebar_refactor, context.current_user) + return ::Sidebars::NilMenuItem.new(item_id: :labels) + end + + ::Sidebars::MenuItem.new( + title: _('Labels'), + link: project_labels_path(context.project), + active_routes: { controller: :labels }, + item_id: :labels + ) + end + + def members_menu_item + if Feature.disabled?(:sidebar_refactor, context.current_user, default_enabled: :yaml) + return ::Sidebars::NilMenuItem.new(item_id: :members) + end + + ::Sidebars::MenuItem.new( + title: _('Members'), + link: project_project_members_path(context.project), + active_routes: { controller: :project_members }, + item_id: :members, + container_html_options: { + id: 'js-onboarding-members-link' + } + ) + end + end + end + end +end diff --git a/lib/sidebars/projects/menus/repository_menu.rb b/lib/sidebars/projects/menus/repository_menu.rb new file mode 100644 index 00000000000..a784aecc3dc --- /dev/null +++ b/lib/sidebars/projects/menus/repository_menu.rb @@ -0,0 +1,123 @@ +# frozen_string_literal: true + +module Sidebars + module Projects + module Menus + class RepositoryMenu < ::Sidebars::Menu + override :configure_menu_items + def configure_menu_items + return false unless can?(context.current_user, :download_code, context.project) + return false if context.project.empty_repo? + + add_item(files_menu_item) + add_item(commits_menu_item) + add_item(branches_menu_item) + add_item(tags_menu_item) + add_item(contributors_menu_item) + add_item(graphs_menu_item) + add_item(compare_menu_item) + + true + end + + override :link + def link + project_tree_path(context.project) + end + + override :extra_container_html_options + def extra_container_html_options + { + class: 'shortcuts-tree' + } + end + + override :title + def title + _('Repository') + end + + override :title_html_options + def title_html_options + { + id: 'js-onboarding-repo-link' + } + end + + override :sprite_icon + def sprite_icon + 'doc-text' + end + + private + + def files_menu_item + ::Sidebars::MenuItem.new( + title: _('Files'), + link: project_tree_path(context.project, context.current_ref), + active_routes: { controller: %w[tree blob blame edit_tree new_tree find_file] }, + item_id: :files + ) + end + + def commits_menu_item + ::Sidebars::MenuItem.new( + title: _('Commits'), + link: project_commits_path(context.project, context.current_ref), + active_routes: { controller: %w(commit commits) }, + item_id: :commits, + container_html_options: { id: 'js-onboarding-commits-link' } + ) + end + + def branches_menu_item + ::Sidebars::MenuItem.new( + title: _('Branches'), + link: project_branches_path(context.project), + active_routes: { controller: :branches }, + item_id: :branches, + container_html_options: { id: 'js-onboarding-branches-link' } + ) + end + + def tags_menu_item + ::Sidebars::MenuItem.new( + title: _('Tags'), + link: project_tags_path(context.project), + item_id: :tags, + active_routes: { controller: :tags } + ) + end + + def contributors_menu_item + ::Sidebars::MenuItem.new( + title: _('Contributors'), + link: project_graph_path(context.project, context.current_ref), + active_routes: { path: 'graphs#show' }, + item_id: :contributors + ) + end + + def graphs_menu_item + ::Sidebars::MenuItem.new( + title: _('Graph'), + link: project_network_path(context.project, context.current_ref), + active_routes: { controller: :network }, + item_id: :graphs + ) + end + + def compare_menu_item + ::Sidebars::MenuItem.new( + title: _('Compare'), + link: project_compare_index_path(context.project, from: context.project.repository.root_ref, to: context.current_ref), + active_routes: { controller: :compare }, + item_id: :compare + ) + end + end + end + end +end + +Sidebars::Projects::Menus::RepositoryMenu.prepend_mod_with('Sidebars::Projects::Menus::RepositoryMenu') diff --git a/lib/sidebars/projects/menus/scope_menu.rb b/lib/sidebars/projects/menus/scope_menu.rb new file mode 100644 index 00000000000..1d1cf11b271 --- /dev/null +++ b/lib/sidebars/projects/menus/scope_menu.rb @@ -0,0 +1,19 @@ +# frozen_string_literal: true + +module Sidebars + module Projects + module Menus + class ScopeMenu < ::Sidebars::Menu + override :link + def link + project_path(context.project) + end + + override :title + def title + context.project.name + end + end + end + end +end diff --git a/lib/sidebars/projects/menus/security_compliance_menu.rb b/lib/sidebars/projects/menus/security_compliance_menu.rb new file mode 100644 index 00000000000..6c9fb8312bd --- /dev/null +++ b/lib/sidebars/projects/menus/security_compliance_menu.rb @@ -0,0 +1,64 @@ +# frozen_string_literal: true + +module Sidebars + module Projects + module Menus + class SecurityComplianceMenu < ::Sidebars::Menu + include Gitlab::Utils::StrongMemoize + + override :configure_menu_items + def configure_menu_items + return false unless can?(context.current_user, :access_security_and_compliance, context.project) + + add_item(configuration_menu_item) + + true + end + + override :link + def link + project_security_configuration_path(context.project) + end + + override :title + def title + _('Security & Compliance') + end + + override :sprite_icon + def sprite_icon + 'shield' + end + + private + + def configuration_menu_item + strong_memoize(:configuration_menu_item) do + unless render_configuration_menu_item? + next ::Sidebars::NilMenuItem.new(item_id: :configuration) + end + + ::Sidebars::MenuItem.new( + title: _('Configuration'), + link: project_security_configuration_path(context.project), + active_routes: { path: configuration_menu_item_paths }, + item_id: :configuration + ) + end + end + + def render_configuration_menu_item? + can?(context.current_user, :read_security_configuration, context.project) + end + + def configuration_menu_item_paths + %w[ + projects/security/configuration#show + ] + end + end + end + end +end + +Sidebars::Projects::Menus::SecurityComplianceMenu.prepend_mod_with('Sidebars::Projects::Menus::SecurityComplianceMenu') diff --git a/lib/sidebars/projects/menus/settings_menu.rb b/lib/sidebars/projects/menus/settings_menu.rb new file mode 100644 index 00000000000..4ea6f5e298a --- /dev/null +++ b/lib/sidebars/projects/menus/settings_menu.rb @@ -0,0 +1,154 @@ +# frozen_string_literal: true + +module Sidebars + module Projects + module Menus + class SettingsMenu < ::Sidebars::Menu + override :configure_menu_items + def configure_menu_items + return false unless can?(context.current_user, :admin_project, context.project) + + add_item(general_menu_item) + add_item(integrations_menu_item) + add_item(webhooks_menu_item) + add_item(access_tokens_menu_item) + add_item(repository_menu_item) + add_item(ci_cd_menu_item) + add_item(monitor_menu_item) + add_item(pages_menu_item) + add_item(packages_and_registries_menu_item) + + true + end + + override :link + def link + edit_project_path(context.project) + end + + override :title + def title + _('Settings') + end + + override :title_html_options + def title_html_options + { + id: 'js-onboarding-settings-link' + } + end + + override :sprite_icon + def sprite_icon + 'settings' + end + + private + + def general_menu_item + ::Sidebars::MenuItem.new( + title: _('General'), + link: edit_project_path(context.project), + active_routes: { path: 'projects#edit' }, + item_id: :general + ) + end + + def integrations_menu_item + ::Sidebars::MenuItem.new( + title: _('Integrations'), + link: project_settings_integrations_path(context.project), + active_routes: { path: %w[integrations#show services#edit] }, + item_id: :integrations + ) + end + + def webhooks_menu_item + ::Sidebars::MenuItem.new( + title: _('Webhooks'), + link: project_hooks_path(context.project), + active_routes: { path: %w[hooks#index hooks#edit hook_logs#show] }, + item_id: :webhooks + ) + end + + def access_tokens_menu_item + unless can?(context.current_user, :read_resource_access_tokens, context.project) + return ::Sidebars::NilMenuItem.new(item_id: :access_tokens) + end + + ::Sidebars::MenuItem.new( + title: _('Access Tokens'), + link: project_settings_access_tokens_path(context.project), + active_routes: { path: 'access_tokens#index' }, + item_id: :access_tokens + ) + end + + def repository_menu_item + ::Sidebars::MenuItem.new( + title: _('Repository'), + link: project_settings_repository_path(context.project), + active_routes: { path: 'repository#show' }, + item_id: :repository + ) + end + + def ci_cd_menu_item + if context.project.archived? || !context.project.feature_available?(:builds, context.current_user) + return ::Sidebars::NilMenuItem.new(item_id: :ci_cd) + end + + ::Sidebars::MenuItem.new( + title: _('CI/CD'), + link: project_settings_ci_cd_path(context.project), + active_routes: { path: 'ci_cd#show' }, + item_id: :ci_cd + ) + end + + def monitor_menu_item + if context.project.archived? || !can?(context.current_user, :admin_operations, context.project) + return ::Sidebars::NilMenuItem.new(item_id: :monitor) + end + + title = Feature.enabled?(:sidebar_refactor, context.current_user, default_enabled: :yaml) ? _('Monitor') : _('Operations') + ::Sidebars::MenuItem.new( + title: title, + link: project_settings_operations_path(context.project), + active_routes: { path: 'operations#show' }, + item_id: :monitor + ) + end + + def pages_menu_item + unless context.project.pages_available? + return ::Sidebars::NilMenuItem.new(item_id: :pages) + end + + ::Sidebars::MenuItem.new( + title: _('Pages'), + link: project_pages_path(context.project), + active_routes: { path: 'pages#show' }, + item_id: :pages + ) + end + + def packages_and_registries_menu_item + if !Gitlab.config.registry.enabled || + Feature.disabled?(:sidebar_refactor, context.current_user) || + !can?(context.current_user, :destroy_container_image, context.project) + return ::Sidebars::NilMenuItem.new(item_id: :packages_and_registries) + end + + ::Sidebars::MenuItem.new( + title: _('Packages & Registries'), + link: project_settings_packages_and_registries_path(context.project), + active_routes: { path: 'packages_and_registries#index' }, + item_id: :packages_and_registries + ) + end + end + end + end +end diff --git a/lib/sidebars/projects/menus/snippets_menu.rb b/lib/sidebars/projects/menus/snippets_menu.rb new file mode 100644 index 00000000000..060341b3c51 --- /dev/null +++ b/lib/sidebars/projects/menus/snippets_menu.rb @@ -0,0 +1,41 @@ +# frozen_string_literal: true + +module Sidebars + module Projects + module Menus + class SnippetsMenu < ::Sidebars::Menu + override :link + def link + project_snippets_path(context.project) + end + + override :extra_container_html_options + def extra_container_html_options + { + class: 'shortcuts-snippets' + } + end + + override :title + def title + _('Snippets') + end + + override :sprite_icon + def sprite_icon + 'snippet' + end + + override :render? + def render? + can?(context.current_user, :read_snippet, context.project) + end + + override :active_routes + def active_routes + { controller: :snippets } + end + end + end + end +end diff --git a/lib/sidebars/projects/menus/wiki_menu.rb b/lib/sidebars/projects/menus/wiki_menu.rb new file mode 100644 index 00000000000..3980b193fd1 --- /dev/null +++ b/lib/sidebars/projects/menus/wiki_menu.rb @@ -0,0 +1,41 @@ +# frozen_string_literal: true + +module Sidebars + module Projects + module Menus + class WikiMenu < ::Sidebars::Menu + override :link + def link + wiki_path(context.project.wiki) + end + + override :extra_container_html_options + def extra_container_html_options + { + class: 'shortcuts-wiki' + } + end + + override :title + def title + _('Wiki') + end + + override :sprite_icon + def sprite_icon + 'book' + end + + override :render? + def render? + can?(context.current_user, :read_wiki, context.project) + end + + override :active_routes + def active_routes + { controller: :wikis } + end + end + end + end +end diff --git a/lib/sidebars/projects/panel.rb b/lib/sidebars/projects/panel.rb new file mode 100644 index 00000000000..ac7c043a96e --- /dev/null +++ b/lib/sidebars/projects/panel.rb @@ -0,0 +1,51 @@ +# frozen_string_literal: true + +module Sidebars + module Projects + class Panel < ::Sidebars::Panel + override :configure_menus + def configure_menus + set_scope_menu(Sidebars::Projects::Menus::ScopeMenu.new(context)) + set_hidden_menu(Sidebars::Projects::Menus::HiddenMenu.new(context)) + add_menus + end + + override :aria_label + def aria_label + _('Project navigation') + end + + private + + def add_menus + add_menu(Sidebars::Projects::Menus::ProjectInformationMenu.new(context)) + add_menu(Sidebars::Projects::Menus::LearnGitlabMenu.new(context)) + add_menu(Sidebars::Projects::Menus::RepositoryMenu.new(context)) + add_menu(Sidebars::Projects::Menus::IssuesMenu.new(context)) + add_menu(Sidebars::Projects::Menus::ExternalIssueTrackerMenu.new(context)) + add_menu(Sidebars::Projects::Menus::LabelsMenu.new(context)) + add_menu(Sidebars::Projects::Menus::MergeRequestsMenu.new(context)) + add_menu(Sidebars::Projects::Menus::CiCdMenu.new(context)) + add_menu(Sidebars::Projects::Menus::SecurityComplianceMenu.new(context)) + add_menu(Sidebars::Projects::Menus::DeploymentsMenu.new(context)) + add_menu(Sidebars::Projects::Menus::MonitorMenu.new(context)) + add_menu(Sidebars::Projects::Menus::InfrastructureMenu.new(context)) + add_menu(Sidebars::Projects::Menus::PackagesRegistriesMenu.new(context)) + add_menu(Sidebars::Projects::Menus::AnalyticsMenu.new(context)) + add_menu(confluence_or_wiki_menu) + add_menu(Sidebars::Projects::Menus::ExternalWikiMenu.new(context)) + add_menu(Sidebars::Projects::Menus::SnippetsMenu.new(context)) + add_menu(Sidebars::Projects::Menus::MembersMenu.new(context)) + add_menu(Sidebars::Projects::Menus::SettingsMenu.new(context)) + end + + def confluence_or_wiki_menu + confluence_menu = ::Sidebars::Projects::Menus::ConfluenceMenu.new(context) + + confluence_menu.render? ? confluence_menu : Sidebars::Projects::Menus::WikiMenu.new(context) + end + end + end +end + +Sidebars::Projects::Panel.prepend_mod_with('Sidebars::Projects::Panel') |