Commit a3982e3c authored by Vitaly Slobodin's avatar Vitaly Slobodin

Merge branch 'psi-iteration-date-smarts' into 'master'

Prefill iteration date with sensible value

See merge request gitlab-org/gitlab!72165
parents 38bbc861 efac77f3
...@@ -2,12 +2,16 @@ ...@@ -2,12 +2,16 @@
import { GlAlert, GlButton, GlForm, GlFormInput } from '@gitlab/ui'; import { GlAlert, GlButton, GlForm, GlFormInput } from '@gitlab/ui';
import initDatePicker from '~/behaviors/date_picker'; import initDatePicker from '~/behaviors/date_picker';
import createFlash from '~/flash'; import createFlash from '~/flash';
import { dayAfter, formatDate } from '~/lib/utils/datetime_utility';
import { TYPE_ITERATIONS_CADENCE } from '~/graphql_shared/constants';
import { convertToGraphQLId, getIdFromGraphQLId } from '~/graphql_shared/utils'; import { convertToGraphQLId, getIdFromGraphQLId } from '~/graphql_shared/utils';
import { __, s__ } from '~/locale'; import { __, s__ } from '~/locale';
import MarkdownField from '~/vue_shared/components/markdown/field.vue'; import MarkdownField from '~/vue_shared/components/markdown/field.vue';
import readIteration from '../queries/iteration.query.graphql'; import readIteration from '../queries/iteration.query.graphql';
import createIteration from '../queries/iteration_create.mutation.graphql'; import createIteration from '../queries/iteration_create.mutation.graphql';
import readCadence from '../queries/iteration_cadence.query.graphql';
import updateIteration from '../queries/update_iteration.mutation.graphql'; import updateIteration from '../queries/update_iteration.mutation.graphql';
import iterationsInCadence from '../queries/group_iterations_in_cadence.query.graphql';
export default { export default {
cadencesList: { cadencesList: {
...@@ -40,13 +44,15 @@ export default { ...@@ -40,13 +44,15 @@ export default {
if (!iteration) { if (!iteration) {
this.error = s__('Iterations|Unable to find iteration.'); this.error = s__('Iterations|Unable to find iteration.');
return; return null;
} }
this.title = iteration.title; this.title = iteration.title;
this.description = iteration.description; this.description = iteration.description;
this.startDate = iteration.startDate; this.startDate = iteration.startDate;
this.dueDate = iteration.dueDate; this.dueDate = iteration.dueDate;
return iteration;
}, },
error(err) { error(err) {
this.error = err.message; this.error = err.message;
...@@ -59,6 +65,7 @@ export default { ...@@ -59,6 +65,7 @@ export default {
loading: false, loading: false,
error: '', error: '',
group: { iteration: {} }, group: { iteration: {} },
cadence: {},
title: '', title: '',
description: '', description: '',
startDate: '', startDate: '',
...@@ -85,9 +92,51 @@ export default { ...@@ -85,9 +92,51 @@ export default {
}; };
}, },
}, },
mounted() { async mounted() {
// TODO: utilize GlDatepicker instead of relying on this jQuery behavior // TODO: utilize GlDatepicker instead of relying on this jQuery behavior
initDatePicker(); initDatePicker();
// prefill start date for the New cadence form
// if there's iterations in the cadence, use last end_date + 1
// else use cadence startDate
if (!this.isEditing && this.cadenceId) {
const { data } = await this.$apollo.query({
query: iterationsInCadence,
variables: {
fullPath: this.fullPath,
iterationCadenceId: convertToGraphQLId(TYPE_ITERATIONS_CADENCE, this.cadenceId),
lastPageSize: 1,
state: 'all',
},
});
const iteration = data.workspace.iterations?.nodes[0];
if (iteration) {
this.startDate = formatDate(
dayAfter(new Date(iteration.dueDate), { utc: true }),
'yyyy-mm-dd',
);
} else {
const { data: cadenceData } = await this.$apollo.query({
query: readCadence,
variables: {
fullPath: this.fullPath,
id: this.cadenceId,
},
});
if (cadenceData.group) {
const cadence = cadenceData.group?.iterationCadences?.nodes[0];
if (!cadence) {
this.error = s__('Iterations|Unable to find iteration cadence.');
return;
}
this.startDate = cadence.startDate;
}
}
}
}, },
methods: { methods: {
save() { save() {
......
...@@ -9,6 +9,7 @@ import createMockApollo from 'helpers/mock_apollo_helper'; ...@@ -9,6 +9,7 @@ import createMockApollo from 'helpers/mock_apollo_helper';
import { TEST_HOST } from 'helpers/test_constants'; import { TEST_HOST } from 'helpers/test_constants';
import { extendedWrapper } from 'helpers/vue_test_utils_helper'; import { extendedWrapper } from 'helpers/vue_test_utils_helper';
import waitForPromises from 'helpers/wait_for_promises'; import waitForPromises from 'helpers/wait_for_promises';
import { manualIterationCadence } from '../mock_data';
const push = jest.fn(); const push = jest.fn();
const $router = { const $router = {
...@@ -28,17 +29,7 @@ describe('Iteration cadence form', () => { ...@@ -28,17 +29,7 @@ describe('Iteration cadence form', () => {
let wrapper; let wrapper;
const groupPath = 'gitlab-org'; const groupPath = 'gitlab-org';
const id = 72; const id = 72;
const iterationCadence = { const iterationCadence = manualIterationCadence;
id: `gid://gitlab/Iterations::Cadence/${id}`,
title: 'An iteration',
automatic: true,
rollOver: false,
durationInWeeks: '3',
description: 'The words',
duration: '3',
startDate: '2020-06-28',
iterationsInAdvance: '2',
};
const createMutationSuccess = { const createMutationSuccess = {
data: { result: { iterationCadence, errors: [] } }, data: { result: { iterationCadence, errors: [] } },
...@@ -53,7 +44,7 @@ describe('Iteration cadence form', () => { ...@@ -53,7 +44,7 @@ describe('Iteration cadence form', () => {
group: { group: {
id: 'gid://gitlab/Group/114', id: 'gid://gitlab/Group/114',
iterationCadences: { iterationCadences: {
nodes: [iterationCadence], nodes: [manualIterationCadence],
}, },
}, },
}, },
......
...@@ -5,13 +5,29 @@ import IterationForm from 'ee/iterations/components/iteration_form.vue'; ...@@ -5,13 +5,29 @@ 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';
import updateIteration from 'ee/iterations/queries/update_iteration.mutation.graphql'; import updateIteration from 'ee/iterations/queries/update_iteration.mutation.graphql';
import groupIterationsInCadenceQuery from 'ee/iterations/queries/group_iterations_in_cadence.query.graphql';
import readCadence from 'ee/iterations/queries/iteration_cadence.query.graphql';
import createRouter from 'ee/iterations/router'; import createRouter from 'ee/iterations/router';
import createMockApollo from 'helpers/mock_apollo_helper'; import createMockApollo from 'helpers/mock_apollo_helper';
import { extendedWrapper } from 'helpers/vue_test_utils_helper'; import { extendedWrapper } from 'helpers/vue_test_utils_helper';
import waitForPromises from 'helpers/wait_for_promises'; import waitForPromises from 'helpers/wait_for_promises';
import { convertToGraphQLId } from '~/graphql_shared/utils'; import { convertToGraphQLId, getIdFromGraphQLId } from '~/graphql_shared/utils';
import { dayAfter, formatDate } from '~/lib/utils/datetime_utility';
import {
manualIterationCadence as cadence,
mockGroupIterations,
mockIterationNode as iteration,
createMutationSuccess,
createMutationFailure,
updateMutationSuccess,
emptyGroupIterationsSuccess,
nonEmptyGroupIterationsSuccess,
readCadenceSuccess,
} from '../mock_data';
const baseUrl = '/cadences/'; const baseUrl = '/cadences/';
const iterationId = getIdFromGraphQLId(iteration.id);
const cadenceId = getIdFromGraphQLId(cadence.id);
function createMockApolloProvider(requestHandlers) { function createMockApolloProvider(requestHandlers) {
Vue.use(VueApollo); Vue.use(VueApollo);
...@@ -23,41 +39,20 @@ describe('Iteration Form', () => { ...@@ -23,41 +39,20 @@ describe('Iteration Form', () => {
let wrapper; let wrapper;
let router; let router;
const groupPath = 'gitlab-org'; const groupPath = 'gitlab-org';
const iterationId = 72;
const cadenceId = 2;
const iteration = {
id: `gid://gitlab/Iteration/${iterationId}`,
iid: 70,
title: 'An iteration',
state: 'opened',
webPath: '/test',
description: 'The words',
descriptionHtml: '<p>The words</p>',
startDate: '2020-06-28',
dueDate: '2020-07-05',
};
const readMutationSuccess = {
data: {
group: { id: 'gid://gitlab/Group/114', iterations: { nodes: [iteration] }, errors: [] },
},
};
const createMutationSuccess = { data: { iterationCreate: { iteration, errors: [] } } };
const createMutationFailure = {
data: { iterationCreate: { iteration, errors: ['alas, your data is unchanged'] } },
};
const updateMutationSuccess = { data: { updateIteration: { iteration, errors: [] } } };
function createComponent({ function createComponent({
mutationQuery = createIteration, mutationQuery = createIteration,
mutationResult = createMutationSuccess, mutationResult = createMutationSuccess,
query = readIteration, query = readIteration,
result = readMutationSuccess, result = mockGroupIterations,
resolverMock = jest.fn().mockResolvedValue(mutationResult), resolverMock = jest.fn().mockResolvedValue(mutationResult),
groupIterationsSuccess = emptyGroupIterationsSuccess,
} = {}) { } = {}) {
const apolloProvider = createMockApolloProvider([ const apolloProvider = createMockApolloProvider([
[query, jest.fn().mockResolvedValue(result)], [query, jest.fn().mockResolvedValue(result)],
[mutationQuery, resolverMock], [mutationQuery, resolverMock],
[groupIterationsInCadenceQuery, jest.fn().mockResolvedValue(groupIterationsSuccess)],
[readCadence, jest.fn().mockResolvedValue(readCadenceSuccess)],
]); ]);
wrapper = extendedWrapper( wrapper = extendedWrapper(
mount(IterationForm, { mount(IterationForm, {
...@@ -95,7 +90,10 @@ describe('Iteration Form', () => { ...@@ -95,7 +90,10 @@ describe('Iteration Form', () => {
const resolverMock = jest.fn().mockResolvedValue(createMutationSuccess); const resolverMock = jest.fn().mockResolvedValue(createMutationSuccess);
beforeEach(() => { beforeEach(() => {
router.replace({ name: 'newIteration', params: { cadenceId, iterationId: undefined } }); router.replace({
name: 'newIteration',
params: { cadenceId, iterationId: undefined },
});
createComponent({ resolverMock }); createComponent({ resolverMock });
}); });
...@@ -126,7 +124,7 @@ describe('Iteration Form', () => { ...@@ -126,7 +124,7 @@ describe('Iteration Form', () => {
groupPath, groupPath,
title, title,
description, description,
iterationsCadenceId: convertToGraphQLId('Iterations::Cadence', cadenceId), iterationsCadenceId: convertToGraphQLId('Iterations::Cadence', cadence.id),
startDate, startDate,
dueDate, dueDate,
}, },
...@@ -159,11 +157,49 @@ describe('Iteration Form', () => { ...@@ -159,11 +157,49 @@ describe('Iteration Form', () => {
}); });
}); });
}); });
describe('prefill start date field', () => {
describe('cadence with iterations', () => {
it('starts next day after the last iteration', async () => {
await createComponent({
groupIterationsSuccess: nonEmptyGroupIterationsSuccess,
});
await waitForPromises();
const expectedDate = formatDate(
dayAfter(new Date(iteration.dueDate), { utc: true }),
'yyyy-mm-dd',
);
expect(findStartDate().element.value).toBe(expectedDate);
});
});
describe('manual cadence without iterations', () => {
beforeEach(async () => {
await createComponent({
groupIterationsSuccess: emptyGroupIterationsSuccess,
});
await nextTick();
});
it('uses cadence start date', () => {
const expectedDate = cadence.startDate;
expect(findStartDate().element.value).toBe(expectedDate);
});
});
});
}); });
describe('Edit iteration', () => { describe('Edit iteration', () => {
beforeEach(() => { beforeEach(() => {
router.replace({ name: 'editIteration', params: { cadenceId, iterationId } }); router.replace({
name: 'editIteration',
params: { cadenceId: cadence.id, iterationId: iteration.id },
});
}); });
afterEach(() => { afterEach(() => {
...@@ -212,7 +248,7 @@ describe('Iteration Form', () => { ...@@ -212,7 +248,7 @@ describe('Iteration Form', () => {
expect(resolverMock).toHaveBeenCalledWith({ expect(resolverMock).toHaveBeenCalledWith({
input: { input: {
groupPath, groupPath,
id: iterationId, id: iteration.id,
title, title,
description, description,
startDate, startDate,
...@@ -236,7 +272,7 @@ describe('Iteration Form', () => { ...@@ -236,7 +272,7 @@ describe('Iteration Form', () => {
expect(resolverMock).toHaveBeenCalledWith({ expect(resolverMock).toHaveBeenCalledWith({
input: { input: {
groupPath, groupPath,
id: iterationId, id: iteration.id,
startDate: '', startDate: '',
dueDate: '', dueDate: '',
title: '', title: '',
......
...@@ -36,3 +36,78 @@ export const mockProjectIterations = { ...@@ -36,3 +36,78 @@ export const mockProjectIterations = {
}, },
}, },
}; };
export const manualIterationCadence = {
id: `gid://gitlab/Iterations::Cadence/72`,
title: 'A manual iteration cadence',
automatic: true,
rollOver: false,
durationInWeeks: '3',
description: 'The words',
duration: '3',
startDate: '2020-06-28',
iterationsInAdvance: '2',
};
export const createMutationSuccess = {
data: { iterationCreate: { iteration: mockIterationNode, errors: [] } },
};
export const createMutationFailure = {
data: {
iterationCreate: { iteration: mockIterationNode, errors: ['alas, your data is unchanged'] },
},
};
export const updateMutationSuccess = {
data: { updateIteration: { iteration: mockIterationNode, errors: [] } },
};
export const emptyGroupIterationsSuccess = {
data: {
workspace: {
id: 'gid://gitlab/Group/114',
iterations: {
nodes: [],
pageInfo: {
hasNextPage: false,
hasPreviousPage: false,
startCursor: '',
endCursor: '',
},
},
},
},
};
export const nonEmptyGroupIterationsSuccess = {
data: {
workspace: {
id: 1,
iterations: {
nodes: [
{
...mockIterationNode,
scopedPath: '/',
},
],
pageInfo: {
hasNextPage: false,
hasPreviousPage: false,
startCursor: '',
endCursor: '',
},
},
},
},
};
export const readCadenceSuccess = {
data: {
group: {
id: 'gid://gitlab/Group/114',
iterationCadences: {
nodes: [manualIterationCadence],
},
},
},
};
...@@ -19597,6 +19597,9 @@ msgstr "" ...@@ -19597,6 +19597,9 @@ msgstr ""
msgid "Iterations|Title" msgid "Iterations|Title"
msgstr "" msgstr ""
msgid "Iterations|Unable to find iteration cadence."
msgstr ""
msgid "Iterations|Unable to find iteration." msgid "Iterations|Unable to find iteration."
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