Commit b4758cfa authored by Ezekiel Kigbo's avatar Ezekiel Kigbo

FE Edit custom stage

Added vuex actions, mutations and state to
edit custom cycle analytics stages. Additionally
the isAddingStage state was split into
isCreatingStage and isEditingStage to reflect the
states of the form
parent 357b9ec5
...@@ -55,8 +55,9 @@ export default { ...@@ -55,8 +55,9 @@ export default {
'isLoadingChartData', 'isLoadingChartData',
'isLoadingDurationChart', 'isLoadingDurationChart',
'isEmptyStage', 'isEmptyStage',
'isAddingCustomStage',
'isSavingCustomStage', 'isSavingCustomStage',
'isCreatingCustomStage',
'isEditingCustomStage',
'selectedGroup', 'selectedGroup',
'selectedStageId', 'selectedStageId',
'stages', 'stages',
...@@ -68,6 +69,7 @@ export default { ...@@ -68,6 +69,7 @@ export default {
'startDate', 'startDate',
'endDate', 'endDate',
'tasksByType', 'tasksByType',
'customStageFormInitData',
]), ]),
...mapGetters([ ...mapGetters([
'currentStage', 'currentStage',
...@@ -117,11 +119,12 @@ export default { ...@@ -117,11 +119,12 @@ export default {
'showCustomStageForm', 'showCustomStageForm',
'setDateRange', 'setDateRange',
'fetchTasksByTypeData', 'fetchTasksByTypeData',
'updateSelectedDurationChartStages',
'createCustomStage', 'createCustomStage',
'updateStage', 'updateStage',
'removeStage', 'removeStage',
'updateSelectedDurationChartStages',
'setFeatureFlags', 'setFeatureFlags',
'editCustomStage',
]), ]),
onGroupSelect(group) { onGroupSelect(group) {
this.setSelectedGroup(group); this.setSelectedGroup(group);
...@@ -140,6 +143,9 @@ export default { ...@@ -140,6 +143,9 @@ export default {
onShowAddStageForm() { onShowAddStageForm() {
this.showCustomStageForm(); this.showCustomStageForm();
}, },
onShowEditStageForm(initData = {}) {
this.editCustomStage(initData);
},
initDateRange() { initDateRange() {
const endDate = new Date(Date.now()); const endDate = new Date(Date.now());
const startDate = getDateInPast(endDate, DEFAULT_DAYS_IN_PAST); const startDate = getDateInPast(endDate, DEFAULT_DAYS_IN_PAST);
...@@ -243,15 +249,18 @@ export default { ...@@ -243,15 +249,18 @@ export default {
:stages="stages" :stages="stages"
:is-loading="isLoadingStage" :is-loading="isLoadingStage"
:is-empty-stage="isEmptyStage" :is-empty-stage="isEmptyStage"
:is-adding-custom-stage="isAddingCustomStage"
:is-saving-custom-stage="isSavingCustomStage" :is-saving-custom-stage="isSavingCustomStage"
:is-creating-custom-stage="isCreatingCustomStage"
:is-editing-custom-stage="isEditingCustomStage"
:current-stage-events="currentStageEvents" :current-stage-events="currentStageEvents"
:custom-stage-form-events="customStageFormEvents" :custom-stage-form-events="customStageFormEvents"
:labels="labels" :labels="labels"
:no-data-svg-path="noDataSvgPath" :no-data-svg-path="noDataSvgPath"
:no-access-svg-path="noAccessSvgPath" :no-access-svg-path="noAccessSvgPath"
:can-edit-stages="hasCustomizableCycleAnalytics" :can-edit-stages="hasCustomizableCycleAnalytics"
:custom-stage-form-init-data="customStageFormInitData"
@selectStage="onStageSelect" @selectStage="onStageSelect"
@editStage="onShowEditStageForm"
@showAddStageForm="onShowAddStageForm" @showAddStageForm="onShowAddStageForm"
@submit="onCreateCustomStage" @submit="onCreateCustomStage"
@hideStage="onUpdateStage" @hideStage="onUpdateStage"
......
...@@ -2,7 +2,9 @@ ...@@ -2,7 +2,9 @@
import { isEqual } from 'underscore'; import { isEqual } from 'underscore';
import { GlButton, GlFormGroup, GlFormInput, GlFormSelect, GlLoadingIcon } from '@gitlab/ui'; import { GlButton, GlFormGroup, GlFormInput, GlFormSelect, GlLoadingIcon } from '@gitlab/ui';
import { s__ } from '~/locale'; import { s__ } from '~/locale';
import { convertToSnakeCase } from '~/lib/utils/text_utility';
import LabelsSelector from './labels_selector.vue'; import LabelsSelector from './labels_selector.vue';
import { STAGE_ACTIONS } from '../constants';
import { import {
isStartEvent, isStartEvent,
isLabelEvent, isLabelEvent,
...@@ -13,13 +15,20 @@ import { ...@@ -13,13 +15,20 @@ import {
} from '../utils'; } from '../utils';
const initFields = { const initFields = {
name: '', name: null,
startEvent: '', startEventIdentifier: null,
startEventLabel: null, startEventLabelId: null,
stopEvent: '', endEventIdentifier: null,
stopEventLabel: null, endEventLabelId: null,
}; };
// TODO: should be a util / use a util if exists...
const snakeFields = (fields = {}) =>
Object.entries(fields).reduce((acc, curr) => {
const [key, value] = curr;
return { ...acc, [convertToSnakeCase(key)]: value };
}, {});
export default { export default {
components: { components: {
GlButton, GlButton,
...@@ -50,11 +59,16 @@ export default { ...@@ -50,11 +59,16 @@ export default {
required: false, required: false,
default: false, default: false,
}, },
isEditingCustomStage: {
type: Boolean,
required: false,
default: false,
},
}, },
data() { data() {
return { return {
fields: { fields: {
...initFields, ...this.initialFields,
}, },
}; };
}, },
...@@ -65,30 +79,34 @@ export default { ...@@ -65,30 +79,34 @@ export default {
...this.events.filter(isStartEvent).map(eventToOption), ...this.events.filter(isStartEvent).map(eventToOption),
]; ];
}, },
stopEventOptions() { endEventOptions() {
const stopEvents = getAllowedEndEvents(this.events, this.fields.startEvent); const endEvents = getAllowedEndEvents(this.events, this.fields.startEventIdentifier);
return [ return [
{ value: null, text: s__('CustomCycleAnalytics|Select stop event') }, { value: null, text: s__('CustomCycleAnalytics|Select stop event') },
...eventsByIdentifier(this.events, stopEvents).map(eventToOption), ...eventsByIdentifier(this.events, endEvents).map(eventToOption),
]; ];
}, },
hasStartEvent() { hasStartEvent() {
return this.fields.startEvent; return this.fields.startEventIdentifier;
}, },
startEventRequiresLabel() { startEventRequiresLabel() {
return isLabelEvent(this.labelEvents, this.fields.startEvent); return isLabelEvent(this.labelEvents, this.fields.startEventIdentifier);
}, },
stopEventRequiresLabel() { endEventRequiresLabel() {
return isLabelEvent(this.labelEvents, this.fields.stopEvent); return isLabelEvent(this.labelEvents, this.fields.endEventIdentifier);
}, },
isComplete() { isComplete() {
if (!this.hasValidStartAndStopEventPair) return false; if (!this.hasValidStartAndEndEventPair) return false;
const requiredFields = [this.fields.startEvent, this.fields.stopEvent, this.fields.name]; const requiredFields = [
this.fields.startEventIdentifier,
this.fields.endEventIdentifier,
this.fields.name,
];
if (this.startEventRequiresLabel) { if (this.startEventRequiresLabel) {
requiredFields.push(this.fields.startEventLabel); requiredFields.push(this.fields.startEventLabelId);
} }
if (this.stopEventRequiresLabel) { if (this.endEventRequiresLabel) {
requiredFields.push(this.fields.stopEventLabel); requiredFields.push(this.fields.endEventLabelId);
} }
return requiredFields.every( return requiredFields.every(
fieldValue => fieldValue && (fieldValue.length > 0 || fieldValue > 0), fieldValue => fieldValue && (fieldValue.length > 0 || fieldValue > 0),
...@@ -97,21 +115,31 @@ export default { ...@@ -97,21 +115,31 @@ export default {
isDirty() { isDirty() {
return !isEqual(this.initialFields, this.fields); return !isEqual(this.initialFields, this.fields);
}, },
hasValidStartAndStopEventPair() { hasValidStartAndEndEventPair() {
const { const {
fields: { startEvent, stopEvent }, fields: { startEventIdentifier, endEventIdentifier },
} = this; } = this;
if (startEvent && stopEvent) { if (startEventIdentifier && endEventIdentifier) {
const stopEvents = getAllowedEndEvents(this.events, startEvent); const endEvents = getAllowedEndEvents(this.events, startEventIdentifier);
return stopEvents.length && stopEvents.includes(stopEvent); return endEvents.length && endEvents.includes(endEventIdentifier);
} }
return true; return true;
}, },
stopEventError() { endEventError() {
return !this.hasValidStartAndStopEventPair return !this.hasValidStartAndEndEventPair
? s__('CustomCycleAnalytics|Start event changed, please select a valid stop event') ? s__('CustomCycleAnalytics|Start event changed, please select a valid stop event')
: null; : null;
}, },
saveStageText() {
return this.isEditingCustomStage
? s__('CustomCycleAnalytics|Edit stage')
: s__('CustomCycleAnalytics|Add stage');
},
formTitle() {
return this.isEditingCustomStage
? s__('CustomCycleAnalytics|Editing stage')
: s__('CustomCycleAnalytics|New stage');
},
}, },
mounted() { mounted() {
this.labelEvents = getLabelEventsIdentifiers(this.events); this.labelEvents = getLabelEventsIdentifiers(this.events);
...@@ -122,14 +150,10 @@ export default { ...@@ -122,14 +150,10 @@ export default {
this.$emit('cancel'); this.$emit('cancel');
}, },
handleSave() { handleSave() {
const { startEvent, startEventLabel, stopEvent, stopEventLabel, name } = this.fields; this.$emit(
this.$emit('submit', { this.isEditingCustomStage ? STAGE_ACTIONS.EDIT : STAGE_ACTIONS.SAVE,
name, snakeFields(this.fields),
start_event_identifier: startEvent, );
start_event_label_id: startEventLabel,
end_event_identifier: stopEvent,
end_event_label_id: stopEventLabel,
});
}, },
handleSelectLabel(key, labelId = null) { handleSelectLabel(key, labelId = null) {
this.fields[key] = labelId; this.fields[key] = labelId;
...@@ -143,7 +167,7 @@ export default { ...@@ -143,7 +167,7 @@ export default {
<template> <template>
<form class="custom-stage-form m-4 mt-0"> <form class="custom-stage-form m-4 mt-0">
<div class="mb-1"> <div class="mb-1">
<h4>{{ s__('CustomCycleAnalytics|New stage') }}</h4> <h4>{{ formTitle }}</h4>
</div> </div>
<gl-form-group :label="s__('CustomCycleAnalytics|Name')"> <gl-form-group :label="s__('CustomCycleAnalytics|Name')">
<gl-form-input <gl-form-input
...@@ -159,8 +183,8 @@ export default { ...@@ -159,8 +183,8 @@ export default {
<div :class="[startEventRequiresLabel ? 'w-50 mr-1' : 'w-100']"> <div :class="[startEventRequiresLabel ? 'w-50 mr-1' : 'w-100']">
<gl-form-group :label="s__('CustomCycleAnalytics|Start event')"> <gl-form-group :label="s__('CustomCycleAnalytics|Start event')">
<gl-form-select <gl-form-select
v-model="fields.startEvent" v-model="fields.startEventIdentifier"
name="custom-stage-start-event" name="add-stage-start-event"
:required="true" :required="true"
:options="startEventOptions" :options="startEventOptions"
/> />
...@@ -170,41 +194,41 @@ export default { ...@@ -170,41 +194,41 @@ export default {
<gl-form-group :label="s__('CustomCycleAnalytics|Start event label')"> <gl-form-group :label="s__('CustomCycleAnalytics|Start event label')">
<labels-selector <labels-selector
:labels="labels" :labels="labels"
:selected-label-id="fields.startEventLabel" :selected-label-id="fields.startEventLabelId"
name="custom-stage-start-event-label" name="add-stage-start-event-label"
@selectLabel="labelId => handleSelectLabel('startEventLabel', labelId)" @selectLabel="labelId => handleSelectLabel('startEventLabelId', labelId)"
@clearLabel="handleClearLabel('startEventLabel')" @clearLabel="handleClearLabel('startEventLabelId')"
/> />
</gl-form-group> </gl-form-group>
</div> </div>
</div> </div>
<div class="d-flex" :class="{ 'justify-content-between': stopEventRequiresLabel }"> <div class="d-flex" :class="{ 'justify-content-between': endEventRequiresLabel }">
<div :class="[stopEventRequiresLabel ? 'w-50 mr-1' : 'w-100']"> <div :class="[endEventRequiresLabel ? 'w-50 mr-1' : 'w-100']">
<gl-form-group <gl-form-group
:label="s__('CustomCycleAnalytics|Stop event')" :label="s__('CustomCycleAnalytics|Stop event')"
:description=" :description="
!hasStartEvent ? s__('CustomCycleAnalytics|Please select a start event first') : '' !hasStartEvent ? s__('CustomCycleAnalytics|Please select a start event first') : ''
" "
:state="hasValidStartAndStopEventPair" :state="hasValidStartAndEndEventPair"
:invalid-feedback="stopEventError" :invalid-feedback="endEventError"
> >
<gl-form-select <gl-form-select
v-model="fields.stopEvent" v-model="fields.endEventIdentifier"
name="custom-stage-stop-event" name="add-stage-stop-event"
:options="stopEventOptions" :options="endEventOptions"
:required="true" :required="true"
:disabled="!hasStartEvent" :disabled="!hasStartEvent"
/> />
</gl-form-group> </gl-form-group>
</div> </div>
<div v-if="stopEventRequiresLabel" class="w-50 ml-1"> <div v-if="endEventRequiresLabel" class="w-50 ml-1">
<gl-form-group :label="s__('CustomCycleAnalytics|Stop event label')"> <gl-form-group :label="s__('CustomCycleAnalytics|Stop event label')">
<labels-selector <labels-selector
:labels="labels" :labels="labels"
:selected-label-id="fields.stopEventLabel" :selected-label-id="fields.endEventLabelId"
name="custom-stage-stop-event-label" name="add-stage-stop-event-label"
@selectLabel="labelId => handleSelectLabel('stopEventLabel', labelId)" @selectLabel="labelId => handleSelectLabel('endEventLabelId', labelId)"
@clearLabel="handleClearLabel('stopEventLabel')" @clearLabel="handleClearLabel('endEventLabelId')"
/> />
</gl-form-group> </gl-form-group>
</div> </div>
...@@ -213,7 +237,7 @@ export default { ...@@ -213,7 +237,7 @@ export default {
<div class="custom-stage-form-actions"> <div class="custom-stage-form-actions">
<button <button
:disabled="!isDirty" :disabled="!isDirty"
class="btn btn-cancel js-custom-stage-form-cancel" class="btn btn-cancel js-save-stage-cancel"
type="button" type="button"
@click="handleCancel" @click="handleCancel"
> >
...@@ -222,11 +246,11 @@ export default { ...@@ -222,11 +246,11 @@ export default {
<button <button
:disabled="!isComplete || !isDirty" :disabled="!isComplete || !isDirty"
type="button" type="button"
class="js-custom-stage-form-submit btn btn-success" class="js-save-stage btn btn-success"
@click="handleSave" @click="handleSave"
> >
<gl-loading-icon v-if="isSavingCustomStage" size="sm" inline /> <gl-loading-icon v-if="isSavingCustomStage" size="sm" inline />
{{ s__('CustomCycleAnalytics|Add stage') }} {{ saveStageText }}
</button> </button>
</div> </div>
</form> </form>
......
...@@ -41,7 +41,11 @@ export default { ...@@ -41,7 +41,11 @@ export default {
type: Boolean, type: Boolean,
required: true, required: true,
}, },
isAddingCustomStage: { isCreatingCustomStage: {
type: Boolean,
required: true,
},
isEditingCustomStage: {
type: Boolean, type: Boolean,
required: true, required: true,
}, },
...@@ -73,6 +77,11 @@ export default { ...@@ -73,6 +77,11 @@ export default {
type: Boolean, type: Boolean,
required: true, required: true,
}, },
customStageFormInitData: {
type: Object,
required: false,
default: () => ({}),
},
}, },
computed: { computed: {
stageName() { stageName() {
...@@ -147,12 +156,13 @@ export default { ...@@ -147,12 +156,13 @@ export default {
:key="`ca-stage-title-${stage.title}`" :key="`ca-stage-title-${stage.title}`"
:title="stage.title" :title="stage.title"
:value="stage.value" :value="stage.value"
:is-active="!isAddingCustomStage && stage.id === currentStage.id" :is-active="!isCreatingCustomStage && stage.id === currentStage.id"
:can-edit="canEditStages" :can-edit="canEditStages"
:is-default-stage="!stage.custom" :is-default-stage="!stage.custom"
@select="$emit('selectStage', stage)" @select="$emit('selectStage', stage)"
@remove="removeStage(stage.id)" @remove="removeStage(stage.id)"
@hide="hideStage(stage.id)" @hide="hideStage(stage.id)"
@edit="$emit(STAGE_ACTIONS.EDIT, stageData)"
/> />
<add-stage-button <add-stage-button
v-if="canEditStages" v-if="canEditStages"
...@@ -164,11 +174,14 @@ export default { ...@@ -164,11 +174,14 @@ export default {
<div class="section stage-events"> <div class="section stage-events">
<gl-loading-icon v-if="isLoading" class="mt-4" size="md" /> <gl-loading-icon v-if="isLoading" class="mt-4" size="md" />
<custom-stage-form <custom-stage-form
v-else-if="isAddingCustomStage" v-else-if="isCreatingCustomStage || isEditingCustomStage"
:events="customStageFormEvents" :events="customStageFormEvents"
:labels="labels" :labels="labels"
:is-saving-custom-stage="isSavingCustomStage" :is-saving-custom-stage="isSavingCustomStage"
:initial-fields="customStageFormInitData"
:is-editing-custom-stage="isEditingCustomStage"
@submit="$emit('submit', $event)" @submit="$emit('submit', $event)"
@saveStage="saveStage"
/> />
<template v-else> <template v-else>
<stage-event-list <stage-event-list
......
...@@ -34,6 +34,7 @@ export const TASKS_BY_TYPE_SUBJECT_ISSUE = 'Issue'; ...@@ -34,6 +34,7 @@ export const TASKS_BY_TYPE_SUBJECT_ISSUE = 'Issue';
export const TASKS_BY_TYPE_SUBJECT_MERGE_REQUEST = 'MergeRequest'; export const TASKS_BY_TYPE_SUBJECT_MERGE_REQUEST = 'MergeRequest';
export const STAGE_ACTIONS = { export const STAGE_ACTIONS = {
SELECT: 'selectStage',
EDIT: 'editStage', EDIT: 'editStage',
REMOVE: 'removeStage', REMOVE: 'removeStage',
SAVE: 'saveStage', SAVE: 'saveStage',
......
...@@ -83,6 +83,16 @@ export const fetchCycleAnalyticsData = ({ dispatch }) => { ...@@ -83,6 +83,16 @@ export const fetchCycleAnalyticsData = ({ dispatch }) => {
.catch(error => dispatch('receiveCycleAnalyticsDataError', error)); .catch(error => dispatch('receiveCycleAnalyticsDataError', error));
}; };
export const hideCustomStageForm = ({ commit }) => commit(types.HIDE_CUSTOM_STAGE_FORM);
export const showCustomStageForm = ({ commit }) => commit(types.SHOW_CUSTOM_STAGE_FORM);
export const editCustomStage = ({ commit, dispatch }, initData = {}) => {
commit(types.EDIT_CUSTOM_STAGE, initData);
if (initData.id) {
dispatch('setSelectedStageId', initData.id);
}
};
export const requestSummaryData = ({ commit }) => commit(types.REQUEST_SUMMARY_DATA); export const requestSummaryData = ({ commit }) => commit(types.REQUEST_SUMMARY_DATA);
export const receiveSummaryDataError = ({ commit }, error) => { export const receiveSummaryDataError = ({ commit }, error) => {
...@@ -112,9 +122,6 @@ export const fetchSummaryData = ({ state, dispatch, getters }) => { ...@@ -112,9 +122,6 @@ export const fetchSummaryData = ({ state, dispatch, getters }) => {
export const requestGroupStagesAndEvents = ({ commit }) => export const requestGroupStagesAndEvents = ({ commit }) =>
commit(types.REQUEST_GROUP_STAGES_AND_EVENTS); commit(types.REQUEST_GROUP_STAGES_AND_EVENTS);
export const hideCustomStageForm = ({ commit }) => commit(types.HIDE_CUSTOM_STAGE_FORM);
export const showCustomStageForm = ({ commit }) => commit(types.SHOW_CUSTOM_STAGE_FORM);
export const receiveGroupLabelsSuccess = ({ commit }, data) => export const receiveGroupLabelsSuccess = ({ commit }, data) =>
commit(types.RECEIVE_GROUP_LABELS_SUCCESS, data); commit(types.RECEIVE_GROUP_LABELS_SUCCESS, data);
...@@ -197,7 +204,6 @@ export const createCustomStage = ({ dispatch, state }, data) => { ...@@ -197,7 +204,6 @@ export const createCustomStage = ({ dispatch, state }, data) => {
const { const {
selectedGroup: { fullPath }, selectedGroup: { fullPath },
} = state; } = state;
dispatch('requestCreateCustomStage'); dispatch('requestCreateCustomStage');
return Api.cycleAnalyticsCreateStage(fullPath, data) return Api.cycleAnalyticsCreateStage(fullPath, data)
...@@ -249,7 +255,7 @@ export const receiveUpdateStageSuccess = ({ commit, dispatch }) => { ...@@ -249,7 +255,7 @@ export const receiveUpdateStageSuccess = ({ commit, dispatch }) => {
commit(types.RECEIVE_UPDATE_STAGE_RESPONSE); commit(types.RECEIVE_UPDATE_STAGE_RESPONSE);
createFlash(__(`Stage data updated`), 'notice'); createFlash(__(`Stage data updated`), 'notice');
dispatch('fetchCycleAnalyticsData'); dispatch('fetchGroupStagesAndEvents');
}; };
export const receiveUpdateStageError = ({ commit }) => { export const receiveUpdateStageError = ({ commit }) => {
......
...@@ -18,6 +18,7 @@ export const RECEIVE_STAGE_DATA_ERROR = 'RECEIVE_STAGE_DATA_ERROR'; ...@@ -18,6 +18,7 @@ export const RECEIVE_STAGE_DATA_ERROR = 'RECEIVE_STAGE_DATA_ERROR';
export const HIDE_CUSTOM_STAGE_FORM = 'HIDE_CUSTOM_STAGE_FORM'; export const HIDE_CUSTOM_STAGE_FORM = 'HIDE_CUSTOM_STAGE_FORM';
export const SHOW_CUSTOM_STAGE_FORM = 'SHOW_CUSTOM_STAGE_FORM'; export const SHOW_CUSTOM_STAGE_FORM = 'SHOW_CUSTOM_STAGE_FORM';
export const EDIT_CUSTOM_STAGE = 'EDIT_CUSTOM_STAGE';
export const REQUEST_GROUP_LABELS = 'REQUEST_GROUP_LABELS'; export const REQUEST_GROUP_LABELS = 'REQUEST_GROUP_LABELS';
export const RECEIVE_GROUP_LABELS_SUCCESS = 'RECEIVE_GROUP_LABELS_SUCCESS'; export const RECEIVE_GROUP_LABELS_SUCCESS = 'RECEIVE_GROUP_LABELS_SUCCESS';
......
...@@ -25,7 +25,8 @@ export default { ...@@ -25,7 +25,8 @@ export default {
}, },
[types.REQUEST_CYCLE_ANALYTICS_DATA](state) { [types.REQUEST_CYCLE_ANALYTICS_DATA](state) {
state.isLoading = true; state.isLoading = true;
state.isAddingCustomStage = false; state.isCreatingCustomStage = false;
state.isEditingCustomStage = false;
}, },
[types.RECEIVE_CYCLE_ANALYTICS_DATA_SUCCESS](state) { [types.RECEIVE_CYCLE_ANALYTICS_DATA_SUCCESS](state) {
state.errorCode = null; state.errorCode = null;
...@@ -75,8 +76,34 @@ export default { ...@@ -75,8 +76,34 @@ export default {
labelIds: [], labelIds: [],
}; };
}, },
[types.SHOW_CUSTOM_STAGE_FORM](state) {
state.isCreatingCustomStage = true;
state.customStageFormInitData = {};
},
[types.EDIT_CUSTOM_STAGE](state, initData) {
console.log('EDIT_CUSTOM_STAGE::initData', initData);
const {
title: name,
startEventIdentifier,
endEventIdentifier,
startEventLabelId,
endEventLabelId,
} = initData;
state.isEditingCustomStage = true;
state.customStageFormInitData = {
...state.customStageFormInitData,
name,
startEventIdentifier,
endEventIdentifier,
startEventLabelId,
endEventLabelId,
};
},
[types.HIDE_CUSTOM_STAGE_FORM](state) { [types.HIDE_CUSTOM_STAGE_FORM](state) {
state.isAddingCustomStage = false; state.isEditingCustomStage = false;
state.isCreatingCustomStage = false;
state.customStageFormInitData = {};
}, },
[types.SHOW_CUSTOM_STAGE_FORM](state) { [types.SHOW_CUSTOM_STAGE_FORM](state) {
state.isAddingCustomStage = true; state.isAddingCustomStage = true;
......
...@@ -14,8 +14,9 @@ export default () => ({ ...@@ -14,8 +14,9 @@ export default () => ({
isEmptyStage: false, isEmptyStage: false,
errorCode: null, errorCode: null,
isAddingCustomStage: false,
isSavingCustomStage: false, isSavingCustomStage: false,
isCreatingCustomStage: false,
isEditingCustomStage: false,
selectedGroup: null, selectedGroup: null,
selectedProjectIds: [], selectedProjectIds: [],
......
import Vue from 'vue'; import Vue from 'vue';
import { mount } from '@vue/test-utils'; import { mount } from '@vue/test-utils';
import CustomStageForm from 'ee/analytics/cycle_analytics/components/custom_stage_form.vue'; import CustomStageForm from 'ee/analytics/cycle_analytics/components/custom_stage_form.vue';
import { STAGE_ACTIONS } from 'ee/analytics/cycle_analytics/constants';
import { import {
groupLabels, groupLabels,
customStageEvents as events, customStageEvents as events,
...@@ -12,10 +13,10 @@ import { ...@@ -12,10 +13,10 @@ import {
const initData = { const initData = {
name: 'Cool stage pre', name: 'Cool stage pre',
startEvent: labelStartEvent.identifier, startEventIdentifier: labelStartEvent.identifier,
startEventLabel: groupLabels[0].id, startEventLabelId: groupLabels[0].id,
stopEvent: labelStopEvent.identifier, endEventIdentifier: labelStopEvent.identifier,
stopEventLabel: groupLabels[1].id, endEventLabelId: groupLabels[1].id,
}; };
describe('CustomStageForm', () => { describe('CustomStageForm', () => {
...@@ -31,15 +32,16 @@ describe('CustomStageForm', () => { ...@@ -31,15 +32,16 @@ describe('CustomStageForm', () => {
} }
let wrapper = null; let wrapper = null;
const findEvent = ev => wrapper.emitted()[ev];
const sel = { const sel = {
name: '[name="custom-stage-name"]', name: '[name="add-stage-name"]',
startEvent: '[name="custom-stage-start-event"]', startEvent: '[name="add-stage-start-event"]',
startEventLabel: '[name="custom-stage-start-event-label"]', startEventLabel: '[name="add-stage-start-event-label"]',
stopEvent: '[name="custom-stage-stop-event"]', endEvent: '[name="add-stage-stop-event"]',
stopEventLabel: '[name="custom-stage-stop-event-label"]', endEventLabel: '[name="add-stage-stop-event-label"]',
submit: '.js-custom-stage-form-submit', submit: '.js-save-stage',
cancel: '.js-custom-stage-form-cancel', cancel: '.js-save-stage-cancel',
invalidFeedback: '.invalid-feedback', invalidFeedback: '.invalid-feedback',
}; };
...@@ -66,7 +68,7 @@ describe('CustomStageForm', () => { ...@@ -66,7 +68,7 @@ describe('CustomStageForm', () => {
describe.each([ describe.each([
['Name', sel.name, true], ['Name', sel.name, true],
['Start event', sel.startEvent, true], ['Start event', sel.startEvent, true],
['Stop event', sel.stopEvent, false], ['Stop event', sel.endEvent, false],
['Submit', sel.submit, false], ['Submit', sel.submit, false],
['Cancel', sel.cancel, false], ['Cancel', sel.cancel, false],
])('by default', (field, $sel, enabledState) => { ])('by default', (field, $sel, enabledState) => {
...@@ -119,7 +121,7 @@ describe('CustomStageForm', () => { ...@@ -119,7 +121,7 @@ describe('CustomStageForm', () => {
it('will display the start event label field if a label event is selected', done => { it('will display the start event label field if a label event is selected', done => {
wrapper.setData({ wrapper.setData({
fields: { fields: {
startEvent: labelStartEvent.identifier, startEventIdentifier: labelStartEvent.identifier,
}, },
}); });
...@@ -129,9 +131,9 @@ describe('CustomStageForm', () => { ...@@ -129,9 +131,9 @@ describe('CustomStageForm', () => {
}); });
}); });
it('will set the "startEventLabel" field when selected', done => { it('will set the "startEventLabelId" field when selected', done => {
const selectedLabelId = groupLabels[0].id; const selectedLabelId = groupLabels[0].id;
expect(wrapper.vm.fields.startEventLabel).toEqual(null); expect(wrapper.vm.fields.startEventLabelId).toEqual(null);
wrapper.find(sel.startEvent).setValue(labelStartEvent.identifier); wrapper.find(sel.startEvent).setValue(labelStartEvent.identifier);
Vue.nextTick(() => { Vue.nextTick(() => {
...@@ -142,7 +144,7 @@ describe('CustomStageForm', () => { ...@@ -142,7 +144,7 @@ describe('CustomStageForm', () => {
.trigger('click'); .trigger('click');
Vue.nextTick(() => { Vue.nextTick(() => {
expect(wrapper.vm.fields.startEventLabel).toEqual(selectedLabelId); expect(wrapper.vm.fields.startEventLabelId).toEqual(selectedLabelId);
done(); done();
}); });
}); });
...@@ -171,7 +173,7 @@ describe('CustomStageForm', () => { ...@@ -171,7 +173,7 @@ describe('CustomStageForm', () => {
}); });
it('is enabled when a start event is selected', done => { it('is enabled when a start event is selected', done => {
const el = wrapper.find(sel.stopEvent); const el = wrapper.find(sel.endEvent);
expect(el.attributes('disabled')).toEqual('disabled'); expect(el.attributes('disabled')).toEqual('disabled');
selectDropdownOption(wrapper, sel.startEvent, 1); selectDropdownOption(wrapper, sel.startEvent, 1);
...@@ -243,10 +245,10 @@ describe('CustomStageForm', () => { ...@@ -243,10 +245,10 @@ describe('CustomStageForm', () => {
wrapper.setData({ wrapper.setData({
fields: { fields: {
name: 'Cool stage', name: 'Cool stage',
startEvent: 'issue_created', startEventIdentifier: 'issue_created',
startEventLabel: null, startEventLabelId: null,
stopEvent: 'issue_stage_end', endEventIdentifier: 'issue_stage_end',
stopEventLabel: null, endEventLabelId: null,
}, },
}); });
}); });
...@@ -270,15 +272,15 @@ describe('CustomStageForm', () => { ...@@ -270,15 +272,15 @@ describe('CustomStageForm', () => {
}); });
it('will update the list of stop events', done => { it('will update the list of stop events', done => {
const se = wrapper.vm.stopEventOptions; const se = wrapper.vm.endEventOptions;
selectDropdownOption(wrapper, sel.startEvent, 2); selectDropdownOption(wrapper, sel.startEvent, 2);
Vue.nextTick(() => { Vue.nextTick(() => {
expect(se[1].value).not.toEqual(wrapper.vm.stopEventOptions[1].value); expect(se[1].value).not.toEqual(wrapper.vm.endEventOptions[1].value);
done(); done();
}); });
}); });
it('will disable the submit button until a valid stopEvent is selected', done => { it('will disable the submit button until a valid endEvent is selected', done => {
selectDropdownOption(wrapper, sel.startEvent, 2); selectDropdownOption(wrapper, sel.startEvent, 2);
Vue.nextTick(() => { Vue.nextTick(() => {
expect(wrapper.find(sel.submit).attributes('disabled')).toEqual('disabled'); expect(wrapper.find(sel.submit).attributes('disabled')).toEqual('disabled');
...@@ -301,41 +303,41 @@ describe('CustomStageForm', () => { ...@@ -301,41 +303,41 @@ describe('CustomStageForm', () => {
}); });
it('will display the stop event label field if a label event is selected', done => { it('will display the stop event label field if a label event is selected', done => {
expect(wrapper.find(sel.stopEventLabel).exists()).toEqual(false); expect(wrapper.find(sel.endEventLabel).exists()).toEqual(false);
wrapper.setData({ wrapper.setData({
fields: { fields: {
stopEvent: labelStopEvent.identifier, endEventIdentifier: labelStopEvent.identifier,
startEvent: labelStartEvent.identifier, startEventIdentifier: labelStartEvent.identifier,
}, },
}); });
Vue.nextTick(() => { Vue.nextTick(() => {
expect(wrapper.find(sel.stopEventLabel).exists()).toEqual(true); expect(wrapper.find(sel.endEventLabel).exists()).toEqual(true);
done(); done();
}); });
}); });
it('will set the "stopEventLabel" field when selected', done => { it('will set the "endEventLabelId" field when selected', done => {
const selectedLabelId = groupLabels[1].id; const selectedLabelId = groupLabels[1].id;
expect(wrapper.vm.fields.stopEventLabel).toEqual(null); expect(wrapper.vm.fields.endEventLabelId).toEqual(null);
wrapper.setData({ wrapper.setData({
fields: { fields: {
startEvent: labelStartEvent.identifier, startEventIdentifier: labelStartEvent.identifier,
stopEvent: labelStopEvent.identifier, endEventIdentifier: labelStopEvent.identifier,
}, },
}); });
Vue.nextTick(() => { Vue.nextTick(() => {
wrapper wrapper
.find(sel.stopEventLabel) .find(sel.endEventLabel)
.findAll('.dropdown-item') .findAll('.dropdown-item')
.at(2) // item at index 0 is 'select a label' .at(2) // item at index 0 is 'select a label'
.trigger('click'); .trigger('click');
Vue.nextTick(() => { Vue.nextTick(() => {
expect(wrapper.vm.fields.stopEventLabel).toEqual(selectedLabelId); expect(wrapper.vm.fields.endEventLabelId).toEqual(selectedLabelId);
done(); done();
}); });
}); });
...@@ -350,7 +352,7 @@ describe('CustomStageForm', () => { ...@@ -350,7 +352,7 @@ describe('CustomStageForm', () => {
selectDropdownOption(wrapper, sel.startEvent, 1); selectDropdownOption(wrapper, sel.startEvent, 1);
return Vue.nextTick(() => { return Vue.nextTick(() => {
selectDropdownOption(wrapper, sel.stopEvent, 1); selectDropdownOption(wrapper, sel.endEvent, 1);
}); });
}); });
...@@ -358,6 +360,10 @@ describe('CustomStageForm', () => { ...@@ -358,6 +360,10 @@ describe('CustomStageForm', () => {
wrapper.destroy(); wrapper.destroy();
}); });
it('has text `Edit stage`', () => {
expect(wrapper.find(sel.submit).text('value')).toEqual('Add stage');
});
it('is enabled when all required fields are filled', done => { it('is enabled when all required fields are filled', done => {
const btn = wrapper.find(sel.submit); const btn = wrapper.find(sel.submit);
...@@ -389,19 +395,23 @@ describe('CustomStageForm', () => { ...@@ -389,19 +395,23 @@ describe('CustomStageForm', () => {
wrapper.destroy(); wrapper.destroy();
}); });
it('emits a `submit` event when clicked', () => { it(`emits a ${STAGE_ACTIONS.SAVE} event when clicked`, () => {
expect(wrapper.emitted().submit).toBeUndefined(); let event = findEvent(STAGE_ACTIONS.SAVE);
expect(event).toBeUndefined();
wrapper.find(sel.submit).trigger('click'); wrapper.find(sel.submit).trigger('click');
expect(wrapper.emitted().submit).toBeTruthy(); event = findEvent(STAGE_ACTIONS.SAVE);
expect(wrapper.emitted().submit.length).toEqual(1); expect(event).toBeTruthy();
expect(event.length).toEqual(1);
}); });
it('`submit` event receives the latest data', () => { it('`submit` event receives the latest data', () => {
expect(wrapper.emitted().submit).toBeUndefined();
const startEv = startEvents[startEventIndex]; const startEv = startEvents[startEventIndex];
const selectedStopEvent = getDropdownOption(wrapper, sel.stopEvent, stopEventIndex); const selectedStopEvent = getDropdownOption(wrapper, sel.stopEvent, stopEventIndex);
let event = findEvent(STAGE_ACTIONS.SAVE);
expect(event).toBeUndefined();
const res = [ const res = [
{ {
name: 'Cool stage', name: 'Cool stage',
...@@ -413,7 +423,8 @@ describe('CustomStageForm', () => { ...@@ -413,7 +423,8 @@ describe('CustomStageForm', () => {
]; ];
wrapper.find(sel.submit).trigger('click'); wrapper.find(sel.submit).trigger('click');
expect(wrapper.emitted().submit[0]).toEqual(res); event = findEvent(STAGE_ACTIONS.SAVE);
expect(event[0]).toEqual(res);
}); });
}); });
}); });
...@@ -443,8 +454,8 @@ describe('CustomStageForm', () => { ...@@ -443,8 +454,8 @@ describe('CustomStageForm', () => {
wrapper.setData({ wrapper.setData({
fields: { fields: {
name: 'Cool stage pre', name: 'Cool stage pre',
startEvent: labelStartEvent.identifier, startEventIdentifier: labelStartEvent.identifier,
stopEvent: labelStopEvent.identifier, endEventIdentifier: labelStopEvent.identifier,
}, },
}); });
...@@ -453,11 +464,11 @@ describe('CustomStageForm', () => { ...@@ -453,11 +464,11 @@ describe('CustomStageForm', () => {
Vue.nextTick(() => { Vue.nextTick(() => {
expect(wrapper.vm.fields).toEqual({ expect(wrapper.vm.fields).toEqual({
name: '', name: null,
startEvent: '', startEventIdentifier: null,
startEventLabel: null, startEventLabelId: null,
stopEvent: '', endEventIdentifier: null,
stopEventLabel: null, endEventLabelId: null,
}); });
done(); done();
}); });
...@@ -465,7 +476,8 @@ describe('CustomStageForm', () => { ...@@ -465,7 +476,8 @@ describe('CustomStageForm', () => {
}); });
it('will emit the `cancel` event when clicked', done => { it('will emit the `cancel` event when clicked', done => {
expect(wrapper.emitted().cancel).toBeUndefined(); let ev = findEvent('cancel');
expect(ev).toBeUndefined();
wrapper.setData({ wrapper.setData({
fields: { fields: {
...@@ -477,8 +489,9 @@ describe('CustomStageForm', () => { ...@@ -477,8 +489,9 @@ describe('CustomStageForm', () => {
wrapper.find(sel.cancel).trigger('click'); wrapper.find(sel.cancel).trigger('click');
Vue.nextTick(() => { Vue.nextTick(() => {
expect(wrapper.emitted().cancel).toBeTruthy(); ev = findEvent('cancel');
expect(wrapper.emitted().cancel.length).toEqual(1); expect(ev).toBeTruthy();
expect(ev.length).toEqual(1);
done(); done();
}); });
}); });
...@@ -486,10 +499,11 @@ describe('CustomStageForm', () => { ...@@ -486,10 +499,11 @@ describe('CustomStageForm', () => {
}); });
}); });
describe('Prepopulated form', () => { describe('Editing a custom stage', () => {
beforeEach(() => { beforeEach(() => {
wrapper = createComponent( wrapper = createComponent(
{ {
isEditingCustomStage: true,
initialFields: { initialFields: {
...initData, ...initData,
}, },
...@@ -515,8 +529,8 @@ describe('CustomStageForm', () => { ...@@ -515,8 +529,8 @@ describe('CustomStageForm', () => {
wrapper.setData({ wrapper.setData({
fields: { fields: {
name: 'Cool stage pre', name: 'Cool stage pre',
startEvent: labelStartEvent.identifier, startEventIdentifier: labelStartEvent.identifier,
stopEvent: labelStopEvent.identifier, endEventIdentifier: labelStopEvent.identifier,
}, },
}); });
...@@ -533,7 +547,11 @@ describe('CustomStageForm', () => { ...@@ -533,7 +547,11 @@ describe('CustomStageForm', () => {
}); });
}); });
describe('Add stage button', () => { describe('Edit stage button', () => {
it('has text `Edit stage`', () => {
expect(wrapper.find(sel.submit).text('value')).toEqual('Edit stage');
});
it('is disabled by default', () => { it('is disabled by default', () => {
expect(wrapper.find(sel.submit).attributes('disabled')).toEqual('disabled'); expect(wrapper.find(sel.submit).attributes('disabled')).toEqual('disabled');
}); });
...@@ -564,8 +582,9 @@ describe('CustomStageForm', () => { ...@@ -564,8 +582,9 @@ describe('CustomStageForm', () => {
}); });
}); });
it('emits a `submit` event when clicked', done => { it(`emits a ${STAGE_ACTIONS.EDIT} event when clicked`, done => {
expect(wrapper.emitted().submit).toBeUndefined(); let ev = findEvent(STAGE_ACTIONS.EDIT);
expect(ev).toBeUndefined();
wrapper.setData({ wrapper.setData({
fields: { fields: {
...@@ -577,8 +596,9 @@ describe('CustomStageForm', () => { ...@@ -577,8 +596,9 @@ describe('CustomStageForm', () => {
wrapper.find(sel.submit).trigger('click'); wrapper.find(sel.submit).trigger('click');
Vue.nextTick(() => { Vue.nextTick(() => {
expect(wrapper.emitted().submit).toBeTruthy(); ev = findEvent(STAGE_ACTIONS.EDIT);
expect(wrapper.emitted().submit.length).toEqual(1); expect(ev).toBeTruthy();
expect(ev.length).toEqual(1);
done(); done();
}); });
}); });
...@@ -595,7 +615,7 @@ describe('CustomStageForm', () => { ...@@ -595,7 +615,7 @@ describe('CustomStageForm', () => {
wrapper.find(sel.submit).trigger('click'); wrapper.find(sel.submit).trigger('click');
Vue.nextTick(() => { Vue.nextTick(() => {
const submitted = wrapper.emitted().submit[0]; const submitted = findEvent(STAGE_ACTIONS.EDIT)[0];
expect(submitted).not.toEqual([initData]); expect(submitted).not.toEqual([initData]);
expect(submitted).toEqual([ expect(submitted).toEqual([
{ {
...@@ -606,6 +626,7 @@ describe('CustomStageForm', () => { ...@@ -606,6 +626,7 @@ describe('CustomStageForm', () => {
name: 'Cool updated form', name: 'Cool updated form',
}, },
]); ]);
done(); done();
}); });
}); });
......
...@@ -36,8 +36,9 @@ function createComponent(props = {}, shallow = false) { ...@@ -36,8 +36,9 @@ function createComponent(props = {}, shallow = false) {
isLoading: false, isLoading: false,
isLoadingSummaryData: false, isLoadingSummaryData: false,
isEmptyStage: false, isEmptyStage: false,
isAddingCustomStage: false,
isSavingCustomStage: false, isSavingCustomStage: false,
isCreatingCustomStage: false,
isEditingCustomStage: false,
noDataSvgPath, noDataSvgPath,
noAccessSvgPath, noAccessSvgPath,
canEditStages: false, canEditStages: false,
......
...@@ -33,8 +33,9 @@ describe('Cycle analytics mutations', () => { ...@@ -33,8 +33,9 @@ describe('Cycle analytics mutations', () => {
it.each` it.each`
mutation | stateKey | value mutation | stateKey | value
${types.HIDE_CUSTOM_STAGE_FORM} | ${'isAddingCustomStage'} | ${false} ${types.HIDE_CUSTOM_STAGE_FORM} | ${'isCreatingCustomStage'} | ${false}
${types.SHOW_CUSTOM_STAGE_FORM} | ${'isAddingCustomStage'} | ${true} ${types.SHOW_CUSTOM_STAGE_FORM} | ${'isCreatingCustomStage'} | ${true}
${types.EDIT_CUSTOM_STAGE} | ${'isEditingCustomStage'} | ${true}
${types.REQUEST_STAGE_DATA} | ${'isLoadingStage'} | ${true} ${types.REQUEST_STAGE_DATA} | ${'isLoadingStage'} | ${true}
${types.RECEIVE_STAGE_DATA_ERROR} | ${'isEmptyStage'} | ${true} ${types.RECEIVE_STAGE_DATA_ERROR} | ${'isEmptyStage'} | ${true}
${types.RECEIVE_STAGE_DATA_ERROR} | ${'isLoadingStage'} | ${false} ${types.RECEIVE_STAGE_DATA_ERROR} | ${'isLoadingStage'} | ${false}
......
...@@ -5196,6 +5196,12 @@ msgstr "" ...@@ -5196,6 +5196,12 @@ msgstr ""
msgid "CustomCycleAnalytics|Add stage" msgid "CustomCycleAnalytics|Add stage"
msgstr "" msgstr ""
msgid "CustomCycleAnalytics|Edit stage"
msgstr ""
msgid "CustomCycleAnalytics|Editing stage"
msgstr ""
msgid "CustomCycleAnalytics|Enter a name for the stage" msgid "CustomCycleAnalytics|Enter a name for the stage"
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