diff options
author | GitLab Bot <gitlab-bot@gitlab.com> | 2021-05-19 18:44:42 +0300 |
---|---|---|
committer | GitLab Bot <gitlab-bot@gitlab.com> | 2021-05-19 18:44:42 +0300 |
commit | 4555e1b21c365ed8303ffb7a3325d773c9b8bf31 (patch) | |
tree | 5423a1c7516cffe36384133ade12572cf709398d /doc/development/cascading_settings.md | |
parent | e570267f2f6b326480d284e0164a6464ba4081bc (diff) |
Add latest changes from gitlab-org/gitlab@13-12-stable-eev13.12.0-rc42
Diffstat (limited to 'doc/development/cascading_settings.md')
-rw-r--r-- | doc/development/cascading_settings.md | 241 |
1 files changed, 241 insertions, 0 deletions
diff --git a/doc/development/cascading_settings.md b/doc/development/cascading_settings.md new file mode 100644 index 00000000000..631de544238 --- /dev/null +++ b/doc/development/cascading_settings.md @@ -0,0 +1,241 @@ +--- +stage: Manage +group: Access +info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/engineering/ux/technical-writing/#assignments +--- + +# Cascading Settings + +> Introduced in [GitLab 13.11](https://gitlab.com/gitlab-org/gitlab/-/issues/321724). + +The cascading settings framework allows groups to essentially inherit settings +values from ancestors (parent group on up the group hierarchy) and from +instance-level application settings. The framework also allows settings values +to be enforced on groups lower in the hierarchy. + +Cascading settings can currently only be defined within `NamespaceSetting`, though +the framework may be extended to other objects in the future. + +## Add a new cascading setting + +Settings are not cascading by default. To define a cascading setting, take the following steps: + +1. In the `NamespaceSetting` model, define the new attribute using the `cascading_attr` + helper method. You can use an array to define multiple attributes on a single line. + + ```ruby + class NamespaceSetting + include CascadingNamespaceSettingAttribute + + cascading_attr :delayed_project_removal + end + ``` + +1. Create the database columns. + + You can use the following database migration helper for a completely new setting. + The helper creates four columns, two each in `namespace_settings` and + `application_settings`. + + ```ruby + class AddDelayedProjectRemovalCascadingSetting < ActiveRecord::Migration[6.0] + include Gitlab::Database::MigrationHelpers::CascadingNamespaceSettings + + def up + add_cascading_namespace_setting :delayed_project_removal, :boolean, default: false, null: false + end + + def down + remove_cascading_namespace_setting :delayed_project_removal + end + end + ``` + + Existing settings being converted to a cascading setting will require individual + migrations to add columns and change existing columns. Use the specifications + below to create migrations as required: + + 1. Columns in `namespace_settings` table: + - `delayed_project_removal`: No default value. Null values allowed. Use any column type. + - `lock_delayed_project_removal`: Boolean column. Default value is false. Null values not allowed. + 1. Columns in `application_settings` table: + - `delayed_project_removal`: Type matching for the column created in `namespace_settings`. + Set default value as desired. Null values not allowed. + - `lock_delayed_project_removal`: Boolean column. Default value is false. Null values not allowed. + +## Convenience methods + +By defining an attribute using the `cascading_attr` method, a number of convenience +methods are automatically defined. + +**Definition:** + +```ruby +cascading_attr :delayed_project_removal +``` + +**Convenience Methods Available:** + +- `delayed_project_removal` +- `delayed_project_removal=` +- `delayed_project_removal_locked?` +- `delayed_project_removal_locked_by_ancestor?` +- `delayed_project_removal_locked_by_application_setting?` +- `delayed_project_removal?` (Boolean attributes only) +- `delayed_project_removal_locked_ancestor` (Returns locked namespace settings object [namespace_id]) + +### Attribute reader method (`delayed_project_removal`) + +The attribute reader method (`delayed_project_removal`) returns the correct +cascaded value using the following criteria: + +1. Returns the dirty value, if the attribute has changed. This allows standard + Rails validators to be used on the attribute, though `nil` values *must* be allowed. +1. Return locked ancestor value. +1. Return locked instance-level application settings value. +1. Return this namespace's attribute, if not nil. +1. Return value from nearest ancestor where value is not nil. +1. Return instance-level application setting. + +### `_locked?` method + +By default, the `_locked?` method (`delayed_project_removal_locked?`) returns +`true` if an ancestor of the group or application setting locks the attribute. +It returns `false` when called from the group that locked the attribute. + +When `include_self: true` is specified, it returns `true` when called from the group that locked the attribute. +This would be relevant, for example, when checking if an attribute is locked from a project. + +## Display cascading settings on the frontend + +There are a few Rails view helpers, HAML partials, and JavaScript functions that can be used to display a cascading setting on the frontend. + +### Rails view helpers + +[`cascading_namespace_setting_locked?`](https://gitlab.com/gitlab-org/gitlab/-/blob/c2736823b8e922e26fd35df4f0cd77019243c858/app/helpers/namespaces_helper.rb#L86) + +Calls through to the [`_locked?` method](#_locked-method) to check if the setting is locked. + +| Argument | Description | Type | Required (default value) | +|:------------|:---------------------------------------------------------------------------------|:----------------------------------------------------------------------------------|:-------------------------| +| `attribute` | Name of the setting. For example, `:delayed_project_removal`. | `String` or `Symbol` | `true` | +| `group` | Current group. | [`Group`](https://gitlab.com/gitlab-org/gitlab/-/blob/master/app/models/group.rb) | `true` | +| `**args` | Additional arguments to pass through to the [`_locked?` method](#_locked-method) | | `false` | + +### HAML partials + +[`_enforcement_checkbox.html.haml`](https://gitlab.com/gitlab-org/gitlab/-/blob/c2736823b8e922e26fd35df4f0cd77019243c858/app/views/shared/namespaces/cascading_settings/_enforcement_checkbox.html.haml) + +Renders the enforcement checkbox. + +| Local | Description | Type | Required (default value) | +|:-----------------|:-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|:-----------------------------------------------------------------------------------------------|:------------------------------------------------| +| `attribute` | Name of the setting. For example, `:delayed_project_removal`. | `String` or `Symbol` | `true` | +| `form` | [Rails FormBuilder object](https://apidock.com/rails/ActionView/Helpers/FormBuilder). | [`ActionView::Helpers::FormBuilder`](https://apidock.com/rails/ActionView/Helpers/FormBuilder) | `true` | +| `setting_locked` | If the setting is locked by an ancestor group or admin setting. Can be calculated with [`cascading_namespace_setting_locked?`](https://gitlab.com/gitlab-org/gitlab/-/blob/c2736823b8e922e26fd35df4f0cd77019243c858/app/helpers/namespaces_helper.rb#L86). | `Boolean` | `true` | +| `help_text` | Text shown below the checkbox. | `String` | `false` (Subgroups cannot change this setting.) | + +[`_setting_label_checkbox.html.haml`](https://gitlab.com/gitlab-org/gitlab/-/blob/c2736823b8e922e26fd35df4f0cd77019243c858/app/views/shared/namespaces/cascading_settings/_setting_label_checkbox.html.haml) + +Renders the label for a checkbox setting. + +| Local | Description | Type | Required (default value) | +|:-----------------------|:-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|:-----------------------------------------------------------------------------------------------|:-------------------------| +| `attribute` | Name of the setting. For example, `:delayed_project_removal`. | `String` or `Symbol` | `true` | +| `form` | [Rails FormBuilder object](https://apidock.com/rails/ActionView/Helpers/FormBuilder). | [`ActionView::Helpers::FormBuilder`](https://apidock.com/rails/ActionView/Helpers/FormBuilder) | `true` | +| `setting_locked` | If the setting is locked by an ancestor group or admin setting. Can be calculated with [`cascading_namespace_setting_locked?`](https://gitlab.com/gitlab-org/gitlab/-/blob/c2736823b8e922e26fd35df4f0cd77019243c858/app/helpers/namespaces_helper.rb#L86). | `Boolean` | `true` | +| `settings_path_helper` | Lambda function that generates a path to the ancestor setting. For example, `settings_path_helper: -> (locked_ancestor) { edit_group_path(locked_ancestor, anchor: 'js-permissions-settings') }` | `Lambda` | `true` | +| `help_text` | Text shown below the checkbox. | `String` | `false` (`nil`) | + +[`_setting_label_fieldset.html.haml`](https://gitlab.com/gitlab-org/gitlab/-/blob/c2736823b8e922e26fd35df4f0cd77019243c858/app/views/shared/namespaces/cascading_settings/_setting_label_fieldset.html.haml) + +Renders the label for a fieldset setting. + +| Local | Description | Type | Required (default value) | +|:-----------------------|:---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|:---------------------|:-------------------------| +| `attribute` | Name of the setting. For example, `:delayed_project_removal`. | `String` or `Symbol` | `true` | +| `setting_locked` | If the setting is locked. Can be calculated with [`cascading_namespace_setting_locked?`](https://gitlab.com/gitlab-org/gitlab/-/blob/c2736823b8e922e26fd35df4f0cd77019243c858/app/helpers/namespaces_helper.rb#L86). | `Boolean` | `true` | +| `settings_path_helper` | Lambda function that generates a path to the ancestor setting. For example, `-> (locked_ancestor) { edit_group_path(locked_ancestor, anchor: 'js-permissions-settings') }` | `Lambda` | `true` | +| `help_text` | Text shown below the checkbox. | `String` | `false` (`nil`) | + +[`_lock_popovers.html.haml`](https://gitlab.com/gitlab-org/gitlab/-/blob/b73353e47e283a7d9c9eda5bdedb345dcfb685b6/app/views/shared/namespaces/cascading_settings/_lock_popovers.html.haml) + +Renders the mount element needed to initialize the JavaScript used to display the popover when hovering over the lock icon. This partial is only needed once per page. + +### JavaScript + +[`initCascadingSettingsLockPopovers`](https://gitlab.com/gitlab-org/gitlab/-/blob/b73353e47e283a7d9c9eda5bdedb345dcfb685b6/app/assets/javascripts/namespaces/cascading_settings/index.js#L4) + +Initializes the JavaScript needed to display the popover when hovering over the lock icon (**{lock}**). +This function should be imported and called in the [page-specific JavaScript](fe_guide/performance.md#page-specific-javascript). + +### Put it all together + +```haml +-# app/views/groups/edit.html.haml + += render 'shared/namespaces/cascading_settings/lock_popovers' + +- delayed_project_removal_locked = cascading_namespace_setting_locked?(:delayed_project_removal, @group) +- merge_method_locked = cascading_namespace_setting_locked?(:merge_method, @group) + += form_for @group do |f| + .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: delayed_project_removal_locked, class: 'custom-control-input' + = render 'shared/namespaces/cascading_settings/setting_label_checkbox', attribute: :delayed_project_removal, + group: @group, + form: f, + setting_locked: delayed_project_removal_locked, + settings_path_helper: -> (locked_ancestor) { edit_group_path(locked_ancestor, anchor: 'js-permissions-settings') }, + help_text: s_('Settings|Projects will be permanently deleted after a 7-day delay. Inherited by subgroups.') do + = s_('Settings|Enable delayed project removal') + = render 'shared/namespaces/cascading_settings/enforcement_checkbox', + attribute: :delayed_project_removal, + group: @group, + form: f, + setting_locked: delayed_project_removal_locked + + %fieldset.form-group + = render 'shared/namespaces/cascading_settings/setting_label_fieldset', attribute: :merge_method, + group: @group, + setting_locked: merge_method_locked, + settings_path_helper: -> (locked_ancestor) { edit_group_path(locked_ancestor, anchor: 'js-permissions-settings') }, + help_text: s_('Settings|Determine what happens to the commit history when you merge a merge request.') do + = s_('Settings|Merge method') + + .gl-form-radio.custom-control.custom-radio + = f.radio_button :merge_method, :merge, class: "custom-control-input", disabled: merge_method_locked + = f.label :merge_method_merge, class: 'custom-control-label' do + = s_('Settings|Merge commit') + %p.help-text + = s_('Settings|Every merge creates a merge commit.') + + .gl-form-radio.custom-control.custom-radio + = f.radio_button :merge_method, :rebase_merge, class: "custom-control-input", disabled: merge_method_locked + = f.label :merge_method_rebase_merge, class: 'custom-control-label' do + = s_('Settings|Merge commit with semi-linear history') + %p.help-text + = s_('Settings|Every merge creates a merge commit.') + + .gl-form-radio.custom-control.custom-radio + = f.radio_button :merge_method, :ff, class: "custom-control-input", disabled: merge_method_locked + = f.label :merge_method_ff, class: 'custom-control-label' do + = s_('Settings|Fast-forward merge') + %p.help-text + = s_('Settings|No merge commits are created.') + + = render 'shared/namespaces/cascading_settings/enforcement_checkbox', + attribute: :merge_method, + group: @group, + form: f, + setting_locked: merge_method_locked +``` + +```javascript +// app/assets/javascripts/pages/groups/edit/index.js + +import { initCascadingSettingsLockPopovers } from '~/namespaces/cascading_settings'; + +initCascadingSettingsLockPopovers(); +``` |