diff --git a/ee/app/services/ai/catalog/item_consumers/create_service.rb b/ee/app/services/ai/catalog/item_consumers/create_service.rb index e86c7f53c05556438d81a5fa2f089b33852f5c39..04c590619c4ef941a5ba0615494b9929c3c604cd 100644 --- a/ee/app/services/ai/catalog/item_consumers/create_service.rb +++ b/ee/app/services/ai/catalog/item_consumers/create_service.rb @@ -4,15 +4,21 @@ module Ai module Catalog module ItemConsumers class CreateService < ::BaseContainerService + include InternalEventsTracking + def execute return error_no_permissions unless allowed? return error('Catalog item is not a flow') unless item.flow? params.merge!(project: project, group: group) item_consumer = ::Ai::Catalog::ItemConsumer.create(params) - return ServiceResponse.success(payload: { item_consumer: item_consumer }) if item_consumer.save - error_creating(item_consumer) + if item_consumer.save + track_item_consumer_event(item_consumer, 'create_ai_catalog_item_consumer') + ServiceResponse.success(payload: { item_consumer: item_consumer }) + else + error_creating(item_consumer) + end end private diff --git a/ee/app/services/ai/catalog/item_consumers/destroy_service.rb b/ee/app/services/ai/catalog/item_consumers/destroy_service.rb index b06b58b77cff607fbfabedee7e9107341ed13075..0dd890bceeb5d891f62f3bf721a0e55785f8cf4f 100644 --- a/ee/app/services/ai/catalog/item_consumers/destroy_service.rb +++ b/ee/app/services/ai/catalog/item_consumers/destroy_service.rb @@ -4,6 +4,8 @@ module Ai module Catalog module ItemConsumers class DestroyService + include InternalEventsTracking + def initialize(item_consumer, current_user) @current_user = current_user @item_consumer = item_consumer @@ -12,9 +14,12 @@ def initialize(item_consumer, current_user) def execute return error_no_permissions unless allowed? - return ServiceResponse.success(payload: { item_consumer: item_consumer }) if item_consumer.destroy - - error(item_consumer.errors.full_messages) + if item_consumer.destroy + track_item_consumer_event(item_consumer, 'delete_ai_catalog_item_consumer', additional_properties: nil) + ServiceResponse.success(payload: { item_consumer: item_consumer }) + else + error(item_consumer.errors.full_messages) + end end private diff --git a/ee/app/services/ai/catalog/item_consumers/internal_events_tracking.rb b/ee/app/services/ai/catalog/item_consumers/internal_events_tracking.rb new file mode 100644 index 0000000000000000000000000000000000000000..66d83de7fc87a55b07015d645b9d016589895622 --- /dev/null +++ b/ee/app/services/ai/catalog/item_consumers/internal_events_tracking.rb @@ -0,0 +1,26 @@ +# frozen_string_literal: true + +module Ai + module Catalog + module ItemConsumers + module InternalEventsTracking + include Gitlab::InternalEventsTracking + + def track_item_consumer_event(item_consumer, event_name, custom_attrs = {}) + track_internal_event( + event_name, + **{ + user: current_user, + project: item_consumer.project, + namespace: item_consumer.group, + additional_properties: { + label: item_consumer.enabled.to_s, + property: item_consumer.locked.to_s + } + }.merge(custom_attrs).compact + ) + end + end + end + end +end diff --git a/ee/app/services/ai/catalog/item_consumers/update_service.rb b/ee/app/services/ai/catalog/item_consumers/update_service.rb index e486da29f0f9d6d0e79cc2c5364b6bfbd634307e..abbbcc29674d32435722330744e5553f3be26c30 100644 --- a/ee/app/services/ai/catalog/item_consumers/update_service.rb +++ b/ee/app/services/ai/catalog/item_consumers/update_service.rb @@ -4,6 +4,8 @@ module Ai module Catalog module ItemConsumers class UpdateService + include InternalEventsTracking + def initialize(item_consumer, current_user, params) @current_user = current_user @item_consumer = item_consumer @@ -14,6 +16,7 @@ def execute return error_no_permissions unless allowed? if item_consumer.update(params) + track_item_consumer_event(item_consumer, 'update_ai_catalog_item_consumer') ServiceResponse.success(payload: { item_consumer: item_consumer }) else error_updating diff --git a/ee/config/events/create_ai_catalog_item_consumer.yml b/ee/config/events/create_ai_catalog_item_consumer.yml new file mode 100644 index 0000000000000000000000000000000000000000..9c9f3004b6e423998d013bbb97ac1ff18030982f --- /dev/null +++ b/ee/config/events/create_ai_catalog_item_consumer.yml @@ -0,0 +1,20 @@ +--- +description: AI Catalog item consumer created +internal_events: true +action: create_ai_catalog_item_consumer +identifiers: +- project +- namespace +additional_properties: + label: + description: Enabled state of the item consumer + property: + description: Locked state of the item consumer +product_group: workflow_catalog +product_categories: +- workflow_catalog +milestone: '18.3' +introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/200024 +tiers: +- premium +- ultimate diff --git a/ee/config/events/delete_ai_catalog_item_consumer.yml b/ee/config/events/delete_ai_catalog_item_consumer.yml new file mode 100644 index 0000000000000000000000000000000000000000..0fc3c80369a582ac8acb875c056064ebe9bf62de --- /dev/null +++ b/ee/config/events/delete_ai_catalog_item_consumer.yml @@ -0,0 +1,15 @@ +--- +description: AI Catalog item consumer deleted +internal_events: true +action: delete_ai_catalog_item_consumer +identifiers: +- project +- namespace +product_group: workflow_catalog +product_categories: +- workflow_catalog +milestone: '18.3' +introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/200024 +tiers: +- premium +- ultimate diff --git a/ee/config/events/update_ai_catalog_item_consumer.yml b/ee/config/events/update_ai_catalog_item_consumer.yml new file mode 100644 index 0000000000000000000000000000000000000000..8cc18c034690d38c19a8d645c6709c3de1eb1fb8 --- /dev/null +++ b/ee/config/events/update_ai_catalog_item_consumer.yml @@ -0,0 +1,20 @@ +--- +description: AI Catalog item consumer updated +internal_events: true +action: update_ai_catalog_item_consumer +identifiers: +- project +- namespace +additional_properties: + label: + description: Enabled state of the item consumer + property: + description: Locked state of the item consumer +product_group: workflow_catalog +product_categories: +- workflow_catalog +milestone: '18.3' +introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/200024 +tiers: +- premium +- ultimate diff --git a/ee/spec/services/ai/catalog/item_consumers/create_service_spec.rb b/ee/spec/services/ai/catalog/item_consumers/create_service_spec.rb index b9f9beb4f3c942cf36f731057768aea270c70ce5..c4de5ece66e0eba4866f8fbbf3ca8a55a9346403 100644 --- a/ee/spec/services/ai/catalog/item_consumers/create_service_spec.rb +++ b/ee/spec/services/ai/catalog/item_consumers/create_service_spec.rb @@ -1,6 +1,7 @@ # frozen_string_literal: true require 'spec_helper' +require_relative './shared_examples/internal_events_tracking' RSpec.describe Ai::Catalog::ItemConsumers::CreateService, feature_category: :workflow_catalog do let_it_be(:user) { create(:user) } @@ -21,6 +22,10 @@ subject(:execute) { described_class.new(container: container, current_user: user, params: params).execute } + it_behaves_like 'ItemConsumers::InternalEventsTracking' do + subject { described_class.new(container: container, current_user: user, params: params) } + end + shared_examples 'a failure' do |message| it 'does not create a catalog item consumer' do expect { execute }.not_to change { Ai::Catalog::ItemConsumer.count } @@ -32,6 +37,10 @@ expect(response).to be_error expect(response.message).to contain_exactly(message) end + + it 'does not track internal event' do + expect { execute }.not_to trigger_internal_events('create_ai_catalog_item_consumer') + end end it 'creates a catalog item consumer with expected data' do @@ -46,6 +55,18 @@ ) end + it 'tracks internal event on successful creation' do + expect { execute }.to trigger_internal_events('create_ai_catalog_item_consumer').with( + user: user, + project: consumer_project, + namespace: nil, + additional_properties: { + label: 'true', + property: 'true' + } + ) + end + context 'when the item is already configured in the project' do before do create(:ai_catalog_item_consumer, project: consumer_project, item: item) @@ -69,6 +90,18 @@ ) end + it 'tracks internal event with group namespace' do + expect { execute }.to trigger_internal_events('create_ai_catalog_item_consumer').with( + user: user, + project: nil, + namespace: consumer_group, + additional_properties: { + label: 'true', + property: 'true' + } + ) + end + context 'when the item is already configured in the group' do before do create(:ai_catalog_item_consumer, group: consumer_group, item: item) diff --git a/ee/spec/services/ai/catalog/item_consumers/destroy_service_spec.rb b/ee/spec/services/ai/catalog/item_consumers/destroy_service_spec.rb index 38730b627413298cb401465ac9351ddf492e8c76..faa5bf15316427d52c1d8ed9ccd6c0e139fa8397 100644 --- a/ee/spec/services/ai/catalog/item_consumers/destroy_service_spec.rb +++ b/ee/spec/services/ai/catalog/item_consumers/destroy_service_spec.rb @@ -1,8 +1,13 @@ # frozen_string_literal: true require 'spec_helper' +require_relative './shared_examples/internal_events_tracking' RSpec.describe Ai::Catalog::ItemConsumers::DestroyService, feature_category: :workflow_catalog do + it_behaves_like 'ItemConsumers::InternalEventsTracking' do + subject { described_class.new(build(:ai_catalog_item_consumer), build(:user)) } + end + describe '#execute' do let_it_be(:developer) { create(:user) } let_it_be(:maintainer) { create(:user) } @@ -21,6 +26,10 @@ expect(response).to be_error expect(response.message).to contain_exactly('You have insufficient permissions to delete this item consumer') end + + it 'does not track internal event on failure' do + expect { response }.not_to trigger_internal_events('delete_ai_catalog_item_consumer') + end end context 'when user has permission' do @@ -31,6 +40,14 @@ expect(response).to be_success end + it 'tracks internal event on successful deletion' do + expect { response }.to trigger_internal_events('delete_ai_catalog_item_consumer').with( + user: maintainer, + project: project, + namespace: nil + ) + end + context 'when destroy fails' do before do allow(item_consumer).to receive(:destroy) do @@ -44,6 +61,10 @@ expect(response).to be_error expect(response.message).to contain_exactly('Deletion failed') end + + it 'does not track internal event on failure' do + expect { response }.not_to trigger_internal_events('delete_ai_catalog_item_consumer') + end end end end @@ -58,6 +79,10 @@ expect(response).to be_error expect(response.message).to contain_exactly('You have insufficient permissions to delete this item consumer') end + + it 'does not track internal event' do + expect { response }.not_to trigger_internal_events('delete_ai_catalog_item_consumer') + end end context 'when user has permission' do @@ -67,6 +92,14 @@ expect { response }.to change { Ai::Catalog::ItemConsumer.count }.by(-1) expect(response).to be_success end + + it 'tracks internal event with group namespace' do + expect { response }.to trigger_internal_events('delete_ai_catalog_item_consumer').with( + user: maintainer, + project: nil, + namespace: group + ) + end end end end diff --git a/ee/spec/services/ai/catalog/item_consumers/shared_examples/internal_events_tracking.rb b/ee/spec/services/ai/catalog/item_consumers/shared_examples/internal_events_tracking.rb new file mode 100644 index 0000000000000000000000000000000000000000..c24aa9132e0df26dc3d6c0239bf9f4a4b37af117 --- /dev/null +++ b/ee/spec/services/ai/catalog/item_consumers/shared_examples/internal_events_tracking.rb @@ -0,0 +1,44 @@ +# frozen_string_literal: true + +RSpec.shared_examples_for 'ItemConsumers::InternalEventsTracking' do + let(:event_name) { 'create_ai_catalog_item_consumer' } + let(:project) { build(:project) } + let(:group) { build(:group) } + let(:item_consumer) { build_stubbed(:ai_catalog_item_consumer, project:, group:) } + + it 'tracks an event with the given event name, user, project, namespace, and additional properties' do + expect(subject).to receive(:track_internal_event).with( + event_name, + user: subject.send(:current_user), # Method may be private + project: project, + namespace: group, + additional_properties: { + label: item_consumer.enabled.to_s, + property: item_consumer.locked.to_s + } + ) + + subject.track_item_consumer_event(item_consumer, event_name) + end + + context 'when passing in additional_attributes' do + it 'overwrites the defaults' do + expect(subject).to receive(:track_internal_event).with( + anything, + a_hash_including(additional_properties: { label: 1, property: 2 }) + ) + + subject.track_item_consumer_event(item_consumer, event_name, { additional_properties: { label: 1, property: 2 } }) + end + + context 'when the additional_properties key is set to nil' do + it 'does not pass in additional_properties' do + expect(subject).to receive(:track_internal_event) do |_, hash| + expect(hash).not_to have_key(:additional_properties) + end + + subject.track_item_consumer_event(item_consumer, event_name, { additional_properties: nil }) + end + end + end +end diff --git a/ee/spec/services/ai/catalog/item_consumers/update_service_spec.rb b/ee/spec/services/ai/catalog/item_consumers/update_service_spec.rb index d84a479ba8e0f077211af097dd1c3abfe7a435a1..ef08aab404c4ac4185a9e0a55372a123261cb979 100644 --- a/ee/spec/services/ai/catalog/item_consumers/update_service_spec.rb +++ b/ee/spec/services/ai/catalog/item_consumers/update_service_spec.rb @@ -1,8 +1,13 @@ # frozen_string_literal: true require 'spec_helper' +require_relative './shared_examples/internal_events_tracking' RSpec.describe Ai::Catalog::ItemConsumers::UpdateService, feature_category: :workflow_catalog do + it_behaves_like 'ItemConsumers::InternalEventsTracking' do + subject { described_class.new(build(:ai_catalog_item_consumer), build(:user), {}) } + end + describe '#execute' do let_it_be(:developer) { create(:user) } let_it_be(:maintainer) { create(:user) } @@ -21,6 +26,10 @@ expect(response).to be_error expect(response.message).to contain_exactly('You have insufficient permission to update this item consumer') end + + it 'does not track internal event on failure' do + expect { response }.not_to trigger_internal_events('update_ai_catalog_item_consumer') + end end context 'when user has permission' do @@ -36,6 +45,18 @@ .and change { item_consumer.pinned_version_prefix }.from(nil).to('1.1') end + it 'tracks internal event on successful update' do + expect { response }.to trigger_internal_events('update_ai_catalog_item_consumer').with( + user: maintainer, + project: item_consumer.project, + namespace: item_consumer.group, + additional_properties: { + label: 'false', + property: 'false' + } + ) + end + context 'when the item consumer cannot be updated' do let(:params) { { pinned_version_prefix: 'a' * 51 } } @@ -43,6 +64,10 @@ expect(response).to be_error expect(response.message).to contain_exactly('Pinned version prefix is too long (maximum is 50 characters)') end + + it 'does not track internal event on failure' do + expect { response }.not_to trigger_internal_events('update_ai_catalog_item_consumer') + end end end end