Commit 3399b4c5 authored by Natalia Tepluhina's avatar Natalia Tepluhina

Merge branch...

Merge branch '337156-options-to-ban-delete-user-and-delete-user-contributions-in-admin-are-gone' into 'master'

Add missing `Delete user and contributions` action in admin user view

See merge request gitlab-org/gitlab!67161
parents 4c1e8e26 8cb36fad
...@@ -28,6 +28,7 @@ export default { ...@@ -28,6 +28,7 @@ export default {
modal-type="delete" modal-type="delete"
:username="username" :username="username"
:paths="paths" :paths="paths"
:delete-path="paths.delete"
:oncall-schedules="oncallSchedules" :oncall-schedules="oncallSchedules"
> >
<slot></slot> <slot></slot>
......
...@@ -28,6 +28,7 @@ export default { ...@@ -28,6 +28,7 @@ export default {
modal-type="delete-with-contributions" modal-type="delete-with-contributions"
:username="username" :username="username"
:paths="paths" :paths="paths"
:delete-path="paths.deleteWithContributions"
:oncall-schedules="oncallSchedules" :oncall-schedules="oncallSchedules"
> >
<slot></slot> <slot></slot>
......
...@@ -14,6 +14,10 @@ export default { ...@@ -14,6 +14,10 @@ export default {
type: Object, type: Object,
required: true, required: true,
}, },
deletePath: {
type: String,
required: true,
},
modalType: { modalType: {
type: String, type: String,
required: true, required: true,
...@@ -27,7 +31,7 @@ export default { ...@@ -27,7 +31,7 @@ export default {
modalAttributes() { modalAttributes() {
return { return {
'data-block-user-url': this.paths.block, 'data-block-user-url': this.paths.block,
'data-delete-user-url': this.paths.delete, 'data-delete-user-url': this.deletePath,
'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), 'data-oncall-schedules': JSON.stringify(this.oncallSchedules),
......
...@@ -48,9 +48,9 @@ module Admin ...@@ -48,9 +48,9 @@ module Admin
end end
def delete_actions def delete_actions
return unless can?(current_user, :destroy_user, @user) && !@user.blocked_pending_approval? && @user.can_be_removed? return unless can?(current_user, :destroy_user, @user) && !@user.blocked_pending_approval?
@actions << 'delete' @actions << 'delete' if @user.can_be_removed?
@actions << 'delete_with_contributions' @actions << 'delete_with_contributions'
end end
......
...@@ -184,7 +184,7 @@ module UsersHelper ...@@ -184,7 +184,7 @@ module UsersHelper
activate: activate_admin_user_path(:id), activate: activate_admin_user_path(:id),
unlock: unlock_admin_user_path(:id), unlock: unlock_admin_user_path(:id),
delete: admin_user_path(:id), delete: admin_user_path(:id),
delete_with_contributions: admin_user_path(:id), delete_with_contributions: admin_user_path(:id, hard_delete: true),
admin_user: admin_user_path(:id), admin_user: admin_user_path(:id),
ban: ban_admin_user_path(:id), ban: ban_admin_user_path(:id),
unban: unban_admin_user_path(:id) unban: unban_admin_user_path(:id)
......
...@@ -90,6 +90,39 @@ RSpec.describe 'Admin::Users::User' do ...@@ -90,6 +90,39 @@ RSpec.describe 'Admin::Users::User' do
end end
end end
context 'when user is the sole owner of a group' do
let_it_be(:group) { create(:group) }
let_it_be(:user_sole_owner_of_group) { create(:user) }
before do
group.add_owner(user_sole_owner_of_group)
end
it 'shows `Delete user and contributions` action but not `Delete user` action', :js do
visit admin_user_path(user_sole_owner_of_group)
click_user_dropdown_toggle(user_sole_owner_of_group.id)
expect(page).to have_button('Delete user and contributions')
expect(page).not_to have_button('Delete user', exact: true)
end
it 'allows user to be deleted by using the `Delete user and contributions` action', :js do
visit admin_user_path(user_sole_owner_of_group)
click_action_in_user_dropdown(user_sole_owner_of_group.id, 'Delete user and contributions')
page.within('[role="dialog"]') do
fill_in('username', with: user_sole_owner_of_group.name)
click_button('Delete user and contributions')
end
wait_for_requests
expect(page).to have_content('The user is being deleted.')
end
end
describe 'Impersonation' do describe 'Impersonation' do
let_it_be(:another_user) { create(:user) } let_it_be(:another_user) { create(:user) }
......
...@@ -5,8 +5,8 @@ import { nextTick } from 'vue'; ...@@ -5,8 +5,8 @@ import { nextTick } from 'vue';
import Actions from '~/admin/users/components/actions'; import Actions from '~/admin/users/components/actions';
import SharedDeleteAction from '~/admin/users/components/actions/shared/shared_delete_action.vue'; import SharedDeleteAction from '~/admin/users/components/actions/shared/shared_delete_action.vue';
import { capitalizeFirstCharacter } from '~/lib/utils/text_utility'; import { capitalizeFirstCharacter } from '~/lib/utils/text_utility';
import { CONFIRMATION_ACTIONS, DELETE_ACTIONS } from '../../constants'; import { CONFIRMATION_ACTIONS, DELETE_ACTIONS } from '../../constants';
import { paths } from '../../mock_data';
describe('Action components', () => { describe('Action components', () => {
let wrapper; let wrapper;
...@@ -47,32 +47,33 @@ describe('Action components', () => { ...@@ -47,32 +47,33 @@ describe('Action components', () => {
describe('DELETE_ACTION_COMPONENTS', () => { describe('DELETE_ACTION_COMPONENTS', () => {
const oncallSchedules = [{ name: 'schedule1' }, { name: 'schedule2' }]; const oncallSchedules = [{ name: 'schedule1' }, { name: 'schedule2' }];
it.each(DELETE_ACTIONS)('renders a dropdown item for "%s"', async (action) => {
initComponent({ it.each(DELETE_ACTIONS.map((action) => [action, paths[action]]))(
component: Actions[capitalizeFirstCharacter(action)], 'renders a dropdown item for "%s"',
props: { async (action, expectedPath) => {
username: 'John Doe', initComponent({
paths: { component: Actions[capitalizeFirstCharacter(action)],
delete: '/delete', props: {
block: '/block', username: 'John Doe',
paths,
oncallSchedules,
}, },
oncallSchedules, stubs: { SharedDeleteAction },
}, });
stubs: { SharedDeleteAction },
});
await nextTick(); await nextTick();
const sharedAction = wrapper.find(SharedDeleteAction); const sharedAction = wrapper.find(SharedDeleteAction);
expect(sharedAction.attributes('data-block-user-url')).toBe('/block'); expect(sharedAction.attributes('data-block-user-url')).toBe(paths.block);
expect(sharedAction.attributes('data-delete-user-url')).toBe('/delete'); expect(sharedAction.attributes('data-delete-user-url')).toBe(expectedPath);
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( expect(sharedAction.attributes('data-oncall-schedules')).toBe(
JSON.stringify(oncallSchedules), JSON.stringify(oncallSchedules),
); );
expect(findDropdownItem().exists()).toBe(true); expect(findDropdownItem().exists()).toBe(true);
}); },
);
}); });
}); });
...@@ -30,7 +30,7 @@ export const paths = { ...@@ -30,7 +30,7 @@ export const paths = {
activate: '/admin/users/id/activate', activate: '/admin/users/id/activate',
unlock: '/admin/users/id/unlock', unlock: '/admin/users/id/unlock',
delete: '/admin/users/id', delete: '/admin/users/id',
deleteWithContributions: '/admin/users/id', deleteWithContributions: '/admin/users/id?hard_delete=true',
adminUser: '/admin/users/id', adminUser: '/admin/users/id',
ban: '/admin/users/id/ban', ban: '/admin/users/id/ban',
unban: '/admin/users/id/unban', unban: '/admin/users/id/unban',
......
...@@ -106,7 +106,7 @@ RSpec.describe Admin::UserActionsHelper do ...@@ -106,7 +106,7 @@ RSpec.describe Admin::UserActionsHelper do
group.add_owner(user) group.add_owner(user)
end end
it { is_expected.to contain_exactly("edit", "block", "ban", "deactivate") } it { is_expected.to contain_exactly("edit", "block", "ban", "deactivate", "delete_with_contributions") }
end end
context 'the user is a bot' do context 'the user is a bot' do
......
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