Commit 9828565d authored by Simon Knox's avatar Simon Knox

Add assignee to new board list form

Lot of vuex duplication that can be trimmed down later
parent 7a19b738
<script> <script>
import { import {
GlAvatarLabeled,
GlFormGroup, GlFormGroup,
GlFormRadio, GlFormRadio,
GlFormRadioGroup, GlFormRadioGroup,
...@@ -19,18 +20,23 @@ export default { ...@@ -19,18 +20,23 @@ export default {
i18n: { i18n: {
listType: __('List type'), listType: __('List type'),
labelListDescription: __('A label list displays issues with the selected label.'), labelListDescription: __('A label list displays issues with the selected label.'),
assigneeListDescription: __('An assignee list displays issues assigned to the selected user'),
milestoneListDescription: __('A milestone list displays issues in the selected milestone.'), milestoneListDescription: __('A milestone list displays issues in the selected milestone.'),
selectLabel: __('Select label'), selectLabel: __('Select label'),
selectAssignee: __('Select assignee'),
selectMilestone: __('Select milestone'), selectMilestone: __('Select milestone'),
searchLabels: __('Search labels'), searchLabels: __('Search labels'),
searchAssignees: __('Search assignees'),
searchMilestones: __('Search milestones'), searchMilestones: __('Search milestones'),
}, },
columnTypes: [ columnTypes: [
{ value: ListType.label, text: __('Label') }, { value: ListType.label, text: __('Label') },
{ value: ListType.assignee, text: __('Assignee') },
{ value: ListType.milestone, text: __('Milestone') }, { value: ListType.milestone, text: __('Milestone') },
], ],
components: { components: {
BoardAddNewColumnForm, BoardAddNewColumnForm,
GlAvatarLabeled,
GlFormGroup, GlFormGroup,
GlFormRadio, GlFormRadio,
GlFormRadioGroup, GlFormRadioGroup,
...@@ -48,13 +54,23 @@ export default { ...@@ -48,13 +54,23 @@ export default {
}; };
}, },
computed: { computed: {
...mapState(['labels', 'labelsLoading', 'milestones', 'milestonesLoading']), ...mapState([
'labels',
'labelsLoading',
'assignees',
'assigneesLoading',
'milestones',
'milestonesLoading',
]),
...mapGetters(['getListByTypeId', 'shouldUseGraphQL', 'isEpicBoard']), ...mapGetters(['getListByTypeId', 'shouldUseGraphQL', 'isEpicBoard']),
items() { items() {
if (this.labelTypeSelected) { if (this.labelTypeSelected) {
return this.labels; return this.labels;
} }
if (this.assigneeTypeSelected) {
return this.assignees;
}
if (this.milestoneTypeSelected) { if (this.milestoneTypeSelected) {
return this.milestones; return this.milestones;
} }
...@@ -64,6 +80,9 @@ export default { ...@@ -64,6 +80,9 @@ export default {
labelTypeSelected() { labelTypeSelected() {
return this.columnType === ListType.label; return this.columnType === ListType.label;
}, },
assigneeTypeSelected() {
return this.columnType === ListType.assignee;
},
milestoneTypeSelected() { milestoneTypeSelected() {
return this.columnType === ListType.milestone; return this.columnType === ListType.milestone;
}, },
...@@ -74,6 +93,12 @@ export default { ...@@ -74,6 +93,12 @@ export default {
} }
return this.labels.find(({ id }) => id === this.selectedId); return this.labels.find(({ id }) => id === this.selectedId);
}, },
selectedAssignee() {
if (!this.assigneeTypeSelected) {
return null;
}
return this.assignees.find(({ id }) => id === this.selectedId);
},
selectedMilestone() { selectedMilestone() {
if (!this.milestoneTypeSelected) { if (!this.milestoneTypeSelected) {
return null; return null;
...@@ -87,6 +112,9 @@ export default { ...@@ -87,6 +112,9 @@ export default {
if (this.labelTypeSelected) { if (this.labelTypeSelected) {
return this.selectedLabel; return this.selectedLabel;
} }
if (this.assigneeTypeSelected) {
return this.selectedAssignee;
}
if (this.milestoneTypeSelected) { if (this.milestoneTypeSelected) {
return this.selectedMilestone; return this.selectedMilestone;
} }
...@@ -108,6 +136,9 @@ export default { ...@@ -108,6 +136,9 @@ export default {
if (this.columnType === ListType.label) { if (this.columnType === ListType.label) {
return this.labelsLoading; return this.labelsLoading;
} }
if (this.assigneeTypeSelected) {
return this.assigneesLoading;
}
if (this.columnType === ListType.milestone) { if (this.columnType === ListType.milestone) {
return this.milestonesLoading; return this.milestonesLoading;
} }
...@@ -119,6 +150,10 @@ export default { ...@@ -119,6 +150,10 @@ export default {
return this.$options.i18n.labelListDescription; return this.$options.i18n.labelListDescription;
} }
if (this.assigneeTypeSelected) {
return this.$options.i18n.assigneeListDescription;
}
if (this.milestoneTypeSelected) { if (this.milestoneTypeSelected) {
return this.$options.i18n.milestoneListDescription; return this.$options.i18n.milestoneListDescription;
} }
...@@ -131,6 +166,10 @@ export default { ...@@ -131,6 +166,10 @@ export default {
return this.$options.i18n.selectLabel; return this.$options.i18n.selectLabel;
} }
if (this.assigneeTypeSelected) {
return this.$options.i18n.selectAssignee;
}
if (this.milestoneTypeSelected) { if (this.milestoneTypeSelected) {
return this.$options.i18n.selectMilestone; return this.$options.i18n.selectMilestone;
} }
...@@ -143,6 +182,10 @@ export default { ...@@ -143,6 +182,10 @@ export default {
return this.$options.i18n.searchLabels; return this.$options.i18n.searchLabels;
} }
if (this.assigneeTypeSelected) {
return this.$options.i18n.searchAssignees;
}
if (this.milestoneTypeSelected) { if (this.milestoneTypeSelected) {
return this.$options.i18n.searchMilestones; return this.$options.i18n.searchMilestones;
} }
...@@ -159,6 +202,7 @@ export default { ...@@ -159,6 +202,7 @@ export default {
'fetchLabels', 'fetchLabels',
'highlightList', 'highlightList',
'setAddColumnFormVisibility', 'setAddColumnFormVisibility',
'fetchAssignees',
'fetchMilestones', 'fetchMilestones',
]), ]),
highlight(listId) { highlight(listId) {
...@@ -206,6 +250,11 @@ export default { ...@@ -206,6 +250,11 @@ export default {
...this.selectedMilestone, ...this.selectedMilestone,
id: getIdFromGraphQLId(this.selectedMilestone.id), id: getIdFromGraphQLId(this.selectedMilestone.id),
}; };
} else if (this.assigneeTypeSelected) {
listObj.assignee = {
...this.selectedAssignee,
id: getIdFromGraphQLId(this.selectedAssignee.id),
};
} }
boardsStore.new(listObj); boardsStore.new(listObj);
...@@ -217,6 +266,9 @@ export default { ...@@ -217,6 +266,9 @@ export default {
case ListType.milestone: case ListType.milestone:
this.fetchMilestones(searchTerm); this.fetchMilestones(searchTerm);
break; break;
case ListType.assignee:
this.fetchAssignees(searchTerm);
break;
case ListType.label: case ListType.label:
default: default:
this.fetchLabels(searchTerm); this.fetchLabels(searchTerm);
...@@ -227,7 +279,8 @@ export default { ...@@ -227,7 +279,8 @@ export default {
return this.scopedLabelsAvailable && isScopedLabel(label); return this.scopedLabelsAvailable && isScopedLabel(label);
}, },
setColumnType() { setColumnType(type) {
this.columnType = type;
this.selectedId = null; this.selectedId = null;
this.filterItems(); this.filterItems();
}, },
...@@ -287,7 +340,7 @@ export default { ...@@ -287,7 +340,7 @@ export default {
:key="item.id" :key="item.id"
class="gl-display-flex gl-flex-align-items-center gl-mb-5 gl-font-weight-normal" class="gl-display-flex gl-flex-align-items-center gl-mb-5 gl-font-weight-normal"
> >
<gl-form-radio :value="item.id" class="gl-mb-0" /> <gl-form-radio :value="item.id" class="gl-mb-0 gl-align-self-center" />
<span <span
v-if="labelTypeSelected" v-if="labelTypeSelected"
class="dropdown-label-box gl-top-0" class="dropdown-label-box gl-top-0"
...@@ -295,7 +348,15 @@ export default { ...@@ -295,7 +348,15 @@ export default {
backgroundColor: item.color, backgroundColor: item.color,
}" }"
></span> ></span>
<span>{{ item.title }}</span>
<gl-avatar-labeled
v-if="assigneeTypeSelected"
:size="32"
:label="item.name"
:sub-label="item.username"
:src="item.avatarUrl"
/>
<span v-else>{{ item.title }}</span>
</label> </label>
</gl-form-radio-group> </gl-form-radio-group>
</template> </template>
......
#import "~/graphql_shared/fragments/user.fragment.graphql"
query GroupBoardAssignees($fullPath: ID!, $search: String) {
workspace: group(fullPath: $fullPath) {
__typename
assignees: groupMembers(search: $search) {
__typename
nodes {
id
user {
...User
}
}
}
}
}
#import "~/graphql_shared/fragments/user.fragment.graphql"
query ProjectBoardAssignees($fullPath: ID!, $search: String) {
workspace: project(fullPath: $fullPath) {
__typename
assignees: projectMembers(search: $search) {
__typename
nodes {
id
user {
...User
}
}
}
}
}
...@@ -34,12 +34,14 @@ import createEpicBoardListMutation from '../graphql/epic_board_list_create.mutat ...@@ -34,12 +34,14 @@ import createEpicBoardListMutation from '../graphql/epic_board_list_create.mutat
import epicBoardListsQuery from '../graphql/epic_board_lists.query.graphql'; import epicBoardListsQuery from '../graphql/epic_board_lists.query.graphql';
import epicMoveListMutation from '../graphql/epic_move_list.mutation.graphql'; import epicMoveListMutation from '../graphql/epic_move_list.mutation.graphql';
import epicsSwimlanesQuery from '../graphql/epics_swimlanes.query.graphql'; import epicsSwimlanesQuery from '../graphql/epics_swimlanes.query.graphql';
import groupBoardAssigneesQuery from '../graphql/group_board_assignees.query.graphql';
import groupBoardMilestonesQuery from '../graphql/group_board_milestones.query.graphql'; import groupBoardMilestonesQuery from '../graphql/group_board_milestones.query.graphql';
import issueMoveListMutation from '../graphql/issue_move_list.mutation.graphql'; import issueMoveListMutation from '../graphql/issue_move_list.mutation.graphql';
import issueSetEpicMutation from '../graphql/issue_set_epic.mutation.graphql'; import issueSetEpicMutation from '../graphql/issue_set_epic.mutation.graphql';
import issueSetWeightMutation from '../graphql/issue_set_weight.mutation.graphql'; import issueSetWeightMutation from '../graphql/issue_set_weight.mutation.graphql';
import listUpdateLimitMetricsMutation from '../graphql/list_update_limit_metrics.mutation.graphql'; import listUpdateLimitMetricsMutation from '../graphql/list_update_limit_metrics.mutation.graphql';
import listsEpicsQuery from '../graphql/lists_epics.query.graphql'; import listsEpicsQuery from '../graphql/lists_epics.query.graphql';
import projectBoardAssigneesQuery from '../graphql/project_board_assignees.query.graphql';
import projectBoardMilestonesQuery from '../graphql/project_board_milestones.query.graphql'; import projectBoardMilestonesQuery from '../graphql/project_board_milestones.query.graphql';
import updateBoardEpicUserPreferencesMutation from '../graphql/update_board_epic_user_preferences.mutation.graphql'; import updateBoardEpicUserPreferencesMutation from '../graphql/update_board_epic_user_preferences.mutation.graphql';
...@@ -646,6 +648,52 @@ export default { ...@@ -646,6 +648,52 @@ export default {
}); });
}, },
fetchAssignees({ state, commit }, search) {
commit(types.RECEIVE_ASSIGNEES_REQUEST);
const { fullPath, boardType } = state;
const variables = {
fullPath,
search,
};
let query;
if (boardType === BoardType.project) {
query = projectBoardAssigneesQuery;
}
if (boardType === BoardType.group) {
query = groupBoardAssigneesQuery;
}
if (!query) {
// eslint-disable-next-line @gitlab/require-i18n-strings
throw new Error('Unknown board type');
}
return gqlClient
.query({
query,
variables,
})
.then(({ data }) => {
const [firstError] = data.workspace.errors || [];
const assignees = data.workspace.assignees.nodes;
if (firstError) {
throw new Error(firstError);
}
commit(
types.RECEIVE_ASSIGNEES_SUCCESS,
assignees.map(({ user }) => user),
);
})
.catch((e) => {
commit(types.RECEIVE_ASSIGNEES_FAILURE);
throw e;
});
},
createList: ({ getters, dispatch }, { backlog, labelId, milestoneId, assigneeId }) => { createList: ({ getters, dispatch }, { backlog, labelId, milestoneId, assigneeId }) => {
if (!getters.isEpicBoard) { if (!getters.isEpicBoard) {
dispatch('createIssueList', { backlog, labelId, milestoneId, assigneeId }); dispatch('createIssueList', { backlog, labelId, milestoneId, assigneeId });
......
...@@ -38,3 +38,6 @@ export const SET_BOARD_EPIC_USER_PREFERENCES = 'SET_BOARD_EPIC_USER_PREFERENCES' ...@@ -38,3 +38,6 @@ export const SET_BOARD_EPIC_USER_PREFERENCES = 'SET_BOARD_EPIC_USER_PREFERENCES'
export const RECEIVE_MILESTONES_REQUEST = 'RECEIVE_MILESTONES_REQUEST'; export const RECEIVE_MILESTONES_REQUEST = 'RECEIVE_MILESTONES_REQUEST';
export const RECEIVE_MILESTONES_SUCCESS = 'RECEIVE_MILESTONES_SUCCESS'; export const RECEIVE_MILESTONES_SUCCESS = 'RECEIVE_MILESTONES_SUCCESS';
export const RECEIVE_MILESTONES_FAILURE = 'RECEIVE_MILESTONES_FAILURE'; export const RECEIVE_MILESTONES_FAILURE = 'RECEIVE_MILESTONES_FAILURE';
export const RECEIVE_ASSIGNEES_REQUEST = 'RECEIVE_ASSIGNEES_REQUEST';
export const RECEIVE_ASSIGNEES_SUCCESS = 'RECEIVE_ASSIGNEES_SUCCESS';
export const RECEIVE_ASSIGNEES_FAILURE = 'RECEIVE_ASSIGNEES_FAILURE';
...@@ -232,4 +232,18 @@ export default { ...@@ -232,4 +232,18 @@ export default {
state.milestonesLoading = false; state.milestonesLoading = false;
state.error = __('Failed to load milestones.'); state.error = __('Failed to load milestones.');
}, },
[mutationTypes.RECEIVE_ASSIGNEES_REQUEST](state) {
state.assigneesLoading = true;
},
[mutationTypes.RECEIVE_ASSIGNEES_SUCCESS](state, assignees) {
state.assignees = assignees;
state.assigneesLoading = false;
},
[mutationTypes.RECEIVE_ASSIGNEES_FAILURE](state) {
state.assigneesLoading = false;
state.error = __('Failed to load assignees.');
},
}; };
...@@ -14,4 +14,6 @@ export default () => ({ ...@@ -14,4 +14,6 @@ export default () => ({
epicsFlags: {}, epicsFlags: {},
milestones: [], milestones: [],
milestonesLoading: false, milestonesLoading: false,
assignees: [],
assigneesLoading: false,
}); });
...@@ -15,7 +15,8 @@ RSpec.describe 'User adds milestone lists', :js do ...@@ -15,7 +15,8 @@ RSpec.describe 'User adds milestone lists', :js do
let_it_be(:group_backlog_list) { create(:backlog_list, board: group_board) } let_it_be(:group_backlog_list) { create(:backlog_list, board: group_board) }
let_it_be(:issue) { create(:issue, project: project, milestone: milestone) } let_it_be(:issue_with_milestone) { create(:issue, project: project, milestone: milestone) }
let_it_be(:issue_with_assignee) { create(:issue, project: project, assignees: [user]) }
before_all do before_all do
project.add_maintainer(user) project.add_maintainer(user)
...@@ -31,7 +32,10 @@ RSpec.describe 'User adds milestone lists', :js do ...@@ -31,7 +32,10 @@ RSpec.describe 'User adds milestone lists', :js do
with_them do with_them do
before do before do
stub_licensed_features(board_milestone_lists: true) stub_licensed_features(
board_milestone_lists: true,
board_assignee_lists: true
)
sign_in(user) sign_in(user)
set_cookie('sidebar_collapsed', 'true') set_cookie('sidebar_collapsed', 'true')
...@@ -51,28 +55,31 @@ RSpec.describe 'User adds milestone lists', :js do ...@@ -51,28 +55,31 @@ RSpec.describe 'User adds milestone lists', :js do
end end
it 'creates milestone column' do it 'creates milestone column' do
click_button button_text add_list('Milestone', milestone.title)
wait_for_all_requests
select('Milestone', from: 'List type')
add_milestone_list(milestone) expect(page).to have_selector('.board', text: milestone.title)
expect(find('.board:nth-child(2) .board-card')).to have_content(issue_with_milestone.title)
end
wait_for_all_requests it 'creates assignee column' do
add_list('Assignee', user.name)
expect(page).to have_selector('.board', text: milestone.title) expect(page).to have_selector('.board', text: user.name)
expect(find('.board:nth-child(2) .board-card')).to have_content(issue.title) expect(find('.board:nth-child(2) .board-card')).to have_content(issue_with_assignee.title)
end end
end end
def add_milestone_list(milestone) def add_list(list_type, title)
click_button 'Create list'
wait_for_all_requests
select(list_type, from: 'List type')
page.within('.board-add-new-list') do page.within('.board-add-new-list') do
find('label', text: milestone.title).click find('label', text: title).click
click_button 'Add' click_button 'Add'
end end
end
def button_text wait_for_all_requests
'Create list'
end end
end end
import { GlSearchBoxByType } from '@gitlab/ui'; import { GlAvatarLabeled, GlSearchBoxByType, GlFormSelect } from '@gitlab/ui';
import { shallowMount } from '@vue/test-utils'; import { shallowMount } from '@vue/test-utils';
import Vue, { nextTick } from 'vue'; import Vue, { nextTick } from 'vue';
import Vuex from 'vuex'; import Vuex from 'vuex';
import BoardAddNewColumn from 'ee/boards/components/board_add_new_column.vue'; import BoardAddNewColumn from 'ee/boards/components/board_add_new_column.vue';
import { extendedWrapper } from 'helpers/vue_test_utils_helper'; import { extendedWrapper } from 'helpers/vue_test_utils_helper';
import BoardAddNewColumnForm from '~/boards/components/board_add_new_column_form.vue'; import BoardAddNewColumnForm from '~/boards/components/board_add_new_column_form.vue';
import { ListType } from '~/boards/constants';
import defaultState from '~/boards/stores/state'; import defaultState from '~/boards/stores/state';
import { mockLists } from '../mock_data'; import { mockAssignees, mockLists } from '../mock_data';
const mockLabelList = mockLists[1]; const mockLabelList = mockLists[1];
Vue.use(Vuex); Vue.use(Vuex);
describe('Board card layout', () => { describe('BoardAddNewColumn', () => {
let wrapper; let wrapper;
let shouldUseGraphQL; let shouldUseGraphQL;
...@@ -30,6 +31,7 @@ describe('Board card layout', () => { ...@@ -30,6 +31,7 @@ describe('Board card layout', () => {
const mountComponent = ({ const mountComponent = ({
selectedId, selectedId,
labels = [], labels = [],
assignees = [],
getListByTypeId = jest.fn(), getListByTypeId = jest.fn(),
actions = {}, actions = {},
} = {}) => { } = {}) => {
...@@ -57,6 +59,8 @@ describe('Board card layout', () => { ...@@ -57,6 +59,8 @@ describe('Board card layout', () => {
state: { state: {
labels, labels,
labelsLoading: false, labelsLoading: false,
assignees,
assigneesLoading: false,
}, },
}), }),
provide: { provide: {
...@@ -71,10 +75,12 @@ describe('Board card layout', () => { ...@@ -71,10 +75,12 @@ describe('Board card layout', () => {
wrapper = null; wrapper = null;
}); });
const findForm = () => wrapper.findComponent(BoardAddNewColumnForm);
const formTitle = () => wrapper.findByTestId('board-add-column-form-title').text(); const formTitle = () => wrapper.findByTestId('board-add-column-form-title').text();
const findSearchInput = () => wrapper.find(GlSearchBoxByType); const findSearchInput = () => wrapper.find(GlSearchBoxByType);
const cancelButton = () => wrapper.findByTestId('cancelAddNewColumn'); const cancelButton = () => wrapper.findByTestId('cancelAddNewColumn');
const submitButton = () => wrapper.findByTestId('addNewColumnButton'); const submitButton = () => wrapper.findByTestId('addNewColumnButton');
const listTypeSelect = () => wrapper.findComponent(GlFormSelect);
beforeEach(() => { beforeEach(() => {
shouldUseGraphQL = true; shouldUseGraphQL = true;
...@@ -152,4 +158,39 @@ describe('Board card layout', () => { ...@@ -152,4 +158,39 @@ describe('Board card layout', () => {
expect(createList).not.toHaveBeenCalled(); expect(createList).not.toHaveBeenCalled();
}); });
}); });
describe('assignee list', () => {
beforeEach(async () => {
mountComponent({
assignees: mockAssignees,
actions: {
fetchAssignees: jest.fn(),
},
});
listTypeSelect().vm.$emit('change', ListType.assignee);
await nextTick();
});
it('sets assignee placeholder text in form', async () => {
expect(findForm().props()).toMatchObject({
formDescription: BoardAddNewColumn.i18n.assigneeListDescription,
searchLabel: BoardAddNewColumn.i18n.selectAssignee,
searchPlaceholder: BoardAddNewColumn.i18n.searchAssignees,
});
});
it('shows list of assignees', () => {
const userList = wrapper.findAllComponents(GlAvatarLabeled);
const [firstUser] = mockAssignees;
expect(userList).toHaveLength(mockAssignees.length);
expect(userList.at(0).props()).toMatchObject({
label: firstUser.name,
subLabel: firstUser.username,
});
});
});
}); });
...@@ -76,7 +76,7 @@ const defaultDescendantCounts = { ...@@ -76,7 +76,7 @@ const defaultDescendantCounts = {
closedIssues: 0, closedIssues: 0,
}; };
const assignees = [ export const mockAssignees = [
{ {
id: 'gid://gitlab/User/2', id: 'gid://gitlab/User/2',
username: 'angelina.herman', username: 'angelina.herman',
...@@ -84,6 +84,13 @@ const assignees = [ ...@@ -84,6 +84,13 @@ const assignees = [
avatar: 'https://www.gravatar.com/avatar/eb7b664b13a30ad9f9ba4b61d7075470?s=80&d=identicon', avatar: 'https://www.gravatar.com/avatar/eb7b664b13a30ad9f9ba4b61d7075470?s=80&d=identicon',
webUrl: 'http://127.0.0.1:3000/angelina.herman', webUrl: 'http://127.0.0.1:3000/angelina.herman',
}, },
{
id: 'gid://gitlab/User/118',
username: 'jacklyn.moore',
name: 'Brock Jaskolski',
avatar: 'https://www.gravatar.com/avatar/af29c072d9fcf315772cfd802c7a7d35?s=80&d=identicon',
webUrl: 'http://127.0.0.1:3000/jacklyn.moore',
},
]; ];
export const mockMilestones = [ export const mockMilestones = [
...@@ -127,7 +134,7 @@ export const rawIssue = { ...@@ -127,7 +134,7 @@ export const rawIssue = {
], ],
}, },
assignees: { assignees: {
nodes: assignees, nodes: mockAssignees,
}, },
epic: { epic: {
id: 'gid://gitlab/Epic/41', id: 'gid://gitlab/Epic/41',
...@@ -147,7 +154,7 @@ export const mockIssue = { ...@@ -147,7 +154,7 @@ export const mockIssue = {
weight: null, weight: null,
confidential: false, confidential: false,
path: `/${mockIssueProjectPath}/-/issues/27`, path: `/${mockIssueProjectPath}/-/issues/27`,
assignees, assignees: mockAssignees,
labels, labels,
epic: { epic: {
id: 'gid://gitlab/Epic/41', id: 'gid://gitlab/Epic/41',
...@@ -165,7 +172,7 @@ export const mockIssue2 = { ...@@ -165,7 +172,7 @@ export const mockIssue2 = {
weight: null, weight: null,
confidential: false, confidential: false,
path: '/gitlab-org/gitlab-test/-/issues/28', path: '/gitlab-org/gitlab-test/-/issues/28',
assignees, assignees: mockAssignees,
labels, labels,
epic: { epic: {
id: 'gid://gitlab/Epic/40', id: 'gid://gitlab/Epic/40',
...@@ -183,7 +190,7 @@ export const mockIssue3 = { ...@@ -183,7 +190,7 @@ export const mockIssue3 = {
weight: null, weight: null,
confidential: false, confidential: false,
path: '/gitlab-org/gitlab-test/-/issues/28', path: '/gitlab-org/gitlab-test/-/issues/28',
assignees, assignees: mockAssignees,
labels, labels,
epic: null, epic: null,
}; };
...@@ -198,7 +205,7 @@ export const mockIssue4 = { ...@@ -198,7 +205,7 @@ export const mockIssue4 = {
weight: null, weight: null,
confidential: false, confidential: false,
path: '/gitlab-org/gitlab-test/-/issues/28', path: '/gitlab-org/gitlab-test/-/issues/28',
assignees, assignees: mockAssignees,
labels, labels,
epic: null, epic: null,
}; };
......
...@@ -19,9 +19,10 @@ import { ...@@ -19,9 +19,10 @@ import {
mockIssue, mockIssue,
mockIssue2, mockIssue2,
mockEpic, mockEpic,
mockEpics,
rawIssue, rawIssue,
mockMilestones, mockMilestones,
mockAssignees,
mockEpics,
} from '../mock_data'; } from '../mock_data';
Vue.use(Vuex); Vue.use(Vuex);
...@@ -1268,3 +1269,84 @@ describe('fetchMilestones', () => { ...@@ -1268,3 +1269,84 @@ describe('fetchMilestones', () => {
}); });
}); });
}); });
describe('fetchAssignees', () => {
const queryResponse = {
data: {
workspace: {
assignees: {
nodes: mockAssignees.map((assignee) => ({ user: assignee })),
},
},
},
};
const queryErrors = {
data: {
project: {
errors: ['You cannot view these assignees'],
assignees: {},
},
},
};
function createStore({
state = {
boardType: 'project',
fullPath: 'gitlab-org/gitlab',
assignees: [],
assigneesLoading: false,
},
} = {}) {
return new Vuex.Store({
state,
mutations,
});
}
it('throws error if state.boardType is not group or project', () => {
const store = createStore({
state: {
boardType: 'invalid',
},
});
expect(() => actions.fetchAssignees(store)).toThrow(new Error('Unknown board type'));
});
it('sets assigneesLoading to true', async () => {
jest.spyOn(gqlClient, 'query').mockResolvedValue(queryResponse);
const store = createStore();
actions.fetchAssignees(store);
expect(store.state.assigneesLoading).toBe(true);
});
describe('success', () => {
it('sets state.assignees from query result', async () => {
jest.spyOn(gqlClient, 'query').mockResolvedValue(queryResponse);
const store = createStore();
await actions.fetchAssignees(store);
expect(store.state.assigneesLoading).toBe(false);
expect(store.state.assignees).toEqual(expect.objectContaining(mockAssignees));
});
});
describe('failure', () => {
it('throws an error and displays an error message', async () => {
jest.spyOn(gqlClient, 'query').mockResolvedValue(queryErrors);
const store = createStore();
await expect(actions.fetchAssignees(store)).rejects.toThrow();
expect(store.state.assigneesLoading).toBe(false);
expect(store.state.error).toBe('Failed to load assignees.');
});
});
});
...@@ -3224,6 +3224,9 @@ msgstr "" ...@@ -3224,6 +3224,9 @@ msgstr ""
msgid "An application called %{link_to_client} is requesting access to your GitLab account." msgid "An application called %{link_to_client} is requesting access to your GitLab account."
msgstr "" msgstr ""
msgid "An assignee list displays issues assigned to the selected user"
msgstr ""
msgid "An email notification was recently sent from the admin panel. Please wait %{wait_time_in_words} before attempting to send another message." msgid "An email notification was recently sent from the admin panel. Please wait %{wait_time_in_words} before attempting to send another message."
msgstr "" msgstr ""
...@@ -12629,6 +12632,9 @@ msgstr "" ...@@ -12629,6 +12632,9 @@ msgstr ""
msgid "Failed to install." msgid "Failed to install."
msgstr "" msgstr ""
msgid "Failed to load assignees."
msgstr ""
msgid "Failed to load assignees. Please try again." msgid "Failed to load assignees. Please try again."
msgstr "" msgstr ""
...@@ -26413,6 +26419,9 @@ msgstr "" ...@@ -26413,6 +26419,9 @@ msgstr ""
msgid "Search an environment spec" msgid "Search an environment spec"
msgstr "" msgstr ""
msgid "Search assignees"
msgstr ""
msgid "Search authors" msgid "Search authors"
msgstr "" msgstr ""
......
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