Commit b8e814ed authored by Eulyeon Ko's avatar Eulyeon Ko Committed by Kushal Pandya

Update iteration form and report

Iteration form:
- For manual cadences, allow updating all fields.
- For automatic cadences, only allow updating description.

Iteration report:
- Only allow deleting iterations for manual cadences.

Fix lint errors

Fix mock data

Apply 3 suggestion(s) to 1 file(s)
parent 296e97cb
...@@ -268,7 +268,7 @@ export default { ...@@ -268,7 +268,7 @@ export default {
data-qa-selector="save_iteration_button" data-qa-selector="save_iteration_button"
@click="save" @click="save"
> >
{{ isEditing ? __('Update iteration') : __('Create iteration') }} {{ isEditing ? __('Save changes') : __('Create iteration') }}
</gl-button> </gl-button>
<gl-button class="ml-auto" data-testid="cancel-iteration" @click="cancel"> <gl-button class="ml-auto" data-testid="cancel-iteration" @click="cancel">
{{ __('Cancel') }} {{ __('Cancel') }}
......
...@@ -14,7 +14,7 @@ import { TYPE_ITERATION } from '~/graphql_shared/constants'; ...@@ -14,7 +14,7 @@ import { TYPE_ITERATION } from '~/graphql_shared/constants';
import { convertToGraphQLId } from '~/graphql_shared/utils'; import { convertToGraphQLId } from '~/graphql_shared/utils';
import { formatDate } from '~/lib/utils/datetime_utility'; import { formatDate } from '~/lib/utils/datetime_utility';
import { s__ } from '~/locale'; import { s__ } from '~/locale';
import { Namespace, iterationStates } from '../constants'; import { Namespace } from '../constants';
import deleteIteration from '../queries/destroy_iteration.mutation.graphql'; import deleteIteration from '../queries/destroy_iteration.mutation.graphql';
import query from '../queries/iteration.query.graphql'; import query from '../queries/iteration.query.graphql';
import { getIterationPeriod } from '../utils'; import { getIterationPeriod } from '../utils';
...@@ -95,7 +95,8 @@ export default { ...@@ -95,7 +95,8 @@ export default {
return getIterationPeriod(this.iteration); return getIterationPeriod(this.iteration);
}, },
showDelete() { showDelete() {
return this.iteration.state !== iterationStates.closed; // We only support deleting iterations for manual cadences.
return !this.iteration.iterationCadence.automatic;
}, },
}, },
methods: { methods: {
......
...@@ -8,4 +8,8 @@ fragment IterationReport on Iteration { ...@@ -8,4 +8,8 @@ fragment IterationReport on Iteration {
state state
title title
webPath webPath
iterationCadence {
id
automatic
}
} }
import { mount } from '@vue/test-utils'; import { mount } from '@vue/test-utils';
import Vue, { nextTick } from 'vue'; import Vue, { nextTick } from 'vue';
import VueApollo from 'vue-apollo'; import VueApollo from 'vue-apollo';
import { GlFormInput } from '@gitlab/ui';
import IterationForm from 'ee/iterations/components/iteration_form.vue'; import IterationForm from 'ee/iterations/components/iteration_form.vue';
import readIteration from 'ee/iterations/queries/iteration.query.graphql'; import readIteration from 'ee/iterations/queries/iteration.query.graphql';
import createIteration from 'ee/iterations/queries/iteration_create.mutation.graphql'; import createIteration from 'ee/iterations/queries/iteration_create.mutation.graphql';
...@@ -15,14 +16,13 @@ import { convertToGraphQLId, getIdFromGraphQLId } from '~/graphql_shared/utils'; ...@@ -15,14 +16,13 @@ import { convertToGraphQLId, getIdFromGraphQLId } from '~/graphql_shared/utils';
import { dayAfter, formatDate } from '~/lib/utils/datetime_utility'; import { dayAfter, formatDate } from '~/lib/utils/datetime_utility';
import { import {
manualIterationCadence as cadence, manualIterationCadence as cadence,
mockGroupIterations, mockManualIterationNode as iteration,
mockIterationNode as iteration,
createMutationSuccess, createMutationSuccess,
createMutationFailure, createMutationFailure,
updateMutationSuccess, updateMutationSuccess,
emptyGroupIterationsSuccess, emptyGroupIterationsSuccess,
nonEmptyGroupIterationsSuccess, nonEmptyGroupIterationsSuccess,
readCadenceSuccess, readManualCadenceSuccess,
} from '../mock_data'; } from '../mock_data';
const baseUrl = '/cadences/'; const baseUrl = '/cadences/';
...@@ -35,6 +35,28 @@ function createMockApolloProvider(requestHandlers) { ...@@ -35,6 +35,28 @@ function createMockApolloProvider(requestHandlers) {
return createMockApollo(requestHandlers); return createMockApollo(requestHandlers);
} }
const mockGroupIterationsFactory = (nodes = [iteration]) => {
return {
data: {
group: {
id: 'gid://gitlab/Group/114',
iterations: {
nodes,
pageInfo: {
hasNextPage: true,
hasPreviousPage: true,
startCursor: 'first-item',
endCursor: 'last-item',
__typename: 'PageInfo',
},
__typename: 'IterationConnection',
},
__typename: 'Group',
},
},
};
};
describe('Iteration Form', () => { describe('Iteration Form', () => {
let wrapper; let wrapper;
let router; let router;
...@@ -44,7 +66,7 @@ describe('Iteration Form', () => { ...@@ -44,7 +66,7 @@ describe('Iteration Form', () => {
mutationQuery = createIteration, mutationQuery = createIteration,
mutationResult = createMutationSuccess, mutationResult = createMutationSuccess,
query = readIteration, query = readIteration,
result = mockGroupIterations, result = mockGroupIterationsFactory(),
resolverMock = jest.fn().mockResolvedValue(mutationResult), resolverMock = jest.fn().mockResolvedValue(mutationResult),
groupIterationsSuccess = emptyGroupIterationsSuccess, groupIterationsSuccess = emptyGroupIterationsSuccess,
} = {}) { } = {}) {
...@@ -52,7 +74,7 @@ describe('Iteration Form', () => { ...@@ -52,7 +74,7 @@ describe('Iteration Form', () => {
[query, jest.fn().mockResolvedValue(result)], [query, jest.fn().mockResolvedValue(result)],
[mutationQuery, resolverMock], [mutationQuery, resolverMock],
[groupIterationsInCadenceQuery, jest.fn().mockResolvedValue(groupIterationsSuccess)], [groupIterationsInCadenceQuery, jest.fn().mockResolvedValue(groupIterationsSuccess)],
[readCadence, jest.fn().mockResolvedValue(readCadenceSuccess)], [readCadence, jest.fn().mockResolvedValue(readManualCadenceSuccess)],
]); ]);
wrapper = extendedWrapper( wrapper = extendedWrapper(
mount(IterationForm, { mount(IterationForm, {
...@@ -78,10 +100,12 @@ describe('Iteration Form', () => { ...@@ -78,10 +100,12 @@ describe('Iteration Form', () => {
}); });
const findPageTitle = () => wrapper.findComponent({ ref: 'pageTitle' }); const findPageTitle = () => wrapper.findComponent({ ref: 'pageTitle' });
const findTitle = () => wrapper.find('#iteration-title'); const findTitle = () => wrapper.findByLabelText('Title');
const findDescription = () => wrapper.find('#iteration-description'); const findDescription = () => wrapper.findByLabelText('Description');
const findStartDate = () => wrapper.find('#iteration-start-date'); const findStartDate = () => wrapper.findByTestId('start-date');
const findDueDate = () => wrapper.find('#iteration-due-date'); const findStartDateInputText = () => findStartDate().find(GlFormInput).element.value;
const findDueDate = () => wrapper.findByTestId('due-date');
const findDueDateInputText = () => findDueDate().find(GlFormInput).element.value;
const findSaveButton = () => wrapper.findByTestId('save-iteration'); const findSaveButton = () => wrapper.findByTestId('save-iteration');
const findCancelButton = () => wrapper.findByTestId('cancel-iteration'); const findCancelButton = () => wrapper.findByTestId('cancel-iteration');
const clickSave = () => findSaveButton().trigger('click'); const clickSave = () => findSaveButton().trigger('click');
...@@ -112,11 +136,10 @@ describe('Iteration Form', () => { ...@@ -112,11 +136,10 @@ describe('Iteration Form', () => {
const startDate = '2020-05-05'; const startDate = '2020-05-05';
const dueDate = '2020-05-25'; const dueDate = '2020-05-25';
findTitle().vm.$emit('input', title); findTitle().setValue(title);
findDescription().setValue(description); findDescription().setValue(description);
findStartDate().vm.$emit('input', startDate); findStartDate().vm.$emit('input', new Date(startDate));
findDueDate().vm.$emit('input', dueDate); findDueDate().vm.$emit('input', new Date(dueDate));
await clickSave(); await clickSave();
expect(resolverMock).toHaveBeenCalledWith({ expect(resolverMock).toHaveBeenCalledWith({
...@@ -172,7 +195,7 @@ describe('Iteration Form', () => { ...@@ -172,7 +195,7 @@ describe('Iteration Form', () => {
'yyyy-mm-dd', 'yyyy-mm-dd',
); );
expect(findStartDate().element.value).toBe(expectedDate); expect(findStartDateInputText()).toBe(expectedDate);
}); });
}); });
...@@ -188,17 +211,17 @@ describe('Iteration Form', () => { ...@@ -188,17 +211,17 @@ describe('Iteration Form', () => {
it('uses cadence start date', () => { it('uses cadence start date', () => {
const expectedDate = cadence.startDate; const expectedDate = cadence.startDate;
expect(findStartDate().element.value).toBe(expectedDate); expect(findStartDateInputText()).toBe(expectedDate);
}); });
}); });
}); });
}); });
describe('Edit iteration', () => { describe('Edit iteration for manual cadence', () => {
beforeEach(() => { beforeEach(() => {
router.replace({ router.replace({
name: 'editIteration', name: 'editIteration',
params: { cadenceId: cadence.id, iterationId: iteration.id }, params: { cadenceId, iterationId },
}); });
}); });
...@@ -219,29 +242,31 @@ describe('Iteration Form', () => { ...@@ -219,29 +242,31 @@ describe('Iteration Form', () => {
expect(findTitle().element.value).toBe(iteration.title); expect(findTitle().element.value).toBe(iteration.title);
expect(findDescription().element.value).toBe(iteration.description); expect(findDescription().element.value).toBe(iteration.description);
expect(findStartDate().element.value).toBe(iteration.startDate); expect(findStartDateInputText()).toBe(iteration.startDate);
expect(findDueDate().element.value).toBe(iteration.dueDate); expect(findDueDateInputText()).toBe(iteration.dueDate);
}); });
it('shows update text on submit button', () => { it('shows update text on submit button', () => {
createComponent(); createComponent();
expect(findSaveButton().text()).toBe('Update iteration'); expect(findSaveButton().text()).toBe('Save changes');
}); });
it('triggers mutation with form data', async () => { it('triggers mutation with form data', async () => {
const resolverMock = jest.fn().mockResolvedValue(updateMutationSuccess); const resolverMock = jest.fn().mockResolvedValue(updateMutationSuccess);
createComponent({ mutationQuery: updateIteration, resolverMock }); createComponent({ mutationQuery: updateIteration, resolverMock });
await waitForPromises();
const title = 'Updated title'; const title = 'Updated title';
const description = 'Updated description'; const description = 'Updated description';
const startDate = '2020-05-06'; const startDate = '2020-05-06';
const dueDate = '2020-05-26'; const dueDate = '2020-05-26';
findTitle().vm.$emit('input', title); findTitle().setValue(title);
findDescription().setValue(description); findDescription().setValue(description);
findStartDate().vm.$emit('input', startDate); findStartDate().vm.$emit('input', new Date(startDate));
findDueDate().vm.$emit('input', dueDate); findDueDate().vm.$emit('input', new Date(dueDate));
clickSave(); clickSave();
await waitForPromises(); await waitForPromises();
...@@ -249,7 +274,7 @@ describe('Iteration Form', () => { ...@@ -249,7 +274,7 @@ describe('Iteration Form', () => {
expect(resolverMock).toHaveBeenCalledWith({ expect(resolverMock).toHaveBeenCalledWith({
input: { input: {
groupPath, groupPath,
id: iteration.id, id: iterationId,
title, title,
description, description,
startDate, startDate,
...@@ -264,6 +289,7 @@ describe('Iteration Form', () => { ...@@ -264,6 +289,7 @@ describe('Iteration Form', () => {
mutationQuery: updateIteration, mutationQuery: updateIteration,
resolverMock, resolverMock,
}); });
await waitForPromises();
clickSave(); clickSave();
await nextTick(); await nextTick();
...@@ -274,11 +300,11 @@ describe('Iteration Form', () => { ...@@ -274,11 +300,11 @@ describe('Iteration Form', () => {
expect(resolverMock).toHaveBeenCalledWith({ expect(resolverMock).toHaveBeenCalledWith({
input: { input: {
groupPath, groupPath,
id: iteration.id, id: iterationId,
startDate: '', startDate: iteration.startDate,
dueDate: '', dueDate: iteration.dueDate,
title: '', title: iteration.title,
description: '', description: iteration.description,
}, },
}); });
}); });
......
...@@ -181,7 +181,7 @@ describe('Iteration Form', () => { ...@@ -181,7 +181,7 @@ describe('Iteration Form', () => {
props: propsWithIteration, props: propsWithIteration,
}); });
expect(findSaveButton().text()).toBe('Update iteration'); expect(findSaveButton().text()).toBe('Save changes');
}); });
it('triggers mutation with form data', () => { it('triggers mutation with form data', () => {
......
...@@ -17,7 +17,7 @@ import { extendedWrapper } from 'helpers/vue_test_utils_helper'; ...@@ -17,7 +17,7 @@ import { extendedWrapper } from 'helpers/vue_test_utils_helper';
import { __ } from '~/locale'; import { __ } from '~/locale';
import { import {
mockIterationNode, mockIterationNode,
mockPastIterationNode, mockManualIterationNode,
createMockGroupIterations, createMockGroupIterations,
mockIterationNodeWithoutTitle, mockIterationNodeWithoutTitle,
mockProjectIterations, mockProjectIterations,
...@@ -155,8 +155,16 @@ describe('Iterations report', () => { ...@@ -155,8 +155,16 @@ describe('Iterations report', () => {
}); });
describe('delete iteration', () => { describe('delete iteration', () => {
it('does not show delete option for past iterations', async () => { it('does not show delete option when iteration belongs to automatic cadence', async () => {
mountComponent({ mockQueryResponse: createMockGroupIterations(mockPastIterationNode) }); mountComponent({ mockQueryResponse: createMockGroupIterations(mockIterationNode) });
await waitForPromises();
expect(findDeleteButton().exists()).toBe(false);
});
it('shows delete option when iteration belongs to automatic cadence', async () => {
mountComponent({ mockQueryResponse: createMockGroupIterations(mockManualIterationNode) });
await waitForPromises(); await waitForPromises();
......
...@@ -11,12 +11,21 @@ export const mockIterationNode = { ...@@ -11,12 +11,21 @@ export const mockIterationNode = {
title: 'top-level-iteration', title: 'top-level-iteration',
webPath: '/groups/top-level-group/-/iterations/4', webPath: '/groups/top-level-group/-/iterations/4',
scopedPath: '/groups/top-level-group/-/iterations/4', scopedPath: '/groups/top-level-group/-/iterations/4',
iterationCadence: {
__typename: 'IterationCadence',
id: 'gid://gitlab/Iterations::Cadence/72',
automatic: true,
},
__typename: 'Iteration', __typename: 'Iteration',
}; };
export const mockPastIterationNode = { export const mockManualIterationNode = {
...mockIterationNode, ...mockIterationNode,
state: iterationStates.closed, iterationCadence: {
__typename: 'IterationCadence',
id: 'gid://gitlab/Iterations::Cadence/72',
automatic: false,
},
}; };
export const mockIterationNodeWithoutTitle = { export const mockIterationNodeWithoutTitle = {
...@@ -171,7 +180,18 @@ export const nonEmptyGroupIterationsSuccess = { ...@@ -171,7 +180,18 @@ export const nonEmptyGroupIterationsSuccess = {
}, },
}; };
export const readCadenceSuccess = { export const readAutomaticCadenceSuccess = {
data: {
group: {
id: 'gid://gitlab/Group/114',
iterationCadences: {
nodes: [automaticIterationCadence],
},
},
},
};
export const readManualCadenceSuccess = {
data: { data: {
group: { group: {
id: 'gid://gitlab/Group/114', id: 'gid://gitlab/Group/114',
......
...@@ -21125,12 +21125,18 @@ msgstr "" ...@@ -21125,12 +21125,18 @@ msgstr ""
msgid "Iterations|Cadence name" msgid "Iterations|Cadence name"
msgstr "" msgstr ""
msgid "Iterations|Cancel"
msgstr ""
msgid "Iterations|Couldn't find iteration cadence" msgid "Iterations|Couldn't find iteration cadence"
msgstr "" msgstr ""
msgid "Iterations|Create cadence" msgid "Iterations|Create cadence"
msgstr "" msgstr ""
msgid "Iterations|Create iteration"
msgstr ""
msgid "Iterations|Delete cadence" msgid "Iterations|Delete cadence"
msgstr "" msgstr ""
...@@ -21140,9 +21146,15 @@ msgstr "" ...@@ -21140,9 +21146,15 @@ msgstr ""
msgid "Iterations|Delete iteration?" msgid "Iterations|Delete iteration?"
msgstr "" msgstr ""
msgid "Iterations|Description"
msgstr ""
msgid "Iterations|Done" msgid "Iterations|Done"
msgstr "" msgstr ""
msgid "Iterations|Due date"
msgstr ""
msgid "Iterations|Duration" msgid "Iterations|Duration"
msgstr "" msgstr ""
...@@ -40277,9 +40289,6 @@ msgstr "" ...@@ -40277,9 +40289,6 @@ msgstr ""
msgid "Update it" msgid "Update it"
msgstr "" msgstr ""
msgid "Update iteration"
msgstr ""
msgid "Update milestone" msgid "Update milestone"
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