diff --git a/config/audit_events/types/self_hosted_model_feature_changed.yml b/config/audit_events/types/self_hosted_model_feature_changed.yml new file mode 100644 index 0000000000000000000000000000000000000000..e6d76a0f564c598085010600126d6145fdea28a3 --- /dev/null +++ b/config/audit_events/types/self_hosted_model_feature_changed.yml @@ -0,0 +1,9 @@ +name: self_hosted_model_feature_changed +description: A self-hosted model feature had its configuration changed +introduced_by_issue: https://gitlab.com/gitlab-org/gitlab/-/issues/463215 +introduced_by_mr: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/165489 +feature_category: self-hosted_models +milestone: '17.4' +saved_to_database: true +scope: [Project] +streamed: true diff --git a/doc/user/compliance/audit_event_types.md b/doc/user/compliance/audit_event_types.md index c34c4aeabe3d82e2b9728a50013655640d04b293..2975ebb088a6415c7bd34c8eef44560ce3f83b21 100644 --- a/doc/user/compliance/audit_event_types.md +++ b/doc/user/compliance/audit_event_types.md @@ -453,6 +453,7 @@ Audit event types belong to the following product categories. | Name | Description | Saved to database | Streamed | Introduced in | Scope | |:------------|:------------|:------------------|:---------|:--------------|:--------------| +| [`self_hosted_model_feature_changed`](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/165489) | A self-hosted model feature had its configuration changed | **{check-circle}** Yes | **{check-circle}** Yes | GitLab [17.4](https://gitlab.com/gitlab-org/gitlab/-/issues/463215) | Project | | [`self_hosted_model_terms_accepted`](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/165480) | Terms for usage of self-hosted models were accepted | **{check-circle}** Yes | **{check-circle}** Yes | GitLab [17.4](https://gitlab.com/gitlab-org/gitlab/-/issues/477999) | Instance, User | ### Source code management diff --git a/ee/app/controllers/admin/ai/feature_settings_controller.rb b/ee/app/controllers/admin/ai/feature_settings_controller.rb index 4dd63f132a75e335ab75f91b541e26e05f2a56b1..cff1a9d96fe4e7bb27d85f447f923096d44a5bc5 100644 --- a/ee/app/controllers/admin/ai/feature_settings_controller.rb +++ b/ee/app/controllers/admin/ai/feature_settings_controller.rb @@ -41,15 +41,17 @@ def create def update @feature_setting = ::Ai::FeatureSetting.find(params[:id]) - if @feature_setting.update(feature_settings_params) + result = ::Ai::FeatureSettings::UpdateService.new( + @feature_setting, current_user, feature_settings_params + ).execute + + if result.success? redirect_to admin_ai_feature_settings_url, notice: _("Feature settings updated successfully") else render :edit end end - private - def feature_settings_params params.require(:feature_setting).permit( :feature, :provider, :ai_self_hosted_model_id diff --git a/ee/app/services/ai/feature_settings/update_service.rb b/ee/app/services/ai/feature_settings/update_service.rb new file mode 100644 index 0000000000000000000000000000000000000000..5d31b2916f181a2c61a785c0fb19ad5e0500f4a6 --- /dev/null +++ b/ee/app/services/ai/feature_settings/update_service.rb @@ -0,0 +1,39 @@ +# frozen_string_literal: true + +module Ai + module FeatureSettings + class UpdateService + def initialize(feature_setting, user, params) + @feature_setting = feature_setting + @user = user + @params = params + end + + def execute + if feature_setting.update(@params) + audit_event + + ServiceResponse.success(payload: feature_setting) + else + ServiceResponse.error(payload: feature_setting, message: feature_setting.errors.full_messages.join(", ")) + end + end + + private + + attr_accessor :feature_setting, :user + + def audit_event + audit_context = { + name: 'self_hosted_model_feature_changed', + author: user, + scope: user, + target: feature_setting, + message: "Feature #{feature_setting.feature} changed to #{feature_setting.provider_title}" + } + + ::Gitlab::Audit::Auditor.audit(audit_context) + end + end + end +end diff --git a/ee/spec/services/ai/feature_settings/update_service_spec.rb b/ee/spec/services/ai/feature_settings/update_service_spec.rb new file mode 100644 index 0000000000000000000000000000000000000000..8af16f81c7a4f5587f28eb7c5a97e809d9d3f7f3 --- /dev/null +++ b/ee/spec/services/ai/feature_settings/update_service_spec.rb @@ -0,0 +1,44 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe Ai::FeatureSettings::UpdateService, feature_category: :"self-hosted_models" do + let_it_be(:user) { create(:user) } + let_it_be(:self_hosted_model) { create(:ai_self_hosted_model) } + let(:feature_setting) { create(:ai_feature_setting, provider: :vendored, self_hosted_model: nil) } + + let(:params) { { provider: :self_hosted, self_hosted_model: self_hosted_model } } + + subject(:service_result) { described_class.new(feature_setting, user, params).execute } + + describe '#execute' do + let(:audit_event) do + { + name: 'self_hosted_model_feature_changed', + author: user, + scope: user, + target: feature_setting, + message: "Feature code_generations changed to Self-hosted model (mistral-7b-ollama-api)" + } + end + + it 'returns a success response' do + expect(Gitlab::Audit::Auditor).to receive(:audit).with(audit_event) + expect { service_result }.to change { feature_setting.reload.provider }.to("self_hosted") + + expect(service_result).to be_success + expect(service_result.payload).to eq(feature_setting) + end + + context 'when update fails' do + let(:params) { { provider: '' } } + + it 'returns an error response' do + expect(Gitlab::Audit::Auditor).not_to receive(:audit) + + expect(service_result).to be_error + expect(service_result.message).to include("Provider can't be blank") + end + end + end +end