级联设置
您是否曾在 GitLab 项目和/或组中添加过设置,该设置具有从父级继承的默认值?
如果是这样:我们提供了您需要的框架!
级联设置框架允许组和项目从祖先(组层级中的父组及以上)和实例级应用设置中继承设置值。该框架还允许在层级较低的组上“锁定”(强制执行)设置值。
历史上,级联设置仅在 ApplicationSetting、NamespaceSetting 和 ProjectSetting 上定义,尽管该框架未来可能会扩展到其他对象。
仅向组添加新的级联设置
设置默认不会级联。要定义级联设置,请执行以下步骤:
-
在
NamespaceSetting模型中,使用cascading_attr辅助方法定义新属性。您可以使用数组在一行上定义多个属性。class NamespaceSetting include CascadingNamespaceSettingAttribute cascading_attr :delayed_project_removal end -
创建数据库列。
对于全新的设置,您可以使用以下数据库迁移辅助工具。该工具会创建四列,分别在
namespace_settings和application_settings中各两列。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将现有设置转换为级联设置需要单独的迁移来添加列和更改现有列。根据以下规范创建所需的迁移:
namespace_settings表中的列:delayed_project_removal:无默认值。允许空值。使用任意列类型。lock_delayed_project_removal:布尔列。默认值为 false。不允许空值。
application_settings表中的列:delayed_project_removal:类型与在namespace_settings中创建的列匹配。根据需要设置默认值。不允许空值。lock_delayed_project_removal:布尔列。默认值为 false。不允许空值。
便捷方法
通过使用 cascading_attr 方法定义属性,会自动生成多个便捷方法。
定义:
cascading_attr :delayed_project_removal可用的便捷方法:
delayed_project_removaldelayed_project_removal=delayed_project_removal_locked?delayed_project_removal_locked_by_ancestor?delayed_project_removal_locked_by_application_setting?delayed_project_removal?(仅限布尔属性)delayed_project_removal_locked_ancestor(返回被锁定的命名空间设置对象[namespace_id])
属性读取方法 (delayed_project_removal)
属性读取方法 (delayed_project_removal) 使用以下标准返回正确的级联值:
- 如果属性已更改,则返回脏值。这允许在属性上使用标准的 Rails 验证器,但必须允许
nil值。 - 返回被锁定的祖先值。
- 返回被锁定的实例级应用设置值。
- 返回此命名空间的属性(如果非空)。
- 返回值不为空的最近祖先的值。
- 返回实例级应用设置。
_locked? 方法
默认情况下,_locked? 方法 (delayed_project_removal_locked?) 如果组的祖先或应用设置锁定该属性,则返回 true。当从锁定该属性的组调用时,它返回 false。
当指定 include_self: true 时,从锁定该属性的组调用时返回 true。例如,当从项目检查属性是否被锁定时,这会相关。
向项目添加新的级联设置
背景
级联设置框架的第一个迭代仅用于实例和组级设置。
后来,也需要将此设置添加到项目中。GitLab 中的项目也有命名空间,因此您可能会认为通过使用为组级设置添加的 namespace_settings 表中的同一列来扩展现有框架会很容易。但事实证明,将级联项目设置添加到 project_settings 表更有意义。
为什么您可能会问?嗯,事实证明:
- GitLab 中的每个用户、项目和组都属于一个命名空间
- 命名空间
has_onenamespace_settings 记录 - 创建组或用户时,其命名空间和命名空间设置通过服务对象创建(代码)。
- 创建项目时,会创建命名空间,但不会创建命名空间设置。
此外,我们在 GitLab UI 的任何地方都不暴露项目级命名空间设置。相反,我们使用项目设置。有一天,我们希望能够将命名空间设置用于项目设置。但今天,将项目级设置添加到 project_settings 表更容易。
实现
向项目添加级联设置的示例在 MR 149931 中。
写入时的级联设置值
在新推荐的方式中,唯一在数据库级别实际级联值的级联设置是 duo_features_enabled。该设置从组级联到项目。问题 505335 描述了从应用级到组级添加此级联。
传统的级联设置写入
在级联设置框架的第一个迭代中,“级联”发生在应用代码级别,而不是数据库级别。其工作方式是 application_settings 表中的设置值具有默认值。在 namespace_settings 级别则没有。因此,命名空间在数据库级别具有 nil 值,但“继承” application_settings 值。
如果组更新为具有新的设置值,则该值优先于 application_settings 级别的默认值。任何子组将继承父组的设置值,因为它们在数据库级别也具有 nil 值,但从 namespace_settings 表继承父值。但是,如果其中一个子组更新了该设置,则会覆盖父组。
这引入了一些可能令人困惑的逻辑。
如果 application_settings 级别的设置值更改:
- 任何设置为
nil的顶级组将继承新值。 - 任何设置为非
nil值的顶级组将不会继承新值。
如果 namespace_settings 级别的设置值更改:
- 任何设置为
nil的子组或项目将从父组继承新值。 - 任何设置为非
nil值的子组或项目不会从父组继承新值。
由于数据库级别的值在 UI 或通过 API 中都不可见(因为两者都显示继承的值),实例或组管理员可能不理解哪些组/项目继承了该值。
不一致级联行为的例外是如果设置被 locked。这总是“强制”继承。
除了令人困惑的逻辑之外,这还会在读取值时造成性能问题:如果查询深度嵌套层级的设置值,可能需要读取整个层级的设置值才能知道该设置值。
未来级联设置写入的建议
为了提供更清晰的逻辑链并提高性能,您应该为新添加的级联设置添加默认值,并在设置值更新时对层级中的所有子对象执行写入。这需要启动一个作业,以便更新异步发生。如何执行此操作的示例在 MR 145876 中。
先前添加的级联设置仍然具有默认的 nil 值,并读取祖先层级以查找继承的设置值。但为了最小化混淆,我们应该更新它们以在写入时级联。问题 483143 描述了此维护任务。
在前端显示级联设置
有几个 Rails 视图辅助工具、HAML 部分和 JavaScript 函数可用于在前端显示级联设置。
Rails 视图辅助工具
cascading_namespace_setting_locked?
调用 _locked? 方法 检查设置是否被锁定。
| 参数 | 说明 | 类型 | 必需(默认值) |
|---|---|---|---|
attribute |
设置的名称。例如 :delayed_project_removal。 |
String 或 Symbol |
true |
group |
当前组。 | Group |
true |
**args |
传递给 _locked? 方法 的额外参数。 |
false |
HAML 部分
_enforcement_checkbox.html.haml
渲染强制复选框。
| 本地变量 | 说明 | 类型 | 必需(默认值) |
|---|---|---|---|
attribute |
设置的名称。例如 :delayed_project_removal。 |
String 或 Symbol |
true |
group |
当前组。 | Group |
true |
form |
Rails FormBuilder 对象。 | ActionView::Helpers::FormBuilder |
true |
setting_locked |
如果设置被祖先组或管理员设置锁定。可以使用 cascading_namespace_setting_locked? 计算。 |
Boolean |
true |
help_text |
复选框下方显示的文本。 | String |
false(子组无法更改此设置。) |
渲染复选框设置的标签。
| 本地变量 | 说明 | 类型 | 必需(默认值) |
|---|---|---|---|
attribute |
设置的名称。例如 :delayed_project_removal。 |
String 或 Symbol |
true |
group |
当前组。 | Group |
true |
form |
Rails FormBuilder 对象。 | ActionView::Helpers::FormBuilder |
true |
setting_locked |
如果设置被祖先组或管理员设置锁定。可以使用 cascading_namespace_setting_locked? 计算。 |
Boolean |
true |
settings_path_helper |
生成到祖先设置路径的 Lambda 函数。例如 settings_path_helper: -> (locked_ancestor) { edit_group_path(locked_ancestor, anchor: 'js-permissions-settings') } |
Lambda |
true |
help_text |
复选框下方显示的文本。 | String |
false (nil) |
_setting_label_fieldset.html.haml
渲染 fieldset 设置的标签。
| 本地变量 | 说明 | 类型 | 必需(默认值) |
|---|---|---|---|
attribute |
设置的名称。例如 :delayed_project_removal。 |
String 或 Symbol |
true |
group |
当前组。 | Group |
true |
setting_locked |
如果设置被锁定。可以使用 cascading_namespace_setting_locked? 计算。 |
Boolean |
true |
settings_path_helper |
生成到祖先设置路径的 Lambda 函数。例如 -> (locked_ancestor) { edit_group_path(locked_ancestor, anchor: 'js-permissions-settings') } |
Lambda |
true |
help_text |
复选框下方显示的文本。 | String |
false (nil) |
渲染初始化 JavaScript 所需的挂载元素,用于在悬停锁图标时显示工具提示。此部分每个页面只需一次。
JavaScript
initCascadingSettingsLockTooltips
初始化在悬停锁图标( )时显示工具提示所需的 JavaScript。此函数应在页面特定 JavaScript 中导入并调用。
整合使用
-# 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// app/assets/javascripts/pages/groups/edit/index.js
import { initCascadingSettingsLockTooltips } from '~/namespaces/cascading_settings';
initCascadingSettingsLockTooltips();Vue
| 本地变量 | 说明 | 类型 | 必需(默认值) |
|---|---|---|---|
ancestorNamespace |
关联组的祖先命名空间。 | Object |
false (null) |
isLockedByApplicationSettings |
实例上是否设置了级联变量 locked_by_application_settings 的布尔值。 |
Boolean |
true |
isLockedByGroupAncestor |
组的级联变量 locked_by_ancestor 是否设置的布尔值。 |
Boolean |
true |
使用 Vue
- 在您的 Ruby 辅助工具中,您需要调用以下内容来发送到您的 Vue 组件。请务必将
:replace_attribute_here替换为您的级联属性。
# 从您的 Ruby 辅助工具方法(针对组)调用的示例
cascading_settings_data = cascading_namespace_settings_tooltip_data(:replace_attribute_here, @group, method(:edit_group_path))[:tooltip_data]# 从您的 Ruby 辅助工具方法(针对项目)调用的示例
cascading_settings_data = project_cascading_namespace_settings_tooltip_data(:duo_features_enabled, project, method(:edit_group_path)).to_json- 从您的 Vue 的
index.js文件中,请务必将数据转换为 JSON 和驼峰格式。这将使其在 Vue 中更易于使用。
let cascadingSettingsDataParsed;
try {
cascadingSettingsDataParsed = convertObjectPropsToCamelCase(JSON.parse(cascadingSettingsData), {
deep: true,
});
} catch {
cascadingSettingsDataParsed = null;
}- 从您的 Vue 组件中,通过
provide/inject或将cascadingSettingsDataParsed变量传递给组件。您还需要一个辅助方法,如果级联数据返回null或空对象,则不显示cascading-lock-icon组件。
// ./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>您可以查看以下 MR 示例,了解如何将 cascading_lock_icon.vue 实现到其他 Vue 组件中:
同时支持 HAML 和 Vue 的原因
目标是用 Vue 构建所有新的前端功能,并最终停止在 HAML 中构建功能。但是,仍然有使用级联设置的 HAML 前端功能,因此支持将保留 initCascadingSettingsLockTooltips,直到这些组件迁移到 Vue 为止。