Commit 39130866 authored by Simon Knox's avatar Simon Knox Committed by Ezekiel Kigbo

Add iteration lists to board new list form

Allow adding iteration lists to boards
Supports graphql and plain ol boards
parent ffcbbe06
......@@ -104,6 +104,9 @@ export default () => {
? parseInt($boardApp.dataset.boardWeight, 10)
: null,
scopedLabelsAvailable: parseBoolean($boardApp.dataset.scopedLabels),
milestoneListsAvailable: parseBoolean($boardApp.dataset.milestoneListsAvailable),
assigneeListsAvailable: parseBoolean($boardApp.dataset.assigneeListsAvailable),
iterationListsAvailable: parseBoolean($boardApp.dataset.iterationListsAvailable),
},
store,
apolloProvider,
......
......@@ -134,7 +134,7 @@ export default {
createIssueList: (
{ state, commit, dispatch, getters },
{ backlog, labelId, milestoneId, assigneeId },
{ backlog, labelId, milestoneId, assigneeId, iterationId },
) => {
const { boardId } = state;
......@@ -154,6 +154,7 @@ export default {
labelId,
milestoneId,
assigneeId,
iterationId,
},
})
.then(({ data }) => {
......
......@@ -575,7 +575,7 @@ const boardsStore = {
},
saveList(list) {
const entity = list.label || list.assignee || list.milestone;
const entity = list.label || list.assignee || list.milestone || list.iteration;
let entityType = '';
if (list.label) {
entityType = 'label_id';
......@@ -583,6 +583,8 @@ const boardsStore = {
entityType = 'assignee_id';
} else if (IS_EE && list.milestone) {
entityType = 'milestone_id';
} else if (IS_EE && list.iteration) {
entityType = 'iteration_id';
}
return this.createList(entity.id, entityType)
......
......@@ -16,24 +16,45 @@ import { getIdFromGraphQLId } from '~/graphql_shared/utils';
import { isScopedLabel } from '~/lib/utils/common_utils';
import { __ } from '~/locale';
export const listTypeInfo = {
[ListType.label]: {
listPropertyName: 'labels',
loadingPropertyName: 'labelsLoading',
fetchMethodName: 'fetchLabels',
formDescription: __('A label list displays issues with the selected label.'),
searchLabel: __('Select label'),
searchPlaceholder: __('Search labels'),
},
[ListType.assignee]: {
listPropertyName: 'assignees',
loadingPropertyName: 'assigneesLoading',
fetchMethodName: 'fetchAssignees',
formDescription: __('An assignee list displays issues assigned to the selected user'),
searchLabel: __('Select assignee'),
searchPlaceholder: __('Search assignees'),
},
[ListType.milestone]: {
listPropertyName: 'milestones',
loadingPropertyName: 'milestonesLoading',
fetchMethodName: 'fetchMilestones',
formDescription: __('A milestone list displays issues in the selected milestone.'),
searchLabel: __('Select milestone'),
searchPlaceholder: __('Search milestones'),
},
[ListType.iteration]: {
listPropertyName: 'iterations',
loadingPropertyName: 'iterationsLoading',
fetchMethodName: 'fetchIterations',
formDescription: __('An iteration list displays issues in the selected iteration.'),
searchLabel: __('Select iteration'),
searchPlaceholder: __('Search iterations'),
},
};
export default {
i18n: {
listType: __('List type'),
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.'),
selectLabel: __('Select label'),
selectAssignee: __('Select assignee'),
selectMilestone: __('Select milestone'),
searchLabels: __('Search labels'),
searchAssignees: __('Search assignees'),
searchMilestones: __('Search milestones'),
},
columnTypes: [
{ value: ListType.label, text: __('Label') },
{ value: ListType.assignee, text: __('Assignee') },
{ value: ListType.milestone, text: __('Milestone') },
],
components: {
BoardAddNewColumnForm,
GlAvatarLabeled,
......@@ -46,7 +67,12 @@ export default {
directives: {
GlTooltip,
},
inject: ['scopedLabelsAvailable'],
inject: [
'scopedLabelsAvailable',
'milestoneListsAvailable',
'assigneeListsAvailable',
'iterationListsAvailable',
],
data() {
return {
selectedId: null,
......@@ -57,24 +83,21 @@ export default {
...mapState([
'labels',
'labelsLoading',
'assignees',
'assigneesLoading',
'milestones',
'milestonesLoading',
'iterations',
'iterationsLoading',
'assignees',
'assigneesLoading',
]),
...mapGetters(['getListByTypeId', 'shouldUseGraphQL', 'isEpicBoard']),
info() {
return listTypeInfo[this.columnType] || {};
},
items() {
if (this.labelTypeSelected) {
return this.labels;
}
if (this.assigneeTypeSelected) {
return this.assignees;
}
if (this.milestoneTypeSelected) {
return this.milestones;
}
return [];
return this[this.info.listPropertyName] || [];
},
labelTypeSelected() {
......@@ -86,39 +109,25 @@ export default {
milestoneTypeSelected() {
return this.columnType === ListType.milestone;
},
iterationTypeSelected() {
return this.columnType === ListType.iteration;
},
selectedLabel() {
if (!this.labelTypeSelected) {
return null;
}
return this.labels.find(({ id }) => id === this.selectedId);
selectedItem() {
return this.items.find(({ id }) => id === this.selectedId);
},
selectedAssignee() {
if (!this.assigneeTypeSelected) {
return null;
}
return this.assignees.find(({ id }) => id === this.selectedId);
hasLabelSelection() {
return this.labelTypeSelected && this.selectedItem;
},
selectedMilestone() {
if (!this.milestoneTypeSelected) {
return null;
}
return this.milestones.find(({ id }) => id === this.selectedId);
hasMilestoneSelection() {
return this.milestoneTypeSelected && this.selectedItem;
},
selectedItem() {
if (!this.selectedId) {
return null;
}
if (this.labelTypeSelected) {
return this.selectedLabel;
}
if (this.assigneeTypeSelected) {
return this.selectedAssignee;
}
if (this.milestoneTypeSelected) {
return this.selectedMilestone;
}
return null;
hasIterationSelection() {
return this.iterationTypeSelected && this.selectedItem;
},
hasAssigneeSelection() {
return this.assigneeTypeSelected && this.selectedItem;
},
columnForSelected() {
......@@ -133,64 +142,29 @@ export default {
},
loading() {
if (this.columnType === ListType.label) {
return this.labelsLoading;
}
if (this.assigneeTypeSelected) {
return this.assigneesLoading;
}
if (this.columnType === ListType.milestone) {
return this.milestonesLoading;
}
return false;
return this[this.info.loadingPropertyName];
},
formDescription() {
if (this.labelTypeSelected) {
return this.$options.i18n.labelListDescription;
}
if (this.assigneeTypeSelected) {
return this.$options.i18n.assigneeListDescription;
}
columnTypes() {
const types = [{ value: ListType.label, text: __('Label') }];
if (this.milestoneTypeSelected) {
return this.$options.i18n.milestoneListDescription;
if (this.assigneeListsAvailable) {
types.push({ value: ListType.assignee, text: __('Assignee') });
}
return null;
},
searchLabel() {
if (this.labelTypeSelected) {
return this.$options.i18n.selectLabel;
if (this.milestoneListsAvailable) {
types.push({ value: ListType.milestone, text: __('Milestone') });
}
if (this.assigneeTypeSelected) {
return this.$options.i18n.selectAssignee;
if (this.iterationListsAvailable) {
types.push({ value: ListType.iteration, text: __('Iteration') });
}
if (this.milestoneTypeSelected) {
return this.$options.i18n.selectMilestone;
}
return null;
return types;
},
searchPlaceholder() {
if (this.labelTypeSelected) {
return this.$options.i18n.searchLabels;
}
if (this.assigneeTypeSelected) {
return this.$options.i18n.searchAssignees;
}
if (this.milestoneTypeSelected) {
return this.$options.i18n.searchMilestones;
}
return null;
showListTypeSelector() {
return !this.isEpicBoard && this.columnTypes.length > 1;
},
},
created() {
......@@ -203,6 +177,7 @@ export default {
'highlightList',
'setAddColumnFormVisibility',
'fetchAssignees',
'fetchIterations',
'fetchMilestones',
]),
highlight(listId) {
......@@ -244,16 +219,21 @@ export default {
};
if (this.labelTypeSelected) {
listObj.label = this.selectedLabel;
listObj.label = this.selectedItem;
} else if (this.milestoneTypeSelected) {
listObj.milestone = {
...this.selectedMilestone,
id: getIdFromGraphQLId(this.selectedMilestone.id),
...this.selectedItem,
id: getIdFromGraphQLId(this.selectedItem.id),
};
} else if (this.iterationTypeSelected) {
listObj.iteration = {
...this.selectedItem,
id: getIdFromGraphQLId(this.selectedItem.id),
};
} else if (this.assigneeTypeSelected) {
listObj.assignee = {
...this.selectedAssignee,
id: getIdFromGraphQLId(this.selectedAssignee.id),
...this.selectedItem,
id: getIdFromGraphQLId(this.selectedItem.id),
};
}
......@@ -262,17 +242,7 @@ export default {
},
filterItems(searchTerm) {
switch (this.columnType) {
case ListType.milestone:
this.fetchMilestones(searchTerm);
break;
case ListType.assignee:
this.fetchAssignees(searchTerm);
break;
case ListType.label:
default:
this.fetchLabels(searchTerm);
}
this[this.info.fetchMethodName](searchTerm);
},
showScopedLabels(label) {
......@@ -291,16 +261,16 @@ export default {
<template>
<board-add-new-column-form
:loading="loading"
:form-description="formDescription"
:search-label="searchLabel"
:search-placeholder="searchPlaceholder"
:form-description="info.formDescription"
:search-label="info.searchLabel"
:search-placeholder="info.searchPlaceholder"
:selected-id="selectedId"
@filter-items="filterItems"
@add-list="addList"
>
<template slot="select-list-type">
<gl-form-group
v-if="!isEpicBoard"
v-if="showListTypeSelector"
:label="$options.i18n.listType"
class="gl-px-5 gl-py-0 gl-mt-5"
label-for="list-type"
......@@ -308,24 +278,33 @@ export default {
<gl-form-select
id="list-type"
v-model="columnType"
:options="$options.columnTypes"
:options="columnTypes"
@change="setColumnType"
/>
</gl-form-group>
</template>
<template slot="selected">
<div v-if="selectedLabel">
<div v-if="hasLabelSelection">
<gl-label
v-gl-tooltip
:title="selectedLabel.title"
:description="selectedLabel.description"
:background-color="selectedLabel.color"
:scoped="showScopedLabels(selectedLabel)"
:title="selectedItem.title"
:description="selectedItem.description"
:background-color="selectedItem.color"
:scoped="showScopedLabels(selectedItem)"
/>
</div>
<div v-else-if="hasAssigneeSelection">
<gl-avatar-labeled
:size="32"
:label="selectedItem.name"
:sub-label="selectedItem.username"
:src="selectedItem.avatarUrl"
/>
</div>
<div v-else-if="selectedMilestone" class="gl-text-truncate">
{{ selectedMilestone.title }}
<div v-else-if="hasMilestoneSelection || hasIterationSelection" class="gl-text-truncate">
{{ selectedItem.title }}
</div>
</template>
......
......@@ -17,4 +17,10 @@ fragment BoardListFragment on BoardList {
webPath
description
}
iteration {
id
title
webPath
description
}
}
......@@ -5,6 +5,7 @@ mutation CreateBoardList(
$backlog: Boolean
$labelId: LabelID
$milestoneId: MilestoneID
$iterationId: IterationID
$assigneeId: UserID
) {
boardListCreate(
......@@ -13,6 +14,7 @@ mutation CreateBoardList(
backlog: $backlog
labelId: $labelId
milestoneId: $milestoneId
iterationId: $iterationId
assigneeId: $assigneeId
}
) {
......
query GroupBoardIterations($fullPath: ID!, $title: String) {
group(fullPath: $fullPath) {
iterations(includeAncestors: true, title: $title) {
nodes {
id
title
}
}
}
}
query ProjectBoardIterations($fullPath: ID!, $title: String) {
project(fullPath: $fullPath) {
iterations(includeAncestors: true, title: $title) {
nodes {
id
title
}
}
}
}
......@@ -35,6 +35,7 @@ import epicBoardListsQuery from '../graphql/epic_board_lists.query.graphql';
import epicMoveListMutation from '../graphql/epic_move_list.mutation.graphql';
import epicsSwimlanesQuery from '../graphql/epics_swimlanes.query.graphql';
import groupBoardAssigneesQuery from '../graphql/group_board_assignees.query.graphql';
import groupBoardIterationsQuery from '../graphql/group_board_iterations.query.graphql';
import groupBoardMilestonesQuery from '../graphql/group_board_milestones.query.graphql';
import issueMoveListMutation from '../graphql/issue_move_list.mutation.graphql';
import issueSetEpicMutation from '../graphql/issue_set_epic.mutation.graphql';
......@@ -42,6 +43,7 @@ import issueSetWeightMutation from '../graphql/issue_set_weight.mutation.graphql
import listUpdateLimitMetricsMutation from '../graphql/list_update_limit_metrics.mutation.graphql';
import listsEpicsQuery from '../graphql/lists_epics.query.graphql';
import projectBoardAssigneesQuery from '../graphql/project_board_assignees.query.graphql';
import projectBoardIterationsQuery from '../graphql/project_board_iterations.query.graphql';
import projectBoardMilestonesQuery from '../graphql/project_board_milestones.query.graphql';
import updateBoardEpicUserPreferencesMutation from '../graphql/update_board_epic_user_preferences.mutation.graphql';
......@@ -648,6 +650,50 @@ export default {
});
},
fetchIterations({ state, commit }, title) {
commit(types.RECEIVE_ITERATIONS_REQUEST);
const { fullPath, boardType } = state;
const variables = {
fullPath,
title,
};
let query;
if (boardType === BoardType.project) {
query = projectBoardIterationsQuery;
}
if (boardType === BoardType.group) {
query = groupBoardIterationsQuery;
}
if (!query) {
// eslint-disable-next-line @gitlab/require-i18n-strings
throw new Error('Unknown board type');
}
return gqlClient
.query({
query,
variables,
})
.then(({ data }) => {
const errors = data[boardType]?.errors;
const iterations = data[boardType]?.iterations.nodes;
if (errors?.[0]) {
throw new Error(errors[0]);
}
commit(types.RECEIVE_ITERATIONS_SUCCESS, iterations);
})
.catch((e) => {
commit(types.RECEIVE_ITERATIONS_FAILURE);
throw e;
});
},
fetchAssignees({ state, commit }, search) {
commit(types.RECEIVE_ASSIGNEES_REQUEST);
......@@ -694,9 +740,12 @@ export default {
});
},
createList: ({ getters, dispatch }, { backlog, labelId, milestoneId, assigneeId }) => {
createList: (
{ getters, dispatch },
{ backlog, labelId, milestoneId, assigneeId, iterationId },
) => {
if (!getters.isEpicBoard) {
dispatch('createIssueList', { backlog, labelId, milestoneId, assigneeId });
dispatch('createIssueList', { backlog, labelId, milestoneId, assigneeId, iterationId });
} else {
dispatch('createEpicList', { backlog, labelId });
}
......
......@@ -38,6 +38,9 @@ export const SET_BOARD_EPIC_USER_PREFERENCES = 'SET_BOARD_EPIC_USER_PREFERENCES'
export const RECEIVE_MILESTONES_REQUEST = 'RECEIVE_MILESTONES_REQUEST';
export const RECEIVE_MILESTONES_SUCCESS = 'RECEIVE_MILESTONES_SUCCESS';
export const RECEIVE_MILESTONES_FAILURE = 'RECEIVE_MILESTONES_FAILURE';
export const RECEIVE_ITERATIONS_REQUEST = 'RECEIVE_ITERATIONS_REQUEST';
export const RECEIVE_ITERATIONS_SUCCESS = 'RECEIVE_ITERATIONS_SUCCESS';
export const RECEIVE_ITERATIONS_FAILURE = 'RECEIVE_ITERATIONS_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';
......@@ -233,6 +233,20 @@ export default {
state.error = __('Failed to load milestones.');
},
[mutationTypes.RECEIVE_ITERATIONS_REQUEST](state) {
state.iterationsLoading = true;
},
[mutationTypes.RECEIVE_ITERATIONS_SUCCESS](state, iterations) {
state.iterations = iterations;
state.iterationsLoading = false;
},
[mutationTypes.RECEIVE_ITERATIONS_FAILURE](state) {
state.iterationsLoading = false;
state.error = __('Failed to load iterations.');
},
[mutationTypes.RECEIVE_ASSIGNEES_REQUEST](state) {
state.assigneesLoading = true;
},
......
......@@ -14,6 +14,8 @@ export default () => ({
epicsFlags: {},
milestones: [],
milestonesLoading: false,
iterations: [],
iterationsLoading: false,
assignees: [],
assigneesLoading: false,
});
......@@ -26,6 +26,9 @@ module EE
labels: board.labels.to_json(only: [:id, :title, :color, :text_color] ),
board_weight: board.weight,
weight_feature_available: current_board_parent.feature_available?(:issue_weights).to_s,
milestone_lists_available: current_board_parent.feature_available?(:board_milestone_lists).to_s,
assignee_lists_available: current_board_parent.feature_available?(:board_assignee_lists).to_s,
iteration_lists_available: current_board_parent.feature_available?(:board_iteration_lists).to_s,
show_promotion: show_feature_promotion,
scoped_labels: current_board_parent.feature_available?(:scoped_labels)&.to_s
}
......
......@@ -12,11 +12,13 @@ RSpec.describe 'User adds milestone lists', :js do
let_it_be(:user) { create(:user) }
let_it_be(:milestone) { create(:milestone, group: group) }
let_it_be(:iteration) { create(:iteration, group: group) }
let_it_be(:group_backlog_list) { create(:backlog_list, board: group_board) }
let_it_be(:issue_with_milestone) { create(:issue, project: project, milestone: milestone) }
let_it_be(:issue_with_assignee) { create(:issue, project: project, assignees: [user]) }
let_it_be(:issue_with_iteration) { create(:issue, project: project, iteration: iteration) }
before_all do
project.add_maintainer(user)
......@@ -34,7 +36,8 @@ RSpec.describe 'User adds milestone lists', :js do
before do
stub_licensed_features(
board_milestone_lists: true,
board_assignee_lists: true
board_assignee_lists: true,
board_iteration_lists: true
)
sign_in(user)
......@@ -67,6 +70,45 @@ RSpec.describe 'User adds milestone lists', :js do
expect(page).to have_selector('.board', text: user.name)
expect(find('.board:nth-child(2) .board-card')).to have_content(issue_with_assignee.title)
end
it 'creates iteration column' do
add_list('Iteration', iteration.title)
expect(page).to have_selector('.board', text: iteration.title)
expect(find('.board:nth-child(2) .board-card')).to have_content(issue_with_iteration.title)
end
end
describe 'without a license' do
before do
stub_licensed_features(
board_milestone_lists: false,
board_assignee_lists: false,
board_iteration_lists: false
)
sign_in(user)
stub_feature_flags(
board_new_list: true
)
visit project_board_path(project, project_board)
wait_for_all_requests
end
it 'does not show other list types' do
click_button 'Create list'
wait_for_all_requests
page.within(find("[data-testid='board-add-new-column']")) do
expect(page).not_to have_text('Iteration')
expect(page).not_to have_text('Assignee')
expect(page).not_to have_text('Milestone')
expect(page).not_to have_text('List type')
end
end
end
def add_list(list_type, title)
......
import { GlAvatarLabeled, GlSearchBoxByType, GlFormSelect } from '@gitlab/ui';
import { GlAvatarLabeled, GlSearchBoxByType, GlFormRadio, GlFormSelect } from '@gitlab/ui';
import { shallowMount } from '@vue/test-utils';
import Vue, { nextTick } from 'vue';
import Vuex from 'vuex';
import BoardAddNewColumn from 'ee/boards/components/board_add_new_column.vue';
import BoardAddNewColumn, { listTypeInfo } from 'ee/boards/components/board_add_new_column.vue';
import { extendedWrapper } from 'helpers/vue_test_utils_helper';
import BoardAddNewColumnForm from '~/boards/components/board_add_new_column_form.vue';
import { ListType } from '~/boards/constants';
import defaultState from '~/boards/stores/state';
import { mockAssignees, mockLists } from '../mock_data';
import { mockAssignees, mockLists, mockIterations } from '../mock_data';
const mockLabelList = mockLists[1];
......@@ -32,6 +32,7 @@ describe('BoardAddNewColumn', () => {
selectedId,
labels = [],
assignees = [],
iterations = [],
getListByTypeId = jest.fn(),
actions = {},
} = {}) => {
......@@ -61,10 +62,15 @@ describe('BoardAddNewColumn', () => {
labelsLoading: false,
assignees,
assigneesLoading: false,
iterations,
iterationsLoading: false,
},
}),
provide: {
scopedLabelsAvailable: true,
milestoneListsAvailable: true,
assigneeListsAvailable: true,
iterationListsAvailable: true,
},
}),
);
......@@ -175,9 +181,9 @@ describe('BoardAddNewColumn', () => {
it('sets assignee placeholder text in form', async () => {
expect(findForm().props()).toMatchObject({
formDescription: BoardAddNewColumn.i18n.assigneeListDescription,
searchLabel: BoardAddNewColumn.i18n.selectAssignee,
searchPlaceholder: BoardAddNewColumn.i18n.searchAssignees,
formDescription: listTypeInfo.assignee.formDescription,
searchLabel: listTypeInfo.assignee.searchLabel,
searchPlaceholder: listTypeInfo.assignee.searchPlaceholder,
});
});
......@@ -193,4 +199,35 @@ describe('BoardAddNewColumn', () => {
});
});
});
describe('iteration list', () => {
beforeEach(async () => {
mountComponent({
iterations: mockIterations,
actions: {
fetchIterations: jest.fn(),
},
});
listTypeSelect().vm.$emit('change', ListType.iteration);
await nextTick();
});
it('sets iteration placeholder text in form', async () => {
expect(findForm().props()).toMatchObject({
formDescription: listTypeInfo.iteration.formDescription,
searchLabel: listTypeInfo.iteration.searchLabel,
searchPlaceholder: listTypeInfo.iteration.searchPlaceholder,
});
});
it('shows list of iterations', () => {
const itemList = wrapper.findAllComponents(GlFormRadio);
expect(itemList).toHaveLength(mockIterations.length);
expect(itemList.at(0).attributes('value')).toBe(mockIterations[0].id);
expect(itemList.at(1).attributes('value')).toBe(mockIterations[1].id);
});
});
});
......@@ -104,6 +104,17 @@ export const mockMilestones = [
},
];
export const mockIterations = [
{
id: 'gid://gitlab/Iteration/1',
title: 'Iteration 1',
},
{
id: 'gid://gitlab/Iteration/2',
title: 'Iteration 2',
},
];
const labels = [
{
id: 'gid://gitlab/GroupLabel/5',
......
......@@ -1270,6 +1270,77 @@ describe('fetchMilestones', () => {
});
});
describe('fetchIterations', () => {
const queryResponse = {
data: {
group: {
iterations: {
nodes: mockMilestones,
},
},
},
};
const queryErrors = {
data: {
group: {
errors: ['You cannot view these iterations'],
iterations: {},
},
},
};
function createStore({
state = {
boardType: 'group',
fullPath: 'gitlab-org/gitlab',
iterations: [],
iterationsLoading: false,
},
} = {}) {
return new Vuex.Store({
state,
mutations,
});
}
it('sets iterationsLoading to true', async () => {
jest.spyOn(gqlClient, 'query').mockResolvedValue(queryResponse);
const store = createStore();
actions.fetchIterations(store);
expect(store.state.iterationsLoading).toBe(true);
});
describe('success', () => {
it('sets state.iterations from query result', async () => {
jest.spyOn(gqlClient, 'query').mockResolvedValue(queryResponse);
const store = createStore();
await actions.fetchIterations(store);
expect(store.state.iterationsLoading).toBe(false);
expect(store.state.iterations).toBe(mockMilestones);
});
});
describe('failure', () => {
it('throws an error and displays an error message', async () => {
jest.spyOn(gqlClient, 'query').mockResolvedValue(queryErrors);
const store = createStore();
await expect(actions.fetchIterations(store)).rejects.toThrow();
expect(store.state.iterationsLoading).toBe(false);
expect(store.state.error).toBe('Failed to load iterations.');
});
});
});
describe('fetchAssignees', () => {
const queryResponse = {
data: {
......
......@@ -3642,6 +3642,9 @@ msgstr ""
msgid "An issue title is required"
msgstr ""
msgid "An iteration list displays issues in the selected iteration."
msgstr ""
msgid "An unauthenticated user"
msgstr ""
......@@ -12728,6 +12731,9 @@ msgstr ""
msgid "Failed to load groups, users and deploy keys."
msgstr ""
msgid "Failed to load iterations."
msgstr ""
msgid "Failed to load labels. Please try again."
msgstr ""
......@@ -26554,6 +26560,9 @@ msgstr ""
msgid "Search forks"
msgstr ""
msgid "Search iterations"
msgstr ""
msgid "Search labels"
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