InfoGrab DocsInfoGrab Docs

계단식 설정(Cascading Settings)

요약

GitLab 프로젝트 및/또는 그룹에 계층 구조의 상위에서 기본값을 상속받는 설정을 추가하고 싶으신가요? 그렇다면 여러분이 찾던 프레임워크가 바로 여기 있습니다! 계단식 설정 프레임워크는 그룹과 프로젝트가 상위 항목(상위 그룹부터 그룹 계층 전체)과 인스턴스 수준 애플리케이션 설정으로부터 설정 값을 상속받을 수 있도록 합니다.

GitLab 프로젝트 및/또는 그룹에 계층 구조의 상위에서 기본값을 상속받는 설정을 추가하고 싶으신가요?

그렇다면 여러분이 찾던 프레임워크가 바로 여기 있습니다!

계단식 설정 프레임워크는 그룹과 프로젝트가 상위 항목(상위 그룹부터 그룹 계층 전체)과 인스턴스 수준 애플리케이션 설정으로부터 설정 값을 상속받을 수 있도록 합니다. 이 프레임워크는 또한 계층 구조의 하위 그룹에 설정 값을 "잠그기(locked)" (강제 적용)할 수 있도록 합니다.

계단식 설정은 역사적으로 ApplicationSetting, NamespaceSetting, ProjectSetting에서만 정의되었지만, 향후 다른 객체로 프레임워크가 확장될 수 있습니다.

그룹 전용 새 계단식 설정 추가#

설정은 기본적으로 계단식이 아닙니다. 계단식 설정을 정의하려면 다음 단계를 따르세요:

NamespaceSetting 모델에서 cascading_attr 헬퍼 메서드를 사용하여 새 속성을 정의합니다. 배열을 사용하면 한 줄에 여러 속성을 정의할 수 있습니다.

class NamespaceSetting
  include CascadingNamespaceSettingAttribute

  cascading_attr :delayed_project_removal
end

데이터베이스 컬럼을 생성합니다.

완전히 새로운 설정의 경우 다음 데이터베이스 마이그레이션 헬퍼를 사용할 수 있습니다. 헬퍼는 namespace_settingsapplication_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: 기본값 없음. Null 값 허용. 임의의 컬럼 유형 사용 가능.

  • lock_delayed_project_removal: Boolean 컬럼. 기본값은 false. Null 값 허용 안 됨.

  • application_settings 테이블의 컬럼:

delayed_project_removal: namespace_settings에 생성된 컬럼의 타입과 일치. 원하는 기본값 설정. Null 값 허용 안 됨.

  • lock_delayed_project_removal: Boolean 컬럼. 기본값은 false. Null 값 허용 안 됨.

편의 메서드#

cascading_attr 메서드를 사용하여 속성을 정의하면 다수의 편의 메서드가 자동으로 정의됩니다.

정의:

cascading_attr :delayed_project_removal

사용 가능한 편의 메서드:

  • 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 속성 전용)

  • delayed_project_removal_locked_ancestor (잠긴 네임스페이스 설정 객체 [namespace_id]를 반환)

속성 읽기 메서드 (delayed_project_removal)#

속성 읽기 메서드(delayed_project_removal)는 다음 기준을 사용하여 올바른 계단식 값을 반환합니다:

  • 속성이 변경된 경우 더티(dirty) 값을 반환합니다. 이를 통해 속성에 표준 Rails 유효성 검사기를 사용할 수 있지만, nil 값은 허용해야 합니다.

  • 잠긴 상위 항목 값을 반환합니다.

  • 잠긴 인스턴스 수준 애플리케이션 설정 값을 반환합니다.

  • nil이 아닌 경우 이 네임스페이스의 속성을 반환합니다.

  • 값이 nil이 아닌 가장 가까운 상위 항목의 값을 반환합니다.

  • 인스턴스 수준 애플리케이션 설정을 반환합니다.

_locked? 메서드#

기본적으로 _locked? 메서드(delayed_project_removal_locked?)는 그룹의 상위 항목 또는 애플리케이션 설정이 해당 속성을 잠근 경우 true를 반환합니다. 속성을 잠근 그룹에서 호출되면 false를 반환합니다.

include_self: true가 지정되면 속성을 잠근 그룹에서 호출될 때 true를 반환합니다. 예를 들어, 프로젝트에서 속성이 잠겼는지 확인할 때 유용합니다.

프로젝트에 새 계단식 설정 추가#

배경#

계단식 설정 프레임워크의 첫 번째 반복은 인스턴스 및 그룹 수준 설정 전용이었습니다.

이후에 이 설정을 프로젝트에도 추가해야 할 필요성이 생겼습니다. GitLab의 프로젝트도 네임스페이스를 가지므로, 그룹 수준 설정을 위해 추가된 namespace_settings 테이블의 동일한 컬럼을 사용하여 기존 프레임워크를 프로젝트로 쉽게 확장할 수 있다고 생각할 수 있습니다. 하지만 계단식 프로젝트 설정은 project_settings 테이블에 추가하는 것이 더 합리적이었습니다.

왜냐고요? 그 이유는 다음과 같습니다:

  • GitLab의 모든 사용자, 프로젝트, 그룹은 네임스페이스에 속합니다.

  • 네임스페이스는 has_one namespace_settings 레코드를 가집니다.

  • 그룹 또는 사용자가 생성될 때, 해당 네임스페이스와 네임스페이스 설정이 서비스 객체를 통해 생성됩니다(코드).

  • 프로젝트가 생성될 때, 네임스페이스는 생성되지만 네임스페이스 설정은 생성되지 않습니다.

또한, GitLab UI에서 프로젝트 수준의 네임스페이스 설정을 노출하는 곳이 없습니다. 대신 프로젝트 설정을 사용합니다. 언젠가는 프로젝트 설정에 네임스페이스 설정을 사용할 수 있기를 바라지만, 현재로서는 project_settings 테이블에 프로젝트 수준 설정을 추가하는 것이 더 쉽습니다.

구현#

프로젝트에 계단식 설정을 추가하는 예시는 MR 149931에서 확인할 수 있습니다.

쓰기 시 계단식 설정 값#

데이터베이스 수준의 값 전파를 현재 권장하는 방식으로 구현한 계단식 설정은 duo_features_enabledduo_remote_flows_enabled입니다. 이 설정들은 계층적 계단식 패턴을 따릅니다:

  • 그룹에서 프로젝트로 계단식 적용: Namespaces::CascadeDuoSettingsWorker를 통해 구현됨

  • 애플리케이션에서 그룹 및 프로젝트로 계단식 적용: AppConfig::CascadeDuoSettingsWorker를 통해 구현됨

이 아키텍처는 비동기 처리를 통해 최적의 성능을 유지하면서 조직 계층 전체에 걸쳐 일관된 설정 상속을 보장합니다.

레거시 계단식 설정 쓰기#

계단식 설정 프레임워크의 첫 번째 반복에서 "계단식(cascade)"은 데이터베이스 수준이 아닌 애플리케이션 코드 수준에서 이루어졌습니다. application_settings 테이블의 설정 값에는 기본값이 있지만, namespace_settings 수준에서는 기본값이 없습니다. 그 결과, 네임스페이스는 데이터베이스 수준에서 nil 값을 가지지만 application_settings 값을 "상속"합니다.

그룹이 새 설정 값으로 업데이트되면, 해당 값은 application_settings 수준의 기본값보다 우선합니다. 그리고 하위 그룹은 데이터베이스 수준에서 nil 값을 가지지만 namespace_settings 테이블에서 상위 그룹의 설정 값을 상속하기 때문에 상위 그룹의 설정 값을 상속받게 됩니다. 그러나 하위 그룹 중 하나가 설정을 업데이트하면 상위 그룹의 값을 재정의합니다.

이는 잠재적으로 혼란스러운 로직을 초래합니다.

application_settings 수준에서 설정 값이 변경될 경우:

  • 설정 값이 nil인 루트 수준 그룹은 새 값을 상속받습니다.

  • 설정 값이 nil이 아닌 값으로 설정된 루트 수준 그룹은 새 값을 상속받지 않습니다.

namespace_settings 수준에서 설정 값이 변경될 경우:

  • 설정 값이 nil인 하위 그룹 또는 프로젝트는 상위 그룹에서 새 값을 상속받습니다.

  • 설정 값이 nil이 아닌 값으로 설정된 하위 그룹 또는 프로젝트는 상위 그룹에서 새 값을 상속받지 않습니다.

UI나 API를 통해 데이터베이스 수준의 값을 볼 수 없기 때문에(둘 다 상속된 값을 표시하므로), 인스턴스 또는 그룹 관리자는 어떤 그룹/프로젝트가 값을 상속받는지 알지 못할 수 있습니다.

일관성 없는 계단식 동작의 예외는 설정이 잠금(locked) 상태인 경우입니다. 잠금은 항상 상속을 "강제"합니다.

혼란스러운 로직 외에도, 값을 읽을 때마다 성능 문제가 발생합니다: 깊이 중첩된 계층 구조에 대해 설정 값을 조회하는 경우, 설정 값을 알기 위해 전체 계층 구조의 설정 값을 읽어야 할 수 있습니다.

앞으로의 계단식 설정 쓰기 권고 사항#

더 명확한 로직 체인을 제공하고 성능을 향상시키기 위해, 새로 추가된 계단식 설정에 기본값을 추가하고 설정 값이 업데이트될 때 계층 구조의 모든 하위 객체에 쓰기를 수행해야 합니다. 이를 위해 업데이트가 비동기적으로 이루어지도록 job을 실행해야 합니다. 현재 시스템은 이 목적을 위해 두 개의 전용 Sidekiq 워커를 사용합니다:

  • AppConfig::CascadeDuoSettingsWorker - 애플리케이션 수준 설정 전파를 처리합니다.

  • Namespaces::CascadeDuoSettingsWorker - 하위 그룹 및 프로젝트에 대한 네임스페이스 수준 업데이트를 관리합니다.

이 비동기 방식은 시스템 성능에 영향을 주지 않으면서 설정 변경 사항이 조직 계층 전체에 효율적으로 배포되도록 합니다.

새 계단식 AI 설정 추가#

데이터베이스 쓰기를 통해 값을 전파하는 새 계단식 AI 설정을 구현하려면, 설정 범위에 따라 아래에 설명된 절차를 따르세요:

네임스페이스 설정 구성#

네임스페이스 수준 계단식 설정을 구현할 때:

  • ee/app/services/ee/groups/update_service.rb에 위치한 update_cascading_settings 메서드에 설정을 통합합니다.

  • ee/app/services/ai/cascade_duo_settings_service.rb의 허용된 설정 구성에 설정을 등록합니다.

애플리케이션 설정 구성#

애플리케이션 수준 계단식 설정을 구현할 때:

  • ee/app/services/ee/application_settings/update_service.rb에 있는 cascade_duo_features_settings 메서드에 설정을 통합합니다.

  • ee/app/services/ai/cascade_duo_settings_service.rb의 허용된 설정 구성에 설정을 등록합니다.

이러한 구성은 시스템 계층 전체에서 적절한 유효성 검사와 계단식 동작을 보장합니다.

이전에 추가된 계단식 설정은 여전히 기본값 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 (하위 그룹은 이 설정을 변경할 수 없습니다.)

_setting_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
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)

_lock_tooltips.html.haml

잠금 아이콘 위로 마우스를 올렸을 때 툴팁을 표시하는 데 사용되는 JavaScript를 초기화하는 데 필요한 마운트 요소를 렌더링합니다. 이 파셜은 페이지당 한 번만 필요합니다.

JavaScript#

initCascadingSettingsLockTooltips

잠금 아이콘( lock ) 위로 마우스를 올렸을 때 툴팁을 표시하는 데 필요한 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#

cascading_lock_icon.vue

로컬 변수 설명 타입 필수 여부 (기본값)
ancestorNamespace 연결된 그룹의 상위 항목에 대한 네임스페이스. Object false (null)
isLockedByApplicationSettings 인스턴스에서 계단식 변수 locked_by_application_settings가 설정되었는지 여부를 나타내는 Boolean. Boolean true
isLockedByGroupAncestor 그룹에 대해 계단식 변수 locked_by_ancestor가 설정되었는지 여부를 나타내는 Boolean. Boolean true

Vue 사용 방법#

Ruby 헬퍼에서 Vue 컴포넌트에 데이터를 전달하기 위해 다음을 호출해야 합니다. :replace_attribute_here를 계단식 속성으로 교체하세요.

# 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]
# 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

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>

cascading_lock_icon.vue를 다른 Vue 컴포넌트에 구현하는 다음 MR 예시를 참고할 수 있습니다:

HAML과 Vue 모두 지원하는 이유#

모든 새로운 프론트엔드 기능을 Vue로 개발하고 궁극적으로 HAML로 기능을 개발하는 것에서 벗어나는 것이 목표입니다. 그러나 계단식 설정을 사용하는 HAML 프론트엔드 기능이 여전히 있으므로, 해당 컴포넌트들이 Vue로 마이그레이션될 때까지 initCascadingSettingsLockTooltips에 대한 지원이 유지됩니다.

계단식 설정(Cascading Settings)

GitLab v19.1
원문 보기
요약

GitLab 프로젝트 및/또는 그룹에 계층 구조의 상위에서 기본값을 상속받는 설정을 추가하고 싶으신가요? 그렇다면 여러분이 찾던 프레임워크가 바로 여기 있습니다! 계단식 설정 프레임워크는 그룹과 프로젝트가 상위 항목(상위 그룹부터 그룹 계층 전체)과 인스턴스 수준 애플리케이션 설정으로부터 설정 값을 상속받을 수 있도록 합니다.

GitLab 프로젝트 및/또는 그룹에 계층 구조의 상위에서 기본값을 상속받는 설정을 추가하고 싶으신가요?

그렇다면 여러분이 찾던 프레임워크가 바로 여기 있습니다!

계단식 설정 프레임워크는 그룹과 프로젝트가 상위 항목(상위 그룹부터 그룹 계층 전체)과 인스턴스 수준 애플리케이션 설정으로부터 설정 값을 상속받을 수 있도록 합니다. 이 프레임워크는 또한 계층 구조의 하위 그룹에 설정 값을 "잠그기(locked)" (강제 적용)할 수 있도록 합니다.

계단식 설정은 역사적으로 ApplicationSetting, NamespaceSetting, ProjectSetting에서만 정의되었지만, 향후 다른 객체로 프레임워크가 확장될 수 있습니다.

그룹 전용 새 계단식 설정 추가#

설정은 기본적으로 계단식이 아닙니다. 계단식 설정을 정의하려면 다음 단계를 따르세요:

NamespaceSetting 모델에서 cascading_attr 헬퍼 메서드를 사용하여 새 속성을 정의합니다. 배열을 사용하면 한 줄에 여러 속성을 정의할 수 있습니다.

class NamespaceSetting
  include CascadingNamespaceSettingAttribute

  cascading_attr :delayed_project_removal
end

데이터베이스 컬럼을 생성합니다.

완전히 새로운 설정의 경우 다음 데이터베이스 마이그레이션 헬퍼를 사용할 수 있습니다. 헬퍼는 namespace_settingsapplication_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: 기본값 없음. Null 값 허용. 임의의 컬럼 유형 사용 가능.

  • lock_delayed_project_removal: Boolean 컬럼. 기본값은 false. Null 값 허용 안 됨.

  • application_settings 테이블의 컬럼:

delayed_project_removal: namespace_settings에 생성된 컬럼의 타입과 일치. 원하는 기본값 설정. Null 값 허용 안 됨.

  • lock_delayed_project_removal: Boolean 컬럼. 기본값은 false. Null 값 허용 안 됨.

편의 메서드#

cascading_attr 메서드를 사용하여 속성을 정의하면 다수의 편의 메서드가 자동으로 정의됩니다.

정의:

cascading_attr :delayed_project_removal

사용 가능한 편의 메서드:

  • 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 속성 전용)

  • delayed_project_removal_locked_ancestor (잠긴 네임스페이스 설정 객체 [namespace_id]를 반환)

속성 읽기 메서드 (delayed_project_removal)#

속성 읽기 메서드(delayed_project_removal)는 다음 기준을 사용하여 올바른 계단식 값을 반환합니다:

  • 속성이 변경된 경우 더티(dirty) 값을 반환합니다. 이를 통해 속성에 표준 Rails 유효성 검사기를 사용할 수 있지만, nil 값은 허용해야 합니다.

  • 잠긴 상위 항목 값을 반환합니다.

  • 잠긴 인스턴스 수준 애플리케이션 설정 값을 반환합니다.

  • nil이 아닌 경우 이 네임스페이스의 속성을 반환합니다.

  • 값이 nil이 아닌 가장 가까운 상위 항목의 값을 반환합니다.

  • 인스턴스 수준 애플리케이션 설정을 반환합니다.

_locked? 메서드#

기본적으로 _locked? 메서드(delayed_project_removal_locked?)는 그룹의 상위 항목 또는 애플리케이션 설정이 해당 속성을 잠근 경우 true를 반환합니다. 속성을 잠근 그룹에서 호출되면 false를 반환합니다.

include_self: true가 지정되면 속성을 잠근 그룹에서 호출될 때 true를 반환합니다. 예를 들어, 프로젝트에서 속성이 잠겼는지 확인할 때 유용합니다.

프로젝트에 새 계단식 설정 추가#

배경#

계단식 설정 프레임워크의 첫 번째 반복은 인스턴스 및 그룹 수준 설정 전용이었습니다.

이후에 이 설정을 프로젝트에도 추가해야 할 필요성이 생겼습니다. GitLab의 프로젝트도 네임스페이스를 가지므로, 그룹 수준 설정을 위해 추가된 namespace_settings 테이블의 동일한 컬럼을 사용하여 기존 프레임워크를 프로젝트로 쉽게 확장할 수 있다고 생각할 수 있습니다. 하지만 계단식 프로젝트 설정은 project_settings 테이블에 추가하는 것이 더 합리적이었습니다.

왜냐고요? 그 이유는 다음과 같습니다:

  • GitLab의 모든 사용자, 프로젝트, 그룹은 네임스페이스에 속합니다.

  • 네임스페이스는 has_one namespace_settings 레코드를 가집니다.

  • 그룹 또는 사용자가 생성될 때, 해당 네임스페이스와 네임스페이스 설정이 서비스 객체를 통해 생성됩니다(코드).

  • 프로젝트가 생성될 때, 네임스페이스는 생성되지만 네임스페이스 설정은 생성되지 않습니다.

또한, GitLab UI에서 프로젝트 수준의 네임스페이스 설정을 노출하는 곳이 없습니다. 대신 프로젝트 설정을 사용합니다. 언젠가는 프로젝트 설정에 네임스페이스 설정을 사용할 수 있기를 바라지만, 현재로서는 project_settings 테이블에 프로젝트 수준 설정을 추가하는 것이 더 쉽습니다.

구현#

프로젝트에 계단식 설정을 추가하는 예시는 MR 149931에서 확인할 수 있습니다.

쓰기 시 계단식 설정 값#

데이터베이스 수준의 값 전파를 현재 권장하는 방식으로 구현한 계단식 설정은 duo_features_enabledduo_remote_flows_enabled입니다. 이 설정들은 계층적 계단식 패턴을 따릅니다:

  • 그룹에서 프로젝트로 계단식 적용: Namespaces::CascadeDuoSettingsWorker를 통해 구현됨

  • 애플리케이션에서 그룹 및 프로젝트로 계단식 적용: AppConfig::CascadeDuoSettingsWorker를 통해 구현됨

이 아키텍처는 비동기 처리를 통해 최적의 성능을 유지하면서 조직 계층 전체에 걸쳐 일관된 설정 상속을 보장합니다.

레거시 계단식 설정 쓰기#

계단식 설정 프레임워크의 첫 번째 반복에서 "계단식(cascade)"은 데이터베이스 수준이 아닌 애플리케이션 코드 수준에서 이루어졌습니다. application_settings 테이블의 설정 값에는 기본값이 있지만, namespace_settings 수준에서는 기본값이 없습니다. 그 결과, 네임스페이스는 데이터베이스 수준에서 nil 값을 가지지만 application_settings 값을 "상속"합니다.

그룹이 새 설정 값으로 업데이트되면, 해당 값은 application_settings 수준의 기본값보다 우선합니다. 그리고 하위 그룹은 데이터베이스 수준에서 nil 값을 가지지만 namespace_settings 테이블에서 상위 그룹의 설정 값을 상속하기 때문에 상위 그룹의 설정 값을 상속받게 됩니다. 그러나 하위 그룹 중 하나가 설정을 업데이트하면 상위 그룹의 값을 재정의합니다.

이는 잠재적으로 혼란스러운 로직을 초래합니다.

application_settings 수준에서 설정 값이 변경될 경우:

  • 설정 값이 nil인 루트 수준 그룹은 새 값을 상속받습니다.

  • 설정 값이 nil이 아닌 값으로 설정된 루트 수준 그룹은 새 값을 상속받지 않습니다.

namespace_settings 수준에서 설정 값이 변경될 경우:

  • 설정 값이 nil인 하위 그룹 또는 프로젝트는 상위 그룹에서 새 값을 상속받습니다.

  • 설정 값이 nil이 아닌 값으로 설정된 하위 그룹 또는 프로젝트는 상위 그룹에서 새 값을 상속받지 않습니다.

UI나 API를 통해 데이터베이스 수준의 값을 볼 수 없기 때문에(둘 다 상속된 값을 표시하므로), 인스턴스 또는 그룹 관리자는 어떤 그룹/프로젝트가 값을 상속받는지 알지 못할 수 있습니다.

일관성 없는 계단식 동작의 예외는 설정이 잠금(locked) 상태인 경우입니다. 잠금은 항상 상속을 "강제"합니다.

혼란스러운 로직 외에도, 값을 읽을 때마다 성능 문제가 발생합니다: 깊이 중첩된 계층 구조에 대해 설정 값을 조회하는 경우, 설정 값을 알기 위해 전체 계층 구조의 설정 값을 읽어야 할 수 있습니다.

앞으로의 계단식 설정 쓰기 권고 사항#

더 명확한 로직 체인을 제공하고 성능을 향상시키기 위해, 새로 추가된 계단식 설정에 기본값을 추가하고 설정 값이 업데이트될 때 계층 구조의 모든 하위 객체에 쓰기를 수행해야 합니다. 이를 위해 업데이트가 비동기적으로 이루어지도록 job을 실행해야 합니다. 현재 시스템은 이 목적을 위해 두 개의 전용 Sidekiq 워커를 사용합니다:

  • AppConfig::CascadeDuoSettingsWorker - 애플리케이션 수준 설정 전파를 처리합니다.

  • Namespaces::CascadeDuoSettingsWorker - 하위 그룹 및 프로젝트에 대한 네임스페이스 수준 업데이트를 관리합니다.

이 비동기 방식은 시스템 성능에 영향을 주지 않으면서 설정 변경 사항이 조직 계층 전체에 효율적으로 배포되도록 합니다.

새 계단식 AI 설정 추가#

데이터베이스 쓰기를 통해 값을 전파하는 새 계단식 AI 설정을 구현하려면, 설정 범위에 따라 아래에 설명된 절차를 따르세요:

네임스페이스 설정 구성#

네임스페이스 수준 계단식 설정을 구현할 때:

  • ee/app/services/ee/groups/update_service.rb에 위치한 update_cascading_settings 메서드에 설정을 통합합니다.

  • ee/app/services/ai/cascade_duo_settings_service.rb의 허용된 설정 구성에 설정을 등록합니다.

애플리케이션 설정 구성#

애플리케이션 수준 계단식 설정을 구현할 때:

  • ee/app/services/ee/application_settings/update_service.rb에 있는 cascade_duo_features_settings 메서드에 설정을 통합합니다.

  • ee/app/services/ai/cascade_duo_settings_service.rb의 허용된 설정 구성에 설정을 등록합니다.

이러한 구성은 시스템 계층 전체에서 적절한 유효성 검사와 계단식 동작을 보장합니다.

이전에 추가된 계단식 설정은 여전히 기본값 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 (하위 그룹은 이 설정을 변경할 수 없습니다.)

_setting_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
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)

_lock_tooltips.html.haml

잠금 아이콘 위로 마우스를 올렸을 때 툴팁을 표시하는 데 사용되는 JavaScript를 초기화하는 데 필요한 마운트 요소를 렌더링합니다. 이 파셜은 페이지당 한 번만 필요합니다.

JavaScript#

initCascadingSettingsLockTooltips

잠금 아이콘( lock ) 위로 마우스를 올렸을 때 툴팁을 표시하는 데 필요한 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#

cascading_lock_icon.vue

로컬 변수 설명 타입 필수 여부 (기본값)
ancestorNamespace 연결된 그룹의 상위 항목에 대한 네임스페이스. Object false (null)
isLockedByApplicationSettings 인스턴스에서 계단식 변수 locked_by_application_settings가 설정되었는지 여부를 나타내는 Boolean. Boolean true
isLockedByGroupAncestor 그룹에 대해 계단식 변수 locked_by_ancestor가 설정되었는지 여부를 나타내는 Boolean. Boolean true

Vue 사용 방법#

Ruby 헬퍼에서 Vue 컴포넌트에 데이터를 전달하기 위해 다음을 호출해야 합니다. :replace_attribute_here를 계단식 속성으로 교체하세요.

# 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]
# 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

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>

cascading_lock_icon.vue를 다른 Vue 컴포넌트에 구현하는 다음 MR 예시를 참고할 수 있습니다:

HAML과 Vue 모두 지원하는 이유#

모든 새로운 프론트엔드 기능을 Vue로 개발하고 궁극적으로 HAML로 기능을 개발하는 것에서 벗어나는 것이 목표입니다. 그러나 계단식 설정을 사용하는 HAML 프론트엔드 기능이 여전히 있으므로, 해당 컴포넌트들이 Vue로 마이그레이션될 때까지 initCascadingSettingsLockTooltips에 대한 지원이 유지됩니다.