Commit e77edc54 authored by Arturo Herrero's avatar Arturo Herrero

Merge branch '326428-fj-add-issues-menu' into 'master'

Add Issues menu

See merge request gitlab-org/gitlab!59363
parents f296d426 07488ef7
- if project_nav_tab? :issues
= nav_link(controller: @project.issues_enabled? ? ['projects/issues', :labels, :milestones, :boards, :iterations] : 'projects/issues') do
= link_to project_issues_path(@project), class: 'shortcuts-issues qa-issues-item' do
.nav-icon-container
= sprite_icon('issues')
%span.nav-item-name#js-onboarding-issues-link
= _('Issues')
- if @project.issues_enabled?
%span.badge.badge-pill.count.issue_counter
= number_with_delimiter(@project.open_issues_count(current_user))
%ul.sidebar-sub-level-items
= nav_link(controller: 'projects/issues', action: :index, html_options: { class: "fly-out-top-item" } ) do
= link_to project_issues_path(@project) do
%strong.fly-out-top-item-name
= _('Issues')
- if @project.issues_enabled?
%span.badge.badge-pill.count.issue_counter.fly-out-badge
= number_with_delimiter(@project.open_issues_count(current_user))
%li.divider.fly-out-top-item
= nav_link(controller: :issues, action: :index) do
= link_to project_issues_path(@project), title: _('Issues') do
%span
= _('List')
= nav_link(controller: :boards) do
= link_to project_boards_path(@project), title: boards_link_text, data: { qa_selector: "issue_boards_link" } do
%span
= boards_link_text
= nav_link(controller: :labels) do
= link_to project_labels_path(@project), title: _('Labels'), class: 'qa-labels-link' do
%span
= _('Labels')
= render 'projects/sidebar/issues_service_desk'
= nav_link(controller: :milestones) do
= link_to project_milestones_path(@project), title: _('Milestones'), class: 'qa-milestones-link' do
%span
= _('Milestones')
= render_if_exists 'layouts/nav/sidebar/project_iterations_link'
- if project_nav_tab?(:external_issue_tracker) - if project_nav_tab?(:external_issue_tracker)
- issue_tracker = @project.external_issue_tracker - issue_tracker = @project.external_issue_tracker
- if issue_tracker.is_a?(JiraService) && project_jira_issues_integration? - if issue_tracker.is_a?(JiraService) && project_jira_issues_integration?
......
= nav_link(controller: :issues, action: :service_desk ) do
= link_to service_desk_project_issues_path(@project), title: 'Service Desk' do
= _('Service Desk')
- return unless @project.feature_available?(:iterations)
- return unless can?(current_user, :read_iteration, @project)
= nav_link(controller: :iterations) do
= link_to project_iterations_path(@project), title: _('Iterations') do
%span
= _('Iterations')
# frozen_string_literal: true
module EE
module Sidebars
module Projects
module Menus
module IssuesMenu
extend ::Gitlab::Utils::Override
override :configure_menu_items
def configure_menu_items
return false unless super
add_item(iterations_menu_item)
true
end
private
def iterations_menu_item
return unless context.project.licensed_feature_available?(:iterations)
return unless can?(context.current_user, :read_iteration, context.project)
::Sidebars::MenuItem.new(
title: _('Iterations'),
link: project_iterations_path(context.project),
active_routes: { controller: :iterations },
item_id: :iterations
)
end
end
end
end
end
end
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe Sidebars::Projects::Menus::IssuesMenu do
let(:project) { build(:project) }
let(:user) { project.owner }
let(:context) { Sidebars::Projects::Context.new(current_user: user, container: project) }
describe 'Iterations' do
subject { described_class.new(context).items.index { |e| e.item_id == :iterations} }
context 'when licensed feature iterations is not enabled' do
it 'does not include iterations menu item' do
stub_licensed_features(iterations: false)
is_expected.to be_nil
end
end
context 'when licensed feature iterations is enabled' do
before do
stub_licensed_features(iterations: true)
end
context 'when user can read iterations' do
it 'includes iterations menu item' do
is_expected.to be_present
end
end
context 'when user cannot read iterations' do
let(:user) { nil }
it 'does not include iterations menu item' do
is_expected.to be_nil
end
end
end
end
end
...@@ -26,14 +26,16 @@ RSpec.describe 'layouts/nav/sidebar/_project' do ...@@ -26,14 +26,16 @@ RSpec.describe 'layouts/nav/sidebar/_project' do
end end
end end
describe 'issue boards' do describe 'Issues' do
it 'has boards tab' do describe 'Iterations' do
allow(view).to receive(:can?).and_return(true) it 'has a link to the issue iterations path' do
allow(License).to receive(:feature_available?).and_call_original allow(view).to receive(:current_user).and_return(user)
stub_licensed_features(iterations: true)
render render
expect(rendered).to have_css('a[title="Boards"]') expect(rendered).to have_link('Iterations', href: project_iterations_path(project))
end
end end
end end
...@@ -308,66 +310,4 @@ RSpec.describe 'layouts/nav/sidebar/_project' do ...@@ -308,66 +310,4 @@ RSpec.describe 'layouts/nav/sidebar/_project' do
expect(rendered).to have_link('Operations', href: project_settings_operations_path(project)) expect(rendered).to have_link('Operations', href: project_settings_operations_path(project))
end end
end end
describe 'iterations link' do
context 'with authorized user' do
let_it_be(:current_user) { create(:user) }
before do
project.add_guest(current_user)
allow(view).to receive(:current_user).and_return(current_user)
end
context 'with iterations licensed feature available' do
before do
stub_licensed_features(iterations: true)
end
it 'is visible' do
render
expect(rendered).to have_text 'Iterations'
end
end
context 'with iterations licensed feature disabled' do
before do
stub_licensed_features(iterations: false)
end
it 'is not visible' do
render
expect(rendered).not_to have_text 'Iterations'
end
end
end
context 'with unauthorized user' do
context 'with iterations licensed feature available' do
before do
stub_licensed_features(iterations: true)
end
it 'is not visible' do
render
expect(rendered).not_to have_text 'Iterations'
end
end
context 'with iterations licensed feature disabled' do
before do
stub_licensed_features(iterations: false)
end
it 'is not visible' do
render
expect(rendered).not_to have_text 'Iterations'
end
end
end
end
end end
...@@ -9,7 +9,7 @@ module Sidebars ...@@ -9,7 +9,7 @@ module Sidebars
@link = link @link = link
@active_routes = active_routes @active_routes = active_routes
@item_id = item_id @item_id = item_id
@container_html_options = container_html_options @container_html_options = { aria: { label: title } }.merge(container_html_options)
@sprite_icon = sprite_icon @sprite_icon = sprite_icon
@sprite_icon_html_options = sprite_icon_html_options @sprite_icon_html_options = sprite_icon_html_options
@hint_html_options = hint_html_options @hint_html_options = hint_html_options
......
# 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
::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_if_ee('EE::Sidebars::Projects::Menus::IssuesMenu')
...@@ -10,6 +10,7 @@ module Sidebars ...@@ -10,6 +10,7 @@ module Sidebars
add_menu(Sidebars::Projects::Menus::ProjectOverviewMenu.new(context)) add_menu(Sidebars::Projects::Menus::ProjectOverviewMenu.new(context))
add_menu(Sidebars::Projects::Menus::LearnGitlabMenu.new(context)) add_menu(Sidebars::Projects::Menus::LearnGitlabMenu.new(context))
add_menu(Sidebars::Projects::Menus::RepositoryMenu.new(context)) add_menu(Sidebars::Projects::Menus::RepositoryMenu.new(context))
add_menu(Sidebars::Projects::Menus::IssuesMenu.new(context))
end end
override :render_raw_menus_partial override :render_raw_menus_partial
......
...@@ -10,9 +10,7 @@ module QA ...@@ -10,9 +10,7 @@ module QA
def self.prepended(base) def self.prepended(base)
base.class_eval do base.class_eval do
view 'app/views/shared/nav/_sidebar_menu_item.html.haml' do prepend QA::Page::Project::SubMenus::Common
element :sidebar_menu_item_link
end
end end
end end
......
...@@ -338,8 +338,10 @@ module QA ...@@ -338,8 +338,10 @@ module QA
end end
end end
def scroll_to_element(name, *args) def scroll_to_element(name, *kwargs)
scroll_to(element_selector_css(name), *args) text = kwargs.delete(:text)
scroll_to(element_selector_css(name, kwargs), text: text)
end end
def element_selector_css(name, *attributes) def element_selector_css(name, *attributes)
......
...@@ -23,10 +23,6 @@ module QA ...@@ -23,10 +23,6 @@ module QA
element :wiki_link element :wiki_link
end end
view 'app/views/shared/nav/_sidebar_menu_item.html.haml' do
element :sidebar_menu_item_link
end
def click_merge_requests def click_merge_requests
within_sidebar do within_sidebar do
click_element(:merge_requests_link) click_element(:merge_requests_link)
......
...@@ -8,6 +8,20 @@ module QA ...@@ -8,6 +8,20 @@ module QA
extend QA::Page::PageConcern extend QA::Page::PageConcern
include QA::Page::SubMenus::Common include QA::Page::SubMenus::Common
def self.included(base)
super
base.class_eval do
view 'app/views/shared/nav/_sidebar_menu_item.html.haml' do
element :sidebar_menu_item_link
end
view 'app/views/shared/nav/_sidebar_menu.html.haml' do
element :sidebar_menu_link
end
end
end
private private
def sidebar_element def sidebar_element
......
...@@ -12,32 +12,25 @@ module QA ...@@ -12,32 +12,25 @@ module QA
base.class_eval do base.class_eval do
include QA::Page::Project::SubMenus::Common include QA::Page::Project::SubMenus::Common
view 'app/views/layouts/nav/sidebar/_project_menus.html.haml' do
element :issue_boards_link
element :issues_item
element :labels_link
element :milestones_link
end
end end
end end
def click_issues def click_issues
within_sidebar do within_sidebar do
click_link('Issues') click_element(:sidebar_menu_link, menu_item: 'Issues')
end end
end end
def click_milestones def click_milestones
within_sidebar do within_sidebar do
click_element :milestones_link click_element(:sidebar_menu_item_link, menu_item: 'Milestones')
end end
end end
def go_to_boards def go_to_boards
hover_issues do hover_issues do
within_submenu do within_submenu do
click_element(:issue_boards_link) click_element(:sidebar_menu_item_link, menu_item: 'Boards')
end end
end end
end end
...@@ -45,7 +38,7 @@ module QA ...@@ -45,7 +38,7 @@ module QA
def go_to_labels def go_to_labels
hover_issues do hover_issues do
within_submenu do within_submenu do
click_element(:labels_link) click_element(:sidebar_menu_item_link, menu_item: 'Labels')
end end
end end
end end
...@@ -53,7 +46,7 @@ module QA ...@@ -53,7 +46,7 @@ module QA
def go_to_milestones def go_to_milestones
hover_issues do hover_issues do
within_submenu do within_submenu do
click_element(:milestones_link) click_element(:sidebar_menu_item_link, menu_item: 'Milestones')
end end
end end
end end
...@@ -62,8 +55,8 @@ module QA ...@@ -62,8 +55,8 @@ module QA
def hover_issues def hover_issues
within_sidebar do within_sidebar do
scroll_to_element(:issues_item) scroll_to_element(:sidebar_menu_link, menu_item: 'Issues')
find_element(:issues_item).hover find_element(:sidebar_menu_link, menu_item: 'Issues').hover
yield yield
end end
......
...@@ -12,10 +12,6 @@ module QA ...@@ -12,10 +12,6 @@ module QA
base.class_eval do base.class_eval do
include QA::Page::Project::SubMenus::Common include QA::Page::Project::SubMenus::Common
view 'app/views/shared/nav/_sidebar_menu.html.haml' do
element :sidebar_menu_link
end
end end
end end
......
...@@ -12,14 +12,6 @@ module QA ...@@ -12,14 +12,6 @@ module QA
base.class_eval do base.class_eval do
include QA::Page::Project::SubMenus::Common include QA::Page::Project::SubMenus::Common
view 'app/views/shared/nav/_sidebar_menu_item.html.haml' do
element :sidebar_menu_item_link
end
view 'app/views/shared/nav/_sidebar_menu.html.haml' do
element :sidebar_menu_link
end
end end
end end
......
...@@ -21,7 +21,7 @@ RSpec.describe 'Service Desk Issue Tracker', :js do ...@@ -21,7 +21,7 @@ RSpec.describe 'Service Desk Issue Tracker', :js do
before do before do
visit project_path(project) visit project_path(project)
find('.sidebar-top-level-items .shortcuts-issues').click find('.sidebar-top-level-items .shortcuts-issues').click
find('.sidebar-sub-level-items a[title="Service Desk"]').click find('.sidebar-sub-level-items a', text: 'Service Desk').click
end end
it 'can navigate to the service desk from link in the sidebar' do it 'can navigate to the service desk from link in the sidebar' do
......
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe Sidebars::MenuItem do
let(:title) { 'foo' }
let(:html_options) { {} }
let(:menu_item) { described_class.new(title: title, active_routes: {}, link: '', container_html_options: html_options) }
it 'includes by default aria-label attribute set to the title' do
expect(menu_item.container_html_options).to eq({ aria: { label: title } })
end
context 'when aria-label is overridde during initialization' do
let(:html_options) { { aria: { label: 'bar' } } }
it 'sets the aria-label to the new attribute' do
expect(menu_item.container_html_options).to eq html_options
end
end
end
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe Sidebars::Projects::Menus::IssuesMenu do
let(:project) { build(:project) }
let(:user) { project.owner }
let(:context) { Sidebars::Projects::Context.new(current_user: user, container: project) }
subject { described_class.new(context) }
describe '#render?' do
context 'when user can read issues' do
it 'returns true' do
expect(subject.render?).to eq true
end
end
context 'when user cannot read issues' do
let(:user) { nil }
it 'returns false' do
expect(subject.render?).to eq false
end
end
end
describe '#has_pill?' do
context 'when issues feature is enabled' do
it 'returns true' do
expect(subject.has_pill?).to eq true
end
end
context 'when issue feature is disabled' do
it 'returns false' do
allow(project).to receive(:issues_enabled?).and_return(false)
expect(subject.has_pill?).to eq false
end
end
end
describe '#pill_count' do
it 'returns zero when there are no open issues' do
expect(subject.pill_count).to eq 0
end
it 'memoizes the query' do
subject.pill_count
control = ActiveRecord::QueryRecorder.new do
subject.pill_count
end
expect(control.count).to eq 0
end
context 'when there are open issues' do
it 'returns the number of open issues' do
create_list(:issue, 2, :opened, project: project)
create(:issue, :closed, project: project)
expect(subject.pill_count).to eq 2
end
end
end
end
...@@ -127,11 +127,57 @@ RSpec.describe 'layouts/nav/sidebar/_project' do ...@@ -127,11 +127,57 @@ RSpec.describe 'layouts/nav/sidebar/_project' do
end end
end end
describe 'issue boards' do describe 'Issues' do
it 'has board tab' do it 'has a link to the issue list path' do
render render
expect(rendered).to have_css('a[title="Boards"]') expect(rendered).to have_link('Issues', href: project_issues_path(project))
end
it 'shows pill with the number of open issues' do
render
expect(rendered).to have_css('span.badge.badge-pill.issue_counter')
end
describe 'Issue List' do
it 'has a link to the issue list path' do
render
expect(rendered).to have_link('List', href: project_issues_path(project))
end
end
describe 'Issue Boards' do
it 'has a link to the issue boards path' do
render
expect(rendered).to have_link('Boards', href: project_boards_path(project))
end
end
describe 'Labels' do
it 'has a link to the labels path' do
render
expect(rendered).to have_link('Labels', href: project_labels_path(project))
end
end
describe 'Service Desk' do
it 'has a link to the service desk path' do
render
expect(rendered).to have_link('Service Desk', href: service_desk_project_issues_path(project))
end
end
describe 'Milestones' do
it 'has a link to the milestones path' do
render
expect(rendered).to have_link('Milestones', href: project_milestones_path(project))
end
end end
end end
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment