Commit 037369be authored by Martin Wortschack's avatar Martin Wortschack

Merge branch '267537-edit-value-stream-vuex' into 'master'

[FE] VSA - Vuex actions for editing a value stream

See merge request gitlab-org/gitlab!53568
parents ef45fde7 ca3b7038
......@@ -8,7 +8,7 @@ export const I18N = {
FORM_CREATED: s__("CreateValueStreamForm|'%{name}' Value Stream created"),
RECOVER_HIDDEN_STAGE: s__('CreateValueStreamForm|Recover hidden stage'),
RESTORE_HIDDEN_STAGE: s__('CreateValueStreamForm|Restore stage'),
RESTORE_STAGES: s__('CreateValueStreamForm|Restore defaults'),
RESTORE_DEFAULTS: s__('CreateValueStreamForm|Restore defaults'),
RECOVER_STAGE_TITLE: s__('CreateValueStreamForm|Default stages'),
RECOVER_STAGES_VISIBLE: s__('CreateValueStreamForm|All default stages are currently visible'),
SELECT_START_EVENT: s__('CreateValueStreamForm|Select start event'),
......
......@@ -354,6 +354,24 @@ export const createValueStream = ({ commit, dispatch, getters }, data) => {
});
};
export const updateValueStream = (
{ commit, dispatch, getters },
{ id: valueStreamId, ...data },
) => {
const { currentGroupPath } = getters;
commit(types.REQUEST_UPDATE_VALUE_STREAM);
return Api.cycleAnalyticsUpdateValueStream({ groupId: currentGroupPath, valueStreamId, data })
.then(({ data: newValueStream }) => {
commit(types.RECEIVE_UPDATE_VALUE_STREAM_SUCCESS, newValueStream);
return dispatch('fetchCycleAnalyticsData');
})
.catch(({ response } = {}) => {
const { data: { message, payload: { errors } } = null } = response;
commit(types.RECEIVE_UPDATE_VALUE_STREAM_ERROR, { message, errors, data });
});
};
export const deleteValueStream = ({ commit, dispatch, getters }, valueStreamId) => {
const { currentGroupPath } = getters;
commit(types.REQUEST_DELETE_VALUE_STREAM);
......
......@@ -39,6 +39,10 @@ export const REQUEST_CREATE_VALUE_STREAM = 'REQUEST_CREATE_VALUE_STREAM';
export const RECEIVE_CREATE_VALUE_STREAM_SUCCESS = 'RECEIVE_CREATE_VALUE_STREAM_SUCCESS';
export const RECEIVE_CREATE_VALUE_STREAM_ERROR = 'RECEIVE_CREATE_VALUE_STREAM_ERROR';
export const REQUEST_UPDATE_VALUE_STREAM = 'REQUEST_UPDATE_VALUE_STREAM';
export const RECEIVE_UPDATE_VALUE_STREAM_SUCCESS = 'RECEIVE_UPDATE_VALUE_STREAM_SUCCESS';
export const RECEIVE_UPDATE_VALUE_STREAM_ERROR = 'RECEIVE_UPDATE_VALUE_STREAM_ERROR';
export const REQUEST_DELETE_VALUE_STREAM = 'REQUEST_DELETE_VALUE_STREAM';
export const RECEIVE_DELETE_VALUE_STREAM_SUCCESS = 'RECEIVE_DELETE_VALUE_STREAM_SUCCESS';
export const RECEIVE_DELETE_VALUE_STREAM_ERROR = 'RECEIVE_DELETE_VALUE_STREAM_ERROR';
......
......@@ -129,7 +129,21 @@ export default {
[types.RECEIVE_CREATE_VALUE_STREAM_SUCCESS](state, valueStream) {
state.isCreatingValueStream = false;
state.createValueStreamErrors = {};
state.selectedValueStream = convertObjectPropsToCamelCase(valueStream);
state.selectedValueStream = convertObjectPropsToCamelCase(valueStream, { deep: true });
},
[types.REQUEST_UPDATE_VALUE_STREAM](state) {
state.isEditingValueStream = true;
state.createValueStreamErrors = {};
},
[types.RECEIVE_UPDATE_VALUE_STREAM_ERROR](state, { data: { stages = [] }, errors = {} }) {
const { stages: stageErrors = {}, ...rest } = errors;
state.createValueStreamErrors = { ...rest, stages: prepareStageErrors(stages, stageErrors) };
state.isEditingValueStream = false;
},
[types.RECEIVE_UPDATE_VALUE_STREAM_SUCCESS](state, valueStream) {
state.isEditingValueStream = false;
state.createValueStreamErrors = {};
state.selectedValueStream = convertObjectPropsToCamelCase(valueStream, { deep: true });
},
[types.REQUEST_DELETE_VALUE_STREAM](state) {
state.isDeletingValueStream = true;
......@@ -145,7 +159,7 @@ export default {
state.selectedValueStream = null;
},
[types.SET_SELECTED_VALUE_STREAM](state, valueStream) {
state.selectedValueStream = convertObjectPropsToCamelCase(valueStream);
state.selectedValueStream = convertObjectPropsToCamelCase(valueStream, { deep: true });
},
[types.REQUEST_VALUE_STREAMS](state) {
state.isLoadingValueStreams = true;
......
......@@ -22,6 +22,7 @@ export default () => ({
isLoadingValueStreams: false,
isCreatingValueStream: false,
isEditingValueStream: false,
isDeletingValueStream: false,
createValueStreamErrors: {},
......
......@@ -194,6 +194,13 @@ export default {
return axios.post(url, data);
},
cycleAnalyticsUpdateValueStream({ groupId, valueStreamId, data }) {
const url = Api.buildUrl(this.cycleAnalyticsValueStreamPath)
.replace(':id', groupId)
.replace(':value_stream_id', valueStreamId);
return axios.put(url, data);
},
cycleAnalyticsDeleteValueStream(groupId, valueStreamId) {
const url = Api.buildUrl(this.cycleAnalyticsValueStreamPath)
.replace(':id', groupId)
......
......@@ -16,6 +16,13 @@ import {
valueStreams,
} from '../mock_data';
const mockStartEventIdentifier = 'issue_first_mentioned_in_commit';
const mockEndEventIdentifier = 'issue_first_added_to_board';
const mockEvents = {
startEventIdentifier: mockStartEventIdentifier,
endEventIdentifier: mockEndEventIdentifier,
};
const group = { fullPath: 'fake_group_full_path' };
const milestonesPath = 'fake_milestones_path';
const labelsPath = 'fake_labels_path';
......@@ -904,7 +911,18 @@ describe('Value Stream Analytics actions', () => {
});
describe('createValueStream', () => {
const payload = { name: 'cool value stream' };
const payload = {
name: 'cool value stream',
stages: [
{
...selectedStage,
...mockEvents,
id: null,
},
{ ...hiddenStage, ...mockEvents },
],
};
const createResp = { id: 'new value stream', is_custom: true, ...payload };
beforeEach(() => {
......@@ -957,6 +975,63 @@ describe('Value Stream Analytics actions', () => {
});
});
describe('updateValueStream', () => {
const payload = {
name: 'cool value stream',
stages: [
{
...selectedStage,
...mockEvents,
id: 'stage-1',
},
{ ...hiddenStage, ...mockEvents },
],
};
const updateResp = { id: 'new value stream', is_custom: true, ...payload };
beforeEach(() => {
state = { currentGroup };
});
describe('with no errors', () => {
beforeEach(() => {
mock.onPut(endpoints.valueStreamData).replyOnce(httpStatusCodes.OK, updateResp);
});
it(`commits the ${types.REQUEST_UPDATE_VALUE_STREAM} and ${types.RECEIVE_UPDATE_VALUE_STREAM_SUCCESS} actions`, () => {
return testAction(
actions.updateValueStream,
payload,
state,
[
{ type: types.REQUEST_UPDATE_VALUE_STREAM },
{ type: types.RECEIVE_UPDATE_VALUE_STREAM_SUCCESS, payload: updateResp },
],
[{ type: 'fetchCycleAnalyticsData' }],
);
});
});
describe('with errors', () => {
const errors = { name: ['is taken'] };
const message = { message: 'error' };
const resp = { message, payload: { errors } };
beforeEach(() => {
mock.onPut(endpoints.valueStreamData).replyOnce(httpStatusCodes.NOT_FOUND, resp);
});
it(`commits the ${types.REQUEST_UPDATE_VALUE_STREAM} and ${types.RECEIVE_UPDATE_VALUE_STREAM_ERROR} actions `, () => {
return testAction(actions.updateValueStream, payload, state, [
{ type: types.REQUEST_UPDATE_VALUE_STREAM },
{
type: types.RECEIVE_UPDATE_VALUE_STREAM_ERROR,
payload: { message, data: payload, errors },
},
]);
});
});
});
describe('deleteValueStream', () => {
const payload = 'my-fake-value-stream';
......
......@@ -49,6 +49,10 @@ describe('Value Stream Analytics mutations', () => {
${types.RECEIVE_CREATE_VALUE_STREAM_SUCCESS} | ${'isCreatingValueStream'} | ${false}
${types.REQUEST_CREATE_VALUE_STREAM} | ${'createValueStreamErrors'} | ${{}}
${types.RECEIVE_CREATE_VALUE_STREAM_SUCCESS} | ${'createValueStreamErrors'} | ${{}}
${types.REQUEST_UPDATE_VALUE_STREAM} | ${'isEditingValueStream'} | ${true}
${types.RECEIVE_UPDATE_VALUE_STREAM_SUCCESS} | ${'isEditingValueStream'} | ${false}
${types.REQUEST_UPDATE_VALUE_STREAM} | ${'createValueStreamErrors'} | ${{}}
${types.RECEIVE_UPDATE_VALUE_STREAM_SUCCESS} | ${'createValueStreamErrors'} | ${{}}
${types.REQUEST_DELETE_VALUE_STREAM} | ${'isDeletingValueStream'} | ${true}
${types.RECEIVE_DELETE_VALUE_STREAM_SUCCESS} | ${'isDeletingValueStream'} | ${false}
${types.REQUEST_DELETE_VALUE_STREAM} | ${'deleteValueStreamError'} | ${null}
......@@ -61,17 +65,32 @@ describe('Value Stream Analytics mutations', () => {
expect(state[stateKey]).toEqual(value);
});
const valueStreamErrors = {
data: { stages },
errors: {
name: ['is required'],
stages: { 1: { name: "Can't be blank" } },
},
};
const expectedValueStreamErrors = {
name: ['is required'],
stages: [{}, { name: "Can't be blank" }, {}, {}, {}, {}, {}, {}],
};
it.each`
mutation | payload | expectedState
${types.SET_FEATURE_FLAGS} | ${{ hasDurationChart: true }} | ${{ featureFlags: { hasDurationChart: true } }}
${types.SET_SELECTED_PROJECTS} | ${selectedProjects} | ${{ selectedProjects }}
${types.SET_DATE_RANGE} | ${{ startDate, endDate }} | ${{ startDate, endDate }}
${types.SET_SELECTED_STAGE} | ${{ id: 'first-stage' }} | ${{ selectedStage: { id: 'first-stage' } }}
${types.RECEIVE_CREATE_VALUE_STREAM_ERROR} | ${{ data: { stages }, errors: { name: ['is required'] } }} | ${{ createValueStreamErrors: { name: ['is required'] }, isCreatingValueStream: false }}
${types.RECEIVE_DELETE_VALUE_STREAM_ERROR} | ${'Some error occurred'} | ${{ deleteValueStreamError: 'Some error occurred' }}
${types.RECEIVE_VALUE_STREAMS_SUCCESS} | ${valueStreams} | ${{ valueStreams, isLoadingValueStreams: false }}
${types.SET_SELECTED_VALUE_STREAM} | ${valueStreams[1].id} | ${{ selectedValueStream: {} }}
${types.RECEIVE_CREATE_VALUE_STREAM_SUCCESS} | ${valueStreams[1]} | ${{ selectedValueStream: valueStreams[1] }}
mutation | payload | expectedState
${types.SET_FEATURE_FLAGS} | ${{ hasDurationChart: true }} | ${{ featureFlags: { hasDurationChart: true } }}
${types.SET_SELECTED_PROJECTS} | ${selectedProjects} | ${{ selectedProjects }}
${types.SET_DATE_RANGE} | ${{ startDate, endDate }} | ${{ startDate, endDate }}
${types.SET_SELECTED_STAGE} | ${{ id: 'first-stage' }} | ${{ selectedStage: { id: 'first-stage' } }}
${types.RECEIVE_CREATE_VALUE_STREAM_ERROR} | ${valueStreamErrors} | ${{ createValueStreamErrors: expectedValueStreamErrors, isCreatingValueStream: false }}
${types.RECEIVE_UPDATE_VALUE_STREAM_ERROR} | ${valueStreamErrors} | ${{ createValueStreamErrors: expectedValueStreamErrors, isEditingValueStream: false }}
${types.RECEIVE_DELETE_VALUE_STREAM_ERROR} | ${'Some error occurred'} | ${{ deleteValueStreamError: 'Some error occurred' }}
${types.RECEIVE_VALUE_STREAMS_SUCCESS} | ${valueStreams} | ${{ valueStreams, isLoadingValueStreams: false }}
${types.SET_SELECTED_VALUE_STREAM} | ${valueStreams[1].id} | ${{ selectedValueStream: {} }}
${types.RECEIVE_CREATE_VALUE_STREAM_SUCCESS} | ${valueStreams[1]} | ${{ selectedValueStream: valueStreams[1] }}
${types.RECEIVE_UPDATE_VALUE_STREAM_SUCCESS} | ${valueStreams[1]} | ${{ selectedValueStream: valueStreams[1] }}
`(
'$mutation with payload $payload will update state with $expectedState',
({ mutation, payload, expectedState }) => {
......
......@@ -374,6 +374,24 @@ describe('Api', () => {
});
});
describe('cycleAnalyticsUpdateValueStream', () => {
it('updates the custom value stream data', (done) => {
const response = {};
const customValueStream = { name: 'cool-value-stream-stage', stages: [] };
const expectedUrl = valueStreamBaseUrl({ resource: `value_streams/${valueStreamId}` });
mock.onPut(expectedUrl).reply(httpStatus.OK, response);
Api.cycleAnalyticsUpdateValueStream({ groupId, valueStreamId, data: customValueStream })
.then(({ data, config: { data: reqData, url } }) => {
expect(data).toEqual(response);
expect(JSON.parse(reqData)).toMatchObject(customValueStream);
expect(url).toEqual(expectedUrl);
})
.then(done)
.catch(done.fail);
});
});
describe('cycleAnalyticsDeleteValueStream', () => {
it('delete the custom value stream', (done) => {
const response = {};
......
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