--- stage: Manage group: Authentication and Authorization info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/product/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 < Gitlab::Database::Migration[2.1] include Gitlab::Database::MigrationHelpers::CascadingNamespaceSettings enable_lock_retries! 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` | | `group` | Current group. | [`Group`](https://gitlab.com/gitlab-org/gitlab/-/blob/master/app/models/group.rb) | `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 administrator 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` | | `group` | Current group. | [`Group`](https://gitlab.com/gitlab-org/gitlab/-/blob/master/app/models/group.rb) | `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 administrator 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` | | `group` | Current group. | [`Group`](https://gitlab.com/gitlab-org/gitlab/-/blob/master/app/models/group.rb) | `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 deletion') = 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.gitlab_ui_radio_component :merge_method, :merge, s_('Settings|Merge commit'), help_text: s_('Settings|Every merge creates a merge commit.'), radio_options: { disabled: merge_method_locked } .gl-form-radio.custom-control.custom-radio = f.gitlab_ui_radio_component :merge_method, :rebase_merge, s_('Settings|Merge commit with semi-linear history'), help_text: s_('Settings|Every merge creates a merge commit.'), radio_options: { disabled: merge_method_locked } .gl-form-radio.custom-control.custom-radio = f.gitlab_ui_radio_component :merge_method, :ff, s_('Settings|Fast-forward merge'), help_text: s_('Settings|No merge commits are created.'), radio_options: { disabled: merge_method_locked } = 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(); ```