Welcome to mirror list, hosted at ThFree Co, Russian Federation.

gitlab.com/gitlab-org/gitlab-foss.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGitLab Bot <gitlab-bot@gitlab.com>2021-05-19 18:44:42 +0300
committerGitLab Bot <gitlab-bot@gitlab.com>2021-05-19 18:44:42 +0300
commit4555e1b21c365ed8303ffb7a3325d773c9b8bf31 (patch)
tree5423a1c7516cffe36384133ade12572cf709398d /lib/sidebars
parente570267f2f6b326480d284e0164a6464ba4081bc (diff)
Add latest changes from gitlab-org/gitlab@13-12-stable-eev13.12.0-rc42
Diffstat (limited to 'lib/sidebars')
-rw-r--r--lib/sidebars/concerns/container_with_html_options.rb60
-rw-r--r--lib/sidebars/concerns/has_active_routes.rb18
-rw-r--r--lib/sidebars/concerns/has_hint.rb18
-rw-r--r--lib/sidebars/concerns/has_icon.rb29
-rw-r--r--lib/sidebars/concerns/has_pill.rb23
-rw-r--r--lib/sidebars/concerns/positionable_list.rb56
-rw-r--r--lib/sidebars/concerns/renderable.rb14
-rw-r--r--lib/sidebars/context.rb21
-rw-r--r--lib/sidebars/menu.rb93
-rw-r--r--lib/sidebars/menu_item.rb26
-rw-r--r--lib/sidebars/nil_menu_item.rb16
-rw-r--r--lib/sidebars/panel.rb86
-rw-r--r--lib/sidebars/projects/context.rb11
-rw-r--r--lib/sidebars/projects/menus/analytics_menu.rb95
-rw-r--r--lib/sidebars/projects/menus/ci_cd_menu.rb118
-rw-r--r--lib/sidebars/projects/menus/confluence_menu.rb43
-rw-r--r--lib/sidebars/projects/menus/deployments_menu.rb87
-rw-r--r--lib/sidebars/projects/menus/external_issue_tracker_menu.rb59
-rw-r--r--lib/sidebars/projects/menus/external_wiki_menu.rb52
-rw-r--r--lib/sidebars/projects/menus/hidden_menu.rb105
-rw-r--r--lib/sidebars/projects/menus/infrastructure_menu.rb99
-rw-r--r--lib/sidebars/projects/menus/issues_menu.rb135
-rw-r--r--lib/sidebars/projects/menus/labels_menu.rb50
-rw-r--r--lib/sidebars/projects/menus/learn_gitlab_menu.rb62
-rw-r--r--lib/sidebars/projects/menus/members_menu.rb43
-rw-r--r--lib/sidebars/projects/menus/merge_requests_menu.rb70
-rw-r--r--lib/sidebars/projects/menus/monitor_menu.rb246
-rw-r--r--lib/sidebars/projects/menus/packages_registries_menu.rb75
-rw-r--r--lib/sidebars/projects/menus/project_information_menu.rb136
-rw-r--r--lib/sidebars/projects/menus/repository_menu.rb123
-rw-r--r--lib/sidebars/projects/menus/scope_menu.rb19
-rw-r--r--lib/sidebars/projects/menus/security_compliance_menu.rb64
-rw-r--r--lib/sidebars/projects/menus/settings_menu.rb154
-rw-r--r--lib/sidebars/projects/menus/snippets_menu.rb41
-rw-r--r--lib/sidebars/projects/menus/wiki_menu.rb41
-rw-r--r--lib/sidebars/projects/panel.rb51
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')