Commit 9b7534cc authored by Clement Ho's avatar Clement Ho

Merge branch '41301-new-design-for-user-deletion-confirmation-in-admin-area' into 'master'

Resolve "New design for user deletion confirmation in admin area"

Closes #41301

See merge request gitlab-org/gitlab-ce!16811
parents 6d7df4f8 3cb19dd4
...@@ -114,6 +114,16 @@ var Dispatcher; ...@@ -114,6 +114,16 @@ var Dispatcher;
.then(callDefault) .then(callDefault)
.catch(fail); .catch(fail);
break; break;
case 'admin:users:index':
import('./pages/admin/users/shared')
.then(callDefault)
.catch(fail);
break;
case 'admin:users:show':
import('./pages/admin/users/shared')
.then(callDefault)
.catch(fail);
break;
case 'dashboard:projects:index': case 'dashboard:projects:index':
case 'dashboard:projects:starred': case 'dashboard:projects:starred':
import('./pages/dashboard/projects') import('./pages/dashboard/projects')
......
<script>
import _ from 'underscore';
import modal from '~/vue_shared/components/modal.vue';
import { s__, sprintf } from '~/locale';
export default {
components: {
modal,
},
props: {
deleteUserUrl: {
type: String,
required: false,
default: '',
},
blockUserUrl: {
type: String,
required: false,
default: '',
},
deleteContributions: {
type: Boolean,
required: false,
default: false,
},
username: {
type: String,
required: false,
default: '',
},
csrfToken: {
type: String,
required: false,
default: '',
},
},
data() {
return {
enteredUsername: '',
};
},
computed: {
title() {
const keepContributionsTitle = s__('AdminUsers|Delete User %{username}?');
const deleteContributionsTitle = s__('AdminUsers|Delete User %{username} and contributions?');
return sprintf(
this.deleteContributions ? deleteContributionsTitle : keepContributionsTitle, {
username: `'${_.escape(this.username)}'`,
}, false);
},
text() {
const keepContributionsText = s__(`AdminArea|
You are about to permanently delete the user %{username}.
This will delete all of the issues, merge requests, and groups linked to them.
To avoid data loss, consider using the %{strong_start}block user%{strong_end} feature instead.
Once you %{strong_start}Delete user%{strong_end}, it cannot be undone or recovered.`);
const deleteContributionsText = s__(`AdminArea|
You are about to permanently delete the user %{username}.
Issues, merge requests, and groups linked to them will be transferred to a system-wide "Ghost-user".
To avoid data loss, consider using the %{strong_start}block user%{strong_end} feature instead.
Once you %{strong_start}Delete user%{strong_end}, it cannot be undone or recovered.`);
return sprintf(this.deleteContributions ? deleteContributionsText : keepContributionsText,
{
username: `<strong>${_.escape(this.username)}</strong>`,
strong_start: '<strong>',
strong_end: '</strong>',
},
false,
);
},
confirmationTextLabel() {
return sprintf(s__('AdminUsers|To confirm, type %{username}'),
{
username: `<code>${_.escape(this.username)}</code>`,
},
false,
);
},
primaryButtonLabel() {
const keepContributionsLabel = s__('AdminUsers|Delete user');
const deleteContributionsLabel = s__('AdminUsers|Delete user and contributions');
return this.deleteContributions ? deleteContributionsLabel : keepContributionsLabel;
},
secondaryButtonLabel() {
return s__('AdminUsers|Block user');
},
canSubmit() {
return this.enteredUsername === this.username;
},
},
methods: {
onCancel() {
this.enteredUsername = '';
},
onSecondaryAction() {
const form = this.$refs.form;
form.action = this.blockUserUrl;
this.$refs.method.value = 'put';
form.submit();
},
onSubmit() {
this.$refs.form.submit();
this.enteredUsername = '';
},
},
};
</script>
<template>
<modal
id="delete-user-modal"
:title="title"
:text="text"
kind="danger"
:primary-button-label="primaryButtonLabel"
:secondary-button-label="secondaryButtonLabel"
:submit-disabled="!canSubmit"
@submit="onSubmit"
@cancel="onCancel"
>
<template
slot="body"
slot-scope="props"
>
<p v-html="props.text"></p>
<p v-html="confirmationTextLabel"></p>
<form
ref="form"
:action="deleteUserUrl"
method="post"
>
<input
ref="method"
type="hidden"
name="_method"
value="delete"
/>
<input
type="hidden"
name="authenticity_token"
:value="csrfToken"
/>
<input
type="text"
name="username"
class="form-control"
v-model="enteredUsername"
aria-labelledby="input-label"
autocomplete="off"
/>
</form>
</template>
<template
slot="secondary-button"
slot-scope="props"
>
<button
type="button"
class="btn js-secondary-button btn-warning"
:disabled="!canSubmit"
@click="onSecondaryAction"
data-dismiss="modal"
>
{{ secondaryButtonLabel }}
</button>
</template>
</modal>
</template>
import Vue from 'vue';
import Translate from '~/vue_shared/translate';
import csrf from '~/lib/utils/csrf';
import deleteUserModal from './components/delete_user_modal.vue';
export default () => {
Vue.use(Translate);
const deleteUserModalEl = document.getElementById('delete-user-modal');
const deleteModal = new Vue({
el: deleteUserModalEl,
data: {
deleteUserUrl: '',
blockUserUrl: '',
deleteContributions: '',
username: '',
},
render(createElement) {
return createElement(deleteUserModal, {
props: {
deleteUserUrl: this.deleteUserUrl,
blockUserUrl: this.blockUserUrl,
deleteContributions: this.deleteContributions,
username: this.username,
csrfToken: csrf.token,
},
});
},
});
$(document).on('shown.bs.modal', (event) => {
if (event.relatedTarget.classList.contains('delete-user-button')) {
const buttonProps = event.relatedTarget.dataset;
deleteModal.deleteUserUrl = buttonProps.deleteUserUrl;
deleteModal.blockUserUrl = buttonProps.blockUserUrl;
deleteModal.deleteContributions = event.relatedTarget.hasAttribute('data-delete-contributions');
deleteModal.username = buttonProps.username;
}
});
};
...@@ -46,6 +46,11 @@ ...@@ -46,6 +46,11 @@
required: false, required: false,
default: '', default: '',
}, },
secondaryButtonLabel: {
type: String,
required: false,
default: '',
},
submitDisabled: { submitDisabled: {
type: Boolean, type: Boolean,
required: false, required: false,
...@@ -129,6 +134,21 @@ ...@@ -129,6 +134,21 @@
> >
{{ closeButtonLabel }} {{ closeButtonLabel }}
</button> </button>
<slot
v-if="secondaryButtonLabel"
name="secondary-button"
>
<button
v-if="secondaryButtonLabel"
type="button"
class="btn"
data-dismiss="modal"
>
{{ secondaryButtonLabel }}
</button>
</slot>
<button <button
v-if="primaryButtonLabel" v-if="primaryButtonLabel"
type="button" type="button"
......
...@@ -38,12 +38,19 @@ ...@@ -38,12 +38,19 @@
%li.divider %li.divider
- if user.can_be_removed? - if user.can_be_removed?
%li %li
= link_to 'Remove user', admin_user_path(user), %button.delete-user-button.btn.text-danger{ data: { toggle: 'modal',
data: { confirm: "USER #{user.name} WILL BE REMOVED! Are you sure?" }, target: '#delete-user-modal',
class: 'text-danger', delete_user_url: admin_user_path(user),
method: :delete block_user_url: block_admin_user_path(user),
username: user.name,
delete_contributions: 'false' }, type: 'button' }
= s_('AdminUsers|Delete user')
%li %li
= link_to 'Remove user and contributions', admin_user_path(user, hard_delete: true), %button.delete-user-button.btn.text-danger{ data: { toggle: 'modal',
data: { confirm: "USER #{user.name} WILL BE REMOVED! All issues, merge requests and comments authored by this user, and groups owned solely by them, will also be removed! Are you sure?" }, target: '#delete-user-modal',
class: 'text-danger', delete_user_url: admin_user_path(user, hard_delete: true),
method: :delete block_user_url: block_admin_user_path(user),
username: user.name,
delete_contributions: 'true' }, type: 'button' }
= s_('AdminUsers|Delete user and contributions')
...@@ -76,3 +76,6 @@ ...@@ -76,3 +76,6 @@
= render partial: 'admin/users/user', collection: @users = render partial: 'admin/users/user', collection: @users
= paginate @users, theme: "gitlab" = paginate @users, theme: "gitlab"
#delete-user-modal
...@@ -172,13 +172,19 @@ ...@@ -172,13 +172,19 @@
.panel.panel-danger .panel.panel-danger
.panel-heading .panel-heading
Remove user = s_('AdminUsers|Delete user')
.panel-body .panel-body
- if @user.can_be_removed? && can?(current_user, :destroy_user, @user) - if @user.can_be_removed? && can?(current_user, :destroy_user, @user)
%p Deleting a user has the following effects: %p Deleting a user has the following effects:
= render 'users/deletion_guidance', user: @user = render 'users/deletion_guidance', user: @user
%br %br
= link_to 'Remove user', admin_user_path(@user), data: { confirm: "USER #{@user.name} WILL BE REMOVED! Are you sure?" }, method: :delete, class: "btn btn-remove" %button.delete-user-button.btn.text-danger{ data: { toggle: 'modal',
target: '#delete-user-modal',
delete_user_url: admin_user_path(@user),
block_user_url: block_admin_user_path(@user),
username: @user.name,
delete_contributions: 'false' }, type: 'button' }
= s_('AdminUsers|Delete user')
- else - else
- if @user.solo_owned_groups.present? - if @user.solo_owned_groups.present?
%p %p
...@@ -192,7 +198,7 @@ ...@@ -192,7 +198,7 @@
.panel.panel-danger .panel.panel-danger
.panel-heading .panel-heading
Remove user and contributions = s_('AdminUsers|Delete user and contributions')
.panel-body .panel-body
- if can?(current_user, :destroy_user, @user) - if can?(current_user, :destroy_user, @user)
%p %p
...@@ -204,7 +210,15 @@ ...@@ -204,7 +210,15 @@
the user, and projects in them, will also be removed. Commits the user, and projects in them, will also be removed. Commits
to other projects are unaffected. to other projects are unaffected.
%br %br
= link_to 'Remove user and contributions', admin_user_path(@user, hard_delete: true), data: { confirm: "USER #{@user.name} WILL BE REMOVED! Are you sure?" }, method: :delete, class: "btn btn-remove" %button.delete-user-button.btn.text-danger{ data: { toggle: 'modal',
target: '#delete-user-modal',
delete_user_url: admin_user_path(@user, hard_delete: true),
block_user_url: block_admin_user_path(@user),
username: @user.name,
delete_contributions: 'true' }, type: 'button' }
= s_('AdminUsers|Delete user and contributions')
- else - else
%p %p
You don't have access to delete this user. You don't have access to delete this user.
#delete-user-modal
# Deleting a User Account # Deleting a User Account
- As a user, you can delete your own account by navigating to **Settings** > **Account** and selecting **Delete account** - As a user, you can delete your own account by navigating to **Settings** > **Account** and selecting **Delete account**
- As an admin, you can delete a user account by navigating to the **Admin Area**, selecting the **Users** tab, selecting a user, and clicking on **Remove user** - As an admin, you can delete a user account by navigating to the **Admin Area**, selecting the **Users** tab, selecting a user, and clicking on **Delete user**
## Associated Records ## Associated Records
......
...@@ -26,8 +26,8 @@ describe "Admin::Users" do ...@@ -26,8 +26,8 @@ describe "Admin::Users" do
expect(page).to have_content(user.email) expect(page).to have_content(user.email)
expect(page).to have_content(user.name) expect(page).to have_content(user.name)
expect(page).to have_link('Block', href: block_admin_user_path(user)) expect(page).to have_link('Block', href: block_admin_user_path(user))
expect(page).to have_link('Remove user', href: admin_user_path(user)) expect(page).to have_button('Delete user')
expect(page).to have_link('Remove user and contributions', href: admin_user_path(user, hard_delete: true)) expect(page).to have_button('Delete user and contributions')
end end
describe 'Two-factor Authentication filters' do describe 'Two-factor Authentication filters' do
...@@ -122,8 +122,8 @@ describe "Admin::Users" do ...@@ -122,8 +122,8 @@ describe "Admin::Users" do
expect(page).to have_content(user.email) expect(page).to have_content(user.email)
expect(page).to have_content(user.name) expect(page).to have_content(user.name)
expect(page).to have_link('Block user', href: block_admin_user_path(user)) expect(page).to have_link('Block user', href: block_admin_user_path(user))
expect(page).to have_link('Remove user', href: admin_user_path(user)) expect(page).to have_button('Delete user')
expect(page).to have_link('Remove user and contributions', href: admin_user_path(user, hard_delete: true)) expect(page).to have_button('Delete user and contributions')
end end
describe 'Impersonation' do describe 'Impersonation' 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