Commit dd20956b authored by Justin Ho Tuan Duong's avatar Justin Ho Tuan Duong Committed by Francisco Javier López

Move Jira issues menu to be submenu of Issues

parent 16a9ff55
<svg id="Logos" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="80" height="80" viewBox="0 0 80 80"><defs><style>.cls-1{fill:#7a869a;}.cls-2{fill:url(#linear-gradient);}.cls-3{fill:url(#linear-gradient-2);}</style><linearGradient id="linear-gradient" x1="38.11" y1="18.54" x2="23.17" y2="33.48" gradientUnits="userSpaceOnUse"><stop offset="0.18" stop-color="#344563"/><stop offset="1" stop-color="#7a869a"/></linearGradient><linearGradient id="linear-gradient-2" x1="42.07" y1="61.47" x2="56.98" y2="46.55" xlink:href="#linear-gradient"/></defs><title>jira software-icon-gradient-neutral</title><path class="cls-1" d="M74.18,38,43,6.9l-3-3h0L16.58,27.32h0L5.86,38a2.86,2.86,0,0,0,0,4.05L27.28,63.51,40,76.25,63.47,52.81l.36-.36L74.18,42.09A2.86,2.86,0,0,0,74.18,38ZM40,50.77l-10.7-10.7L40,29.37l10.7,10.7Z"/><path class="cls-2" d="M40,29.37A18,18,0,0,1,40,4L16.54,27.37,29.28,40.11,40,29.37Z"/><path class="cls-3" d="M50.75,40,40,50.77a18,18,0,0,1,0,25.48h0L63.5,52.78Z"/></svg>
...@@ -101,10 +101,10 @@ Consider this example: ...@@ -101,10 +101,10 @@ Consider this example:
You can browse, search, and view issues from a selected Jira project directly in GitLab, You can browse, search, and view issues from a selected Jira project directly in GitLab,
if your GitLab administrator [has configured it](configure.md). if your GitLab administrator [has configured it](configure.md).
To do this, in GitLab, go to your project and select **Jira > Issues list**. The issue list To do this, in GitLab, go to your project and select **Issues > Jira issues**. The issue list
sorts by **Created date** by default, with the newest issues listed at the top: sorts by **Created date** by default, with the newest issues listed at the top:
![Jira issues integration enabled](img/open_jira_issues_list_v13.2.png) ![Jira issues integration enabled](img/open_jira_issues_list_v14_6.png)
- To display the most recently updated issues first, select **Last updated**. - To display the most recently updated issues first, select **Last updated**.
- You can [search and filter](#search-and-filter-the-issues-list) the issues list. - You can [search and filter](#search-and-filter-the-issues-list) the issues list.
......
- page_title _('Jira Issues') - page_title s_('JiraService|Jira issues')
- add_page_specific_style 'page_bundles/issues_list' - add_page_specific_style 'page_bundles/issues_list'
.js-jira-issues-list{ data: { issues_fetch_path: project_integrations_jira_issues_path(@project, format: :json), .js-jira-issues-list{ data: { issues_fetch_path: project_integrations_jira_issues_path(@project, format: :json),
......
- add_to_breadcrumbs _('Jira Issues'), project_integrations_jira_issues_path(@project) - add_to_breadcrumbs s_('JiraService|Jira issues'), project_integrations_jira_issues_path(@project)
- breadcrumb_title jira_issue_breadcrumb_link(@issue_json[:references][:relative]) - breadcrumb_title jira_issue_breadcrumb_link(@issue_json[:references][:relative])
- page_title html_escape(@issue_json[:title]) - page_title html_escape(@issue_json[:title])
......
...@@ -13,10 +13,16 @@ module EE ...@@ -13,10 +13,16 @@ module EE
add_item(iterations_menu_item) add_item(iterations_menu_item)
add_item(requirements_menu_item) add_item(requirements_menu_item)
add_item(jira_issue_list_menu_item)
add_item(jira_external_link_menu_item)
true true
end end
def show_jira_menu_items?
external_issue_tracker.is_a?(Integrations::Jira) && context.jira_issues_integration
end
private private
def iterations_menu_item def iterations_menu_item
...@@ -49,6 +55,37 @@ module EE ...@@ -49,6 +55,37 @@ module EE
item_id: :requirements item_id: :requirements
) )
end end
def external_issue_tracker
@external_issue_tracker ||= context.project.external_issue_tracker
end
def jira_issue_list_menu_item
return ::Sidebars::NilMenuItem.new(item_id: :jira_issue_list) unless show_jira_menu_items?
::Sidebars::MenuItem.new(
title: s_('JiraService|Jira issues'),
link: project_integrations_jira_issues_path(context.project),
active_routes: { controller: 'projects/integrations/jira/issues' },
item_id: :jira_issue_list
)
end
def jira_external_link_menu_item
return ::Sidebars::NilMenuItem.new(item_id: :jira_external_link) unless show_jira_menu_items?
::Sidebars::MenuItem.new(
title: s_('JiraService|Open Jira'),
link: external_issue_tracker.issue_tracker_path,
active_routes: {},
item_id: :jira_external_link,
sprite_icon: 'external-link',
container_html_options: {
target: '_blank',
rel: 'noopener noreferrer'
}
)
end
end end
end end
end end
......
...@@ -10,16 +10,10 @@ module EE ...@@ -10,16 +10,10 @@ module EE
def configure_menus def configure_menus
super super
if jira_menu.render? if ::Sidebars::Projects::Menus::IssuesMenu.new(context).show_jira_menu_items?
replace_menu(::Sidebars::Projects::Menus::ExternalIssueTrackerMenu, jira_menu) remove_menu(::Sidebars::Projects::Menus::ExternalIssueTrackerMenu)
end end
end end
private
def jira_menu
@jira_menu ||= ::Sidebars::Projects::Menus::JiraMenu.new(context)
end
end end
end end
end end
......
# frozen_string_literal: true
module Sidebars
module Projects
module Menus
class JiraMenu < ::Sidebars::Menu
override :configure_menu_items
def configure_menu_items
return false unless external_issue_tracker
add_item(issue_list_menu_item)
add_item(open_jira_menu_item)
true
end
override :link
def link
project_integrations_jira_issues_path(context.project)
end
override :title
def title
s_('JiraService|Jira Issues')
end
override :title_html_options
def title_html_options
{
id: 'js-onboarding-settings-link'
}
end
override :image_path
def image_path
'logos/jira-gray.svg'
end
# Hardcode sizes so image doesn't flash before CSS loads https://gitlab.com/gitlab-org/gitlab/-/issues/321022
override :image_html_options
def image_html_options
{
size: 16
}
end
override :render?
def render?
external_issue_tracker.is_a?(Integrations::Jira) && context.jira_issues_integration
end
private
def external_issue_tracker
@external_issue_tracker ||= context.project.external_issue_tracker
end
def issue_list_menu_item
::Sidebars::MenuItem.new(
title: s_('JiraService|Issue List'),
link: project_integrations_jira_issues_path(context.project),
active_routes: { controller: 'projects/integrations/jira/issues' },
item_id: :issue_list
)
end
def open_jira_menu_item
::Sidebars::MenuItem.new(
title: s_('JiraService|Open Jira'),
link: external_issue_tracker.issue_tracker_path,
active_routes: {},
item_id: :open_jira,
sprite_icon: 'external-link',
container_html_options: {
target: '_blank',
rel: 'noopener noreferrer'
}
)
end
end
end
end
end
...@@ -25,10 +25,9 @@ RSpec.describe 'User activates Jira', :js do ...@@ -25,10 +25,9 @@ RSpec.describe 'User activates Jira', :js do
click_test_then_save_integration(expect_test_to_fail: false) click_test_then_save_integration(expect_test_to_fail: false)
end end
it 'adds Jira links to sidebar menu' do it 'adds Jira links to "Issues" sidebar menu' do
page.within('.nav-sidebar') do page.within('.nav-sidebar') do
expect(page).to have_link('Jira Issues', href: project_integrations_jira_issues_path(project)) expect(page).to have_link('Jira issues', href: project_integrations_jira_issues_path(project), visible: false)
expect(page).to have_link('Issue List', href: project_integrations_jira_issues_path(project), visible: false)
expect(page).to have_link('Open Jira', href: url, visible: false) expect(page).to have_link('Open Jira', href: url, visible: false)
expect(page).not_to have_link('Jira', href: url) expect(page).not_to have_link('Jira', href: url)
end end
...@@ -44,10 +43,9 @@ RSpec.describe 'User activates Jira', :js do ...@@ -44,10 +43,9 @@ RSpec.describe 'User activates Jira', :js do
click_save_integration click_save_integration
end end
it 'does not show Jira links in sidebar menu' do it 'does not show Jira links in "Issues" sidebar menu' do
page.within('.nav-sidebar') do page.within('.nav-sidebar') do
expect(page).not_to have_link('Jira Issues', href: project_integrations_jira_issues_path(project)) expect(page).not_to have_link('Jira issues', href: project_integrations_jira_issues_path(project), visible: false)
expect(page).not_to have_link('Issue List', href: project_integrations_jira_issues_path(project), visible: false)
expect(page).not_to have_link('Open Jira', href: url, visible: false) expect(page).not_to have_link('Open Jira', href: url, visible: false)
expect(page).to have_link('Jira', href: url) expect(page).to have_link('Jira', href: url)
end end
......
...@@ -70,4 +70,43 @@ RSpec.describe Sidebars::Projects::Menus::IssuesMenu do ...@@ -70,4 +70,43 @@ RSpec.describe Sidebars::Projects::Menus::IssuesMenu do
end end
end end
end end
describe 'Jira issues' do
let_it_be_with_refind(:project) { create(:project, has_external_issue_tracker: true) }
let(:context) { Sidebars::Projects::Context.new(current_user: user, container: project, jira_issues_integration: jira_issues_integration) }
let(:jira_issues_integration) { false }
subject { described_class.new(context) }
context 'when issue tracker is not Jira' do
it 'does not include Jira issues menu items' do
create(:custom_issue_tracker_integration, active: true, project: project, project_url: 'http://test.com')
expect(subject.show_jira_menu_items?).to eq(false)
expect(subject.renderable_items.any? { |e| e.item_id == :jira_issue_list}).to eq(false)
end
end
context 'when issue tracker is Jira' do
let_it_be(:jira) { create(:jira_integration, project: project, project_key: 'GL') }
context 'when issues integration is disabled' do
it 'does not include Jira issues menu items' do
expect(subject.show_jira_menu_items?).to eq(false)
expect(subject.renderable_items.any? { |e| e.item_id == :jira_issue_list}).to eq(false)
end
end
context 'when issues integration is enabled' do
let(:jira_issues_integration) { true }
it 'includes Jira issues menu items' do
expect(subject.show_jira_menu_items?).to eq(true)
expect(subject.renderable_items.any? { |e| e.item_id == :jira_issue_list}).to eq(true)
expect(subject.renderable_items.any? { |e| e.item_id == :jira_external_link}).to eq(true)
end
end
end
end
end end
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe Sidebars::Projects::Panel do
let(:project) { build(:project) }
let(:context) { Sidebars::Projects::Context.new(current_user: nil, container: project) }
describe 'ExternalIssueTrackerMenu' do
before do
allow_next_instance_of(Sidebars::Projects::Menus::IssuesMenu) do |issues_menu|
allow(issues_menu).to receive(:show_jira_menu_items?).and_return(show_jira_menu_items)
end
end
subject { described_class.new(context) }
def contains_external_issue_tracker_menu
subject.instance_variable_get(:@menus).any? { |i| i.is_a?(Sidebars::Projects::Menus::ExternalIssueTrackerMenu) }
end
context 'when show_jira_menu_items? is false' do
let(:show_jira_menu_items) { false }
it 'contains ExternalIssueTracker menu' do
expect(contains_external_issue_tracker_menu).to be(true)
end
end
context 'when show_jira_menu_items? is true' do
let(:show_jira_menu_items) { true }
it 'does not contain ExternalIssueTracker menu' do
expect(contains_external_issue_tracker_menu).to be(false)
end
end
end
end
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe Sidebars::Projects::Menus::JiraMenu do
let_it_be_with_refind(:project) { create(:project, has_external_issue_tracker: true) }
let(:user) { project.owner }
let(:context) { Sidebars::Projects::Context.new(current_user: user, container: project, jira_issues_integration: jira_issues_integration) }
let(:jira_issues_integration) { false }
subject { described_class.new(context) }
describe 'render?' do
context 'when issue tracker is not Jira' do
it 'returns false' do
create(:custom_issue_tracker_integration, active: true, project: project, project_url: 'http://test.com')
expect(subject.render?).to eq false
end
end
context 'when issue tracker is Jira' do
let!(:jira) { create(:jira_integration, project: project, project_key: 'GL') }
context 'when issues integration is disabled' do
it 'returns false' do
expect(subject.render?).to eq false
end
end
context 'when issues integration is enabled' do
let(:jira_issues_integration) { true }
it 'returns true' do
expect(subject.render?).to eq true
end
it 'contains issue list and open jira menu items' do
expect(subject.renderable_items).not_to be_empty
expect(subject.renderable_items[0].item_id).to eq :issue_list
expect(subject.renderable_items[1].item_id).to eq :open_jira
end
end
end
end
end
...@@ -60,42 +60,38 @@ RSpec.describe 'layouts/nav/sidebar/_project' do ...@@ -60,42 +60,38 @@ RSpec.describe 'layouts/nav/sidebar/_project' do
end end
end end
end end
end
describe 'Jira' do describe 'Jira' do
let_it_be_with_refind(:project) { create(:project, has_external_issue_tracker: true) } let_it_be_with_refind(:project) { create(:project, has_external_issue_tracker: true) }
let(:user) { project.owner }
before do
allow(view).to receive(:current_user).and_return(user)
end
context 'when Jira service integration is not set' do context 'when Jira service integration is not set' do
it 'does not have a link to the Jira issues menu' do it 'does not have a link to the Jira issues menu' do
render render
expect(rendered).not_to have_link('Jira Issues', href: project_integrations_jira_issues_path(project)) expect(rendered).not_to have_link('Jira issues', href: project_integrations_jira_issues_path(project))
end end
end end
context 'when Jira service integration is set' do context 'when Jira service integration is set' do
let!(:jira) { create(:jira_integration, project: project, issues_enabled: true, project_key: 'GL') } let_it_be(:jira) { create(:jira_integration, project: project, issues_enabled: true, project_key: 'GL') }
before do before do
stub_licensed_features(jira_issues_integration: true) stub_licensed_features(jira_issues_integration: true)
end end
it 'has a link to the Jira issue tracker' do it 'has a link to Jira issues list' do
render
expect(rendered).to have_link('Jira Issues', href: project_integrations_jira_issues_path(project))
end
describe 'Issue List' do
it 'has a link to Jira issue list' do
render render
expect(rendered).to have_link('Issue List', href: project_integrations_jira_issues_path(project)) expect(rendered).to have_link('Jira issues', href: project_integrations_jira_issues_path(project))
end
end end
describe 'Open Jira' do it 'has an external link to open Jira' do
it 'has a link to open Jira' do
render render
expect(rendered).to have_link('Open Jira', href: project.external_issue_tracker.issue_tracker_path) expect(rendered).to have_link('Open Jira', href: project.external_issue_tracker.issue_tracker_path)
......
...@@ -44,6 +44,14 @@ module Sidebars ...@@ -44,6 +44,14 @@ module Sidebars
list[index] = new_element list[index] = new_element
end end
def remove_element(list, element_to_remove)
index = index_of(list, element_to_remove)
return unless index
list.slice!(index)
end
private private
# Classes including this method will have to define # Classes including this method will have to define
......
...@@ -37,6 +37,10 @@ module Sidebars ...@@ -37,6 +37,10 @@ module Sidebars
replace_element(@menus, menu_to_replace, new_menu) replace_element(@menus, menu_to_replace, new_menu)
end end
def remove_menu(menu_to_remove)
remove_element(@menus, menu_to_remove)
end
def set_scope_menu(scope_menu) def set_scope_menu(scope_menu)
@scope_menu = scope_menu @scope_menu = scope_menu
end end
......
...@@ -19645,9 +19645,6 @@ msgstr "" ...@@ -19645,9 +19645,6 @@ msgstr ""
msgid "Japanese language support using" msgid "Japanese language support using"
msgstr "" msgstr ""
msgid "Jira Issues"
msgstr ""
msgid "Jira display name" msgid "Jira display name"
msgstr "" msgstr ""
...@@ -19777,18 +19774,12 @@ msgstr "" ...@@ -19777,18 +19774,12 @@ msgstr ""
msgid "JiraService|If different from Web URL." msgid "JiraService|If different from Web URL."
msgstr "" msgstr ""
msgid "JiraService|Issue List"
msgstr ""
msgid "JiraService|Issues created from vulnerabilities in this project will be Jira issues, even if GitLab issues are enabled." msgid "JiraService|Issues created from vulnerabilities in this project will be Jira issues, even if GitLab issues are enabled."
msgstr "" msgstr ""
msgid "JiraService|Jira API URL" msgid "JiraService|Jira API URL"
msgstr "" msgstr ""
msgid "JiraService|Jira Issues"
msgstr ""
msgid "JiraService|Jira comments are created when an issue is referenced in a commit." msgid "JiraService|Jira comments are created when an issue is referenced in a commit."
msgstr "" msgstr ""
...@@ -19798,6 +19789,9 @@ msgstr "" ...@@ -19798,6 +19789,9 @@ msgstr ""
msgid "JiraService|Jira issue type" msgid "JiraService|Jira issue type"
msgstr "" msgstr ""
msgid "JiraService|Jira issues"
msgstr ""
msgid "JiraService|Jira project key" msgid "JiraService|Jira project key"
msgstr "" msgstr ""
......
...@@ -26,8 +26,7 @@ RSpec.describe 'User activates Jira', :js do ...@@ -26,8 +26,7 @@ RSpec.describe 'User activates Jira', :js do
unless Gitlab.ee? unless Gitlab.ee?
it 'adds Jira link to sidebar menu' do it 'adds Jira link to sidebar menu' do
page.within('.nav-sidebar') do page.within('.nav-sidebar') do
expect(page).not_to have_link('Jira Issues') expect(page).not_to have_link('Jira issues', visible: false)
expect(page).not_to have_link('Issue List', visible: false)
expect(page).not_to have_link('Open Jira', href: url, visible: false) expect(page).not_to have_link('Open Jira', href: url, visible: false)
expect(page).to have_link('Jira', href: url) expect(page).to have_link('Jira', href: url)
end end
......
...@@ -153,6 +153,25 @@ RSpec.describe Sidebars::Menu do ...@@ -153,6 +153,25 @@ RSpec.describe Sidebars::Menu do
end end
end end
describe '#remove_element' do
let(:item1) { Sidebars::MenuItem.new(title: 'foo1', link: 'foo1', active_routes: {}, item_id: :foo1) }
let(:item2) { Sidebars::MenuItem.new(title: 'foo2', link: 'foo2', active_routes: {}, item_id: :foo2) }
let(:item3) { Sidebars::MenuItem.new(title: 'foo3', link: 'foo3', active_routes: {}, item_id: :foo3) }
let(:list) { [item1, item2, item3] }
it 'removes specific element' do
menu.remove_element(list, :foo2)
expect(list).to eq [item1, item3]
end
it 'does not remove nil elements' do
menu.remove_element(list, nil)
expect(list).to eq [item1, item2, item3]
end
end
describe '#container_html_options' do describe '#container_html_options' do
before do before do
allow(menu).to receive(:title).and_return('Foo Menu') allow(menu).to receive(:title).and_return('Foo Menu')
......
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