Commit 8cb36fad authored by peterhegman's avatar peterhegman

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

The `Delete user and contributions` action was missing for users that
are a sole owner of a group. Add that action back into the
"User administration" dropdown.

Changelog: fixed
parent fe024361
......@@ -28,6 +28,7 @@ export default {
modal-type="delete"
:username="username"
:paths="paths"
:delete-path="paths.delete"
:oncall-schedules="oncallSchedules"
>
<slot></slot>
......
......@@ -28,6 +28,7 @@ export default {
modal-type="delete-with-contributions"
:username="username"
:paths="paths"
:delete-path="paths.deleteWithContributions"
:oncall-schedules="oncallSchedules"
>
<slot></slot>
......
......@@ -14,6 +14,10 @@ export default {
type: Object,
required: true,
},
deletePath: {
type: String,
required: true,
},
modalType: {
type: String,
required: true,
......@@ -27,7 +31,7 @@ export default {
modalAttributes() {
return {
'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-username': this.username,
'data-oncall-schedules': JSON.stringify(this.oncallSchedules),
......
......@@ -48,9 +48,9 @@ module Admin
end
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'
end
......
......@@ -184,7 +184,7 @@ module UsersHelper
activate: activate_admin_user_path(:id),
unlock: unlock_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),
ban: ban_admin_user_path(:id),
unban: unban_admin_user_path(:id)
......
......@@ -90,6 +90,39 @@ RSpec.describe 'Admin::Users::User' do
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
let_it_be(:another_user) { create(:user) }
......
......@@ -5,8 +5,8 @@ import { nextTick } from 'vue';
import Actions from '~/admin/users/components/actions';
import SharedDeleteAction from '~/admin/users/components/actions/shared/shared_delete_action.vue';
import { capitalizeFirstCharacter } from '~/lib/utils/text_utility';
import { CONFIRMATION_ACTIONS, DELETE_ACTIONS } from '../../constants';
import { paths } from '../../mock_data';
describe('Action components', () => {
let wrapper;
......@@ -47,32 +47,33 @@ 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)],
props: {
username: 'John Doe',
paths: {
delete: '/delete',
block: '/block',
it.each(DELETE_ACTIONS.map((action) => [action, paths[action]]))(
'renders a dropdown item for "%s"',
async (action, expectedPath) => {
initComponent({
component: Actions[capitalizeFirstCharacter(action)],
props: {
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-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);
});
expect(sharedAction.attributes('data-block-user-url')).toBe(paths.block);
expect(sharedAction.attributes('data-delete-user-url')).toBe(expectedPath);
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);
},
);
});
});
......@@ -30,7 +30,7 @@ export const paths = {
activate: '/admin/users/id/activate',
unlock: '/admin/users/id/unlock',
delete: '/admin/users/id',
deleteWithContributions: '/admin/users/id',
deleteWithContributions: '/admin/users/id?hard_delete=true',
adminUser: '/admin/users/id',
ban: '/admin/users/id/ban',
unban: '/admin/users/id/unban',
......
......@@ -106,7 +106,7 @@ RSpec.describe Admin::UserActionsHelper do
group.add_owner(user)
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
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