diff --git a/ee/app/assets/javascripts/escalation_policies/components/add_edit_escalation_policy_form.vue b/ee/app/assets/javascripts/escalation_policies/components/add_edit_escalation_policy_form.vue index 665606c3d5de5bb0d7efb16d5b1c323d2266255e..4c71f3d5753ab2722ba29c2d48c72c6fb531dba3 100644 --- a/ee/app/assets/javascripts/escalation_policies/components/add_edit_escalation_policy_form.vue +++ b/ee/app/assets/javascripts/escalation_policies/components/add_edit_escalation_policy_form.vue @@ -1,9 +1,9 @@ <script> import { GlLink, GlForm, GlFormGroup, GlFormInput } from '@gitlab/ui'; -import { cloneDeep } from 'lodash'; +import { cloneDeep, uniqueId } from 'lodash'; import createFlash from '~/flash'; import { s__, __ } from '~/locale'; -import { DEFAULT_ESCALATION_RULE } from '../constants'; +import { DEFAULT_ACTION, DEFAULT_ESCALATION_RULE } from '../constants'; import getOncallSchedulesQuery from '../graphql/queries/get_oncall_schedules.query.graphql'; import EscalationRule from './escalation_rule.vue'; @@ -48,7 +48,6 @@ export default { return { schedules: [], rules: [], - uid: 0, }; }, apollo: { @@ -74,11 +73,29 @@ export default { }, }, mounted() { - this.addRule(); + this.rules = this.form.rules.map((rule) => { + const { + status, + elapsedTimeSeconds, + oncallSchedule: { iid: oncallScheduleIid }, + } = rule; + + return { + status, + elapsedTimeSeconds, + action: DEFAULT_ACTION, + oncallScheduleIid, + key: uniqueId(), + }; + }); + + if (!this.rules.length) { + this.addRule(); + } }, methods: { addRule() { - this.rules.push({ ...cloneDeep(DEFAULT_ESCALATION_RULE), key: this.getUid() }); + this.rules.push({ ...cloneDeep(DEFAULT_ESCALATION_RULE), key: uniqueId() }); }, updateEscalationRules(index, rule) { this.rules[index] = { ...this.rules[index], ...rule }; @@ -91,10 +108,6 @@ export default { emitRulesUpdate() { this.$emit('update-escalation-policy-form', { field: 'rules', value: this.rules }); }, - getUid() { - this.uid += 1; - return this.uid; - }, }, }; </script> diff --git a/ee/app/assets/javascripts/escalation_policies/components/add_edit_escalation_policy_modal.vue b/ee/app/assets/javascripts/escalation_policies/components/add_edit_escalation_policy_modal.vue index 485405250badd33c8ac32a10e4531234c73ab675..729e849cc3c8bf90c32c8c7a7cea0cd7390bb328 100644 --- a/ee/app/assets/javascripts/escalation_policies/components/add_edit_escalation_policy_modal.vue +++ b/ee/app/assets/javascripts/escalation_policies/components/add_edit_escalation_policy_modal.vue @@ -1,10 +1,13 @@ <script> import { GlModal, GlAlert } from '@gitlab/ui'; -import { set } from 'lodash'; +import { set, isEqual } from 'lodash'; import { s__, __ } from '~/locale'; -import { addEscalationPolicyModalId } from '../constants'; -import { updateStoreOnEscalationPolicyCreate } from '../graphql/cache_updates'; +import { + updateStoreOnEscalationPolicyCreate, + updateStoreOnEscalationPolicyUpdate, +} from '../graphql/cache_updates'; import createEscalationPolicyMutation from '../graphql/mutations/create_escalation_policy.mutation.graphql'; +import updateEscalationPolicyMutation from '../graphql/mutations/update_escalation_policy.mutation.graphql'; import getEscalationPoliciesQuery from '../graphql/queries/get_escalation_policies.query.graphql'; import { isNameFieldValid, getRulesValidationState } from '../utils'; import AddEditEscalationPolicyForm from './add_edit_escalation_policy_form.vue'; @@ -17,7 +20,6 @@ export const i18n = { export default { i18n, - addEscalationPolicyModalId, components: { GlModal, GlAlert, @@ -30,15 +32,21 @@ export default { required: false, default: () => ({}), }, + isEditMode: { + type: Boolean, + required: false, + default: false, + }, + modalId: { + type: String, + required: true, + }, }, data() { return { loading: false, - form: { - name: this.escalationPolicy.name, - description: this.escalationPolicy.description, - rules: [], - }, + form: this.getInitialState(), + initialState: this.getInitialState(), validationState: { name: null, rules: [], @@ -47,14 +55,17 @@ export default { }; }, computed: { + title() { + return this.isEditMode ? i18n.editEscalationPolicy : i18n.addEscalationPolicy; + }, actionsProps() { return { primary: { - text: i18n.addEscalationPolicy, + text: this.title, attributes: [ { variant: 'info' }, { loading: this.loading }, - { disabled: !this.isFormValid }, + { disabled: !this.isFormValid || !this.isFormDirty }, ], }, cancel: { @@ -65,13 +76,32 @@ export default { isFormValid() { return ( this.validationState.name && + (this.isEditMode ? true : this.validationState.rules.length) && this.validationState.rules.every( ({ isTimeValid, isScheduleValid }) => isTimeValid && isScheduleValid, ) ); }, + isFormDirty() { + return ( + this.form.name !== this.initialState.name || + this.form.description !== this.initialState.description || + !isEqual(this.getRules(this.form.rules), this.getRules(this.initialState.rules)) + ); + }, + requestParams() { + const id = this.isEditMode ? { id: this.escalationPolicy.id } : {}; + return { ...this.form, ...id, rules: this.getRules(this.form.rules) }; + }, }, methods: { + getInitialState() { + return { + name: this.escalationPolicy.name ?? '', + description: this.escalationPolicy.description ?? '', + rules: this.escalationPolicy.rules ?? [], + }; + }, updateForm({ field, value }) { set(this.form, field, value); this.validateForm(field); @@ -85,7 +115,7 @@ export default { variables: { input: { projectPath, - ...this.getRequestParams(), + ...this.requestParams, }, }, update(store, { data }) { @@ -117,14 +147,51 @@ export default { this.loading = false; }); }, - getRequestParams() { - const rules = this.form.rules.map(({ status, elapsedTimeSeconds, oncallScheduleIid }) => ({ - status, - elapsedTimeSeconds, - oncallScheduleIid, - })); - - return { ...this.form, rules }; + updateEscalationPolicy() { + this.loading = true; + const { projectPath } = this; + this.$apollo + .mutate({ + mutation: updateEscalationPolicyMutation, + variables: { + input: this.requestParams, + }, + update(store, { data }) { + updateStoreOnEscalationPolicyUpdate(store, getEscalationPoliciesQuery, data, { + projectPath, + }); + }, + }) + .then( + ({ + data: { + escalationPolicyUpdate: { + errors: [error], + }, + }, + }) => { + if (error) { + throw error; + } + this.$refs.addUpdateEscalationPolicyModal.hide(); + this.resetForm(); + }, + ) + .catch((error) => { + this.error = error; + }) + .finally(() => { + this.loading = false; + }); + }, + getRules(rules) { + return rules.map( + ({ status, elapsedTimeSeconds, oncallScheduleIid, oncallSchedule: { iid } = {} }) => ({ + status, + elapsedTimeSeconds, + oncallScheduleIid: oncallScheduleIid || iid, + }), + ); }, validateForm(field) { if (field === 'name') { @@ -138,11 +205,21 @@ export default { this.error = null; }, resetForm() { - this.form = { - name: '', - description: '', - rules: [], - }; + if (this.isEditMode) { + const { name, description, rules } = this.escalationPolicy; + this.form = { + name, + description, + rules, + }; + } else { + this.form = { + name: '', + description: '', + rules: [], + }; + } + this.validationState = { name: null, rules: [], @@ -157,11 +234,11 @@ export default { <gl-modal ref="addUpdateEscalationPolicyModal" class="escalation-policy-modal" - :modal-id="$options.addEscalationPolicyModalId" - :title="$options.i18n.addEscalationPolicy" + :modal-id="modalId" + :title="title" :action-primary="actionsProps.primary" :action-cancel="actionsProps.cancel" - @primary.prevent="createEscalationPolicy" + @primary.prevent="isEditMode ? updateEscalationPolicy() : createEscalationPolicy()" @canceled="resetForm" @close="resetForm" > diff --git a/ee/app/assets/javascripts/escalation_policies/components/escalation_policies_wrapper.vue b/ee/app/assets/javascripts/escalation_policies/components/escalation_policies_wrapper.vue index e04e096b1d4e0ef5e8d607bc4890b7d2fc6c5976..ac39b636f01820d6d4505511182396b4f34f6579 100644 --- a/ee/app/assets/javascripts/escalation_policies/components/escalation_policies_wrapper.vue +++ b/ee/app/assets/javascripts/escalation_policies/components/escalation_policies_wrapper.vue @@ -72,15 +72,6 @@ export default { <template v-else-if="hasPolicies"> <div class="gl-display-flex gl-justify-content-space-between gl-align-items-center"> <h2>{{ $options.i18n.title }}</h2> - <gl-button - v-gl-modal="$options.addEscalationPolicyModalId" - :title="$options.i18n.addPolicy" - category="secondary" - variant="confirm" - class="gl-mt-5" - > - {{ $options.i18n.addPolicy }} - </gl-button> </div> <escalation-policy v-for="(policy, index) in escalationPolicies" diff --git a/ee/app/assets/javascripts/escalation_policies/components/escalation_policy.vue b/ee/app/assets/javascripts/escalation_policies/components/escalation_policy.vue index 3d291382e77e8f68302df448c354bf0b483a36fe..7422d9c6fa666ad41aa9939a329e9be865e83e0e 100644 --- a/ee/app/assets/javascripts/escalation_policies/components/escalation_policy.vue +++ b/ee/app/assets/javascripts/escalation_policies/components/escalation_policy.vue @@ -15,7 +15,9 @@ import { ALERT_STATUSES, DEFAULT_ACTION, deleteEscalationPolicyModalId, + editEscalationPolicyModalId, } from '../constants'; +import EditEscalationPolicyModal from './add_edit_escalation_policy_modal.vue'; import DeleteEscalationPolicyModal from './delete_escalation_policy_modal.vue'; export const i18n = { @@ -45,6 +47,7 @@ export default { GlIcon, GlCollapse, DeleteEscalationPolicyModal, + EditEscalationPolicyModal, }, directives: { GlModal: GlModalDirective, @@ -75,6 +78,9 @@ export default { policyVisibleAngleIconLabel() { return this.isPolicyVisible ? __('Collapse') : __('Expand'); }, + editPolicyModalId() { + return `${editEscalationPolicyModalId}-${this.policy.id}`; + }, deletePolicyModalId() { return `${deleteEscalationPolicyModalId}-${this.policy.id}`; }, @@ -106,11 +112,11 @@ export default { <h3 class="gl-font-weight-bold gl-font-lg gl-m-0">{{ policy.name }}</h3> <gl-button-group class="gl-ml-auto"> <gl-button + v-gl-modal="editPolicyModalId" v-gl-tooltip :title="$options.i18n.editPolicy" icon="pencil" :aria-label="$options.i18n.editPolicy" - disabled /> <gl-button v-gl-modal="deletePolicyModalId" @@ -163,5 +169,10 @@ export default { </gl-card> <delete-escalation-policy-modal :escalation-policy="policy" :modal-id="deletePolicyModalId" /> + <edit-escalation-policy-modal + :escalation-policy="policy" + :modal-id="editPolicyModalId" + is-edit-mode + /> </div> </template> diff --git a/ee/app/assets/javascripts/escalation_policies/graphql/cache_updates.js b/ee/app/assets/javascripts/escalation_policies/graphql/cache_updates.js index 0007146d58e0c16e61ccbf6f2fea226eb852f6b2..ff47ab3d08cc745180c64603b549b0fa205a9773 100644 --- a/ee/app/assets/javascripts/escalation_policies/graphql/cache_updates.js +++ b/ee/app/assets/javascripts/escalation_policies/graphql/cache_updates.js @@ -3,10 +3,13 @@ import createFlash from '~/flash'; import { s__ } from '~/locale'; -export const DELETE_ESCALATION_POLICY_ERROR = s__( +const DELETE_ESCALATION_POLICY_ERROR = s__( 'EscalationPolicies|The escalation policy could not be deleted. Please try again.', ); +const UPDATE_ESCALATION_POLICY_ERROR = s__( + 'EscalationPolicies|The escalation policy could not be updated. Please try again', +); const addEscalationPolicyToStore = (store, query, { escalationPolicyCreate }, variables) => { const policy = escalationPolicyCreate?.escalationPolicy; if (!policy) { @@ -29,6 +32,32 @@ const addEscalationPolicyToStore = (store, query, { escalationPolicyCreate }, va }); }; +const updateEscalationPolicyInStore = (store, query, { escalationPolicyUpdate }, variables) => { + const policy = escalationPolicyUpdate?.escalationPolicy; + if (!policy) { + return; + } + + const sourceData = store.readQuery({ + query, + variables, + }); + + const data = produce(sourceData, (draftData) => { + draftData.project.incidentManagementEscalationPolicies.nodes = draftData.project.incidentManagementEscalationPolicies.nodes.map( + (policyToUpdate) => { + return policyToUpdate.id === policy.id ? policy : policyToUpdate; + }, + ); + }); + + store.writeQuery({ + query, + variables, + data, + }); +}; + const deleteEscalationPolicFromStore = (store, query, { escalationPolicyDestroy }, variables) => { const escalationPolicy = escalationPolicyDestroy?.escalationPolicy; @@ -66,6 +95,15 @@ export const updateStoreOnEscalationPolicyCreate = (store, query, data, variable addEscalationPolicyToStore(store, query, data, variables); } }; + +export const updateStoreOnEscalationPolicyUpdate = (store, query, data, variables) => { + if (hasErrors(data)) { + onError(data, UPDATE_ESCALATION_POLICY_ERROR); + } else { + updateEscalationPolicyInStore(store, query, data, variables); + } +}; + export const updateStoreAfterEscalationPolicyDelete = (store, query, data, variables) => { if (hasErrors(data)) { onError(data, DELETE_ESCALATION_POLICY_ERROR); diff --git a/ee/app/assets/javascripts/escalation_policies/graphql/mutations/update_escalation_policy.mutation.graphql b/ee/app/assets/javascripts/escalation_policies/graphql/mutations/update_escalation_policy.mutation.graphql new file mode 100644 index 0000000000000000000000000000000000000000..8b1acf21a7d648d598146a47ebf1f60588a81ffb --- /dev/null +++ b/ee/app/assets/javascripts/escalation_policies/graphql/mutations/update_escalation_policy.mutation.graphql @@ -0,0 +1,10 @@ +#import "../fragments/escalation_policy.fragment.graphql" + +mutation escalationPolicyUpdate($input: EscalationPolicyUpdateInput!) { + escalationPolicyUpdate(input: $input) { + escalationPolicy { + ...EscalationPolicy + } + errors + } +} diff --git a/ee/spec/frontend/escalation_policies/__snapshots__/escalation_policy_spec.js.snap b/ee/spec/frontend/escalation_policies/__snapshots__/escalation_policy_spec.js.snap index 63e39e6bff5a5a4ea558ff4306599f0ecad8a956..29436559e3a940828316ea5fa4035d6719aa020d 100644 --- a/ee/spec/frontend/escalation_policies/__snapshots__/escalation_policy_spec.js.snap +++ b/ee/spec/frontend/escalation_policies/__snapshots__/escalation_policy_spec.js.snap @@ -117,5 +117,11 @@ exports[`EscalationPolicy renders policy with rules 1`] = ` escalationpolicy="[object Object]" modalid="deleteEscalationPolicyModal-37" /> + + <edit-escalation-policy-modal-stub + escalationpolicy="[object Object]" + iseditmode="true" + modalid="editEscalationPolicyModal-37" + /> </div> `; diff --git a/ee/spec/frontend/escalation_policies/add_edit_escalation_policy_form_spec.js b/ee/spec/frontend/escalation_policies/add_edit_escalation_policy_form_spec.js index f114c1945564f76e61f92fb9014db6441ea7d6d6..22439e7ea157bff98aea1107ef57c9ce3742097a 100644 --- a/ee/spec/frontend/escalation_policies/add_edit_escalation_policy_form_spec.js +++ b/ee/spec/frontend/escalation_policies/add_edit_escalation_policy_form_spec.js @@ -18,6 +18,7 @@ describe('AddEscalationPolicyForm', () => { form: { name: mockPolicies[1].name, description: mockPolicies[1].description, + rules: [], }, validationState: { name: true, @@ -48,8 +49,14 @@ describe('AddEscalationPolicyForm', () => { const findAddRuleLink = () => wrapper.findComponent(GlLink); describe('Escalation rules', () => { - it('should render one default rule', () => { - expect(findRules().length).toBe(1); + it('should render one default rule when rules were not provided', () => { + expect(findRules()).toHaveLength(1); + }); + + it('should render all the rules if they were provided', async () => { + createComponent({ props: { form: { rules: mockPolicies[1].rules } } }); + await wrapper.vm.$nextTick(); + expect(findRules()).toHaveLength(mockPolicies[1].rules.length); }); it('should contain a link to add escalation rules', () => { diff --git a/ee/spec/frontend/escalation_policies/add_edit_escalation_policy_modal_spec.js b/ee/spec/frontend/escalation_policies/add_edit_escalation_policy_modal_spec.js index ef40686ae55b560e2eab6343d4c99ba7072e09b4..f196809be3ffbb12987d77b85f17ea2d56687d02 100644 --- a/ee/spec/frontend/escalation_policies/add_edit_escalation_policy_modal_spec.js +++ b/ee/spec/frontend/escalation_policies/add_edit_escalation_policy_modal_spec.js @@ -5,26 +5,37 @@ import AddEscalationPolicyForm from 'ee/escalation_policies/components/add_edit_ import AddEscalationPolicyModal, { i18n, } from 'ee/escalation_policies/components/add_edit_escalation_policy_modal.vue'; + +import { + addEscalationPolicyModalId, + editEscalationPolicyModalId, +} from 'ee/escalation_policies/constants'; +import createEscalationPolicyMutation from 'ee/escalation_policies/graphql/mutations/create_escalation_policy.mutation.graphql'; +import updateEscalationPolicyMutation from 'ee/escalation_policies/graphql/mutations/update_escalation_policy.mutation.graphql'; import waitForPromises from 'helpers/wait_for_promises'; import mockPolicies from './mocks/mockPolicies.json'; -describe('AddEscalationPolicyModal', () => { +describe('AddEditsEscalationPolicyModal', () => { let wrapper; const projectPath = 'group/project'; const mockHideModal = jest.fn(); const mutate = jest.fn(); - const mockPolicy = cloneDeep(mockPolicies[0]); + const mockEscalationPolicy = cloneDeep(mockPolicies[0]); + const updatedName = 'Policy name'; + const updatedDescription = 'Policy description'; + const updatedRules = [{ status: 'RESOLVED', elapsedTimeSeconds: 10, oncallScheduleIid: 1 }]; - const createComponent = ({ escalationPolicy, data } = {}) => { + const createComponent = ({ escalationPolicy, isEditMode = false, modalId, data } = {}) => { wrapper = shallowMount(AddEscalationPolicyModal, { data() { return { - form: mockPolicy, ...data, }; }, propsData: { escalationPolicy, + isEditMode, + modalId, }, provide: { projectPath, @@ -38,9 +49,6 @@ describe('AddEscalationPolicyModal', () => { wrapper.vm.$refs.addUpdateEscalationPolicyModal.hide = mockHideModal; }; - beforeEach(() => { - createComponent(); - }); afterEach(() => { wrapper.destroy(); @@ -50,32 +58,118 @@ describe('AddEscalationPolicyModal', () => { const findEscalationPolicyForm = () => wrapper.findComponent(AddEscalationPolicyForm); const findAlert = () => wrapper.findComponent(GlAlert); - describe('renders create modal with the correct information', () => { - it('renders modal title', () => { - expect(findModal().attributes('title')).toBe(i18n.addEscalationPolicy); + const updateForm = () => { + const emitUpdate = (args) => + findEscalationPolicyForm().vm.$emit('update-escalation-policy-form', args); + + emitUpdate({ + field: 'name', + value: updatedName, + }); + emitUpdate({ + field: 'description', + value: updatedDescription, + }); + emitUpdate({ + field: 'rules', + value: updatedRules, + }); + }; + + describe('Create escalation policy', () => { + beforeEach(() => { + createComponent({ modalId: addEscalationPolicyModalId }); }); - it('renders the form inside the modal', () => { - expect(findEscalationPolicyForm().exists()).toBe(true); + it('renders create modal with correct information', () => { + const modal = findModal(); + expect(modal.props('title')).toBe(i18n.addEscalationPolicy); + expect(modal.props('modalId')).toBe(addEscalationPolicyModalId); }); it('makes a request with form data to create an escalation policy', () => { mutate.mockResolvedValueOnce({}); + updateForm(); findModal().vm.$emit('primary', { preventDefault: jest.fn() }); - const rules = mockPolicy.rules.map( - ({ status, elapsedTimeSeconds, oncallSchedule: { id } }) => ({ - status, - elapsedTimeSeconds, - oncallScheduleIid: id, + expect(mutate).toHaveBeenCalledWith( + expect.objectContaining({ + mutation: createEscalationPolicyMutation, + variables: { + input: { + projectPath, + name: updatedName, + description: updatedDescription, + rules: updatedRules, + }, + }, + update: expect.any(Function), }), ); + }); + + it('clears the form on modal cancel', async () => { + updateForm(); + expect(findEscalationPolicyForm().props('form')).toMatchObject({ + name: updatedName, + description: updatedDescription, + rules: updatedRules, + }); + + findModal().vm.$emit('canceled', { preventDefault: jest.fn() }); + await wrapper.vm.$nextTick(); + expect(findEscalationPolicyForm().props('form')).toMatchObject({ + name: '', + description: '', + rules: [], + }); + }); + + it('clears the validation state on modal cancel', async () => { + const form = findEscalationPolicyForm(); + const getNameValidationState = () => form.props('validationState').name; + expect(getNameValidationState()).toBe(null); + + form.vm.$emit('update-escalation-policy-form', { + field: 'name', + value: '', + }); + await wrapper.vm.$nextTick(); + expect(getNameValidationState()).toBe(false); + + findModal().vm.$emit('canceled', { preventDefault: jest.fn() }); + await wrapper.vm.$nextTick(); + expect(getNameValidationState()).toBe(null); + }); + }); + + describe('Update escalation policy', () => { + beforeEach(() => { + createComponent({ + modalId: editEscalationPolicyModalId, + escalationPolicy: mockEscalationPolicy, + isEditMode: true, + }); + }); + + it('renders update modal with correct information', () => { + const modal = findModal(); + expect(modal.props('title')).toBe(i18n.editEscalationPolicy); + expect(modal.props('modalId')).toBe(editEscalationPolicyModalId); + }); + + it('makes a request with form data to update an escalation policy', () => { + mutate.mockResolvedValueOnce({}); + updateForm(); + findModal().vm.$emit('primary', { preventDefault: jest.fn() }); expect(mutate).toHaveBeenCalledWith( expect.objectContaining({ + mutation: updateEscalationPolicyMutation, variables: { input: { - projectPath, - ...mockPolicy, - rules, + name: updatedName, + description: updatedDescription, + rules: updatedRules, + id: mockEscalationPolicy.id, }, }, update: expect.any(Function), @@ -83,6 +177,52 @@ describe('AddEscalationPolicyModal', () => { ); }); + it('clears the form on modal cancel', async () => { + updateForm(); + const getForm = () => findEscalationPolicyForm().props('form'); + expect(getForm()).toMatchObject({ + name: updatedName, + description: updatedDescription, + rules: updatedRules, + }); + + findModal().vm.$emit('canceled', { preventDefault: jest.fn() }); + const { name, description, rules } = mockEscalationPolicy; + + await wrapper.vm.$nextTick(); + + expect(getForm()).toMatchObject({ + name, + description, + rules, + }); + }); + + it('clears the validation state on modal cancel', async () => { + const form = findEscalationPolicyForm(); + const getNameValidationState = () => form.props('validationState').name; + expect(getNameValidationState()).toBe(null); + + expect(wrapper.vm.validationState.name).toBe(null); + + form.vm.$emit('update-escalation-policy-form', { + field: 'name', + value: '', + }); + await wrapper.vm.$nextTick(); + expect(getNameValidationState()).toBe(false); + + findModal().vm.$emit('canceled', { preventDefault: jest.fn() }); + await wrapper.vm.$nextTick(); + expect(getNameValidationState()).toBe(null); + }); + }); + + describe('Create/update success/failure', () => { + beforeEach(() => { + createComponent({ modalId: addEscalationPolicyModalId }); + }); + it('hides the modal on successful policy creation', async () => { mutate.mockResolvedValueOnce({ data: { escalationPolicyCreate: { errors: [] } } }); findModal().vm.$emit('primary', { preventDefault: jest.fn() }); @@ -100,35 +240,13 @@ describe('AddEscalationPolicyModal', () => { expect(alert.exists()).toBe(true); expect(alert.text()).toContain(error); }); + }); - it('clears the form on modal cancel', () => { - expect(wrapper.vm.form).toEqual(mockPolicy); - findModal().vm.$emit('canceled', { preventDefault: jest.fn() }); - expect(wrapper.vm.form).toEqual({ - name: '', - description: '', - rules: [], - }); - - expect(wrapper.vm.validationState).toEqual({ - name: null, - rules: [], - }); - }); - - it('clears the validation state on modal cancel', () => { - expect(wrapper.vm.validationState.name).toBe(null); - findEscalationPolicyForm().vm.$emit('update-escalation-policy-form', { - field: 'name', - value: '', - }); - expect(wrapper.vm.validationState.name).toBe(false); - findModal().vm.$emit('canceled', { preventDefault: jest.fn() }); - expect(wrapper.vm.validationState.name).toBe(null); + describe('Modal buttons', () => { + beforeEach(() => { + createComponent({ modalId: addEscalationPolicyModalId }); }); - }); - describe('modal buttons', () => { it('should disable primary button when form is invalid', async () => { findEscalationPolicyForm().vm.$emit('update-escalation-policy-form', { field: 'name', @@ -139,10 +257,15 @@ describe('AddEscalationPolicyModal', () => { }); it('should enable primary button when form is valid', async () => { - findEscalationPolicyForm().vm.$emit('update-escalation-policy-form', { + const form = findEscalationPolicyForm(); + form.vm.$emit('update-escalation-policy-form', { field: 'name', value: 'Some policy name', }); + form.vm.$emit('update-escalation-policy-form', { + field: 'rules', + value: [{ status: 'RESOLVED', elapsedTimeSeconds: 10, oncallScheduleIid: 1 }], + }); await wrapper.vm.$nextTick(); expect(findModal().props('actionPrimary').attributes).toContainEqual({ disabled: false }); }); diff --git a/ee/spec/frontend/escalation_policies/escalation_policy_spec.js b/ee/spec/frontend/escalation_policies/escalation_policy_spec.js index 2b91e4efecb1147b5975f89baf94fa94ab6ff697..8fcf159e20983b191091d3ea158340a916dc207f 100644 --- a/ee/spec/frontend/escalation_policies/escalation_policy_spec.js +++ b/ee/spec/frontend/escalation_policies/escalation_policy_spec.js @@ -1,16 +1,24 @@ import { GlSprintf } from '@gitlab/ui'; import { shallowMount } from '@vue/test-utils'; import { cloneDeep } from 'lodash'; +import EditEscalationPolicyModal from 'ee/escalation_policies/components/add_edit_escalation_policy_modal.vue'; +import DeleteEscalationPolicyModal from 'ee/escalation_policies/components/delete_escalation_policy_modal.vue'; import EscalationPolicy from 'ee/escalation_policies/components/escalation_policy.vue'; +import { + deleteEscalationPolicyModalId, + editEscalationPolicyModalId, +} from 'ee/escalation_policies/constants'; import mockPolicies from './mocks/mockPolicies.json'; describe('EscalationPolicy', () => { let wrapper; + const escalationPolicy = cloneDeep(mockPolicies[0]); + const createComponent = () => { wrapper = shallowMount(EscalationPolicy, { propsData: { - policy: cloneDeep(mockPolicies[0]), + policy: escalationPolicy, index: 0, }, stubs: { @@ -27,7 +35,33 @@ describe('EscalationPolicy', () => { wrapper.destroy(); }); + const findDeleteModal = () => wrapper.findComponent(DeleteEscalationPolicyModal); + const findEditModal = () => wrapper.findComponent(EditEscalationPolicyModal); + it('renders policy with rules', () => { expect(wrapper.element).toMatchSnapshot(); }); + + describe('Modals', () => { + describe('delete policy modal', () => { + it('should render a modal and provide it with correct id', () => { + const modal = findDeleteModal(); + expect(modal.exists()).toBe(true); + expect(modal.props('modalId')).toBe( + `${deleteEscalationPolicyModalId}-${escalationPolicy.id}`, + ); + }); + }); + + describe('edit policy modal', () => { + it('should render a modal and provide it with correct id and isEditMode props', () => { + const modal = findEditModal(); + expect(modal.exists()).toBe(true); + expect(modal.props('modalId')).toBe( + `${editEscalationPolicyModalId}-${escalationPolicy.id}`, + ); + expect(modal.props('isEditMode')).toBe(true); + }); + }); + }); }); diff --git a/ee/spec/frontend/escalation_policies/escalation_policy_wrapper_spec.js b/ee/spec/frontend/escalation_policies/escalation_policy_wrapper_spec.js index c1c8ac12f89c4b73f8f39ac2b0e73b32b5bc3a48..9cdbb9063ad50b40ec47b62f1dc536d7cfc1c2f0 100644 --- a/ee/spec/frontend/escalation_policies/escalation_policy_wrapper_spec.js +++ b/ee/spec/frontend/escalation_policies/escalation_policy_wrapper_spec.js @@ -44,8 +44,6 @@ describe('Escalation Policies Wrapper', () => { const findLoader = () => wrapper.findComponent(GlLoadingIcon); const findEmptyState = () => wrapper.findComponent(GlEmptyState); const findEscalationPolicies = () => wrapper.findAllComponents(EscalationPolicy); - const findAddPolicyBtn = () => - wrapper.findByRole('button', { name: EscalationPoliciesWrapper.i18n.addPolicy }); describe.each` state | loading | escalationPolicies | showsEmptyState | showsLoader @@ -72,10 +70,6 @@ describe('Escalation Policies Wrapper', () => { it(`does ${escalationPolicies.length ? 'show' : 'not show'} escalation policies`, () => { expect(findEscalationPolicies()).toHaveLength(escalationPolicies.length); }); - - it(`does ${escalationPolicies.length ? 'show' : 'not show'} "Add policy" button`, () => { - expect(findAddPolicyBtn().exists()).toBe(Boolean(escalationPolicies.length)); - }); }); }); }); diff --git a/locale/gitlab.pot b/locale/gitlab.pot index 88c337eaa57596f64f55b8058f656f288c67ed15..e735fa871046ab8574edc51aa13dd9749c3ab109 100644 --- a/locale/gitlab.pot +++ b/locale/gitlab.pot @@ -13120,6 +13120,9 @@ msgstr "" msgid "EscalationPolicies|The escalation policy could not be deleted. Please try again." msgstr "" +msgid "EscalationPolicies|The escalation policy could not be updated. Please try again" +msgstr "" + msgid "EscalationPolicies|mins" msgstr ""