Commit 05d1186d authored by Ezekiel Kigbo's avatar Ezekiel Kigbo

Merge branch '332245-restrict-minutes-input-to-numbers' into 'master'

Update validation for escalation rule

See merge request gitlab-org/gitlab!62825
parents 1e61497b f23a606c
...@@ -6,10 +6,6 @@ ...@@ -6,10 +6,6 @@
width: 240px; width: 240px;
} }
.rule-elapsed-minutes {
width: 56px;
}
.rule-close-icon { .rule-close-icon {
right: 1rem; right: 1rem;
} }
...@@ -81,14 +81,14 @@ export default { ...@@ -81,14 +81,14 @@ export default {
this.rules.push({ ...cloneDeep(defaultEscalationRule), key: this.getUid() }); this.rules.push({ ...cloneDeep(defaultEscalationRule), key: this.getUid() });
}, },
updateEscalationRules(index, rule) { updateEscalationRules(index, rule) {
this.rules[index] = rule; this.rules[index] = { ...this.rules[index], ...rule };
this.emitUpdate(); this.emitRulesUpdate();
}, },
removeEscalationRule(index) { removeEscalationRule(index) {
this.rules.splice(index, 1); this.rules.splice(index, 1);
this.emitUpdate(); this.emitRulesUpdate();
}, },
emitUpdate() { emitRulesUpdate() {
this.$emit('update-escalation-policy-form', { field: 'rules', value: this.rules }); this.$emit('update-escalation-policy-form', { field: 'rules', value: this.rules });
}, },
getUid() { getUid() {
...@@ -146,7 +146,7 @@ export default { ...@@ -146,7 +146,7 @@ export default {
:index="index" :index="index"
:schedules="schedules" :schedules="schedules"
:schedules-loading="schedulesLoading" :schedules-loading="schedulesLoading"
:is-valid="validationState.rules[index]" :validation-state="validationState.rules[index]"
@update-escalation-rule="updateEscalationRules" @update-escalation-rule="updateEscalationRules"
@remove-escalation-rule="removeEscalationRule" @remove-escalation-rule="removeEscalationRule"
/> />
......
...@@ -61,15 +61,12 @@ export default { ...@@ -61,15 +61,12 @@ export default {
}; };
}, },
isFormValid() { isFormValid() {
return this.validationState.name && this.validationState.rules.every(Boolean); return (
}, this.validationState.name &&
serializedData() { this.validationState.rules.every(
const rules = this.form.rules.map(({ status, elapsedTimeSeconds, oncallScheduleIid }) => ({ ({ isTimeValid, isScheduleValid }) => isTimeValid && isScheduleValid,
status, )
elapsedTimeSeconds, );
oncallScheduleIid,
}));
return { ...this.form, rules };
}, },
}, },
methods: { methods: {
...@@ -86,7 +83,7 @@ export default { ...@@ -86,7 +83,7 @@ export default {
variables: { variables: {
input: { input: {
projectPath, projectPath,
...this.serializedData, ...this.getRequestParams(),
}, },
}, },
}) })
...@@ -113,6 +110,15 @@ export default { ...@@ -113,6 +110,15 @@ export default {
this.loading = false; this.loading = false;
}); });
}, },
getRequestParams() {
const rules = this.form.rules.map(({ status, elapsedTimeSeconds, oncallScheduleIid }) => ({
status,
elapsedTimeSeconds,
oncallScheduleIid,
}));
return { ...this.form, rules };
},
validateForm(field) { validateForm(field) {
if (field === 'name') { if (field === 'name') {
this.validationState.name = isNameFieldValid(this.form.name); this.validationState.name = isNameFieldValid(this.form.name);
......
...@@ -19,13 +19,16 @@ export const i18n = { ...@@ -19,13 +19,16 @@ export const i18n = {
condition: s__('EscalationPolicies|IF alert is not %{alertStatus} in %{minutes} minutes'), condition: s__('EscalationPolicies|IF alert is not %{alertStatus} in %{minutes} minutes'),
action: s__('EscalationPolicies|THEN %{doAction} %{schedule}'), action: s__('EscalationPolicies|THEN %{doAction} %{schedule}'),
selectSchedule: s__('EscalationPolicies|Select schedule'), selectSchedule: s__('EscalationPolicies|Select schedule'),
validationMsg: s__(
'EscalationPolicies|A schedule is required for adding an escalation policy.',
),
noSchedules: s__( noSchedules: s__(
'EscalationPolicies|A schedule is required for adding an escalation policy. Please create an on-call schedule first.', 'EscalationPolicies|A schedule is required for adding an escalation policy. Please create an on-call schedule first.',
), ),
removeRuleLabel: s__('EscalationPolicies|Remove escalation rule'), removeRuleLabel: s__('EscalationPolicies|Remove escalation rule'),
emptyScheduleValidationMsg: s__(
'EscalationPolicies|A schedule is required for adding an escalation policy.',
),
invalidTimeValidationMsg: s__(
'EscalationPolicies|Elapsed time must be greater than or equal to zero.',
),
}, },
}, },
}; };
...@@ -66,10 +69,10 @@ export default { ...@@ -66,10 +69,10 @@ export default {
type: Number, type: Number,
required: true, required: true,
}, },
isValid: { validationState: {
type: Boolean, type: Object,
required: false, required: false,
default: true, default: () => {},
}, },
}, },
data() { data() {
...@@ -90,6 +93,15 @@ export default { ...@@ -90,6 +93,15 @@ export default {
noSchedules() { noSchedules() {
return !this.schedulesLoading && !this.schedules.length; return !this.schedulesLoading && !this.schedules.length;
}, },
isValid() {
return this.isTimeValid && this.isScheduleValid;
},
isTimeValid() {
return this.validationState?.isTimeValid;
},
isScheduleValid() {
return this.validationState?.isScheduleValid;
},
}, },
methods: { methods: {
setOncallSchedule({ iid }) { setOncallSchedule({ iid }) {
...@@ -123,11 +135,16 @@ export default { ...@@ -123,11 +135,16 @@ export default {
class="gl-absolute rule-close-icon" class="gl-absolute rule-close-icon"
@click="$emit('remove-escalation-rule', index)" @click="$emit('remove-escalation-rule', index)"
/> />
<gl-form-group <gl-form-group :state="isValid" class="gl-mb-0">
:invalid-feedback="$options.i18n.fields.rules.validationMsg" <template #invalid-feedback>
:state="isValid" <div v-if="!isScheduleValid">
class="gl-mb-0" {{ $options.i18n.fields.rules.emptyScheduleValidationMsg }}
> </div>
<div v-if="!isTimeValid" class="gl-display-inline-block gl-mt-2">
{{ $options.i18n.fields.rules.invalidTimeValidationMsg }}
</div>
</template>
<div class="gl-display-flex gl-align-items-center"> <div class="gl-display-flex gl-align-items-center">
<gl-sprintf :message="$options.i18n.fields.rules.condition"> <gl-sprintf :message="$options.i18n.fields.rules.condition">
<template #alertStatus> <template #alertStatus>
...@@ -150,10 +167,10 @@ export default { ...@@ -150,10 +167,10 @@ export default {
<template #minutes> <template #minutes>
<gl-form-input <gl-form-input
v-model="elapsedTimeSeconds" v-model="elapsedTimeSeconds"
class="gl-mx-3 gl-inset-border-1-gray-200! rule-elapsed-minutes" class="gl-mx-3 gl-inset-border-1-gray-200! gl-w-12"
type="number" type="number"
min="0" min="0"
@change="emitUpdate" @input="emitUpdate"
/> />
</template> </template>
</gl-sprintf> </gl-sprintf>
......
...@@ -15,5 +15,10 @@ export const isNameFieldValid = (name) => { ...@@ -15,5 +15,10 @@ export const isNameFieldValid = (name) => {
* @returns {Array} * @returns {Array}
*/ */
export const getRulesValidationState = (rules) => { export const getRulesValidationState = (rules) => {
return rules.map((rule) => Boolean(rule.oncallScheduleIid)); return rules.map((rule) => {
return {
isTimeValid: parseInt(rule.elapsedTimeSeconds, 10) >= 0,
isScheduleValid: Boolean(rule.oncallScheduleIid),
};
});
}; };
...@@ -46,23 +46,9 @@ describe('AddEscalationPolicyForm', () => { ...@@ -46,23 +46,9 @@ describe('AddEscalationPolicyForm', () => {
wrapper.destroy(); wrapper.destroy();
}); });
const findPolicyName = () => wrapper.findByTestId('escalation-policy-name');
const findRules = () => wrapper.findAllComponents(EscalationRule); const findRules = () => wrapper.findAllComponents(EscalationRule);
const findAddRuleLink = () => wrapper.findComponent(GlLink); const findAddRuleLink = () => wrapper.findComponent(GlLink);
describe('Escalation policy form validation', () => {
it('should set correct validation state for validated controls', async () => {
createComponent({
props: {
validationState: { name: false, rules: [false] },
},
});
await wrapper.vm.$nextTick();
expect(findPolicyName().attributes('state')).toBeUndefined();
expect(findRules().at(0).attributes('is-valid')).toBeUndefined();
});
});
describe('Escalation rules', () => { describe('Escalation rules', () => {
it('should render one default rule', () => { it('should render one default rule', () => {
expect(findRules().length).toBe(1); expect(findRules().length).toBe(1);
...@@ -96,7 +82,7 @@ describe('AddEscalationPolicyForm', () => { ...@@ -96,7 +82,7 @@ describe('AddEscalationPolicyForm', () => {
}; };
findRules().at(0).vm.$emit('update-escalation-rule', 0, updatedRule); findRules().at(0).vm.$emit('update-escalation-rule', 0, updatedRule);
expect(wrapper.emitted('update-escalation-policy-form')[0]).toEqual([ expect(wrapper.emitted('update-escalation-policy-form')[0]).toEqual([
{ field: 'rules', value: [updatedRule] }, { field: 'rules', value: [expect.objectContaining(updatedRule)] },
]); ]);
}); });
......
import { GlDropdownItem, GlFormGroup, GlSprintf } from '@gitlab/ui'; import { GlDropdownItem, GlFormGroup, GlSprintf } from '@gitlab/ui';
import { shallowMount } from '@vue/test-utils'; import { shallowMount } from '@vue/test-utils';
import { cloneDeep } from 'lodash'; import { cloneDeep } from 'lodash';
import EscalationRule from 'ee/escalation_policies/components/escalation_rule.vue'; import EscalationRule, { i18n } from 'ee/escalation_policies/components/escalation_rule.vue';
import { defaultEscalationRule, ACTIONS, ALERT_STATUSES } from 'ee/escalation_policies/constants'; import { defaultEscalationRule, ACTIONS, ALERT_STATUSES } from 'ee/escalation_policies/constants';
import { extendedWrapper } from 'helpers/vue_test_utils_helper'; import { extendedWrapper } from 'helpers/vue_test_utils_helper';
...@@ -11,6 +11,9 @@ const mockSchedules = [ ...@@ -11,6 +11,9 @@ const mockSchedules = [
{ id: 3, name: 'schedule3' }, { id: 3, name: 'schedule3' },
]; ];
const emptyScheduleMsg = i18n.fields.rules.emptyScheduleValidationMsg;
const invalidTimeMsg = i18n.fields.rules.invalidTimeValidationMsg;
describe('EscalationRule', () => { describe('EscalationRule', () => {
let wrapper; let wrapper;
const createComponent = ({ props = {} } = {}) => { const createComponent = ({ props = {} } = {}) => {
...@@ -25,6 +28,7 @@ describe('EscalationRule', () => { ...@@ -25,6 +28,7 @@ describe('EscalationRule', () => {
...props, ...props,
}, },
stubs: { stubs: {
GlFormGroup,
GlSprintf, GlSprintf,
}, },
}), }),
...@@ -96,17 +100,45 @@ describe('EscalationRule', () => { ...@@ -96,17 +100,45 @@ describe('EscalationRule', () => {
}); });
describe('Validation', () => { describe('Validation', () => {
it.each` describe.each`
isValid | state validationState | formState
${true} | ${'true'} ${{ isTimeValid: true, isScheduleValid: true }} | ${'true'}
${false} | ${undefined} ${{ isTimeValid: false, isScheduleValid: true }} | ${undefined}
`('when $isValid sets from group state to $state', ({ isValid, state }) => { ${{ isTimeValid: true, isScheduleValid: false }} | ${undefined}
createComponent({ ${{ isTimeValid: false, isScheduleValid: false }} | ${undefined}
props: { `(`when`, ({ validationState, formState }) => {
isValid, describe(`elapsed minutes control is ${
}, validationState.isTimeValid ? 'valid' : 'invalid'
} and schedule control is ${validationState.isScheduleValid ? 'valid' : 'invalid'}`, () => {
beforeEach(() => {
createComponent({
props: {
validationState,
},
});
});
it(`sets form group validation state to ${formState}`, () => {
expect(findFormGroup().attributes('state')).toBe(formState);
});
it(`does ${
validationState.isTimeValid ? 'not show' : 'show'
} invalid time error message && does ${
validationState.isScheduleValid ? 'not show' : 'show'
} invalid schedule error message `, () => {
if (validationState.isTimeValid) {
expect(findFormGroup().text()).not.toContain(invalidTimeMsg);
} else {
expect(findFormGroup().text()).toContain(invalidTimeMsg);
}
if (validationState.isScheduleValid) {
expect(findFormGroup().text()).not.toContain(emptyScheduleMsg);
} else {
expect(findFormGroup().text()).toContain(emptyScheduleMsg);
}
});
}); });
expect(findFormGroup().attributes('state')).toBe(state);
}); });
}); });
}); });
...@@ -13111,6 +13111,9 @@ msgstr "" ...@@ -13111,6 +13111,9 @@ msgstr ""
msgid "EscalationPolicies|Edit escalation policy" msgid "EscalationPolicies|Edit escalation policy"
msgstr "" msgstr ""
msgid "EscalationPolicies|Elapsed time must be greater than or equal to zero."
msgstr ""
msgid "EscalationPolicies|Email on-call user in schedule" msgid "EscalationPolicies|Email on-call user in schedule"
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