Commit 06de789b authored by Florie Guibert's avatar Florie Guibert

Epic multiple boards switcher

Review feedback
parent 8ae3a496
......@@ -158,6 +158,18 @@ export default {
cancel() {
this.showPage('');
},
boardUpdate(data) {
if (!data?.[this.parentType]) {
return [];
}
return data[this.parentType].boards.edges.map(({ node }) => ({
id: getIdFromGraphQLId(node.id),
name: node.name,
}));
},
boardQuery() {
return this.groupId ? groupQuery : projectQuery;
},
loadBoards(toggleDropdown = true) {
if (toggleDropdown && this.boards.length > 0) {
return;
......@@ -167,21 +179,14 @@ export default {
variables() {
return { fullPath: this.fullPath };
},
query() {
return this.groupId ? groupQuery : projectQuery;
},
query: this.boardQuery,
loadingKey: 'loadingBoards',
update(data) {
if (!data?.[this.parentType]) {
return [];
}
return data[this.parentType].boards.edges.map(({ node }) => ({
id: getIdFromGraphQLId(node.id),
name: node.name,
}));
},
update: this.boardUpdate,
});
this.loadRecentBoards();
},
loadRecentBoards() {
this.loadingRecentBoards = true;
// Follow up to fetch recent boards using GraphQL
// https://gitlab.com/gitlab-org/gitlab/-/issues/300985
......
......@@ -59,9 +59,7 @@ module BoardsHelper
end
def board_base_url
if board.to_type == "EpicBoard"
group_epic_boards_url(@group)
elsif board.group_board?
if board.group_board?
group_boards_url(@group)
else
project_boards_path(@project)
......
......@@ -10,35 +10,37 @@ import epicBoardsQuery from '../graphql/epic_boards.query.graphql';
export default {
extends: BoardsSelectorFoss,
computed: {
...mapState(['isEpicBoard', 'fullPath']),
...mapState(['isEpicBoard']),
},
methods: {
epicBoardUpdate(data) {
if (!data?.group) {
return [];
}
return data.group.epicBoards.nodes.map((node) => ({
id: getIdFromGraphQLId(node.id),
name: node.name,
}));
},
epicBoardQuery() {
return epicBoardsQuery;
},
loadBoards(toggleDropdown = true) {
if (toggleDropdown && this.boards.length > 0) {
return;
}
if (this.isEpicBoard) {
this.$apollo.addSmartQuery('boards', {
variables() {
return { fullPath: this.fullPath };
},
query() {
return epicBoardsQuery;
},
loadingKey: 'loadingBoards',
update(data) {
if (!data?.group) {
return [];
}
return data.group.epicBoards.nodes.map((node) => ({
id: getIdFromGraphQLId(node.id),
name: node.name,
}));
},
});
} else {
BoardsSelectorFoss.methods.loadBoards.call(this);
this.$apollo.addSmartQuery('boards', {
variables() {
return { fullPath: this.fullPath };
},
query: this.isEpicBoard ? this.epicBoardQuery : this.boardQuery,
loadingKey: 'loadingBoards',
update: this.isEpicBoard ? this.epicBoardUpdate : this.boardUpdate,
});
if (!this.isEpicBoard) {
this.loadRecentBoards();
}
},
},
......
......@@ -97,7 +97,6 @@ export default () => {
? parseInt($boardApp.dataset.boardWeight, 10)
: null,
},
isEpicBoard: true,
});
},
mounted() {
......
......@@ -33,6 +33,13 @@ module EE
super.merge(data)
end
override :board_base_url
def board_base_url
return group_epic_boards_url(@group) if board.is_a?(::Boards::EpicBoard)
super
end
override :recent_boards_path
def recent_boards_path
return recent_group_boards_path(@group) if current_board_parent.is_a?(Group)
......
import { GlDropdown, GlLoadingIcon, GlDropdownSectionHeader } from '@gitlab/ui';
import { createLocalVue, mount } from '@vue/test-utils';
import MockAdapter from 'axios-mock-adapter';
import { nextTick } from 'vue';
import Vuex from 'vuex';
import BoardsSelector from 'ee/boards/components/boards_selector.vue';
import { TEST_HOST } from 'spec/test_constants';
import axios from '~/lib/utils/axios_utils';
const throttleDuration = 1;
const localVue = createLocalVue();
localVue.use(Vuex);
function boardGenerator(n) {
return new Array(n).fill().map((board, index) => {
const id = `${index}`;
const name = `board${id}`;
return {
id,
name,
};
});
}
describe('BoardsSelector', () => {
let wrapper;
let allBoardsResponse;
let recentBoardsResponse;
let mock;
const boards = boardGenerator(20);
const recentBoards = boardGenerator(5);
const createStore = () => {
return new Vuex.Store({
state: {
isEpicBoard: false,
},
});
};
const getDropdownItems = () => wrapper.findAll('.js-dropdown-item');
const getDropdownHeaders = () => wrapper.findAllComponents(GlDropdownSectionHeader);
const getLoadingIcon = () => wrapper.findComponent(GlLoadingIcon);
const findDropdown = () => wrapper.findComponent(GlDropdown);
beforeEach(() => {
mock = new MockAdapter(axios);
const $apollo = {
queries: {
boards: {
loading: false,
},
},
};
allBoardsResponse = Promise.resolve({
data: {
group: {
boards: {
edges: boards.map((board) => ({ node: board })),
},
},
},
});
recentBoardsResponse = Promise.resolve({
data: recentBoards,
});
const store = createStore();
wrapper = mount(BoardsSelector, {
localVue,
propsData: {
throttleDuration,
currentBoard: {
id: 1,
name: 'Development',
milestone_id: null,
weight: null,
assignee_id: null,
labels: [],
},
boardBaseUrl: `${TEST_HOST}/board/base/url`,
hasMissingBoards: false,
canAdminBoard: true,
multipleIssueBoardsAvailable: true,
labelsPath: `${TEST_HOST}/labels/path`,
labelsWebUrl: `${TEST_HOST}/labels`,
projectId: 42,
groupId: 19,
scopedIssueBoardFeatureEnabled: true,
weights: [],
},
mocks: { $apollo },
attachTo: document.body,
provide: {
fullPath: '',
recentBoardsEndpoint: `${TEST_HOST}/recent`,
},
store,
});
wrapper.vm.$apollo.addSmartQuery = jest.fn((_, options) => {
wrapper.setData({
[options.loadingKey]: true,
});
});
mock.onGet(`${TEST_HOST}/recent`).replyOnce(200, recentBoards);
// Emits gl-dropdown show event to simulate the dropdown is opened at initialization time
findDropdown().vm.$emit('show');
});
afterEach(() => {
wrapper.destroy();
wrapper = null;
mock.restore();
});
describe('loading', () => {
// we are testing loading state, so don't resolve responses until after the tests
afterEach(async () => {
await Promise.all([allBoardsResponse, recentBoardsResponse]);
return nextTick();
});
it('shows loading spinner', () => {
expect(getDropdownHeaders()).toHaveLength(0);
expect(getDropdownItems()).toHaveLength(0);
expect(getLoadingIcon().exists()).toBe(true);
});
});
describe('loaded', () => {
beforeEach(async () => {
await wrapper.setData({
loadingBoards: false,
});
await Promise.all([allBoardsResponse, recentBoardsResponse]);
return nextTick();
});
it('hides loading spinner', async () => {
await wrapper.vm.$nextTick();
expect(getLoadingIcon().exists()).toBe(false);
});
});
});
......@@ -33,6 +33,19 @@ RSpec.describe BoardsHelper do
end
end
describe '#board_base_url' do
context 'when epic board' do
let_it_be(:epic_board) { create(:epic_board, group: group) }
it 'generates the correct url' do
@board = epic_board
@group = group
expect(board_base_url).to eq "http://test.host/groups/#{group.full_path}/-/epic_boards"
end
end
end
describe '#board_data' do
let_it_be(:user) { create(:user) }
let_it_be(:board) { create(:board, project: project) }
......
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