Commit 5918bf63 authored by Eulyeon Ko's avatar Eulyeon Ko Committed by David O'Regan

Add link to epic in the new sidebar

Add feature spec for epic dropdown

Optionally check for group
- In a lot of tests, project doesn't have an assigned group
and trying to access @project.group.id can throw an error
- Add safe navigation operator
parent 6a03e7cd
...@@ -4,6 +4,7 @@ fragment EpicNode on Epic { ...@@ -4,6 +4,7 @@ fragment EpicNode on Epic {
title title
state state
reference reference
webPath
webUrl webUrl
createdAt createdAt
closedAt closedAt
......
...@@ -18,7 +18,7 @@ module BoardsHelper ...@@ -18,7 +18,7 @@ module BoardsHelper
time_tracking_limit_to_hours: Gitlab::CurrentSettings.time_tracking_limit_to_hours.to_s, time_tracking_limit_to_hours: Gitlab::CurrentSettings.time_tracking_limit_to_hours.to_s,
recent_boards_endpoint: recent_boards_path, recent_boards_endpoint: recent_boards_path,
parent: current_board_parent.model_name.param_key, parent: current_board_parent.model_name.param_key,
group_id: @group&.id, group_id: group_id,
labels_filter_base_path: build_issue_link_base, labels_filter_base_path: build_issue_link_base,
labels_fetch_path: labels_fetch_path, labels_fetch_path: labels_fetch_path,
labels_manage_path: labels_manage_path, labels_manage_path: labels_manage_path,
...@@ -26,6 +26,12 @@ module BoardsHelper ...@@ -26,6 +26,12 @@ module BoardsHelper
} }
end end
def group_id
return @group.id if board.group_board?
@project&.group&.id
end
def full_path def full_path
if board.group_board? if board.group_board?
@group.full_path @group.full_path
......
<script> <script>
import { GlLink } from '@gitlab/ui';
import { mapState, mapGetters, mapActions } from 'vuex'; import { mapState, mapGetters, mapActions } from 'vuex';
import EpicsSelect from 'ee/vue_shared/components/sidebar/epics_select/base.vue'; import EpicsSelect from 'ee/vue_shared/components/sidebar/epics_select/base.vue';
import BoardEditableItem from '~/boards/components/sidebar/board_editable_item.vue'; import BoardEditableItem from '~/boards/components/sidebar/board_editable_item.vue';
...@@ -11,6 +12,7 @@ export default { ...@@ -11,6 +12,7 @@ export default {
components: { components: {
BoardEditableItem, BoardEditableItem,
EpicsSelect, EpicsSelect,
GlLink,
}, },
i18n: { i18n: {
epic: __('Epic'), epic: __('Epic'),
...@@ -99,13 +101,14 @@ export default { ...@@ -99,13 +101,14 @@ export default {
ref="sidebarItem" ref="sidebarItem"
:title="$options.i18n.epic" :title="$options.i18n.epic"
:loading="epicFetchInProgress" :loading="epicFetchInProgress"
data-testid="sidebar-epic"
@open="handleOpen" @open="handleOpen"
@close="handleClose" @close="handleClose"
> >
<template v-if="epicData.title" #collapsed> <template v-if="epicData.title" #collapsed>
<a class="gl-text-gray-900! gl-font-weight-bold" href="#"> <gl-link class="gl-text-gray-900! gl-font-weight-bold" :href="epicData.webPath">
{{ epicData.title }} {{ epicData.title }}
</a> </gl-link>
</template> </template>
<epics-select <epics-select
v-if="!epicFetchInProgress" v-if="!epicFetchInProgress"
......
...@@ -4,6 +4,7 @@ fragment BoardEpicNode on BoardEpic { ...@@ -4,6 +4,7 @@ fragment BoardEpicNode on BoardEpic {
title title
state state
reference reference
webPath
webUrl webUrl
createdAt createdAt
closedAt closedAt
......
---
title: Fix epics dropdown to work in project epic swimlanes
merge_request: 55954
author:
type: fixed
...@@ -11,6 +11,7 @@ RSpec.describe 'epics swimlanes sidebar', :js do ...@@ -11,6 +11,7 @@ RSpec.describe 'epics swimlanes sidebar', :js do
let_it_be(:label) { create(:label, project: project, name: 'Label 1') } let_it_be(:label) { create(:label, project: project, name: 'Label 1') }
let_it_be(:list) { create(:list, board: board, label: label, position: 0) } let_it_be(:list) { create(:list, board: board, label: label, position: 0) }
let_it_be(:epic1) { create(:epic, group: group) } let_it_be(:epic1) { create(:epic, group: group) }
let_it_be(:epic2) { create(:epic, group: group) }
let_it_be(:issue1, reload: true) { create(:issue, project: project) } let_it_be(:issue1, reload: true) { create(:issue, project: project) }
let_it_be(:epic_issue1, reload: true) { create(:epic_issue, epic: epic1, issue: issue1) } let_it_be(:epic_issue1, reload: true) { create(:epic_issue, epic: epic1, issue: issue1) }
...@@ -40,6 +41,40 @@ RSpec.describe 'epics swimlanes sidebar', :js do ...@@ -40,6 +41,40 @@ RSpec.describe 'epics swimlanes sidebar', :js do
end end
end end
context 'epic dropdown' do
before do
group.add_owner(user)
end
context 'when the issue is associated with an epic' do
it 'displays name of epic and links to it' do
load_epic_swimlanes
click_first_issue_card
page.within('[data-testid="sidebar-epic"]') do
expect(page).to have_link(epic1.title, href: epic_path(epic1))
end
end
it 'updates the epic associated with the issue' do
load_epic_swimlanes
click_first_issue_card
page.within('[data-testid="sidebar-epic"]') do
find("[data-testid='edit-button']").click
wait_for_all_requests
find('.gl-new-dropdown-item', text: epic2.title).click
wait_for_all_requests
expect(page).to have_link(epic2.title, href: epic_path(epic2))
end
end
end
end
context 'notifications subscription' do context 'notifications subscription' do
it 'displays notifications toggle' do it 'displays notifications toggle' do
load_epic_swimlanes load_epic_swimlanes
......
import { GlLink } from '@gitlab/ui';
import { shallowMount } from '@vue/test-utils'; import { shallowMount } from '@vue/test-utils';
import Vuex from 'vuex'; import Vuex from 'vuex';
import BoardSidebarEpicSelect from 'ee/boards/components/sidebar/board_sidebar_epic_select.vue'; import BoardSidebarEpicSelect from 'ee/boards/components/sidebar/board_sidebar_epic_select.vue';
...@@ -69,6 +70,7 @@ describe('ee/boards/components/sidebar/board_sidebar_epic_select.vue', () => { ...@@ -69,6 +70,7 @@ describe('ee/boards/components/sidebar/board_sidebar_epic_select.vue', () => {
const findEpicSelect = () => wrapper.find({ ref: 'epicSelect' }); const findEpicSelect = () => wrapper.find({ ref: 'epicSelect' });
const findItemWrapper = () => wrapper.find({ ref: 'sidebarItem' }); const findItemWrapper = () => wrapper.find({ ref: 'sidebarItem' });
const findCollapsed = () => wrapper.find('[data-testid="collapsed-content"]'); const findCollapsed = () => wrapper.find('[data-testid="collapsed-content"]');
const findEpicLink = () => wrapper.find(GlLink);
const findBoardEditableItem = () => wrapper.find(BoardEditableItem); const findBoardEditableItem = () => wrapper.find(BoardEditableItem);
describe('when not editing', () => { describe('when not editing', () => {
...@@ -173,7 +175,9 @@ describe('ee/boards/components/sidebar/board_sidebar_epic_select.vue', () => { ...@@ -173,7 +175,9 @@ describe('ee/boards/components/sidebar/board_sidebar_epic_select.vue', () => {
await wrapper.vm.$nextTick(); await wrapper.vm.$nextTick();
expect(findCollapsed().text()).toBe(mockAssignedEpic.title); expect(findEpicLink().isVisible()).toBe(true);
expect(findEpicLink().text()).toBe(mockAssignedEpic.title);
expect(findEpicLink().attributes('href')).toBe(mockAssignedEpic.webPath);
}); });
}); });
...@@ -212,8 +216,9 @@ describe('ee/boards/components/sidebar/board_sidebar_epic_select.vue', () => { ...@@ -212,8 +216,9 @@ describe('ee/boards/components/sidebar/board_sidebar_epic_select.vue', () => {
}); });
it('collapses sidebar and renders epic title', () => { it('collapses sidebar and renders epic title', () => {
expect(findCollapsed().isVisible()).toBe(true); expect(findEpicLink().isVisible()).toBe(true);
expect(findCollapsed().text()).toBe(mockAssignedEpic.title); expect(findEpicLink().text()).toBe(mockAssignedEpic.title);
expect(findEpicLink().attributes('href')).toBe(mockAssignedEpic.webPath);
}); });
describe('when the selected epic did not change', () => { describe('when the selected epic did not change', () => {
......
...@@ -226,7 +226,14 @@ export const mockEpic = { ...@@ -226,7 +226,14 @@ export const mockEpic = {
labels: [], labels: [],
}; };
export const mockIssueWithEpic = { ...mockIssue3, epic: { id: mockEpic.id, iid: mockEpic.iid } }; export const mockIssueWithEpic = {
...mockIssue3,
epic: {
id: mockEpic.id,
iid: mockEpic.iid,
webPath: '/gitlab-org/-/epics/41',
},
};
export const mockAssignedEpic = { ...mockIssueWithEpic.epic, title: mockEpic.title }; export const mockAssignedEpic = { ...mockIssueWithEpic.epic, title: mockEpic.title };
export const mockEpics = [ export const mockEpics = [
......
...@@ -4,8 +4,8 @@ require 'spec_helper' ...@@ -4,8 +4,8 @@ require 'spec_helper'
RSpec.describe BoardsHelper do RSpec.describe BoardsHelper do
let_it_be(:user) { create(:user) } let_it_be(:user) { create(:user) }
let_it_be(:project) { create(:project) }
let_it_be(:base_group) { create(:group, path: 'base') } let_it_be(:base_group) { create(:group, path: 'base') }
let_it_be(:project) { create(:project, group: base_group) }
let_it_be(:project_board) { create(:board, project: project) } let_it_be(:project_board) { create(:board, project: project) }
let_it_be(:group_board) { create(:board, group: base_group) } let_it_be(:group_board) { create(:board, group: base_group) }
...@@ -82,6 +82,10 @@ RSpec.describe BoardsHelper do ...@@ -82,6 +82,10 @@ RSpec.describe BoardsHelper do
expect(helper.board_data[:labels_fetch_path]).to eq("/#{project.full_path}/-/labels.json?include_ancestor_groups=true") expect(helper.board_data[:labels_fetch_path]).to eq("/#{project.full_path}/-/labels.json?include_ancestor_groups=true")
expect(helper.board_data[:labels_manage_path]).to eq("/#{project.full_path}/-/labels") expect(helper.board_data[:labels_manage_path]).to eq("/#{project.full_path}/-/labels")
end end
it 'returns the group id of a project' do
expect(helper.board_data[:group_id]).to eq(project.group.id)
end
end end
context 'group board' do context 'group board' do
...@@ -102,6 +106,10 @@ RSpec.describe BoardsHelper do ...@@ -102,6 +106,10 @@ RSpec.describe BoardsHelper do
expect(helper.board_data[:labels_fetch_path]).to eq("/groups/#{base_group.full_path}/-/labels.json?include_ancestor_groups=true&only_group_labels=true") expect(helper.board_data[:labels_fetch_path]).to eq("/groups/#{base_group.full_path}/-/labels.json?include_ancestor_groups=true&only_group_labels=true")
expect(helper.board_data[:labels_manage_path]).to eq("/groups/#{base_group.full_path}/-/labels") expect(helper.board_data[:labels_manage_path]).to eq("/groups/#{base_group.full_path}/-/labels")
end end
it 'returns the group id' do
expect(helper.board_data[:group_id]).to eq(base_group.id)
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