From 6f4acb4ee2e3c0a00b721a522561b47ddbf10aee Mon Sep 17 00:00:00 2001 From: Jiaan Louw Date: Wed, 15 Jul 2020 15:00:52 +0200 Subject: [PATCH 1/6] Update project delete modals - Update remove project setting section - Add a unique modal for regular project deletions - Add a unique modal for adjourned project deletions --- .../javascripts/pages/projects/edit/index.js | 4 +- .../components/project_delete_button.vue | 51 +++++++ .../projects/components/remove_modal.vue | 108 --------------- .../components/shared/delete_button.vue | 103 ++++++++++++++ ...move_modal.js => project_delete_button.js} | 9 +- app/views/projects/_remove.html.haml | 2 +- .../javascripts/pages/projects/edit/index.js | 3 + .../project_adjourned_delete_button.vue | 65 +++++++++ .../project_adjourned_delete_button.js | 25 ++++ ee/app/helpers/ee/projects_helper.rb | 11 ++ ee/app/views/projects/_remove.html.haml | 16 ++- .../settings/_marked_for_removal.html.haml | 6 +- ...oject_adjourned_delete_button_spec.js.snap | 85 ++++++++++++ .../project_adjourned_delete_button_spec.js | 50 +++++++ locale/gitlab.pot | 57 ++++++-- spec/features/projects_spec.rb | 6 +- .../project_delete_button_spec.js.snap | 101 ++++++++++++++ .../__snapshots__/remove_modal_spec.js.snap | 126 ------------------ .../components/project_delete_button_spec.js | 45 +++++++ .../projects/components/remove_modal_spec.js | 62 --------- .../__snapshots__/delete_button_spec.js.snap | 113 ++++++++++++++++ .../components/shared/delete_button_spec.js | 83 ++++++++++++ 22 files changed, 806 insertions(+), 325 deletions(-) create mode 100644 app/assets/javascripts/projects/components/project_delete_button.vue delete mode 100644 app/assets/javascripts/projects/components/remove_modal.vue create mode 100644 app/assets/javascripts/projects/components/shared/delete_button.vue rename app/assets/javascripts/projects/{project_remove_modal.js => project_delete_button.js} (50%) create mode 100644 ee/app/assets/javascripts/projects/components/project_adjourned_delete_button.vue create mode 100644 ee/app/assets/javascripts/projects/project_adjourned_delete_button.js create mode 100644 ee/spec/frontend/projects/components/__snapshots__/project_adjourned_delete_button_spec.js.snap create mode 100644 ee/spec/frontend/projects/components/project_adjourned_delete_button_spec.js create mode 100644 spec/frontend/projects/components/__snapshots__/project_delete_button_spec.js.snap delete mode 100644 spec/frontend/projects/components/__snapshots__/remove_modal_spec.js.snap create mode 100644 spec/frontend/projects/components/project_delete_button_spec.js delete mode 100644 spec/frontend/projects/components/remove_modal_spec.js create mode 100644 spec/frontend/projects/components/shared/__snapshots__/delete_button_spec.js.snap create mode 100644 spec/frontend/projects/components/shared/delete_button_spec.js diff --git a/app/assets/javascripts/pages/projects/edit/index.js b/app/assets/javascripts/pages/projects/edit/index.js index e65c18c07a946d..7eeb0c852e5050 100644 --- a/app/assets/javascripts/pages/projects/edit/index.js +++ b/app/assets/javascripts/pages/projects/edit/index.js @@ -7,7 +7,7 @@ import dirtySubmitFactory from '~/dirty_submit/dirty_submit_factory'; import initFilePickers from '~/file_pickers'; import initProjectLoadingSpinner from '../shared/save_project_loader'; import initProjectPermissionsSettings from '../shared/permissions'; -import initProjectRemoveModal from '~/projects/project_remove_modal'; +import initProjectDeleteButton from '~/projects/project_delete_button'; import UserCallout from '~/user_callout'; import initServiceDesk from '~/projects/settings_service_desk'; @@ -15,7 +15,7 @@ document.addEventListener('DOMContentLoaded', () => { initFilePickers(); initConfirmDangerModal(); initSettingsPanels(); - initProjectRemoveModal(); + initProjectDeleteButton(); mountBadgeSettings(PROJECT_BADGE); new UserCallout({ className: 'js-service-desk-callout' }); // eslint-disable-line no-new diff --git a/app/assets/javascripts/projects/components/project_delete_button.vue b/app/assets/javascripts/projects/components/project_delete_button.vue new file mode 100644 index 00000000000000..07da79598e3939 --- /dev/null +++ b/app/assets/javascripts/projects/components/project_delete_button.vue @@ -0,0 +1,51 @@ + + + diff --git a/app/assets/javascripts/projects/components/remove_modal.vue b/app/assets/javascripts/projects/components/remove_modal.vue deleted file mode 100644 index 37f58efcb307aa..00000000000000 --- a/app/assets/javascripts/projects/components/remove_modal.vue +++ /dev/null @@ -1,108 +0,0 @@ - - - diff --git a/app/assets/javascripts/projects/components/shared/delete_button.vue b/app/assets/javascripts/projects/components/shared/delete_button.vue new file mode 100644 index 00000000000000..d630d6ed56fc5d --- /dev/null +++ b/app/assets/javascripts/projects/components/shared/delete_button.vue @@ -0,0 +1,103 @@ + + + diff --git a/app/assets/javascripts/projects/project_remove_modal.js b/app/assets/javascripts/projects/project_delete_button.js similarity index 50% rename from app/assets/javascripts/projects/project_remove_modal.js rename to app/assets/javascripts/projects/project_delete_button.js index dbdad1bf6f176a..aa7fc31d307cb6 100644 --- a/app/assets/javascripts/projects/project_remove_modal.js +++ b/app/assets/javascripts/projects/project_delete_button.js @@ -1,21 +1,20 @@ import Vue from 'vue'; -import RemoveProjectModal from './components/remove_modal.vue'; +import ProjectDeleteButton from './components/project_delete_button.vue'; -export default (selector = '#js-confirm-project-remove') => { +export default (selector = '#js-project-delete-button') => { const el = document.querySelector(selector); if (!el) return; - const { formPath, confirmPhrase, warningMessage } = el.dataset; + const { confirmPhrase, formPath } = el.dataset; // eslint-disable-next-line no-new new Vue({ el, render(createElement) { - return createElement(RemoveProjectModal, { + return createElement(ProjectDeleteButton, { props: { confirmPhrase, - warningMessage, formPath, }, }); diff --git a/app/views/projects/_remove.html.haml b/app/views/projects/_remove.html.haml index 528d802261c072..e85f74d094fbac 100644 --- a/app/views/projects/_remove.html.haml +++ b/app/views/projects/_remove.html.haml @@ -6,4 +6,4 @@ %strong= _('Removing the project will delete its repository and all related resources including issues, merge requests etc.') %p %strong= _('Removed projects cannot be restored!') - #js-confirm-project-remove{ data: { form_path: project_path(project), confirm_phrase: project.path, warning_message: remove_project_message(project) } } + #js-project-delete-button{ data: { form_path: project_path(project), confirm_phrase: project.path } } diff --git a/ee/app/assets/javascripts/pages/projects/edit/index.js b/ee/app/assets/javascripts/pages/projects/edit/index.js index 0056e4341c3eca..1b8b702836697e 100644 --- a/ee/app/assets/javascripts/pages/projects/edit/index.js +++ b/ee/app/assets/javascripts/pages/projects/edit/index.js @@ -5,6 +5,7 @@ import mountApprovals from 'ee/approvals/mount_project_settings'; import UsersSelect from '~/users_select'; import UserCallout from '~/user_callout'; import groupsSelect from '~/groups_select'; +import initProjectAdjournedDeleteButton from 'ee/projects/project_adjourned_delete_button'; document.addEventListener('DOMContentLoaded', () => { new UsersSelect(); @@ -13,4 +14,6 @@ document.addEventListener('DOMContentLoaded', () => { new UserCallout({ className: 'js-mr-approval-callout' }); mountApprovals(document.getElementById('js-mr-approvals-settings')); + + initProjectAdjournedDeleteButton(); }); diff --git a/ee/app/assets/javascripts/projects/components/project_adjourned_delete_button.vue b/ee/app/assets/javascripts/projects/components/project_adjourned_delete_button.vue new file mode 100644 index 00000000000000..c4af572dbf8f6b --- /dev/null +++ b/ee/app/assets/javascripts/projects/components/project_adjourned_delete_button.vue @@ -0,0 +1,65 @@ + + + diff --git a/ee/app/assets/javascripts/projects/project_adjourned_delete_button.js b/ee/app/assets/javascripts/projects/project_adjourned_delete_button.js new file mode 100644 index 00000000000000..d90061daedeafc --- /dev/null +++ b/ee/app/assets/javascripts/projects/project_adjourned_delete_button.js @@ -0,0 +1,25 @@ +import Vue from 'vue'; +import ProjectAdjournedDeleteButton from './components/project_adjourned_delete_button.vue'; + +export default (selector = '#js-project-adjourned-delete-button') => { + const el = document.querySelector(selector); + + if (!el) return; + + const { adjournedRemovalDate, confirmPhrase, formPath, recoveryHelpPath } = el.dataset; + + // eslint-disable-next-line no-new + new Vue({ + el, + render(createElement) { + return createElement(ProjectAdjournedDeleteButton, { + props: { + adjournedRemovalDate, + confirmPhrase, + formPath, + recoveryHelpPath, + }, + }); + }, + }); +}; diff --git a/ee/app/helpers/ee/projects_helper.rb b/ee/app/helpers/ee/projects_helper.rb index 95abc57a2e9687..3f47bb77a00c80 100644 --- a/ee/app/helpers/ee/projects_helper.rb +++ b/ee/app/helpers/ee/projects_helper.rb @@ -94,6 +94,17 @@ def remove_project_message(project) { date: date } end + def permenant_delete_message(project) + html_escape(_('This action will %{strongOpen}permanently delete%{strongClose} %{codeOpen}%{project}%{codeClose} %{strongOpen}immediately%{strongClose}, including its repositories and all content: issues, merge requests, etc.')) % + { + project: project.path, + strongOpen: ''.html_safe, + strongClose: ''.html_safe, + codeOpen: ''.html_safe, + codeClose: ''.html_safe + } + end + def permanent_deletion_date(date) (date + ::Gitlab::CurrentSettings.deletion_adjourned_period.days).strftime('%F') end diff --git a/ee/app/views/projects/_remove.html.haml b/ee/app/views/projects/_remove.html.haml index 74f3f4aba90c1d..c0704f0514ce0c 100644 --- a/ee/app/views/projects/_remove.html.haml +++ b/ee/app/views/projects/_remove.html.haml @@ -1,5 +1,8 @@ - return unless can?(current_user, :remove_project, project) - adjourned_deletion = project.adjourned_deletion? +- adjourned_date = adjourned_deletion ? permanent_deletion_date(Time.now.utc).to_s : nil; +- admin_help_path = help_page_path('user/admin_area/settings/visibility_and_access_controls', anchor: 'default-deletion-adjourned-period-premium-only') +- recovery_help_path = help_page_path('user/project/settings/index', anchor: 'remove-a-project') - unless project.marked_for_deletion? .sub-section @@ -8,14 +11,15 @@ %strong= s_('Delayed Project Deletion (%{adjourned_deletion})') % { adjourned_deletion: adjourned_deletion ? 'Enabled' : 'Disabled' } - if adjourned_deletion = render 'projects/settings/marked_for_removal' + #js-project-adjourned-delete-button{ data: { recovery_help_path: recovery_help_path, adjourned_removal_date: adjourned_date, form_path: project_path(project), confirm_phrase: project.path } } - else %p - = _("Removing a project deletes it immediately, there will be no delay before the project is permanently removed.") - %p - %strong= _('Removing the project will delete its repository and all related resources including issues, merge requests etc.') - %p - %strong= _('Removed projects cannot be restored!') - #js-confirm-project-remove{ data: { form_path: project_path(project), confirm_phrase: project.path, warning_message: remove_project_message(project) } } + %span.gl-text-gray-500= _('Projects will be permanently deleted immediately.') + = link_to(_('Customizable by an administrator.'), admin_help_path) + %p= permenant_delete_message(project) + %p + %strong= _('Are you ABSOLUTELY SURE you wish to delete this project?') + #js-project-delete-button{ data: { form_path: project_path(project), confirm_phrase: project.path } } - else = render 'projects/settings/restore', project: project diff --git a/ee/app/views/projects/settings/_marked_for_removal.html.haml b/ee/app/views/projects/settings/_marked_for_removal.html.haml index 067fa5edb03696..b8dabddd277cbd 100644 --- a/ee/app/views/projects/settings/_marked_for_removal.html.haml +++ b/ee/app/views/projects/settings/_marked_for_removal.html.haml @@ -3,6 +3,6 @@ - date = permanent_deletion_date(Time.now.utc) %p - = _("Removing a project places it into a read-only state until %{date}, at which point the project will be permanently removed.") %{ date: date } - %br - = _("Until that time, the project can be restored.") + %span.gl-text-gray-500= _('Projects will be permanently deleted after a 7-day waiting period.') + = link_to(_('Customizable by an administrator.'), help_page_path('user/admin_area/settings/visibility_and_access_controls', anchor: 'default-deletion-adjourned-period-premium-only')) +%p= _('This action will permanently delete %{project} on %{date}, including its repositories and all content: issues, merge requests, etc.').html_safe % { project: @project.path, date: date } diff --git a/ee/spec/frontend/projects/components/__snapshots__/project_adjourned_delete_button_spec.js.snap b/ee/spec/frontend/projects/components/__snapshots__/project_adjourned_delete_button_spec.js.snap new file mode 100644 index 00000000000000..c0fb8205ec64ed --- /dev/null +++ b/ee/spec/frontend/projects/components/__snapshots__/project_adjourned_delete_button_spec.js.snap @@ -0,0 +1,85 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`Project remove modal with no adjouned removal date matches the snapshot 1`] = ` +
+ + + + + + Remove project + + + + +
+

+ Once a project is permanently deleted it cannot be recovered. You will lose this project's repository and all content: issues, merge requests etc. +

+ +

+ Please type the following to confirm: +

+ +

+ + foo + +

+ + + +

+ + You can recover this project until 2020-12-12 + + + + +

+
+
+
+`; diff --git a/ee/spec/frontend/projects/components/project_adjourned_delete_button_spec.js b/ee/spec/frontend/projects/components/project_adjourned_delete_button_spec.js new file mode 100644 index 00000000000000..f0c5883d60c531 --- /dev/null +++ b/ee/spec/frontend/projects/components/project_adjourned_delete_button_spec.js @@ -0,0 +1,50 @@ +import { shallowMount } from '@vue/test-utils'; +import ProjectAdjournedDeleteButton from 'ee/projects/components/project_adjourned_delete_button.vue'; +import SharedDeleteButton from '~/projects/components/shared/delete_button.vue'; + +describe('Project remove modal', () => { + let wrapper; + + const findSharedDeleteButton = () => wrapper.find(SharedDeleteButton); + + const defaultProps = { + adjournedRemovalDate: '2020-12-12', + confirmPhrase: 'foo', + formPath: 'some/path', + recoveryHelpPath: 'recovery/help/path', + }; + + const createComponent = (props = {}) => { + wrapper = shallowMount(ProjectAdjournedDeleteButton, { + propsData: { + ...defaultProps, + ...props, + }, + stubs: { + SharedDeleteButton, + }, + }); + }; + + afterEach(() => { + wrapper.destroy(); + wrapper = null; + }); + + describe('initialized', () => { + beforeEach(() => { + createComponent(); + }); + + it('matches the snapshot', () => { + expect(wrapper.element).toMatchSnapshot(); + }); + + it('passes confirmPhrase and formPath props to the shared delete button', () => { + expect(findSharedDeleteButton().props()).toEqual({ + confirmPhrase: defaultProps.confirmPhrase, + formPath: defaultProps.formPath, + }); + }); + }); +}); diff --git a/locale/gitlab.pot b/locale/gitlab.pot index f66c000e58ff54..806ad789d0844a 100644 --- a/locale/gitlab.pot +++ b/locale/gitlab.pot @@ -3083,6 +3083,9 @@ msgstr "" msgid "Archiving the project will make it entirely read only. It is hidden from the dashboard and doesn't show up in searches. %{strong_start}The repository cannot be committed to, and no issues, comments, or other entities can be created.%{strong_end}" msgstr "" +msgid "Are you ABSOLUTELY SURE you wish to delete this project?" +msgstr "" + msgid "Are you setting up GitLab for a company?" msgstr "" @@ -4245,6 +4248,9 @@ msgstr "" msgid "Cancel this job" msgstr "" +msgid "Cancel, keep project" +msgstr "" + msgid "Canceled deployment to" msgstr "" @@ -7283,6 +7289,9 @@ msgstr "" msgid "Customer Portal" msgstr "" +msgid "Customizable by an administrator." +msgstr "" + msgid "Customize colors" msgstr "" @@ -7729,6 +7738,9 @@ msgstr "" msgid "Delete project" msgstr "" +msgid "Delete project. Are you ABSOLUTELY SURE?" +msgstr "" + msgid "Delete serverless domain?" msgstr "" @@ -16547,6 +16559,12 @@ msgstr "" msgid "OnDemandScans|Target URL" msgstr "" +msgid "Once a project is permanently deleted it cannot be recovered. Permanently deleting this project will immediately delete its respositories and all related resources including issues, merge requests etc." +msgstr "" + +msgid "Once a project is permanently deleted it cannot be recovered. You will lose this project's repository and all content: issues, merge requests etc." +msgstr "" + msgid "Once imported, repositories can be mirrored over SSH. Read more %{link_start}here%{link_end}." msgstr "" @@ -17773,6 +17791,9 @@ msgstr "" msgid "Please type %{phrase_code} to proceed or close this modal to cancel." msgstr "" +msgid "Please type the following to confirm:" +msgstr "" + msgid "Please use this form to report to the admin users who create spam issues, comments or behave inappropriately." msgstr "" @@ -18991,6 +19012,12 @@ msgstr "" msgid "Projects to index" msgstr "" +msgid "Projects will be permanently deleted after a 7-day waiting period." +msgstr "" + +msgid "Projects will be permanently deleted immediately." +msgstr "" + msgid "Projects with critical vulnerabilities" msgstr "" @@ -19675,6 +19702,9 @@ msgstr "" msgid "Recover hidden stage" msgstr "" +msgid "Recovering projects" +msgstr "" + msgid "Recovery Codes" msgstr "" @@ -20069,12 +20099,6 @@ msgstr "" msgid "Removes time estimate." msgstr "" -msgid "Removing a project deletes it immediately, there will be no delay before the project is permanently removed." -msgstr "" - -msgid "Removing a project places it into a read-only state until %{date}, at which point the project will be permanently removed." -msgstr "" - msgid "Removing a project places it into a read-only state until %{date}, at which point the project will be permanently removed. Are you ABSOLUTELY sure?" msgstr "" @@ -24392,6 +24416,15 @@ msgstr "" msgid "This action can lead to data loss. To prevent accidental actions we ask you to confirm your intention." msgstr "" +msgid "This action cannot be undone. You will lose the project's respository and all conent: issues, merge requests, etc." +msgstr "" + +msgid "This action will permanently delete %{project} immediately, including its repositories and all content: issues, merge requests, etc." +msgstr "" + +msgid "This action will permanently delete %{project} on %{date}, including its repositories and all content: issues, merge requests, etc." +msgstr "" + msgid "This also resolves all related threads" msgstr "" @@ -25851,9 +25884,6 @@ msgstr "" msgid "Until" msgstr "" -msgid "Until that time, the project can be restored." -msgstr "" - msgid "Unverified" msgstr "" @@ -27315,6 +27345,9 @@ msgstr "" msgid "Yes, close issue" msgstr "" +msgid "Yes, delete project" +msgstr "" + msgid "Yes, let me map Google Code users to full names or GitLab users." msgstr "" @@ -27330,6 +27363,9 @@ msgstr "" msgid "You are about to delete %{domain} from your instance. This domain will no longer be available to any Knative application." msgstr "" +msgid "You are about to permanently delete this project" +msgstr "" + msgid "You are about to transfer the control of your account to %{group_name} group. This action is NOT reversible, you won't be able to access any of your groups and projects outside of %{group_name} once this transfer is complete." msgstr "" @@ -27480,6 +27516,9 @@ msgstr "" msgid "You can only upload one design when dropping onto an existing design." msgstr "" +msgid "You can recover this project until %{date}" +msgstr "" + msgid "You can resolve the merge conflict using either the Interactive mode, by choosing %{use_ours} or %{use_theirs} buttons, or by editing the files directly. Commit these changes into %{branch_name}" msgstr "" diff --git a/spec/features/projects_spec.rb b/spec/features/projects_spec.rb index ab0b6725491439..4ad80b44b4db66 100644 --- a/spec/features/projects_spec.rb +++ b/spec/features/projects_spec.rb @@ -260,7 +260,7 @@ end it 'removes a project', :sidekiq_might_not_need_inline do - expect { remove_with_confirm('Remove project', project.path) }.to change { Project.count }.by(-1) + expect { remove_with_confirm('Remove project', project.path, 'Yes, delete project') }.to change { Project.count }.by(-1) expect(page).to have_content "Project '#{project.full_name}' is in the process of being deleted." expect(Project.all.count).to be_zero expect(project.issues).to be_empty @@ -386,9 +386,9 @@ { form: '.rspec-merge-request-settings', input: '#project_printing_merge_request_link_enabled' }] end - def remove_with_confirm(button_text, confirm_with) + def remove_with_confirm(button_text, confirm_with, confirm_button_text = 'Confirm') click_button button_text fill_in 'confirm_name_input', with: confirm_with - click_button 'Confirm' + click_button confirm_button_text end end diff --git a/spec/frontend/projects/components/__snapshots__/project_delete_button_spec.js.snap b/spec/frontend/projects/components/__snapshots__/project_delete_button_spec.js.snap new file mode 100644 index 00000000000000..3b0235b4c21469 --- /dev/null +++ b/spec/frontend/projects/components/__snapshots__/project_delete_button_spec.js.snap @@ -0,0 +1,101 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`Project remove modal with no adjouned removal date matches the snapshot 1`] = ` +
+ + + + + + Remove project + + + + +
+ +

+ You are about to permanently delete this project +

+ + + Once a project is permanently deleted it + + cannot be recovered + + . Permanently deleting this project will + + immediately delete + + its respositories and + + all related resources + + including issues, merge requests etc. + +
+ +

+ This action cannot be undone. You will lose the project's respository and all conent: issues, merge requests, etc. +

+ +

+ Please type the following to confirm: +

+ +

+ + foo + +

+ + + +
+
+
+`; diff --git a/spec/frontend/projects/components/__snapshots__/remove_modal_spec.js.snap b/spec/frontend/projects/components/__snapshots__/remove_modal_spec.js.snap deleted file mode 100644 index 4d5b6c56a34da5..00000000000000 --- a/spec/frontend/projects/components/__snapshots__/remove_modal_spec.js.snap +++ /dev/null @@ -1,126 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`Project remove modal initialized matches the snapshot 1`] = ` -
- - - - - - - - - - - Remove project - - - - - -
-

- This can lead to data loss. -

- -

- This action can lead to data loss. To prevent accidental actions we ask you to confirm your intention. -

- -

- -

- - -
- -