Revamp project deletion setting options for clearer interpretation
Problem to solve
Discerning how and when projects are deleted can be difficult to understand.
Proposal
No major functionality will be changing. Just small tweaks to streamline the user journey, and reducing the opportunity for ambiguous conflicts.
Admin Area
- Rename field Default project deletion protection to Allowed to delete projects
- Use radio buttons instead of checkbox
- Combine Default delayed project deletion and Default deletion delay into Deletion protection
- Use radio buttons to change state
- Use a dropdown button to change selection
-
✔ ️ groups and projects - groups only
-
- Use an input counter that can't be set to ≤
0
- When set to none, delete immediately, hide the radio buttons from groups and adjust the help text
Groups
- Move setting outside of Permissions into its own group Deletion protection
- Change the description based on who is allowed to delete projects
- Use radio buttons to change selection rather than a checkbox
-
🔘 Keep project for X days -
⚪ ️ None, delete immediately
-
- When enforcement is
☑ ️ for subgroups, disable the radio buttons and show a locked setting.
Enforced by group | Set to none in Admin Area |
---|---|
What does success look like, and how can we measure that?
Users should be able to understand what specific settings are impacting and configure their environments easily to match their expectations.
Measurement (proposal): Uptick in the number of GitLab Premium users keeping deletion protection enabled.
Documentation
-
doc/user/admin_area/settings/visibility_and_access_controls.md
, docs page, docs page doc/api/projects.md
-
doc/user/admin_area/settings/visibility_and_access_controls.md
, docs page
References
Solution Validation (Synthesis)
3 unmoderated usability tests were conducted in UserTesting to validate the ease-of-use associated with the design decisions.
After testing with 15 participants in total, it was clear the tasks were easily completed. However, the ability to switch back to a null
state was still confusing to users. This will be removed from the scope of this issue and followed-up with its own research in #350087.
Implementation plan
WIP
Admin settings
-
backend Add a new instance setting for delayed_group_deletion
(defaulttrue
) that is seperate fromdelayed_project_deletion
. This is needed becausedeletion_adjourned_period
should only be greater than1
and thus no longer be used to check if delayed group deletion should be enabled / disabled. We also don't want to override the user specifieddeletion_adjourned_period
even when the user opted toNone, delete immediately
. Additional context #348332 (comment 840063606).- Add a migration plan to set the value of
delayed_group_deletion
todeletion_adjourned_period > 0
. - When
deletion_adjourned_period
is set, also set thedelayed_group_deletion
todeletion_adjourned_period > 0
. - Update the Remove group button under Group > Advanced Settings to only show the delayed deletion message and modal when both
delayed_group_deletion
istrue
anddeletion_adjourned_period
is>0
. Currently group delayed deletion only depends ondeletion_adjourned_period > 0
. - Update specs.
- Update the applications settings API doc to add
delayed_group_deletion
setting.
- Add a migration plan to set the value of
-
frontend Update _default_project_deletion_protection_setting.html.haml
:- Use
gitlab_ui_radio_component
instead ofcheck_box
- Update strings
- Use
-
frontend Update _default_delayed_project_deletion_setting.html.haml
:- Update strings
- Add help text under form group label
- Use
gitlab_ui_radio_component
instead ofcheck_box
for the settingdelayed_group_delection
-
Keep deleted
=true
-
None, delete immediately
=false
-
- Add a
select
component with the options[['groups only', false], ['groups and projects', true]]
to toggle thedelayed_project_deletion
setting. - Move the
deletion_adjourned_period
to be inline with thedelayed_project_deletion
option. - Delete the
_default_project_deletion_adjourned_period_setting.html.haml
partial.
-
Update docs. -
Update specs.
Note: Here is a rough patch to update the settings HAML. admin_settings.patch
Patch contents
diff --git a/app/views/admin/application_settings/_visibility_and_access.html.haml b/app/views/admin/application_settings/_visibility_and_access.html.haml
index e56c898b236..2f1003b1f80 100644
--- a/app/views/admin/application_settings/_visibility_and_access.html.haml
+++ b/app/views/admin/application_settings/_visibility_and_access.html.haml
@@ -7,8 +7,7 @@
= render 'shared/project_creation_levels', f: f, method: :default_project_creation, legend: s_('ProjectCreationLevel|Default project creation protection')
= render_if_exists 'admin/application_settings/default_project_deletion_protection_setting', form: f
- = render_if_exists 'admin/application_settings/default_delayed_project_deletion_setting', form: f
- = render_if_exists 'admin/application_settings/default_project_deletion_adjourned_period_setting', form: f
+ = render_if_exists 'admin/application_settings/deletion_protection_setting', form: f
.form-group.visibility-level-setting
= f.label :default_project_visibility, class: 'label-bold'
= render('shared/visibility_radios', model_method: :default_project_visibility, form: f, selected_level: @application_setting.default_project_visibility, form_model: Project.new)
diff --git a/ee/app/views/admin/application_settings/_default_delayed_project_deletion_setting.html.haml b/ee/app/views/admin/application_settings/_default_delayed_project_deletion_setting.html.haml
deleted file mode 100644
index 31b4c75d584..00000000000
--- a/ee/app/views/admin/application_settings/_default_delayed_project_deletion_setting.html.haml
+++ /dev/null
@@ -1,11 +0,0 @@
-- return unless License.feature_available?(:adjourned_deletion_for_projects_and_groups)
-
-- f = local_assigns.fetch(:form)
-
-.form-group
- = f.label _('Default delayed project deletion'), class: 'label-bold'
- .form-check
- = f.check_box :delayed_project_deletion, class: 'form-check-input'
- = f.label :delayed_project_deletion, class: 'form-check-label' do
- = _('Enable delayed project deletion by default for newly-created groups.')
- = _('Does not apply to projects in personal namespaces, which are deleted immediately on request.')
diff --git a/ee/app/views/admin/application_settings/_default_project_deletion_adjourned_period_setting.html.haml b/ee/app/views/admin/application_settings/_default_project_deletion_adjourned_period_setting.html.haml
deleted file mode 100644
index 75fe0e9f2f5..00000000000
--- a/ee/app/views/admin/application_settings/_default_project_deletion_adjourned_period_setting.html.haml
+++ /dev/null
@@ -1,9 +0,0 @@
-- return unless License.feature_available?(:adjourned_deletion_for_projects_and_groups)
-
-- f = local_assigns.fetch(:form)
-
-.form-group
- = f.label _('Default deletion delay'), class: 'label-bold'
- = f.select :deletion_adjourned_period, options_for_select(0..90, @application_setting.deletion_adjourned_period), {}, class: 'form-control gl-form-input'
- = f.label :deletion_adjourned_period, class: 'form-check-label' do
- = _('How many days need to pass between marking entity for deletion and actual removing it.')
diff --git a/ee/app/views/admin/application_settings/_default_project_deletion_protection_setting.html.haml b/ee/app/views/admin/application_settings/_default_project_deletion_protection_setting.html.haml
index 44c066335f8..5baf5178375 100644
--- a/ee/app/views/admin/application_settings/_default_project_deletion_protection_setting.html.haml
+++ b/ee/app/views/admin/application_settings/_default_project_deletion_protection_setting.html.haml
@@ -3,8 +3,6 @@
- f = local_assigns.fetch(:form)
.form-group
- = f.label _('Default project deletion protection'), class: 'label-bold'
- .form-check
- = f.check_box :default_project_deletion_protection, class: 'form-check-input'
- = f.label :default_project_deletion_protection, class: 'form-check-label' do
- = _('Only admins can delete project')
+ = f.label _('Allowed to delete projects'), class: 'label-bold'
+ = f.gitlab_ui_radio_component :default_project_deletion_protection, :false, _('Owners and administrators')
+ = f.gitlab_ui_radio_component :default_project_deletion_protection, :true, _('Administrators')
diff --git a/ee/app/views/admin/application_settings/_deletion_protection_setting.html.haml b/ee/app/views/admin/application_settings/_deletion_protection_setting.html.haml
new file mode 100644
index 00000000000..ab92f033e9f
--- /dev/null
+++ b/ee/app/views/admin/application_settings/_deletion_protection_setting.html.haml
@@ -0,0 +1,24 @@
+- return unless License.feature_available?(:adjourned_deletion_for_projects_and_groups)
+- deletion_choices = [[_('groups and projects'), :true], [_('only groups'), :false]]
+
+- f = local_assigns.fetch(:form)
+
+.form-group
+ = f.label _('Deletion protection'), class: 'label-bold gl-mb-2'
+ %p.form-text.text-muted
+ = _('Retention period that deleted groups and projects will remain restorable. Personal projects are always deleted immediately. Some groups can opt-out their projects.')
+ = link_to _('Learn more.'), help_page_path('user/admin_area/settings/visibility_and_access_controls', anchor: 'default-delayed-project-deletion'), target: '_blank', rel: 'noopener noreferrer'
+ .gl-mt-4
+ .gl-display-flex.gl-flex-direction-row.gl-align-items-baseline.gl-mb-3
+ -# TODO: Change this to delayed_group_delection true
+ = f.gitlab_ui_radio_component :delayed_project_deletion, :true, _('Keep deleted'), label_options: { class: 'gl-white-space-nowrap gl-mr-3' }
+ = f.select :delayed_project_deletion, deletion_choices, {}, { disabled: false, class: 'form-control gl-mr-3 gl-w-auto gl-p-0 gl-h-6!' }
+ %span.gl-mr-3=_('for')
+ .input-group.gl-w-auto.input-group-sm
+ = f.number_field :deletion_adjourned_period, min: 1, class: 'form-control gl-form-input gl-py-2 gl-px-3 gl-w-11! gl-h-6!'
+ .input-group-append
+ .input-group-text{ class: 'gl-h-6!' }
+ %span.gl-line-height-normal=_('days')
+ -# TODO: Change this to delayed_group_delection false
+ = f.gitlab_ui_radio_component :delayed_project_deletion, :false, _('None, delete immediately')
+
Group settings
-
frontend Update _delayed_project_removal.html.haml
to match the designs:- Add the subheading
Deletion protection
- When the settings are locked, render a lock icon with a popover next to the subheading.
- When locked by an admin, show the admin message.
- When locked by a group owner, show the group message.
- Add the help text
Owners and administrators can delete projects.
- When locked by admin set the text to
Only administrators can delete projects.
. And when set to delete immediately, addAll projects are deleted immediately.
to the help text. - Add a
Learn more.
link to the help path.
- When locked by admin set the text to
- When deletion is not limited to just administrators:
- Render the
delayed_project_removal
setting with radio buttons. - Render the
enforcement_checkbox
and when the setting is locked disable the checkbox instead.
- Render the
- Add the subheading
-
Update docs. -
Update specs.
Note: Here is a rough patch with the interface changes. group_settings.patch
Patch contents
diff --git a/app/views/shared/namespaces/cascading_settings/_enforcement_checkbox.html.haml b/app/views/shared/namespaces/cascading_settings/_enforcement_checkbox.html.haml
index cfa87351689..bbcb4f3a3cd 100644
--- a/app/views/shared/namespaces/cascading_settings/_enforcement_checkbox.html.haml
+++ b/app/views/shared/namespaces/cascading_settings/_enforcement_checkbox.html.haml
@@ -2,7 +2,7 @@
- group = local_assigns.fetch(:group, nil)
- form = local_assigns.fetch(:form, nil)
- setting_locked = local_assigns.fetch(:setting_locked, false)
-- help_text = local_assigns.fetch(:help_text, s_('CascadingSettings|Subgroups cannot change this setting.'))
+- disabled = local_assigns.fetch(:disabled, false)
- return unless attribute && group && form
- return if setting_locked
@@ -10,9 +10,7 @@
- lock_attribute = "lock_#{attribute}"
.gl-form-checkbox.custom-control.custom-checkbox
- = form.check_box lock_attribute, checked: group.namespace_settings.public_send(lock_attribute), class: 'custom-control-input', data: { testid: 'enforce-for-all-subgroups-checkbox' }
- = form.label lock_attribute, class: 'custom-control-label' do
+ = form.check_box lock_attribute, checked: group.namespace_settings.public_send(lock_attribute), class: 'custom-control-input', data: { testid: 'enforce-for-all-subgroups-checkbox' }, disabled: disabled
+ = form.label lock_attribute, class: 'custom-control-label', aria: { disabled: disabled } do
%span
- = yield.presence || s_('CascadingSettings|Enforce for all subgroups')
- %p.help-text
- = help_text
+ = yield.presence || s_('CascadingSettings|Enforce deletion protection for all subgroups')
diff --git a/ee/app/views/groups/settings/_delayed_project_removal.html.haml b/ee/app/views/groups/settings/_delayed_project_removal.html.haml
index e6b625a6a51..4e8948f5c56 100644
--- a/ee/app/views/groups/settings/_delayed_project_removal.html.haml
+++ b/ee/app/views/groups/settings/_delayed_project_removal.html.haml
@@ -2,18 +2,29 @@
- setting_locked = cascading_namespace_setting_locked?(:delayed_project_removal, group)
+%h5.gl-mb-2.gl-display-flex
+ = _('Deletion protection')
+ - if setting_locked
+ %span.gl-outline-0.gl-ml-3{ tabindex: "0", data: { container: "body",
+ toggle: "popover",
+ placement: "top",
+ html: "true",
+ trigger: "focus",
+ title: _('Setting enforced'),
+ content: _('This setting has been enforced by an instance administrator.')
+ } }
+ = sprite_icon('lock', size: 16)
+%p.form-text.text-muted{ class: 'gl-mb-4!' }
+ = delayed_project_removal_help_text
+ = link_to _('Learn more.'), '#', target: '_blank', rel: 'noopener noreferrer'
.form-group{ data: { testid: 'delayed-project-removal-form-group' } }
- .gl-form-checkbox.custom-control.custom-checkbox
- = f.check_box :delayed_project_removal, checked: group.namespace_settings.delayed_project_removal?, disabled: setting_locked, class: 'custom-control-input', data: { testid: 'delayed-project-removal-checkbox' }
- = render 'shared/namespaces/cascading_settings/setting_label_checkbox', attribute: :delayed_project_removal,
- group: group,
- form: f,
- setting_locked: setting_locked,
- settings_path_helper: -> (locked_ancestor) { edit_group_path(locked_ancestor, anchor: 'js-permissions-settings') },
- help_text: delayed_project_removal_help_text do
- = s_('GroupSettings|Enable delayed project deletion')
- = render 'shared/namespaces/cascading_settings/enforcement_checkbox',
- attribute: :delayed_project_removal,
- group: group,
- form: f,
- setting_locked: setting_locked
+ = render 'shared/namespaces/cascading_settings/deletion_protection',
+ attribute: :delayed_project_removal,
+ group: group,
+ form: f,
+ setting_locked: setting_locked
+ = render 'shared/namespaces/cascading_settings/enforcement_checkbox',
+ attribute: :delayed_project_removal,
+ group: group,
+ form: f,
+ disabled: setting_locked