diff options
author | Clement Ho <clemmakesapps@gmail.com> | 2018-02-08 05:18:29 +0000 |
---|---|---|
committer | Clement Ho <clemmakesapps@gmail.com> | 2018-02-08 05:18:29 +0000 |
commit | 9b7534cc2dff55a69dc836a8cb5990ad6ec51c7a (patch) | |
tree | 27401db8735fe5a70e1dd238a78dc1a23447216a | |
parent | 6d7df4f8b12ca892cb9cbc5223f1ee69b2558bd5 (diff) | |
parent | 3cb19dd42c097ebd1210bbf7961f9ef000d784cb (diff) | |
download | gitlab-ce-9b7534cc2dff55a69dc836a8cb5990ad6ec51c7a.tar.gz |
Merge branch '41301-new-design-for-user-deletion-confirmation-in-admin-area' into 'master'42867-rsa-secureid-app-doesn-t-understand-gitlab-qr-code
Resolve "New design for user deletion confirmation in admin area"
Closes #41301
See merge request gitlab-org/gitlab-ce!16811
-rw-r--r-- | app/assets/javascripts/dispatcher.js | 10 | ||||
-rw-r--r-- | app/assets/javascripts/pages/admin/users/shared/components/delete_user_modal.vue | 174 | ||||
-rw-r--r-- | app/assets/javascripts/pages/admin/users/shared/index.js | 43 | ||||
-rw-r--r-- | app/assets/javascripts/vue_shared/components/modal.vue | 20 | ||||
-rw-r--r-- | app/views/admin/users/_user.html.haml | 23 | ||||
-rw-r--r-- | app/views/admin/users/index.html.haml | 3 | ||||
-rw-r--r-- | app/views/admin/users/show.html.haml | 22 | ||||
-rw-r--r-- | doc/user/profile/account/delete_account.md | 2 | ||||
-rw-r--r-- | spec/features/admin/admin_users_spec.rb | 8 |
9 files changed, 288 insertions, 17 deletions
diff --git a/app/assets/javascripts/dispatcher.js b/app/assets/javascripts/dispatcher.js index 837dbb84c79..aceaffdfcb9 100644 --- a/app/assets/javascripts/dispatcher.js +++ b/app/assets/javascripts/dispatcher.js @@ -114,6 +114,16 @@ var Dispatcher; .then(callDefault) .catch(fail); 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:starred': import('./pages/dashboard/projects') diff --git a/app/assets/javascripts/pages/admin/users/shared/components/delete_user_modal.vue b/app/assets/javascripts/pages/admin/users/shared/components/delete_user_modal.vue new file mode 100644 index 00000000000..7b5e333011e --- /dev/null +++ b/app/assets/javascripts/pages/admin/users/shared/components/delete_user_modal.vue @@ -0,0 +1,174 @@ +<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> diff --git a/app/assets/javascripts/pages/admin/users/shared/index.js b/app/assets/javascripts/pages/admin/users/shared/index.js new file mode 100644 index 00000000000..d2a0f82fa2b --- /dev/null +++ b/app/assets/javascripts/pages/admin/users/shared/index.js @@ -0,0 +1,43 @@ +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; + } + }); +}; diff --git a/app/assets/javascripts/vue_shared/components/modal.vue b/app/assets/javascripts/vue_shared/components/modal.vue index 8227428d8ba..5f1364421aa 100644 --- a/app/assets/javascripts/vue_shared/components/modal.vue +++ b/app/assets/javascripts/vue_shared/components/modal.vue @@ -46,6 +46,11 @@ required: false, default: '', }, + secondaryButtonLabel: { + type: String, + required: false, + default: '', + }, submitDisabled: { type: Boolean, required: false, @@ -129,6 +134,21 @@ > {{ closeButtonLabel }} </button> + + <slot + v-if="secondaryButtonLabel" + name="secondary-button" + > + <button + v-if="secondaryButtonLabel" + type="button" + class="btn" + data-dismiss="modal" + > + {{ secondaryButtonLabel }} + </button> + </slot> + <button v-if="primaryButtonLabel" type="button" diff --git a/app/views/admin/users/_user.html.haml b/app/views/admin/users/_user.html.haml index ca6e43e091c..3b85586e45b 100644 --- a/app/views/admin/users/_user.html.haml +++ b/app/views/admin/users/_user.html.haml @@ -38,12 +38,19 @@ %li.divider - if user.can_be_removed? %li - = link_to 'Remove user', admin_user_path(user), - data: { confirm: "USER #{user.name} WILL BE REMOVED! Are you sure?" }, - class: 'text-danger', - method: :delete + %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') + %li - = link_to 'Remove user and contributions', admin_user_path(user, hard_delete: true), - 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?" }, - class: 'text-danger', - method: :delete + %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') diff --git a/app/views/admin/users/index.html.haml b/app/views/admin/users/index.html.haml index 38ce1564eff..0ef4b71f4fe 100644 --- a/app/views/admin/users/index.html.haml +++ b/app/views/admin/users/index.html.haml @@ -76,3 +76,6 @@ = render partial: 'admin/users/user', collection: @users = paginate @users, theme: "gitlab" + +#delete-user-modal + diff --git a/app/views/admin/users/show.html.haml b/app/views/admin/users/show.html.haml index 63c5a15de1c..101667508a9 100644 --- a/app/views/admin/users/show.html.haml +++ b/app/views/admin/users/show.html.haml @@ -172,13 +172,19 @@ .panel.panel-danger .panel-heading - Remove user + = s_('AdminUsers|Delete user') .panel-body - if @user.can_be_removed? && can?(current_user, :destroy_user, @user) %p Deleting a user has the following effects: = render 'users/deletion_guidance', user: @user %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 - if @user.solo_owned_groups.present? %p @@ -192,7 +198,7 @@ .panel.panel-danger .panel-heading - Remove user and contributions + = s_('AdminUsers|Delete user and contributions') .panel-body - if can?(current_user, :destroy_user, @user) %p @@ -204,7 +210,15 @@ the user, and projects in them, will also be removed. Commits to other projects are unaffected. %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 %p You don't have access to delete this user. + + #delete-user-modal diff --git a/doc/user/profile/account/delete_account.md b/doc/user/profile/account/delete_account.md index e7596f5c577..910bd20f882 100644 --- a/doc/user/profile/account/delete_account.md +++ b/doc/user/profile/account/delete_account.md @@ -1,7 +1,7 @@ # Deleting a User 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 diff --git a/spec/features/admin/admin_users_spec.rb b/spec/features/admin/admin_users_spec.rb index a69b428d117..2307ba5985e 100644 --- a/spec/features/admin/admin_users_spec.rb +++ b/spec/features/admin/admin_users_spec.rb @@ -26,8 +26,8 @@ describe "Admin::Users" do expect(page).to have_content(user.email) expect(page).to have_content(user.name) 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_link('Remove user and contributions', href: admin_user_path(user, hard_delete: true)) + expect(page).to have_button('Delete user') + expect(page).to have_button('Delete user and contributions') end describe 'Two-factor Authentication filters' do @@ -122,8 +122,8 @@ describe "Admin::Users" do expect(page).to have_content(user.email) 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('Remove user', href: admin_user_path(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') + expect(page).to have_button('Delete user and contributions') end describe 'Impersonation' do |