diff --git a/app/assets/javascripts/pages/projects/edit/index.js b/app/assets/javascripts/pages/projects/edit/index.js index e65c18c07a946d3fa992b068bc8404b403392a9b..7eeb0c852e50500770eb5828b00bc9b6ba038244 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 0000000000000000000000000000000000000000..4b27c5e3d30bf9a3d8ca7e4ccac77f577fb87bff --- /dev/null +++ b/app/assets/javascripts/projects/components/project_delete_button.vue @@ -0,0 +1,52 @@ + + + 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 37f58efcb307aa35875fac1222d166b31faf2d45..0000000000000000000000000000000000000000 --- 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 0000000000000000000000000000000000000000..b00edc94abd1143f1cf57d6c626e89347fa2d1fa --- /dev/null +++ b/app/assets/javascripts/projects/components/shared/delete_button.vue @@ -0,0 +1,101 @@ + + + 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 dbdad1bf6f176abbbee783e3403a7628bff42c74..aa7fc31d307cb62cc72710ccbbd76e0ca0460d7f 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 528d802261c072bfd65911d1e278fd584e78db4f..e85f74d094fbac2db442d84717a8c5fecda1d5bb 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/changelogs/unreleased/36596-update-project-delete-modal.yml b/changelogs/unreleased/36596-update-project-delete-modal.yml new file mode 100644 index 0000000000000000000000000000000000000000..93ba3e58b8a496fece6a64c92aa8de4c3adc6018 --- /dev/null +++ b/changelogs/unreleased/36596-update-project-delete-modal.yml @@ -0,0 +1,5 @@ +--- +title: Update project remove modal to add additional warnings +merge_request: 36962 +author: +type: changed diff --git a/ee/app/assets/javascripts/pages/projects/edit/index.js b/ee/app/assets/javascripts/pages/projects/edit/index.js index 0056e4341c3ecabeed98cb71b5bf7f4a3ca6effb..1b8b702836697e1de3a549496524633c42771765 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 0000000000000000000000000000000000000000..df1f3321258416f2a9e5bbae50e7606cf78e7827 --- /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 0000000000000000000000000000000000000000..d90061daedeafcd3b35e08731fbee0ce47464ef9 --- /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 95abc57a2e96877502c785079a7589f447e4dad6..e2e16607f2c6d2f881b7fb91aad85f1ae9b4e06a 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 permanent_delete_message(project) + message = _('This action will %{strongOpen}permanently delete%{strongClose} %{codeOpen}%{project}%{codeClose} %{strongOpen}immediately%{strongClose}, including its repositories and all content: issues, merge requests, etc.') + html_escape(message) % remove_message_data(project) + end + + def marked_for_removal_message(project) + date = permanent_deletion_date(Time.now.utc) + message = _('This action will %{strongOpen}permanently delete%{strongClose} %{codeOpen}%{project}%{codeClose} %{strongOpen}on %{date}%{strongClose}, including its repositories and all content: issues, merge requests, etc.') + html_escape(message) % remove_message_data(project).merge(date: date) + end + def permanent_deletion_date(date) (date + ::Gitlab::CurrentSettings.deletion_adjourned_period.days).strftime('%F') end @@ -282,5 +293,15 @@ def get_project_security_nav_tabs(project, current_user) nav_tabs end + + def remove_message_data(project) + { + project: project.path, + strongOpen: ''.html_safe, + strongClose: ''.html_safe, + codeOpen: ''.html_safe, + codeClose: ''.html_safe + } + end end end diff --git a/ee/app/views/projects/_remove.html.haml b/ee/app/views/projects/_remove.html.haml index 74f3f4aba90c1d55b989ad1e7295cfbb7dba93a3..cdbdc476506a45b41f81f51b63546a5bc98103ee 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= permanent_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 067fa5edb03696553eff64e5a003b930707f4433..b9a0521fc76e2d4203eafa6c501438edaefe4c5b 100644 --- a/ee/app/views/projects/settings/_marked_for_removal.html.haml +++ b/ee/app/views/projects/settings/_marked_for_removal.html.haml @@ -1,8 +1,6 @@ - return unless @project.feature_available?(:adjourned_deletion_for_projects_and_groups) -- 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= marked_for_removal_message(@project) 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 0000000000000000000000000000000000000000..33f4e4350acd801755f97470011baf1e31e5e413 --- /dev/null +++ b/ee/spec/frontend/projects/components/__snapshots__/project_adjourned_delete_button_spec.js.snap @@ -0,0 +1,86 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`Project remove modal initialized 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 + +

+ + + +

+ + + + + +

+
+
+
+`; 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 0000000000000000000000000000000000000000..57870387e037110aa2ee0520d17b6833d81ded40 --- /dev/null +++ b/ee/spec/frontend/projects/components/project_adjourned_delete_button_spec.js @@ -0,0 +1,52 @@ +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'; + +jest.mock('lodash/uniqueId', () => () => 'fakeUniqueId'); + +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 f66c000e58ff546bce6bc62519420aeab495bc06..fe3ece1a65c7ba1b0615b54576d0e340b837278d 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 %{strongStart}cannot be recovered%{strongEnd}. Permanently deleting this project will %{strongStart}immediately delete%{strongEnd} its respositories and %{strongStart}all related resources%{strongEnd} 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 %{strongOpen}permanently delete%{strongClose} %{codeOpen}%{project}%{codeClose} %{strongOpen}immediately%{strongClose}, including its repositories and all content: issues, merge requests, etc." +msgstr "" + +msgid "This action will %{strongOpen}permanently delete%{strongClose} %{codeOpen}%{project}%{codeClose} %{strongOpen}on %{date}%{strongClose}, 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 ab0b672549143987ca4554f3edf70a325b8e15e1..4ad80b44b4db66338b3bd88987d629d3e1831295 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 0000000000000000000000000000000000000000..7d6022cc1b7bbf4a5667a0c0a58cb71e0a1ec524 --- /dev/null +++ b/spec/frontend/projects/components/__snapshots__/project_delete_button_spec.js.snap @@ -0,0 +1,83 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`Project remove modal initialized matches the snapshot 1`] = ` +
+ + + + + + Remove project + + + + +
+ + + + +

+ 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 4d5b6c56a34da5f389c71a68947fbce376ba886a..0000000000000000000000000000000000000000 --- 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. -

- -

- -

- - -
- -