From e5e8441a996e30f5d85581f6d96e3ca36c6fa7b6 Mon Sep 17 00:00:00 2001 From: David O'Regan <doregan@gitlab.com> Date: Fri, 8 Jan 2021 12:34:12 +0000 Subject: [PATCH] fix(feedback): apply merge feedback fix(feedback): apply merge feedback --- config/known_invalid_graphql_queries.yml | 1 + .../components/delete_rotation_modal.vue | 118 ++++++++++++ .../components/rotations_list_section.vue | 23 ++- .../javascripts/oncall_schedules/constants.js | 1 + .../destroy_oncall_rotation.mutation.graphql | 10 + .../oncall_schedules/utils/cache_updates.js | 35 ++++ .../oncall_schedules/utils/error_messages.js | 4 + .../add_edit_schedule_form_spec.js | 1 - .../delete_schedule_modal_spec.js | 6 - .../oncall_schedule/mocks/apollo_mock.js | 26 +++ .../oncall_schedule/oncall_schedule_spec.js | 5 +- .../delete_rotation_modal_spec.js.snap | 20 ++ .../add_edit_rotation_modal_spec.js | 1 - .../components/delete_rotation_modal_spec.js | 182 ++++++++++++++++++ .../components/rotation_assignee_spec.js | 5 +- .../rotations_list_section_spec.js.snap | 5 + .../components/current_day_indicator_spec.js | 5 +- .../components/rotations_list_section_spec.js | 5 +- .../schedule_timeline_section_spec.js | 5 +- locale/gitlab.pot | 6 + 20 files changed, 439 insertions(+), 25 deletions(-) create mode 100644 ee/app/assets/javascripts/oncall_schedules/components/rotations/components/delete_rotation_modal.vue create mode 100644 ee/app/assets/javascripts/oncall_schedules/graphql/mutations/destroy_oncall_rotation.mutation.graphql create mode 100644 ee/spec/frontend/oncall_schedule/rotations/components/__snapshots__/delete_rotation_modal_spec.js.snap create mode 100644 ee/spec/frontend/oncall_schedule/rotations/components/delete_rotation_modal_spec.js diff --git a/config/known_invalid_graphql_queries.yml b/config/known_invalid_graphql_queries.yml index 770366d76cf35..8ea9b662aa76d 100644 --- a/config/known_invalid_graphql_queries.yml +++ b/config/known_invalid_graphql_queries.yml @@ -2,3 +2,4 @@ filenames: - ee/app/assets/javascripts/on_demand_scans/graphql/dast_scan_create.mutation.graphql - ee/app/assets/javascripts/oncall_schedules/graphql/mutations/update_oncall_schedule_rotation.mutation.graphql + - ee/app/assets/javascripts/oncall_schedules/graphql/mutations/destroy_oncall_rotation.mutation.graphql diff --git a/ee/app/assets/javascripts/oncall_schedules/components/rotations/components/delete_rotation_modal.vue b/ee/app/assets/javascripts/oncall_schedules/components/rotations/components/delete_rotation_modal.vue new file mode 100644 index 0000000000000..6bf144d307b8c --- /dev/null +++ b/ee/app/assets/javascripts/oncall_schedules/components/rotations/components/delete_rotation_modal.vue @@ -0,0 +1,118 @@ +<script> +import { isEmpty } from 'lodash'; +import { GlSprintf, GlModal, GlAlert } from '@gitlab/ui'; +import destroyOncallRotationMutation from 'ee/oncall_schedules/graphql/mutations/destroy_oncall_rotation.mutation.graphql'; +import getOncallSchedulesQuery from 'ee/oncall_schedules/graphql/queries/get_oncall_schedules.query.graphql'; +import { updateStoreAfterRotationDelete } from 'ee/oncall_schedules/utils/cache_updates'; +import { s__, __ } from '~/locale'; + +export const i18n = { + deleteRotation: s__('OnCallSchedules|Delete rotation'), + deleteRotationMessage: s__( + 'OnCallSchedules|Are you sure you want to delete the "%{deleteRotation}" rotation? This action cannot be undone.', + ), + cancel: __('Cancel'), +}; + +export default { + i18n, + components: { + GlSprintf, + GlModal, + GlAlert, + }, + inject: ['projectPath'], + props: { + rotation: { + type: Object, + required: true, + validator: (rotation) => + isEmpty(rotation) || [rotation.id, rotation.name, rotation.startsAt].every(Boolean), + }, + modalId: { + type: String, + required: true, + }, + }, + data() { + return { + loading: false, + error: '', + }; + }, + computed: { + primaryProps() { + return { + text: this.$options.i18n.deleteRotation, + attributes: [{ category: 'primary' }, { variant: 'danger' }, { loading: this.loading }], + }; + }, + cancelProps() { + return { + text: this.$options.i18n.cancel, + }; + }, + rotationDeleteModalTestId() { + return `delete-rotation-modal-${this.rotation.id}`; + }, + }, + methods: { + deleteRotation() { + const { + projectPath, + rotation: { id }, + } = this; + + this.loading = true; + this.$apollo + .mutate({ + mutation: destroyOncallRotationMutation, + variables: { + iid: id, + projectPath, + }, + update(store, { data }) { + updateStoreAfterRotationDelete(store, getOncallSchedulesQuery, data, { projectPath }); + }, + }) + .then(({ data: { oncallRotationDestroy } = {} } = {}) => { + const error = oncallRotationDestroy.errors[0]; + if (error) { + throw error; + } + this.$refs.deleteRotationModal.hide(); + }) + .catch((error) => { + this.error = error; + }) + .finally(() => { + this.loading = false; + }); + }, + hideErrorAlert() { + this.error = ''; + }, + }, +}; +</script> + +<template> + <gl-modal + ref="deleteRotationModal" + :modal-id="modalId" + size="sm" + :data-testid="rotationDeleteModalTestId" + :title="$options.i18n.deleteRotation" + :action-primary="primaryProps" + :action-cancel="cancelProps" + @primary.prevent="deleteRotation" + @cancel="$emit('set-rotation-to-update', {})" + > + <gl-alert v-if="error" variant="danger" class="gl-mt-n3 gl-mb-3" @dismiss="hideErrorAlert"> + {{ error || $options.i18n.errorMsg }} + </gl-alert> + <gl-sprintf :message="$options.i18n.deleteRotationMessage"> + <template #deleteRotation>{{ rotation.name }}</template> + </gl-sprintf> + </gl-modal> +</template> diff --git a/ee/app/assets/javascripts/oncall_schedules/components/schedule/components/rotations_list_section.vue b/ee/app/assets/javascripts/oncall_schedules/components/schedule/components/rotations_list_section.vue index 9feaad8e1e340..cbd44b6d72a9e 100644 --- a/ee/app/assets/javascripts/oncall_schedules/components/schedule/components/rotations_list_section.vue +++ b/ee/app/assets/javascripts/oncall_schedules/components/schedule/components/rotations_list_section.vue @@ -3,7 +3,8 @@ import { GlButtonGroup, GlButton, GlTooltipDirective, GlModalDirective } from '@ import { s__ } from '~/locale'; import CurrentDayIndicator from './current_day_indicator.vue'; import RotationAssignee from '../../rotations/components/rotation_assignee.vue'; -import { editRotationModalId } from '../../../constants'; +import DeleteRotationModal from '../../rotations/components/delete_rotation_modal.vue'; +import { editRotationModalId, deleteRotationModalId } from '../../../constants'; export const i18n = { editRotationLabel: s__('OnCallSchedules|Edit rotation'), @@ -13,11 +14,13 @@ export const i18n = { export default { i18n, editRotationModalId, + deleteRotationModalId, components: { GlButtonGroup, GlButton, CurrentDayIndicator, RotationAssignee, + DeleteRotationModal, }, directives: { GlModal: GlModalDirective, @@ -37,6 +40,16 @@ export default { required: true, }, }, + data() { + return { + rotationToUpdate: {}, + }; + }, + methods: { + setRotationToUpdate(rotation) { + this.rotationToUpdate = rotation; + }, + }, }; </script> @@ -61,12 +74,13 @@ export default { :aria-label="$options.i18n.editRotationLabel" /> <gl-button - v-gl-modal="$options.editRotationModalId" + v-gl-modal="$options.deleteRotationModalId" v-gl-tooltip category="tertiary" :title="$options.i18n.deleteRotationLabel" icon="remove" :aria-label="$options.i18n.deleteRotationLabel" + @click="setRotationToUpdate(rotation)" /> </gl-button-group> </span> @@ -85,5 +99,10 @@ export default { /> </span> </div> + <delete-rotation-modal + :rotation="rotationToUpdate" + :modal-id="$options.deleteRotationModalId" + @set-rotation-to-update="setRotationToUpdate" + /> </div> </template> diff --git a/ee/app/assets/javascripts/oncall_schedules/constants.js b/ee/app/assets/javascripts/oncall_schedules/constants.js index 5a143d5f742a9..1f58cd1c69c18 100644 --- a/ee/app/assets/javascripts/oncall_schedules/constants.js +++ b/ee/app/assets/javascripts/oncall_schedules/constants.js @@ -23,3 +23,4 @@ export const PRESET_DEFAULTS = { export const addRotationModalId = 'addRotationModal'; export const editRotationModalId = 'editRotationModal'; +export const deleteRotationModalId = 'deleteRotationModal'; diff --git a/ee/app/assets/javascripts/oncall_schedules/graphql/mutations/destroy_oncall_rotation.mutation.graphql b/ee/app/assets/javascripts/oncall_schedules/graphql/mutations/destroy_oncall_rotation.mutation.graphql new file mode 100644 index 0000000000000..f42fcfc122f74 --- /dev/null +++ b/ee/app/assets/javascripts/oncall_schedules/graphql/mutations/destroy_oncall_rotation.mutation.graphql @@ -0,0 +1,10 @@ +#import "../fragments/oncall_schedule_rotation.fragment.graphql" + +mutation oncallRotationDestroy($iid: String!, $projectPath: ID!) { + oncallRotationDestroy(input: { iid: $iid, projectPath: $projectPath }) { + errors + oncallRotation { + ...OnCallRotation + } + } +} diff --git a/ee/app/assets/javascripts/oncall_schedules/utils/cache_updates.js b/ee/app/assets/javascripts/oncall_schedules/utils/cache_updates.js index f6c0e637e2d58..95fb60e9cdc7a 100644 --- a/ee/app/assets/javascripts/oncall_schedules/utils/cache_updates.js +++ b/ee/app/assets/javascripts/oncall_schedules/utils/cache_updates.js @@ -5,6 +5,7 @@ import { DELETE_SCHEDULE_ERROR, UPDATE_SCHEDULE_ERROR, UPDATE_ROTATION_ERROR, + DELETE_ROTATION_ERROR, } from './error_messages'; const addScheduleToStore = (store, query, { oncallSchedule: schedule }, variables) => { @@ -138,6 +139,32 @@ const updateRotationFromStore = (store, query, { oncallRotationUpdate }, schedul }); }; +const deleteRotationFromStore = (store, query, { oncallRotationDestroy }, variables) => { + const rotation = oncallRotationDestroy?.oncallRotation; + if (!rotation) { + return; + } + + const sourceData = store.readQuery({ + query, + variables, + }); + + // TODO: This needs the rotation backend to be fully integrated to work, for the moment we will place-hold it. https://gitlab.com/gitlab-org/gitlab/-/issues/262863 + const data = produce(sourceData, (draftData) => { + // eslint-disable-next-line no-param-reassign + draftData.project.incidentManagementOncallSchedules.nodes[0].rotations = [rotation].filter( + ({ id }) => id !== rotation.id, + ); + }); + + store.writeQuery({ + query, + variables, + data, + }); +}; + const onError = (data, message) => { createFlash({ message }); throw new Error(data.errors); @@ -180,3 +207,11 @@ export const updateStoreAfterRotationEdit = (store, query, data, scheduleId, var updateRotationFromStore(store, query, data, scheduleId, variables); } }; + +export const updateStoreAfterRotationDelete = (store, query, data, variables) => { + if (hasErrors(data)) { + onError(data, DELETE_ROTATION_ERROR); + } else { + deleteRotationFromStore(store, query, data, variables); + } +}; diff --git a/ee/app/assets/javascripts/oncall_schedules/utils/error_messages.js b/ee/app/assets/javascripts/oncall_schedules/utils/error_messages.js index 158ec951cb91a..67d3ebb06bb8c 100644 --- a/ee/app/assets/javascripts/oncall_schedules/utils/error_messages.js +++ b/ee/app/assets/javascripts/oncall_schedules/utils/error_messages.js @@ -11,3 +11,7 @@ export const UPDATE_SCHEDULE_ERROR = s__( export const UPDATE_ROTATION_ERROR = s__( 'OnCallSchedules|The rotation could not be updated. Please try again.', ); + +export const DELETE_ROTATION_ERROR = s__( + 'OnCallSchedules|The rotation could not be deleted. Please try again.', +); diff --git a/ee/spec/frontend/oncall_schedule/add_edit_schedule_form_spec.js b/ee/spec/frontend/oncall_schedule/add_edit_schedule_form_spec.js index 9fbbfbb66216c..3dc0d706fdc24 100644 --- a/ee/spec/frontend/oncall_schedule/add_edit_schedule_form_spec.js +++ b/ee/spec/frontend/oncall_schedule/add_edit_schedule_form_spec.js @@ -47,7 +47,6 @@ describe('AddEditScheduleForm', () => { afterEach(() => { wrapper.destroy(); - wrapper = null; }); const findTimezoneDropdown = () => wrapper.find(GlDropdown); diff --git a/ee/spec/frontend/oncall_schedule/delete_schedule_modal_spec.js b/ee/spec/frontend/oncall_schedule/delete_schedule_modal_spec.js index f5d15bb403b5b..93b5c9947f5ae 100644 --- a/ee/spec/frontend/oncall_schedule/delete_schedule_modal_spec.js +++ b/ee/spec/frontend/oncall_schedule/delete_schedule_modal_spec.js @@ -22,8 +22,6 @@ const mockHideModal = jest.fn(); const schedule = getOncallSchedulesQueryResponse.data.project.incidentManagementOncallSchedules.nodes[0]; -localVue.use(VueApollo); - describe('DeleteScheduleModal', () => { let wrapper; let fakeApollo; @@ -40,9 +38,6 @@ describe('DeleteScheduleModal', () => { } async function destroySchedule(localWrapper) { - await jest.runOnlyPendingTimers(); - await localWrapper.vm.$nextTick(); - localWrapper.find(GlModal).vm.$emit('primary', { preventDefault: jest.fn() }); } @@ -111,7 +106,6 @@ describe('DeleteScheduleModal', () => { afterEach(() => { wrapper.destroy(); - wrapper = null; }); it('renders delete schedule modal layout', () => { diff --git a/ee/spec/frontend/oncall_schedule/mocks/apollo_mock.js b/ee/spec/frontend/oncall_schedule/mocks/apollo_mock.js index c0a23d02d35c5..04278fd03f6d1 100644 --- a/ee/spec/frontend/oncall_schedule/mocks/apollo_mock.js +++ b/ee/spec/frontend/oncall_schedule/mocks/apollo_mock.js @@ -1,3 +1,5 @@ +import mockRotations from './mock_rotation.json'; + export const participants = [ { id: '1', @@ -165,3 +167,27 @@ export const createRotationResponseWithErrors = { }, }, }; + +export const destroyRotationResponse = { + data: { + oncallRotationDestroy: { + errors: [], + oncallRotation: { + __typename: 'IncidentManagementOncallRotation', + ...mockRotations[0], + }, + }, + }, +}; + +export const destroyRotationResponseWithErrors = { + data: { + oncallRotationDestroy: { + errors: ['Houston, we have a problem'], + oncallRotation: { + __typename: 'IncidentManagementOncallRotation', + ...mockRotations[0], + }, + }, + }, +}; diff --git a/ee/spec/frontend/oncall_schedule/oncall_schedule_spec.js b/ee/spec/frontend/oncall_schedule/oncall_schedule_spec.js index f70081f0bc6e6..9fd2c4e72c3ea 100644 --- a/ee/spec/frontend/oncall_schedule/oncall_schedule_spec.js +++ b/ee/spec/frontend/oncall_schedule/oncall_schedule_spec.js @@ -22,7 +22,7 @@ describe('On-call schedule', () => { const mockWeeksTimeFrame = ['31 Dec 2020', '7 Jan 2021', '14 Jan 2021']; const formattedTimezone = '(UTC-09:00) AKST Alaska'; - function mountComponent({ schedule } = {}) { + function createComponent({ schedule } = {}) { wrapper = extendedWrapper( shallowMount(OnCallSchedule, { propsData: { @@ -42,12 +42,11 @@ describe('On-call schedule', () => { beforeEach(() => { jest.spyOn(utils, 'getTimeframeForWeeksView').mockReturnValue(mockWeeksTimeFrame); jest.spyOn(commonUtils, 'getFormattedTimezone').mockReturnValue(formattedTimezone); - mountComponent({ schedule: mockSchedule }); + createComponent({ schedule: mockSchedule }); }); afterEach(() => { wrapper.destroy(); - wrapper = null; }); const findScheduleHeader = () => wrapper.findByTestId('scheduleHeader'); diff --git a/ee/spec/frontend/oncall_schedule/rotations/components/__snapshots__/delete_rotation_modal_spec.js.snap b/ee/spec/frontend/oncall_schedule/rotations/components/__snapshots__/delete_rotation_modal_spec.js.snap new file mode 100644 index 0000000000000..c7b077d454f0a --- /dev/null +++ b/ee/spec/frontend/oncall_schedule/rotations/components/__snapshots__/delete_rotation_modal_spec.js.snap @@ -0,0 +1,20 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`DeleteRotationModal renders delete rotation modal layout 1`] = ` +<gl-modal-stub + actioncancel="[object Object]" + actionprimary="[object Object]" + data-testid="delete-rotation-modal-gid://gitlab/IncidentManagement::OncallRotation/2" + modalclass="" + modalid="deleteRotationModal" + size="sm" + title="Delete rotation" + titletag="h4" +> + <!----> + + <gl-sprintf-stub + message="Are you sure you want to delete the \\"%{deleteRotation}\\" rotation? This action cannot be undone." + /> +</gl-modal-stub> +`; diff --git a/ee/spec/frontend/oncall_schedule/rotations/components/add_edit_rotation_modal_spec.js b/ee/spec/frontend/oncall_schedule/rotations/components/add_edit_rotation_modal_spec.js index 2490e7a22e91d..f3969552fbadc 100644 --- a/ee/spec/frontend/oncall_schedule/rotations/components/add_edit_rotation_modal_spec.js +++ b/ee/spec/frontend/oncall_schedule/rotations/components/add_edit_rotation_modal_spec.js @@ -123,7 +123,6 @@ describe('AddEditRotationModal', () => { afterEach(() => { wrapper.destroy(); - wrapper = null; }); const findModal = () => wrapper.find(GlModal); diff --git a/ee/spec/frontend/oncall_schedule/rotations/components/delete_rotation_modal_spec.js b/ee/spec/frontend/oncall_schedule/rotations/components/delete_rotation_modal_spec.js new file mode 100644 index 0000000000000..d4bd49ae40b82 --- /dev/null +++ b/ee/spec/frontend/oncall_schedule/rotations/components/delete_rotation_modal_spec.js @@ -0,0 +1,182 @@ +import { shallowMount, createLocalVue } from '@vue/test-utils'; +import createMockApollo from 'jest/helpers/mock_apollo_helper'; +import { GlModal, GlAlert, GlSprintf } from '@gitlab/ui'; +import VueApollo from 'vue-apollo'; +import waitForPromises from 'helpers/wait_for_promises'; +import getOncallSchedulesQuery from 'ee/oncall_schedules/graphql/queries/get_oncall_schedules.query.graphql'; +import destroyOncallRotationMutation from 'ee/oncall_schedules/graphql/mutations/destroy_oncall_rotation.mutation.graphql'; +import DeleteRotationModal, { + i18n, +} from 'ee/oncall_schedules/components/rotations/components/delete_rotation_modal.vue'; +import { deleteRotationModalId } from 'ee/oncall_schedules/constants'; +import { + getOncallSchedulesQueryResponse, + destroyRotationResponse, + destroyRotationResponseWithErrors, +} from '../../mocks/apollo_mock'; +import mockRotations from '../../mocks/mock_rotation.json'; + +const localVue = createLocalVue(); +const projectPath = 'group/project'; +const mutate = jest.fn(); +const mockHideModal = jest.fn(); +const rotation = mockRotations[0]; + +describe('DeleteRotationModal', () => { + let wrapper; + let fakeApollo; + let destroyRotationHandler; + + const findModal = () => wrapper.find(GlModal); + const findModalText = () => wrapper.find(GlSprintf); + const findAlert = () => wrapper.find(GlAlert); + + async function awaitApolloDomMock() { + await wrapper.vm.$nextTick(); // kick off the DOM update + await jest.runOnlyPendingTimers(); // kick off the mocked GQL stuff (promises) + await wrapper.vm.$nextTick(); // kick off the DOM update + } + + async function destroyRotation(localWrapper) { + localWrapper.find(GlModal).vm.$emit('primary', { preventDefault: jest.fn() }); + } + + const createComponent = ({ data = {}, props = {} } = {}) => { + wrapper = shallowMount(DeleteRotationModal, { + data() { + return { + ...data, + }; + }, + propsData: { + modalId: deleteRotationModalId, + rotation, + ...props, + }, + provide: { + projectPath, + }, + mocks: { + $apollo: { + mutate, + }, + }, + stubs: { GlSprintf: false }, + }); + wrapper.vm.$refs.deleteRotationModal.hide = mockHideModal; + }; + + function createComponentWithApollo({ + destroyHandler = jest.fn().mockResolvedValue(destroyRotationResponse), + } = {}) { + localVue.use(VueApollo); + destroyRotationHandler = destroyHandler; + + const requestHandlers = [ + [getOncallSchedulesQuery, jest.fn().mockResolvedValue(getOncallSchedulesQueryResponse)], + [destroyOncallRotationMutation, destroyRotationHandler], + ]; + + fakeApollo = createMockApollo(requestHandlers); + + fakeApollo.clients.defaultClient.cache.writeQuery({ + query: getOncallSchedulesQuery, + variables: { + projectPath: 'group/project', + }, + data: getOncallSchedulesQueryResponse.data, + }); + + wrapper = shallowMount(DeleteRotationModal, { + localVue, + apolloProvider: fakeApollo, + propsData: { + rotation, + modalId: deleteRotationModalId, + }, + provide: { + projectPath, + }, + }); + } + + beforeEach(() => { + createComponent(); + }); + + afterEach(() => { + wrapper.destroy(); + }); + + it('renders delete rotation modal layout', () => { + expect(wrapper.element).toMatchSnapshot(); + }); + + describe('renders delete modal with the correct rotation information', () => { + it('renders name of rotation to destroy', () => { + expect(findModalText().attributes('message')).toBe(i18n.deleteRotationMessage); + }); + }); + + describe('Rotation destroy apollo API call', () => { + it('makes a request with `oncallRotationDestroy` to delete a rotation', () => { + mutate.mockResolvedValueOnce({}); + findModal().vm.$emit('primary', { preventDefault: jest.fn() }); + expect(mutate).toHaveBeenCalledWith({ + mutation: expect.any(Object), + update: expect.anything(), + variables: { iid: rotation.id, projectPath }, + }); + }); + + it('hides the modal on successful rotation deletion', async () => { + mutate.mockResolvedValueOnce({ data: { oncallRotationDestroy: { errors: [] } } }); + findModal().vm.$emit('primary', { preventDefault: jest.fn() }); + await waitForPromises(); + expect(mockHideModal).toHaveBeenCalled(); + }); + + it('does not hide the modal on deletion fail and shows the error alert', async () => { + const error = 'some error'; + mutate.mockResolvedValueOnce({ data: { oncallRotationDestroy: { errors: [error] } } }); + findModal().vm.$emit('primary', { preventDefault: jest.fn() }); + await waitForPromises(); + const alert = findAlert(); + expect(mockHideModal).not.toHaveBeenCalled(); + expect(alert.exists()).toBe(true); + expect(alert.text()).toContain(error); + }); + }); + + describe('with mocked Apollo client', () => { + it('has the name of the rotation to delete based on getOncallSchedulesQuery', async () => { + createComponentWithApollo(); + + await jest.runOnlyPendingTimers(); + await wrapper.vm.$nextTick(); + + expect(findModal().attributes('data-testid')).toBe(`delete-rotation-modal-${rotation.id}`); + }); + + it('calls a mutation with correct parameters and destroys a rotation', async () => { + createComponentWithApollo(); + + await destroyRotation(wrapper); + + expect(destroyRotationHandler).toHaveBeenCalled(); + }); + + it('displays alert if mutation had a recoverable error', async () => { + createComponentWithApollo({ + destroyHandler: jest.fn().mockResolvedValue(destroyRotationResponseWithErrors), + }); + + await destroyRotation(wrapper); + await awaitApolloDomMock(); + + const alert = findAlert(); + expect(alert.exists()).toBe(true); + expect(alert.text()).toContain('Houston, we have a problem'); + }); + }); +}); diff --git a/ee/spec/frontend/oncall_schedule/rotations/components/rotation_assignee_spec.js b/ee/spec/frontend/oncall_schedule/rotations/components/rotation_assignee_spec.js index 8367454d56ddc..e4d6264ccf62a 100644 --- a/ee/spec/frontend/oncall_schedule/rotations/components/rotation_assignee_spec.js +++ b/ee/spec/frontend/oncall_schedule/rotations/components/rotation_assignee_spec.js @@ -14,7 +14,7 @@ describe('RotationAssignee', () => { const findStartsAt = () => wrapper.findByTestId('rotation-assignee-starts-at'); const findEndsAt = () => wrapper.findByTestId('rotation-assignee-ends-at'); - function mountComponent() { + function createComponent() { wrapper = extendedWrapper( shallowMount(RotationAssignee, { propsData: { @@ -28,12 +28,11 @@ describe('RotationAssignee', () => { } beforeEach(() => { - mountComponent(); + createComponent(); }); afterEach(() => { wrapper.destroy(); - wrapper = null; }); describe('rotation assignee token', () => { diff --git a/ee/spec/frontend/oncall_schedule/schedule/components/__snapshots__/rotations_list_section_spec.js.snap b/ee/spec/frontend/oncall_schedule/schedule/components/__snapshots__/rotations_list_section_spec.js.snap index 7ba7f07b6bc52..e1e42512198bc 100644 --- a/ee/spec/frontend/oncall_schedule/schedule/components/__snapshots__/rotations_list_section_spec.js.snap +++ b/ee/spec/frontend/oncall_schedule/schedule/components/__snapshots__/rotations_list_section_spec.js.snap @@ -78,5 +78,10 @@ exports[`RotationsListSectionComponent renders component layout 1`] = ` /> </span> </div> + + <delete-rotation-modal-stub + modalid="deleteRotationModal" + rotation="[object Object]" + /> </div> `; diff --git a/ee/spec/frontend/oncall_schedule/schedule/components/current_day_indicator_spec.js b/ee/spec/frontend/oncall_schedule/schedule/components/current_day_indicator_spec.js index 6fb7837644510..e6fced920d1d0 100644 --- a/ee/spec/frontend/oncall_schedule/schedule/components/current_day_indicator_spec.js +++ b/ee/spec/frontend/oncall_schedule/schedule/components/current_day_indicator_spec.js @@ -12,7 +12,7 @@ describe('CurrentDayIndicator', () => { // current indicator will be rendered const mockTimeframeInitialDate = new Date(2018, 0, 1); - function mountComponent() { + function createComponent() { wrapper = shallowMount(CurrentDayIndicator, { propsData: { presetType: PRESET_TYPES.WEEKS, @@ -22,13 +22,12 @@ describe('CurrentDayIndicator', () => { } beforeEach(() => { - mountComponent(); + createComponent(); }); afterEach(() => { if (wrapper) { wrapper.destroy(); - wrapper = null; } }); diff --git a/ee/spec/frontend/oncall_schedule/schedule/components/rotations_list_section_spec.js b/ee/spec/frontend/oncall_schedule/schedule/components/rotations_list_section_spec.js index 68ee03a275be5..ce9f50fda6e85 100644 --- a/ee/spec/frontend/oncall_schedule/schedule/components/rotations_list_section_spec.js +++ b/ee/spec/frontend/oncall_schedule/schedule/components/rotations_list_section_spec.js @@ -12,7 +12,7 @@ describe('RotationsListSectionComponent', () => { const mockTimeframeInitialDate = new Date(2018, 0, 1); const mockTimeframeWeeks = getTimeframeForWeeksView(mockTimeframeInitialDate); - function mountComponent({ + function createComponent({ presetType = PRESET_TYPES.WEEKS, timeframe = mockTimeframeWeeks, } = {}) { @@ -29,13 +29,12 @@ describe('RotationsListSectionComponent', () => { } beforeEach(() => { - mountComponent(); + createComponent(); }); afterEach(() => { if (wrapper) { wrapper.destroy(); - wrapper = null; } }); diff --git a/ee/spec/frontend/oncall_schedule/schedule/components/schedule_timeline_section_spec.js b/ee/spec/frontend/oncall_schedule/schedule/components/schedule_timeline_section_spec.js index 955c8d9c0fe5c..d15473679cb0c 100644 --- a/ee/spec/frontend/oncall_schedule/schedule/components/schedule_timeline_section_spec.js +++ b/ee/spec/frontend/oncall_schedule/schedule/components/schedule_timeline_section_spec.js @@ -12,7 +12,7 @@ describe('TimelineSectionComponent', () => { const schedule = getOncallSchedulesQueryResponse.data.project.incidentManagementOncallSchedules.nodes[0]; - function mountComponent({ + function createComponent({ presetType = PRESET_TYPES.WEEKS, timeframe = mockTimeframeWeeks, } = {}) { @@ -26,12 +26,11 @@ describe('TimelineSectionComponent', () => { } beforeEach(() => { - mountComponent({}); + createComponent(); }); afterEach(() => { wrapper.destroy(); - wrapper = null; }); it('renders component container element with class `timeline-section`', () => { diff --git a/locale/gitlab.pot b/locale/gitlab.pot index bba8eed23ec0e..f88575673b64e 100644 --- a/locale/gitlab.pot +++ b/locale/gitlab.pot @@ -19526,6 +19526,9 @@ msgstr "" msgid "OnCallSchedules|Add schedule" msgstr "" +msgid "OnCallSchedules|Are you sure you want to delete the \"%{deleteRotation}\" rotation? This action cannot be undone." +msgstr "" + msgid "OnCallSchedules|Are you sure you want to delete the \"%{deleteSchedule}\" schedule? This action cannot be undone." msgstr "" @@ -19592,6 +19595,9 @@ msgstr "" msgid "OnCallSchedules|Successfully edited your rotation" msgstr "" +msgid "OnCallSchedules|The rotation could not be deleted. Please try again." +msgstr "" + msgid "OnCallSchedules|The rotation could not be updated. Please try again." msgstr "" -- GitLab