diff --git a/app/views/groups/settings/_permissions.html.haml b/app/views/groups/settings/_permissions.html.haml
index 32ef830b6cbfbd577b710f730a32ffe41393283e..6fa76297679d9db44c186ffad032fe2f314c9bcb 100644
--- a/app/views/groups/settings/_permissions.html.haml
+++ b/app/views/groups/settings/_permissions.html.haml
@@ -39,6 +39,7 @@
= render_if_exists 'groups/settings/wiki', f: f, group: @group
= render 'groups/settings/lfs', f: f
= render_if_exists 'groups/settings/code_suggestions', f: f, group: @group
+ = render_if_exists 'groups/settings/ai_related_settings', f: f, group: @group
= render 'groups/settings/git_access_protocols', f: f, group: @group
= render 'groups/settings/project_creation_level', f: f, group: @group
= render 'groups/settings/subgroup_creation_level', f: f, group: @group
diff --git a/config/feature_flags/development/ai_related_settings.yml b/config/feature_flags/development/ai_related_settings.yml
new file mode 100644
index 0000000000000000000000000000000000000000..3d4b24c69998bd493c28b8a4bd787f45f2dd8871
--- /dev/null
+++ b/config/feature_flags/development/ai_related_settings.yml
@@ -0,0 +1,8 @@
+---
+name: ai_related_settings
+introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/118222
+rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/408791
+milestone: '16.0'
+type: development
+group: group::ai-enablement
+default_enabled: false
diff --git a/db/migrate/20230420115733_add_ai_settings_to_namespace_settings.rb b/db/migrate/20230420115733_add_ai_settings_to_namespace_settings.rb
new file mode 100644
index 0000000000000000000000000000000000000000..681691d39c74ec20c2652fae592e51c26dcff88a
--- /dev/null
+++ b/db/migrate/20230420115733_add_ai_settings_to_namespace_settings.rb
@@ -0,0 +1,10 @@
+# frozen_string_literal: true
+
+class AddAiSettingsToNamespaceSettings < Gitlab::Database::Migration[2.1]
+ enable_lock_retries!
+
+ def change
+ add_column :namespace_settings, :experiment_features_enabled, :boolean, default: false, null: false
+ add_column :namespace_settings, :third_party_ai_features_enabled, :boolean, default: true, null: false
+ end
+end
diff --git a/db/schema_migrations/20230420115733 b/db/schema_migrations/20230420115733
new file mode 100644
index 0000000000000000000000000000000000000000..cac0ef60f61ad87e42e5111175b1692f3017588e
--- /dev/null
+++ b/db/schema_migrations/20230420115733
@@ -0,0 +1 @@
+d59b8bdea46ede31ff3d66d5aa18f4efb3afc216b13392b27214d7b609695da8
\ No newline at end of file
diff --git a/db/structure.sql b/db/structure.sql
index d75c7822a92351fe0df735288b93e6f317b2a70c..2f878306f5d94ad7d49d149a5fefb7d40f70049f 100644
--- a/db/structure.sql
+++ b/db/structure.sql
@@ -18788,6 +18788,8 @@ CREATE TABLE namespace_settings (
unique_project_download_limit_alertlist integer[] DEFAULT '{}'::integer[] NOT NULL,
emails_enabled boolean DEFAULT true NOT NULL,
code_suggestions boolean DEFAULT false NOT NULL,
+ experiment_features_enabled boolean DEFAULT false NOT NULL,
+ third_party_ai_features_enabled boolean DEFAULT true NOT NULL,
CONSTRAINT check_0ba93c78c7 CHECK ((char_length(default_branch_name) <= 255)),
CONSTRAINT namespace_settings_unique_project_download_limit_alertlist_size CHECK ((cardinality(unique_project_download_limit_alertlist) <= 100)),
CONSTRAINT namespace_settings_unique_project_download_limit_allowlist_size CHECK ((cardinality(unique_project_download_limit_allowlist) <= 100))
diff --git a/ee/app/controllers/ee/groups_controller.rb b/ee/app/controllers/ee/groups_controller.rb
index 757ed6e5b00f7d818ebe8d8bdbce1449c3aca177..9ab843256c5ab569080a7feeb06be914a78df4a2 100644
--- a/ee/app/controllers/ee/groups_controller.rb
+++ b/ee/app/controllers/ee/groups_controller.rb
@@ -99,6 +99,10 @@ def group_params_ee
params_ee << :prevent_forking_outside_group if can_change_prevent_forking?(current_user, current_group)
params_ee << :code_suggestions if ai_assist_ui_enabled?
+ if experimental_and_third_party_ai_settings_enabled?
+ params_ee.push(:experiment_features_enabled, :third_party_ai_features_enabled)
+ end
+
if current_group&.feature_available?(:adjourned_deletion_for_projects_and_groups) &&
::Feature.disabled?(:always_perform_delayed_deletion)
params_ee << :delayed_project_removal
@@ -112,8 +116,12 @@ def ai_assist_ui_enabled?
::Gitlab::CurrentSettings.should_check_namespace_plan? &&
::Feature.enabled?(:ai_assist_ui) &&
::Feature.enabled?(:ai_assist_flag, current_group) &&
- current_group.root? &&
- current_group.licensed_feature_available?(:ai_assist)
+ current_group.licensed_feature_available?(:ai_assist) &&
+ current_group.root?
+ end
+
+ def experimental_and_third_party_ai_settings_enabled?
+ current_group && current_group.ai_settings_allowed?
end
def current_group
diff --git a/ee/app/models/ee/group.rb b/ee/app/models/ee/group.rb
index 4f66bfbeb80f1e9de8c0f13f3b9140782c76feb1..176760c9b67f085eb6e3a86beec1b4b0431f2864 100644
--- a/ee/app/models/ee/group.rb
+++ b/ee/app/models/ee/group.rb
@@ -62,9 +62,6 @@ module Group
has_one :group_merge_request_approval_setting, inverse_of: :group
has_one :deletion_schedule, class_name: 'GroupDeletionSchedule'
- delegate :deleting_user, :marked_for_deletion_on, to: :deletion_schedule, allow_nil: true
- delegate :repository_read_only, :code_suggestions, :code_suggestions=, to: :namespace_settings, allow_nil: true
-
has_one :group_wiki_repository
has_many :repository_storage_moves, class_name: 'Groups::RepositoryStorageMove', inverse_of: :container
@@ -74,6 +71,16 @@ module Group
belongs_to :push_rule, inverse_of: :group
+ delegate :deleting_user, :marked_for_deletion_on, to: :deletion_schedule, allow_nil: true
+
+ delegate :repository_read_only, :code_suggestions, :code_suggestions=,
+ :experiment_features_enabled, :experiment_features_enabled=,
+ :third_party_ai_features_enabled, :third_party_ai_features_enabled=,
+ to: :namespace_settings, allow_nil: true
+
+ delegate :ai_settings_allowed?,
+ to: :namespace_settings
+
delegate :wiki_access_level=, to: :group_feature, allow_nil: true
# Use +checked_file_template_project+ instead, which implements important
diff --git a/ee/app/models/ee/namespace_setting.rb b/ee/app/models/ee/namespace_setting.rb
index 561ecd5622ff56007cec33d4acd34bd9243c3e37..a1ae79428556ecb7abe062921340b2d6731a4538 100644
--- a/ee/app/models/ee/namespace_setting.rb
+++ b/ee/app/models/ee/namespace_setting.rb
@@ -21,8 +21,12 @@ module NamespaceSetting
allow_nil: false,
user_id_existence: true,
if: :unique_project_download_limit_alertlist_changed?
+ validates :experiment_features_enabled, inclusion: { in: [true, false] }
+ validates :third_party_ai_features_enabled, inclusion: { in: [true, false] }
validate :user_cap_allowed, if: -> { enabling_user_cap? }
+ validate :third_party_ai_settings_allowed
+ validate :experiment_features_allowed
before_save :set_prevent_sharing_groups_outside_hierarchy, if: -> { user_cap_enabled? }
after_save :disable_project_sharing!, if: -> { user_cap_enabled? }
@@ -70,6 +74,14 @@ def unique_project_download_limit_alertlist
self[:unique_project_download_limit_alertlist].presence || active_owner_ids
end
+ def ai_settings_allowed?
+ ::Gitlab::CurrentSettings.should_check_namespace_plan? &&
+ ::Feature.enabled?(:openai_experimentation) &&
+ ::Feature.enabled?(:ai_related_settings, namespace) &&
+ namespace.licensed_feature_available?(:ai_features) &&
+ namespace.root?
+ end
+
private
def enabling_user_cap?
@@ -101,6 +113,20 @@ def active_owner_ids
namespace.owners.active.pluck_primary_key
end
+
+ def third_party_ai_settings_allowed
+ return unless third_party_ai_features_enabled_changed?
+ return if ai_settings_allowed?
+
+ errors.add(:third_party_ai_features_enabled, _('Third party AI settings not allowed.'))
+ end
+
+ def experiment_features_allowed
+ return unless experiment_features_enabled_changed?
+ return if ai_settings_allowed?
+
+ errors.add(:experiment_features_enabled, _("Experiment features' settings not allowed."))
+ end
end
class_methods do
diff --git a/ee/app/models/gitlab_subscriptions/features.rb b/ee/app/models/gitlab_subscriptions/features.rb
index e5f6b2ac92190be16fd50b3ea6b4bc3561f318ab..6e5a99861a00fb7a54821e3248e53bc9032c87ea 100644
--- a/ee/app/models/gitlab_subscriptions/features.rb
+++ b/ee/app/models/gitlab_subscriptions/features.rb
@@ -175,6 +175,7 @@ class Features
].freeze
ULTIMATE_FEATURES = %i[
+ ai_features
ai_tanuki_bot
api_discovery
api_fuzzing
diff --git a/ee/app/views/groups/settings/_ai_related_settings.html.haml b/ee/app/views/groups/settings/_ai_related_settings.html.haml
new file mode 100644
index 0000000000000000000000000000000000000000..0c682964f27668ed53ab9eb81619faf63d368a72
--- /dev/null
+++ b/ee/app/views/groups/settings/_ai_related_settings.html.haml
@@ -0,0 +1,30 @@
+- return unless group.ai_settings_allowed?
+
+- docs_link_url = help_page_path('user/project/repository/code_suggestions') #('user/ai_features')
+- docs_link_start = ''.html_safe % { url: docs_link_url }
+- terms_link_start = ''.html_safe
+- how_is_my_data_used = help_page_path('user/project/repository/code_suggestions') #('user/ai_features', anchor: 'data-usage')
+- data_used_link_start = ''.html_safe % { url: how_is_my_data_used }
+
+%h5
+ = s_('AI|Experiment features')
+
+%p
+ = s_('AI|These features could cause performance and stability issues and may change over time.')
+ = s_('AI| %{link_start}What are experiment features?%{link_end}').html_safe % { link_start: docs_link_start, link_end: ''.html_safe }
+
+.form-group.gl-mb-3
+ = f.gitlab_ui_checkbox_component :experiment_features_enabled,
+ s_('AI|Use experiment features'),
+ help_text: s_('AI|Enabling these features is your acceptance of the %{link_start}GitLab Testing Agreement%{link_end}.').html_safe % { link_start: terms_link_start, link_end: ''.html_safe }
+
+%h5
+ = s_('AI|Third-party AI services')
+
+%p
+ = s_('AI|Features that use third-party AI services require transmission of data, including personal data.')
+ = s_('AI| %{link_start}How is my data used?%{link_end}').html_safe % { link_start: data_used_link_start, link_end: ''.html_safe }
+
+.form-group.gl-mb-3
+ = f.gitlab_ui_checkbox_component :third_party_ai_features_enabled,
+ s_('AI|Use third-party AI services')
diff --git a/ee/config/audit_events/types/experiment_features_enabled_updated.yml b/ee/config/audit_events/types/experiment_features_enabled_updated.yml
new file mode 100644
index 0000000000000000000000000000000000000000..9db501a515c55f6fd5fc4fd965a16ef49ecb366a
--- /dev/null
+++ b/ee/config/audit_events/types/experiment_features_enabled_updated.yml
@@ -0,0 +1,9 @@
+---
+name: experiment_features_enabled_updated
+description: Event triggered on toggling setting for enabling experiment AI features
+introduced_by_issue: https://gitlab.com/gitlab-org/gitlab/-/issues/404856/
+introduced_by_mr: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/118222
+feature_category: not_owned
+milestone: '16.0'
+saved_to_database: true
+streamed: true
diff --git a/ee/config/audit_events/types/third_party_ai_features_enabled_updated.yml b/ee/config/audit_events/types/third_party_ai_features_enabled_updated.yml
new file mode 100644
index 0000000000000000000000000000000000000000..ed1c527277c4a6b59f7885ef72dcd5e8a07b2e85
--- /dev/null
+++ b/ee/config/audit_events/types/third_party_ai_features_enabled_updated.yml
@@ -0,0 +1,9 @@
+---
+name: third_party_ai_features_enabled_updated
+description: Event triggered on toggling setting for enabling third-party AI features
+introduced_by_issue: https://gitlab.com/gitlab-org/gitlab/-/issues/404856/
+introduced_by_mr: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/118222
+feature_category: not_owned
+milestone: '16.0'
+saved_to_database: true
+streamed: true
diff --git a/ee/lib/audit/namespace_setting_changes_auditor.rb b/ee/lib/audit/namespace_setting_changes_auditor.rb
index 25e45bd0bb5a819818d797353977fbd5ef6193bb..678784076baf816908700231e2f8dbbbc9f51d6f 100644
--- a/ee/lib/audit/namespace_setting_changes_auditor.rb
+++ b/ee/lib/audit/namespace_setting_changes_auditor.rb
@@ -2,6 +2,12 @@
module Audit
class NamespaceSettingChangesAuditor < BaseChangesAuditor
+ EVENT_NAME_PER_COLUMN = {
+ code_suggestions: 'code_suggestions_updated',
+ experiment_features_enabled: 'experiment_features_enabled_updated',
+ third_party_ai_features_enabled: 'third_party_ai_features_enabled_updated'
+ }.freeze
+
def initialize(current_user, namespace_setting, group)
@group = group
@@ -10,9 +16,10 @@ def initialize(current_user, namespace_setting, group)
def execute
return if model.blank?
- return unless audit_required? :code_suggestions
- audit_changes(:code_suggestions, entity: @group, model: model, event_type: 'code_suggestions_updated')
+ EVENT_NAME_PER_COLUMN.each do |column, event_name|
+ audit_changes(column, entity: @group, model: model, event_type: event_name)
+ end
end
private
diff --git a/ee/spec/controllers/ee/groups_controller_spec.rb b/ee/spec/controllers/ee/groups_controller_spec.rb
index 2f13976a2979aaa74819ca326c081fc473536955..bedd27f1d347ae7cf6466d398f938d96811eacb9 100644
--- a/ee/spec/controllers/ee/groups_controller_spec.rb
+++ b/ee/spec/controllers/ee/groups_controller_spec.rb
@@ -828,6 +828,40 @@ def request(visibility_level)
end
end
+ context 'when ai settings are specified' do
+ let(:group) { create(:group_with_plan, plan: :ultimate_plan, trial_ends_on: Date.tomorrow) }
+
+ before do
+ allow(Gitlab).to receive(:com?).and_return(true)
+ stub_licensed_features(ai_features: true)
+ stub_ee_application_setting(should_check_namespace_plan: true)
+ group.add_owner(user)
+
+ sign_in(user)
+ end
+
+ it 'updates the attribute' do
+ put :update, params: { id: group.to_param, group: { experiment_features_enabled: true,
+ third_party_ai_features_enabled: false } }
+
+ expect(group.reload.experiment_features_enabled).to eq(true)
+ expect(group.reload.third_party_ai_features_enabled).to eq(false)
+ end
+
+ context 'when ai licensed features are not available for the group' do
+ before do
+ stub_licensed_features(ai_features: false)
+ end
+
+ it 'does not update attributes' do
+ expect do
+ put :update, params: { id: group.to_param, group: { experiment_features_enabled: true,
+ third_party_ai_features_enabled: false } }
+ end.to not_change { group.reload.experiment_features_enabled }.and not_change { group.reload.third_party_ai_features_enabled }
+ end
+ end
+ end
+
describe '#ai_assist_ui_enabled?', :saas do
let_it_be(:group) { create(:group_with_plan, plan: :ultimate_plan) }
let_it_be(:subgroup) { create(:group, parent: group) }
diff --git a/ee/spec/lib/audit/namespace_setting_changes_auditor_spec.rb b/ee/spec/lib/audit/namespace_setting_changes_auditor_spec.rb
index 0985bcd0713e59987726b746463b861454040188..0c699679d5eb0d55a5bb7ea31ef06c76f3d34aa8 100644
--- a/ee/spec/lib/audit/namespace_setting_changes_auditor_spec.rb
+++ b/ee/spec/lib/audit/namespace_setting_changes_auditor_spec.rb
@@ -50,17 +50,103 @@
auditor.execute
end
end
+
+ context 'when code_suggestions is not changed' do
+ before do
+ group.namespace_settings.update!(code_suggestions: true)
+ end
+
+ it 'does not create an audit event' do
+ group.namespace_settings.update!(code_suggestions: true)
+
+ expect { auditor.execute }.not_to change { AuditEvent.count }
+ end
+ end
end
- context 'when code_suggestions is not changed' do
+ context 'when ai-related settings are changed' do
+ let(:group) { create(:group_with_plan, plan: :ultimate_plan, trial_ends_on: Date.tomorrow) }
+
before do
- group.namespace_settings.update!(code_suggestions: true)
+ allow(Gitlab).to receive(:com?).and_return(true)
+ stub_licensed_features(ai_features: true)
+ stub_ee_application_setting(should_check_namespace_plan: true)
end
- it 'does not create an audit event' do
- group.namespace_settings.update!(code_suggestions: true)
+ context 'when experiment_features_enabled is changed' do
+ where(:prev_value, :new_value) do
+ true | false
+ false | true
+ end
+
+ with_them do
+ before do
+ group.namespace_settings.update!(experiment_features_enabled: prev_value)
+ end
+
+ it 'creates an audit event' do
+ group.namespace_settings.update!(experiment_features_enabled: new_value)
+
+ expect { auditor.execute }.to change { AuditEvent.count }.by(1)
+ audit_details = {
+ change: :experiment_features_enabled,
+ from: prev_value,
+ to: new_value,
+ target_details: group.full_path
+ }
+ expect(AuditEvent.last.details).to include(audit_details)
+ end
+ end
+ end
+
+ context 'when experiment_features_enabled is not changed' do
+ before do
+ group.namespace_settings.update!(experiment_features_enabled: true)
+ end
- expect { auditor.execute }.not_to change { AuditEvent.count }
+ it 'does not create an audit event' do
+ group.namespace_settings.update!(experiment_features_enabled: true)
+
+ expect { auditor.execute }.not_to change { AuditEvent.count }
+ end
+ end
+
+ context 'when third_party_ai_features_enabled is changed' do
+ where(:prev_value, :new_value) do
+ true | false
+ false | true
+ end
+
+ with_them do
+ before do
+ group.namespace_settings.update!(third_party_ai_features_enabled: prev_value)
+ end
+
+ it 'creates an audit event' do
+ group.namespace_settings.update!(third_party_ai_features_enabled: new_value)
+
+ expect { auditor.execute }.to change { AuditEvent.count }.by(1)
+ audit_details = {
+ change: :third_party_ai_features_enabled,
+ from: prev_value,
+ to: new_value,
+ target_details: group.full_path
+ }
+ expect(AuditEvent.last.details).to include(audit_details)
+ end
+ end
+ end
+
+ context 'when third_party_ai_features_enabled is not changed' do
+ before do
+ group.namespace_settings.update!(third_party_ai_features_enabled: true)
+ end
+
+ it 'does not create an audit event' do
+ group.namespace_settings.update!(third_party_ai_features_enabled: true)
+
+ expect { auditor.execute }.not_to change { AuditEvent.count }
+ end
end
end
end
diff --git a/ee/spec/models/namespace_setting_spec.rb b/ee/spec/models/namespace_setting_spec.rb
index d91a2ea63bef08a3d494fd07e26906aeb540ac25..51003a4e40eaed3bdd97aa0ad1b1a7d16ff4223f 100644
--- a/ee/spec/models/namespace_setting_spec.rb
+++ b/ee/spec/models/namespace_setting_spec.rb
@@ -44,6 +44,36 @@
expect(subject.errors[attr]).to include("exceeds maximum length (100 usernames)")
end
end
+
+ describe 'AI related settings' do
+ subject(:settings) { group.namespace_settings }
+
+ shared_examples 'AI related settings validations' do |attr|
+ before do
+ allow(subject).to receive(:ai_settings_allowed?).and_return(true)
+ end
+
+ it { is_expected.to allow_value(false).for(attr) }
+ it { is_expected.to allow_value(true).for(attr) }
+ it { is_expected.not_to allow_value(nil).for(attr) }
+
+ context 'when AI settings are not allowed' do
+ before do
+ allow(subject).to receive(:ai_settings_allowed?).and_return(false)
+ end
+
+ it "#{attr} is not valid" do
+ subject[attr] = !subject[attr]
+
+ expect(subject).not_to be_valid
+ expect(subject.errors[attr].first).to include("settings not allowed.")
+ end
+ end
+ end
+
+ it_behaves_like 'AI related settings validations', :third_party_ai_features_enabled
+ it_behaves_like 'AI related settings validations', :experiment_features_enabled
+ end
end
describe 'unique_project_download_limit_alertlist', feature_category: :insider_threat do
@@ -373,4 +403,32 @@
it_behaves_like '[configuration](inherit_group_setting: bool) and [configuration]_locked?', :only_allow_merge_if_all_discussions_are_resolved
end
end
+
+ describe '.ai_settings_allowed?' do
+ using RSpec::Parameterized::TableSyntax
+
+ where(:check_namespace_plan, :main_feature_flag, :secondary_feature_flag, :licensed_feature, :is_root, :result) do
+ true | true | true | true | true | true
+ false | true | true | true | true | false
+ true | false | true | true | true | false
+ true | true | false | true | true | false
+ true | true | true | false | true | false
+ true | true | true | true | false | false
+ end
+
+ with_them do
+ let(:group) { create(:group) }
+ subject { group.namespace_settings.ai_settings_allowed? }
+
+ before do
+ allow(Gitlab::CurrentSettings).to receive(:should_check_namespace_plan?).and_return(check_namespace_plan)
+ stub_feature_flags(openai_experimentation: main_feature_flag)
+ stub_feature_flags(ai_related_settings: secondary_feature_flag)
+ allow(group).to receive(:licensed_feature_available?).with(:ai_features).and_return(licensed_feature)
+ allow(group).to receive(:root?).and_return(is_root)
+ end
+
+ it { is_expected.to eq result }
+ end
+ end
end
diff --git a/locale/gitlab.pot b/locale/gitlab.pot
index 2b40c48772a9bdef82d279e4bf3ef904b22bb753..665c5192d492290a9a9fde98e3eeace3e6c563b2 100644
--- a/locale/gitlab.pot
+++ b/locale/gitlab.pot
@@ -1836,18 +1836,33 @@ msgstr ""
msgid "AI actions"
msgstr ""
+msgid "AI| %{link_start}How is my data used?%{link_end}"
+msgstr ""
+
+msgid "AI| %{link_start}What are experiment features?%{link_end}"
+msgstr ""
+
msgid "AI|Close the Code Explanation"
msgstr ""
msgid "AI|Code Explanation"
msgstr ""
+msgid "AI|Enabling these features is your acceptance of the %{link_start}GitLab Testing Agreement%{link_end}."
+msgstr ""
+
msgid "AI|Experiment"
msgstr ""
+msgid "AI|Experiment features"
+msgstr ""
+
msgid "AI|Explain the code from %{filePath} in human understandable language presented in Markdown format. In the response add neither original code snippet nor any title. `%{text}`"
msgstr ""
+msgid "AI|Features that use third-party AI services require transmission of data, including personal data."
+msgstr ""
+
msgid "AI|Helpful"
msgstr ""
@@ -1863,9 +1878,21 @@ msgstr ""
msgid "AI|There is too much text in the chat. Please try again with a shorter text."
msgstr ""
+msgid "AI|These features could cause performance and stability issues and may change over time."
+msgstr ""
+
+msgid "AI|Third-party AI services"
+msgstr ""
+
msgid "AI|Unhelpful"
msgstr ""
+msgid "AI|Use experiment features"
+msgstr ""
+
+msgid "AI|Use third-party AI services"
+msgstr ""
+
msgid "AI|What does the selected code mean?"
msgstr ""
@@ -17571,6 +17598,9 @@ msgstr ""
msgid "Experiment"
msgstr ""
+msgid "Experiment features' settings not allowed."
+msgstr ""
+
msgid "Experiments"
msgstr ""
@@ -45134,6 +45164,9 @@ msgstr ""
msgid "Third Party Advisory Link"
msgstr ""
+msgid "Third party AI settings not allowed."
+msgstr ""
+
msgid "This %{issuableDisplayName} is locked. Only project members can comment."
msgstr ""