Commit e0f718c2 authored by Olena Horal-Koretska's avatar Olena Horal-Koretska Committed by Frédéric Caplette

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

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