Commit 937ea2f3 authored by Frédéric Caplette's avatar Frédéric Caplette

Merge branch '323629-warn-user-is-a-part-of-oncall-shedule-on-delete' into 'master'

When DELETING a user, warn Admin user is part of an on-call schedule

See merge request gitlab-org/gitlab!60295
parents 958ffa99 e0f718c2
......@@ -14,12 +14,22 @@ export default {
type: Object,
required: true,
},
oncallSchedules: {
type: Array,
required: false,
default: () => [],
},
},
};
</script>
<template>
<shared-delete-action modal-type="delete" :username="username" :paths="paths">
<shared-delete-action
modal-type="delete"
:username="username"
:paths="paths"
:oncall-schedules="oncallSchedules"
>
<slot></slot>
</shared-delete-action>
</template>
......@@ -14,12 +14,22 @@ export default {
type: Object,
required: true,
},
oncallSchedules: {
type: Array,
required: false,
default: () => [],
},
},
};
</script>
<template>
<shared-delete-action modal-type="delete-with-contributions" :username="username" :paths="paths">
<shared-delete-action
modal-type="delete-with-contributions"
:username="username"
:paths="paths"
:oncall-schedules="oncallSchedules"
>
<slot></slot>
</shared-delete-action>
</template>
......@@ -18,6 +18,10 @@ export default {
type: String,
required: true,
},
oncallSchedules: {
type: Array,
required: true,
},
},
computed: {
modalAttributes() {
......@@ -26,6 +30,7 @@ export default {
'data-delete-user-url': this.paths.delete,
'data-gl-modal-action': this.modalType,
'data-username': this.username,
'data-oncall-schedules': JSON.stringify(this.oncallSchedules),
};
},
},
......
......@@ -109,6 +109,7 @@ export default {
:key="action"
:paths="userPaths"
:username="user.name"
:oncall-schedules="user.oncallSchedules"
:data-testid="`delete-${action}`"
>
{{ $options.i18n[action] }}
......
<script>
import { GlModal, GlButton, GlFormInput, GlSprintf } from '@gitlab/ui';
import * as Sentry from '@sentry/browser';
import { s__, sprintf } from '~/locale';
import OncallSchedulesList from '~/vue_shared/components/oncall_schedules_list.vue';
export default {
components: {
......@@ -8,6 +10,7 @@ export default {
GlButton,
GlFormInput,
GlSprintf,
OncallSchedulesList,
},
props: {
title: {
......@@ -42,6 +45,11 @@ export default {
type: String,
required: true,
},
oncallSchedules: {
type: String,
required: false,
default: '[]',
},
},
data() {
return {
......@@ -58,6 +66,14 @@ export default {
canSubmit() {
return this.enteredUsername === this.username;
},
schedules() {
try {
return JSON.parse(this.oncallSchedules);
} catch (e) {
Sentry.captureException(e);
}
return [];
},
},
methods: {
show() {
......@@ -96,6 +112,8 @@ export default {
</gl-sprintf>
</p>
<oncall-schedules-list v-if="schedules.length" :schedules="schedules" />
<p>
<gl-sprintf :message="s__('AdminUsers|To confirm, type %{username}')">
<template #username>
......
......@@ -55,13 +55,12 @@ export default {
return !this.isAccessRequest && this.oncallSchedules.schedules?.length;
},
oncallSchedules() {
let schedules = {};
try {
schedules = JSON.parse(this.modalData.oncallSchedules);
return JSON.parse(this.modalData.oncallSchedules);
} catch (e) {
Sentry.captureException(e);
}
return schedules;
return {};
},
},
mounted() {
......
......@@ -71,6 +71,7 @@ describe('Action components', () => {
});
describe('DELETE_ACTION_COMPONENTS', () => {
const oncallSchedules = [{ name: 'schedule1' }, { name: 'schedule2' }];
it.each(DELETE_ACTIONS)('renders a dropdown item for "%s"', async (action) => {
initComponent({
component: Actions[capitalizeFirstCharacter(action)],
......@@ -80,6 +81,7 @@ describe('Action components', () => {
delete: '/delete',
block: '/block',
},
oncallSchedules,
},
stubs: { SharedDeleteAction },
});
......@@ -92,6 +94,9 @@ describe('Action components', () => {
expect(sharedAction.attributes('data-delete-user-url')).toBe('/delete');
expect(sharedAction.attributes('data-gl-modal-action')).toBe(kebabCase(action));
expect(sharedAction.attributes('data-username')).toBe('John Doe');
expect(sharedAction.attributes('data-oncall-schedules')).toBe(
JSON.stringify(oncallSchedules),
);
expect(findDropdownItem().exists()).toBe(true);
});
});
......
......@@ -8,6 +8,10 @@ exports[`User Operation confirmation modal renders modal with form included 1`]
/>
</p>
<oncall-schedules-list-stub
schedules="schedule1,schedule2"
/>
<p>
<gl-sprintf-stub
message="To confirm, type %{username}"
......
import { GlButton, GlFormInput } from '@gitlab/ui';
import { shallowMount } from '@vue/test-utils';
import DeleteUserModal from '~/pages/admin/users/components/delete_user_modal.vue';
import OncallSchedulesList from '~/vue_shared/components/oncall_schedules_list.vue';
import ModalStub from './stubs/modal_stub';
const TEST_DELETE_USER_URL = 'delete-url';
......@@ -17,13 +18,14 @@ describe('User Operation confirmation modal', () => {
.filter((w) => w.attributes('variant') === variant && w.attributes('category') === category)
.at(0);
const findForm = () => wrapper.find('form');
const findUsernameInput = () => wrapper.find(GlFormInput);
const findUsernameInput = () => wrapper.findComponent(GlFormInput);
const findPrimaryButton = () => findButton('danger', 'primary');
const findSecondaryButton = () => findButton('danger', 'secondary');
const findAuthenticityToken = () => new FormData(findForm().element).get('authenticity_token');
const getUsername = () => findUsernameInput().attributes('value');
const getMethodParam = () => new FormData(findForm().element).get('_method');
const getFormAction = () => findForm().attributes('action');
const findOnCallSchedulesList = () => wrapper.findComponent(OncallSchedulesList);
const setUsername = (username) => {
findUsernameInput().vm.$emit('input', username);
......@@ -31,6 +33,7 @@ describe('User Operation confirmation modal', () => {
const username = 'username';
const badUsername = 'bad_username';
const oncallSchedules = '["schedule1", "schedule2"]';
const createComponent = (props = {}) => {
wrapper = shallowMount(DeleteUserModal, {
......@@ -43,6 +46,7 @@ describe('User Operation confirmation modal', () => {
deleteUserUrl: TEST_DELETE_USER_URL,
blockUserUrl: TEST_BLOCK_USER_URL,
csrfToken: TEST_CSRF,
oncallSchedules,
...props,
},
stubs: {
......@@ -145,4 +149,19 @@ describe('User Operation confirmation modal', () => {
});
});
});
describe('Related oncall-schedules list', () => {
it('does NOT render the list when user has no related schedules', () => {
createComponent({ oncallSchedules: '[]' });
expect(findOnCallSchedulesList().exists()).toBe(false);
});
it('renders the list when user has related schedules', () => {
createComponent();
const schedules = findOnCallSchedulesList();
expect(schedules.exists()).toBe(true);
expect(schedules.props('schedules')).toEqual(JSON.parse(oncallSchedules));
});
});
});
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