Commit 8219c379 authored by Ezekiel Kigbo's avatar Ezekiel Kigbo

Validate the form before submission

Ensure we validate when the submit button is clicked
but before we submit the request
parent a9243d6f
...@@ -88,20 +88,16 @@ export const validateStage = (fields) => { ...@@ -88,20 +88,16 @@ export const validateStage = (fields) => {
if (fields?.name) { if (fields?.name) {
if (fields.name.length > NAME_MAX_LENGTH) { if (fields.name.length > NAME_MAX_LENGTH) {
newErrors.name = [ERRORS.MAX_LENGTH]; newErrors.name = [ERRORS.MAX_LENGTH];
} else { }
newErrors.name = if (fields?.custom && DEFAULT_STAGE_NAMES.includes(fields.name.toLowerCase())) {
fields?.custom && DEFAULT_STAGE_NAMES.includes(fields.name.toLowerCase()) newErrors.name = [ERRORS.STAGE_NAME_EXISTS];
? [ERRORS.STAGE_NAME_EXISTS]
: [];
} }
} else { } else {
newErrors.name = [ERRORS.STAGE_NAME_MIN_LENGTH]; newErrors.name = [ERRORS.STAGE_NAME_MIN_LENGTH];
} }
if (fields?.startEventIdentifier) { if (fields?.startEventIdentifier) {
if (fields?.endEventIdentifier) { if (!fields?.endEventIdentifier) {
newErrors.endEventIdentifier = [];
} else {
newErrors.endEventIdentifier = [ERRORS.END_EVENT_REQUIRED]; newErrors.endEventIdentifier = [ERRORS.END_EVENT_REQUIRED];
} }
} else { } else {
......
<script> <script>
import Vue from 'vue';
import { mapGetters, mapState } from 'vuex'; import { mapGetters, mapState } from 'vuex';
import { isEqual } from 'lodash'; import { isEqual } from 'lodash';
import { import {
...@@ -163,7 +164,7 @@ export default { ...@@ -163,7 +164,7 @@ export default {
this.fields.startEventIdentifier && this.eventMismatchError this.fields.startEventIdentifier && this.eventMismatchError
? [ERRORS.INVALID_EVENT_PAIRS] ? [ERRORS.INVALID_EVENT_PAIRS]
: newErrors.endEventIdentifier; : newErrors.endEventIdentifier;
this.errors = { ...this.errors, ...newErrors }; Vue.set(this, 'errors', newErrors);
}, },
}, },
I18N, I18N,
......
...@@ -3,6 +3,7 @@ import Vue from 'vue'; ...@@ -3,6 +3,7 @@ import Vue from 'vue';
import { GlButton, GlForm, GlFormInput, GlFormGroup, GlFormRadioGroup, GlModal } from '@gitlab/ui'; import { GlButton, GlForm, GlFormInput, GlFormGroup, GlFormRadioGroup, GlModal } from '@gitlab/ui';
import { mapState, mapActions } from 'vuex'; import { mapState, mapActions } from 'vuex';
import { sprintf } from '~/locale'; import { sprintf } from '~/locale';
import { convertObjectPropsToSnakeCase } from '~/lib/utils/common_utils';
import { swapArrayItems } from '~/lib/utils/array_utility'; import { swapArrayItems } from '~/lib/utils/array_utility';
import { import {
DEFAULT_STAGE_CONFIG, DEFAULT_STAGE_CONFIG,
...@@ -22,45 +23,20 @@ const findStageIndexByName = (stages, target = '') => ...@@ -22,45 +23,20 @@ const findStageIndexByName = (stages, target = '') =>
const initializeStageErrors = (selectedPreset = PRESET_OPTIONS_DEFAULT) => const initializeStageErrors = (selectedPreset = PRESET_OPTIONS_DEFAULT) =>
selectedPreset === PRESET_OPTIONS_DEFAULT ? DEFAULT_STAGE_CONFIG.map(() => ({})) : [{}]; selectedPreset === PRESET_OPTIONS_DEFAULT ? DEFAULT_STAGE_CONFIG.map(() => ({})) : [{}];
// Not great, we're mixing types const initializeStages = (selectedPreset = PRESET_OPTIONS_DEFAULT) =>
// better to make everything arrays 🤔 selectedPreset === PRESET_OPTIONS_DEFAULT
const maybeFirstElem = (arr = null) => { ? DEFAULT_STAGE_CONFIG
if (Array.isArray(arr)) { : [{ ...defaultCustomStageFields }];
return arr.length ? arr[0] : null;
}
return arr || null;
};
// TODO: move to utils const formatStageDataForSubmission = (stages) => {
const formatStageData = (stages) => { return stages.map(({ custom = false, name, ...rest }) => {
return stages const additionalProps = custom ? convertObjectPropsToSnakeCase({ ...rest }) : {};
.filter(({ hidden = false }) => !hidden) return {
.map( ...additionalProps,
({ custom,
startEventIdentifier, name,
endEventIdentifier, };
startEventLabelId, });
endEventLabelId,
custom = false,
name,
...rest
}) => {
const additionalProps = custom
? {
start_event_identifier: maybeFirstElem(startEventIdentifier),
end_event_identifier: maybeFirstElem(endEventIdentifier),
start_event_label_id: maybeFirstElem(startEventLabelId),
end_event_label_id: maybeFirstElem(endEventLabelId),
}
: {};
return {
...rest,
...additionalProps,
custom,
name,
};
},
);
}; };
export default { export default {
...@@ -102,10 +78,7 @@ export default { ...@@ -102,10 +78,7 @@ export default {
const { name: nameError = [], stages: stageErrors = [{}] } = initialFormErrors; const { name: nameError = [], stages: stageErrors = [{}] } = initialFormErrors;
const additionalFields = hasExtendedFormFields const additionalFields = hasExtendedFormFields
? { ? {
stages: stages: initializeStages(initialPreset),
initialPreset === PRESET_OPTIONS_DEFAULT
? DEFAULT_STAGE_CONFIG
: [{ ...defaultCustomStageFields }],
stageErrors: stageErrors || initializeStageErrors(initialPreset), stageErrors: stageErrors || initializeStageErrors(initialPreset),
...initialData, ...initialData,
} }
...@@ -128,7 +101,7 @@ export default { ...@@ -128,7 +101,7 @@ export default {
isValueStreamNameValid() { isValueStreamNameValid() {
return !this.nameError?.length; return !this.nameError?.length;
}, },
invalidFeedback() { invalidNameFeedback() {
return this.nameError?.length ? this.nameError.join('\n\n') : null; return this.nameError?.length ? this.nameError.join('\n\n') : null;
}, },
hasInitialFormErrors() { hasInitialFormErrors() {
...@@ -160,6 +133,11 @@ export default { ...@@ -160,6 +133,11 @@ export default {
activeStages() { activeStages() {
return this.stages.filter((stage) => !stage.hidden); return this.stages.filter((stage) => !stage.hidden);
}, },
hasFormErrors() {
return Boolean(
this.nameError.length || this.stageErrors.some((obj) => Object.keys(obj).length),
);
},
}, },
watch: { watch: {
initialFormErrors({ name: nameError, stages: stageErrors }) { initialFormErrors({ name: nameError, stages: stageErrors }) {
...@@ -170,19 +148,20 @@ export default { ...@@ -170,19 +148,20 @@ export default {
methods: { methods: {
...mapActions(['createValueStream']), ...mapActions(['createValueStream']),
onSubmit() { onSubmit() {
const { name, stages } = this; this.validate();
console.log('onSubmit::this.nameError', this.nameError); if (this.hasFormErrors) return false;
console.log('onSubmit::this.stageErrors', this.stageErrors);
// TODO: validate before submission
return this.createValueStream({ return this.createValueStream({
name, name: this.name,
stages: formatStageData(stages), stages: formatStageDataForSubmission(this.stages),
}).then(() => { }).then(() => {
if (!this.hasInitialFormErrors) { if (!this.hasInitialFormErrors) {
this.$toast.show(sprintf(this.$options.I18N.FORM_CREATED, { name }), { this.$toast.show(sprintf(this.$options.I18N.FORM_CREATED, { name: this.name }), {
position: 'top-center', position: 'top-center',
}); });
this.name = ''; this.name = '';
this.nameError = [];
this.stages = initializeStages(this.selectedPreset);
this.stageErrors = initializeStageErrors(this.selectedPreset);
} }
}); });
}, },
...@@ -287,7 +266,7 @@ export default { ...@@ -287,7 +266,7 @@ export default {
data-testid="create-value-stream-name" data-testid="create-value-stream-name"
label-for="create-value-stream-name" label-for="create-value-stream-name"
:label="$options.I18N.FORM_FIELD_NAME_LABEL" :label="$options.I18N.FORM_FIELD_NAME_LABEL"
:invalid-feedback="invalidFeedback" :invalid-feedback="invalidNameFeedback"
:state="isValueStreamNameValid" :state="isValueStreamNameValid"
> >
<div class="gl-display-flex gl-justify-content-space-between"> <div class="gl-display-flex gl-justify-content-space-between">
......
...@@ -133,7 +133,7 @@ describe('CustomStageForm', () => { ...@@ -133,7 +133,7 @@ describe('CustomStageForm', () => {
it('clears the error when the field changes', async () => { it('clears the error when the field changes', async () => {
await setNameField('not an issue'); await setNameField('not an issue');
expect(findFieldErrors('name')).not.toContain('Stage name already exists'); expect(findFieldErrors('name')).toBeUndefined();
}); });
}); });
}); });
......
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