File: cascading_settings.md

package info (click to toggle)
gitlab 17.6.5-19
  • links: PTS, VCS
  • area: main
  • in suites: sid
  • size: 629,368 kB
  • sloc: ruby: 1,915,304; javascript: 557,307; sql: 60,639; xml: 6,509; sh: 4,567; makefile: 1,239; python: 406
file content (303 lines) | stat: -rw-r--r-- 21,799 bytes parent folder | download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
---
stage: Foundations
group: Personal Productivity
info: Any user with at least the Maintainer role can merge updates to this content. For details, see https://docs.gitlab.com/ee/development/development_processes.html#development-guidelines-review.
---

# Cascading Settings

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

     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_checkbox.html.haml`](https://gitlab.com/gitlab-org/gitlab/-/blob/e915f204f9eb5930760722ce28b4db60b1159677/app/views/shared/namespaces/cascading_settings/_setting_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_tooltips.html.haml`](https://gitlab.com/gitlab-org/gitlab/-/blob/b73353e47e283a7d9c9eda5bdedb345dcfb685b6/app/views/shared/namespaces/cascading_settings/_lock_tooltips.html.haml)

Renders the mount element needed to initialize the JavaScript used to display the tooltip when hovering over the lock icon. This partial is only needed once per page.

### JavaScript

[`initCascadingSettingsLockTooltips`](https://gitlab.com/gitlab-org/gitlab/-/blob/acb2ef4dbbd06f93615e8e6a1c0a78e7ebe20441/app/assets/javascripts/namespaces/cascading_settings/index.js#L4)

Initializes the JavaScript needed to display the tooltip 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_tooltips'

- 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' } }
    = render 'shared/namespaces/cascading_settings/setting_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 { initCascadingSettingsLockTooltips } from '~/namespaces/cascading_settings';

initCascadingSettingsLockTooltips();
```

### Vue

[`cascading_lock_icon.vue`](https://gitlab.com/gitlab-org/gitlab/-/blob/acb2ef4dbbd06f93615e8e6a1c0a78e7ebe20441/app/assets/javascripts/namespaces/cascading_settings/components/cascading_lock_icon.vue)

| Local                  | Description                                                                                                                                                                                                          | Type                 | Required (default value) |
|:-----------------------|:---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|:---------------------|:-------------------------|
| `ancestorNamespace`            | The namespace for associated group's ancestor.                                                                                                                                                        | `Object` | `false` (`null`)                  |
| `isLockedByApplicationSettings`            | Boolean for if the cascading variable `locked_by_application_settings` is set or not on the instance.                                                                                                                                                        | `Boolean` | `true`                   |
| `isLockedByGroupAncestor`            | Boolean for if the cascading variable `locked_by_ancestor` is set or not for a group.                                                                                                                                                        | `Boolean` | `true`                   |

### Using Vue

 1. In the your Ruby helper, you will need to call the following to send do your Vue component. Be sure to switch out `:replace_attribute_here` with your cascading attribute.

 ```ruby
 # Example call from your Ruby helper  method for groups
 cascading_settings_data = cascading_namespace_settings_tooltip_data(:replace_attribute_here, @group, method(:edit_group_path))[:tooltip_data]
 ```

 ```ruby
 # Example call from your Ruby helper  method for projects
cascading_settings_data = project_cascading_namespace_settings_tooltip_data(:duo_features_enabled, project, method(:edit_group_path)).to_json
 ```

1. From your Vue's `index.js` file, be sure to convert the data into JSON and camel case format. This will make it easier to use in Vue.

```javascript
let cascadingSettingsDataParsed;
try {
  cascadingSettingsDataParsed = convertObjectPropsToCamelCase(JSON.parse(cascadingSettingsData), {
    deep: true,
  });
} catch {
  cascadingSettingsDataParsed = null;
}
```

1. From your Vue component, either `provide/inject` or pass your `cascadingSettingsDataParsed` variable to the component. You will also want to have a helper method to not show the `cascading-lock-icon` component if the cascading data returned is either null or an empty object.

```vue
// ./ee/my_component.vue

<script>
export default {
  computed: {
    showCascadingIcon() {
      return (
        this.cascadingSettingsData &&
        Object.keys(this.cascadingSettingsData).length
      );
    },
  },
}
</script>

<template>
  <cascading-lock-icon
    v-if="showCascadingIcon"
    :is-locked-by-group-ancestor="cascadingSettingsData.lockedByAncestor"
    :is-locked-by-application-settings="cascadingSettingsData.lockedByApplicationSetting"
    :ancestor-namespace="cascadingSettingsData.ancestorNamespace"
    class="gl-ml-1"
  />
</template>
```

You can look into the following examples of MRs for implementing `cascading_lock_icon.vue` into other Vue components:

- [Add cascading settings in Groups](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/162101)
- [Add cascading settings in Projects](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/163050)

### Reasoning for supporing both HAML and Vue

It is the goal to build all new frontend features in Vue and to eventually move away from building features in HAML. However there are still HAML frontend features that utilize cascading settings, so support will remain with `initCascadingSettingsLockTooltips` until those components have been migrated into Vue.