diff --git a/config/known_invalid_graphql_queries.yml b/config/known_invalid_graphql_queries.yml index 770366d76cf35f7dd2accab6c8f8d95a3f68e1aa..8ea9b662aa76db68d356d90950116473ec31e489 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 0000000000000000000000000000000000000000..6bf144d307b8c60dbb2ea0e1bf15eab1571a7617 --- /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 9feaad8e1e340e56691bda77c1281a8e458b50b9..cbd44b6d72a9e8a4dbce43d066bbbe12217364f5 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 5a143d5f742a94c7770a1d26df45921f30104a9f..1f58cd1c69c181af6988b1e2fb5da1f4e56d2d98 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 0000000000000000000000000000000000000000..f42fcfc122f7452a533e0ea0b3a99e498bf5602e --- /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 f6c0e637e2d589f9cf9965f25540c82dfa717752..95fb60e9cdc7aac678e691566b144a16fa55d732 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 158ec951cb91a4833854df51d1eb63b50b7f4076..67d3ebb06bb8c7e730af6cd205630d220b76cfcd 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 9fbbfbb66216ce4a7fb1b63f4761d67fa0f66cbb..3dc0d706fdc24fc2b3070338223c7bc356bc5170 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 f5d15bb403b5bd73898c9ef383606f243b9e30af..93b5c9947f5ae5a593490c6f4320b2074124e37f 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 c0a23d02d35c56d62ad050b3aecff8f3c954b195..04278fd03f6d1b610fd219ad941f3680dbe0be7b 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 f70081f0bc6e650a950fe63b0f28e66f9198881d..9fd2c4e72c3ea16804d7f9e061c69855c0f4fab7 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 0000000000000000000000000000000000000000..c7b077d454f0a4924ebab57418ef546fd28bc673 --- /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 2490e7a22e91d345a253f67fa12d38048bfb89df..f3969552fbadce2620cf6be5d942326b7281d06d 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 0000000000000000000000000000000000000000..d4bd49ae40b8236d4f6d609eb419712cba9f7291 --- /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 8367454d56ddcfc883ed7b306b2d9c57a7b8184e..e4d6264ccf62a6b8fc78dff61b30d662fa5a7595 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 7ba7f07b6bc52a3467f78bf276f5a94c84b7a46e..e1e42512198bcddfd30951abe2e53d5da788b062 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 6fb7837644510e1203adf29fa7b289ff70dbcc3f..e6fced920d1d04acd65321ec039ff6cde52def5b 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 68ee03a275be5cd37f4d47993e7a66cd7fac5425..ce9f50fda6e8511faaf0304c331757f8bd572e0f 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 955c8d9c0fe5c0abda435e4ee34b9805cbd393b1..d15473679cb0c6d562db98c179d9a2c6e95c2a0f 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 bba8eed23ec0e5f3a87f9507ffc44aa2a618b346..f88575673b64ec9f6e9760c2f5c849d7a7d22403 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 ""