From 3a4415973e4cdadd51cf5c5727eda04b633bc906 Mon Sep 17 00:00:00 2001 From: Keeyan Nejad Date: Fri, 1 Aug 2025 11:39:51 +0100 Subject: [PATCH 1/9] Add event tracking for AI Catalog ItemConsumer mutations This commit implements internal event tracking for AI Catalog ItemConsumer create, update, and delete operations. The tracking includes: - create_ai_catalog_item_consumer event for creation - update_ai_catalog_item_consumer event for updates - delete_ai_catalog_item_consumer event for deletion Additional properties track enabled and locked states for create/update operations, following the recommended naming convention with 'label' and 'property' keys. Events are tracked with appropriate project/namespace context based on the container type (project vs group). --- .../catalog/item_consumers/create_service.rb | 25 ++++++++++++++++--- .../catalog/item_consumers/destroy_service.rb | 20 ++++++++++++--- .../catalog/item_consumers/update_service.rb | 16 ++++++++++++ 3 files changed, 55 insertions(+), 6 deletions(-) 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 e86c7f53c05556..b9ba0c7d95a410 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 Gitlab::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_create_event(item_consumer) + ServiceResponse.success(payload: { item_consumer: item_consumer }) + else + error_creating(item_consumer) + end end private @@ -37,6 +43,19 @@ def error(message) def error_no_permissions error('Item does not exist, or you have insufficient permissions') end + + def track_create_event(item_consumer) + track_internal_event( + 'create_ai_catalog_item_consumer', + 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 + } + ) + end end end end 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 b06b58b77cff60..4664a7fb066b9b 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 Gitlab::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_delete_event(item_consumer) + ServiceResponse.success(payload: { item_consumer: item_consumer }) + else + error(item_consumer.errors.full_messages) + end end private @@ -32,6 +37,15 @@ def error_no_permissions def error(message) ServiceResponse.error(message: Array(message)) end + + def track_delete_event(item_consumer) + track_internal_event( + 'delete_ai_catalog_item_consumer', + user: current_user, + project: item_consumer.project, + namespace: item_consumer.group + ) + 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 e486da29f0f9d6..39632af68a4224 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 Gitlab::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_update_event(item_consumer) ServiceResponse.success(payload: { item_consumer: item_consumer }) else error_updating @@ -39,6 +42,19 @@ def error(message) def error_updating error(item_consumer.errors.full_messages.presence || 'Failed to update item consumer') end + + def track_update_event(item_consumer) + track_internal_event( + 'update_ai_catalog_item_consumer', + 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 + } + ) + end end end end -- GitLab From 161f4f43b3a6385fefde291d3ca02b732f41105d Mon Sep 17 00:00:00 2001 From: Keeyan Nejad Date: Fri, 1 Aug 2025 11:43:41 +0100 Subject: [PATCH 2/9] Add tests for AI Catalog ItemConsumer event tracking This commit adds comprehensive test coverage for the internal event tracking functionality added to the AI Catalog ItemConsumer services. The tests verify: - Events are tracked on successful operations - Events are not tracked on failures - Correct event names and parameters are used - Project vs group context is handled properly - Additional properties (enabled/locked states) are tracked correctly Tests use the standard GitLab pattern of mocking Gitlab::InternalEvents to verify event tracking behavior. --- .../item_consumers/create_service_spec.rb | 42 +++++++++++++ .../item_consumers/destroy_service_spec.rb | 40 +++++++++++++ .../item_consumers/update_service_spec.rb | 60 +++++++++++++++++++ 3 files changed, 142 insertions(+) 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 b9f9beb4f3c942..634f05addbd8f7 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 @@ -46,12 +46,36 @@ ) end + it 'tracks internal event on successful creation' do + expect(Gitlab::InternalEvents).to receive(:track_event).with( + 'create_ai_catalog_item_consumer', + hash_including( + category: 'Ai::Catalog::ItemConsumers::CreateService', + user: user, + project: consumer_project, + namespace: nil, + additional_properties: { + label: 'true', + property: 'true' + } + ) + ) + + execute + end + context 'when the item is already configured in the project' do before do create(:ai_catalog_item_consumer, project: consumer_project, item: item) end it_behaves_like 'a failure', 'Item already configured' + + it 'does not track internal event on failure' do + expect(Gitlab::InternalEvents).not_to receive(:track_event) + + execute + end end context 'when the consumer is a group' do @@ -69,6 +93,24 @@ ) end + it 'tracks internal event with group namespace' do + expect(Gitlab::InternalEvents).to receive(:track_event).with( + 'create_ai_catalog_item_consumer', + hash_including( + category: 'Ai::Catalog::ItemConsumers::CreateService', + user: user, + project: nil, + namespace: consumer_group, + additional_properties: { + label: 'true', + property: 'true' + } + ) + ) + + execute + 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 38730b62741329..27d9afdaa2be8f 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 @@ -21,6 +21,12 @@ 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(Gitlab::InternalEvents).not_to receive(:track_event) + + response + end end context 'when user has permission' do @@ -31,6 +37,20 @@ expect(response).to be_success end + it 'tracks internal event on successful deletion' do + expect(Gitlab::InternalEvents).to receive(:track_event).with( + 'delete_ai_catalog_item_consumer', + hash_including( + category: 'Ai::Catalog::ItemConsumers::DestroyService', + user: maintainer, + project: project, + namespace: nil + ) + ) + + response + end + context 'when destroy fails' do before do allow(item_consumer).to receive(:destroy) do @@ -44,6 +64,12 @@ expect(response).to be_error expect(response.message).to contain_exactly('Deletion failed') end + + it 'does not track internal event on failure' do + expect(Gitlab::InternalEvents).not_to receive(:track_event) + + response + end end end end @@ -67,6 +93,20 @@ 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(Gitlab::InternalEvents).to receive(:track_event).with( + 'delete_ai_catalog_item_consumer', + hash_including( + category: 'Ai::Catalog::ItemConsumers::DestroyService', + user: maintainer, + project: nil, + namespace: group + ) + ) + + response + 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 d84a479ba8e0f0..db78f00342b211 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 @@ -21,6 +21,12 @@ 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(Gitlab::InternalEvents).not_to receive(:track_event) + + response + end end context 'when user has permission' do @@ -36,6 +42,22 @@ .and change { item_consumer.pinned_version_prefix }.from(nil).to('1.1') end + it 'tracks internal event on successful update' do + expect(Gitlab::InternalEvents).to receive(:track_event).with( + 'update_ai_catalog_item_consumer', + hash_including( + category: 'Ai::Catalog::ItemConsumers::UpdateService', + user: maintainer, + additional_properties: { + label: 'false', + property: 'false' + } + ) + ) + + response + end + context 'when the item consumer cannot be updated' do let(:params) { { pinned_version_prefix: 'a' * 51 } } @@ -43,6 +65,12 @@ 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(Gitlab::InternalEvents).not_to receive(:track_event) + + response + end end end end @@ -51,12 +79,44 @@ let_it_be_with_reload(:item_consumer) { create(:ai_catalog_item_consumer, project: project) } it_behaves_like 'Ai::Catalog::ItemConsumers::UpdateService' + + context 'when user has permission' do + let(:user) { maintainer } + + it 'tracks internal event with project context' do + expect(Gitlab::InternalEvents).to receive(:track_event).with( + 'update_ai_catalog_item_consumer', + hash_including( + project: project, + namespace: nil + ) + ) + + response + end + end end context 'with a group level item consumer' do let_it_be_with_reload(:item_consumer) { create(:ai_catalog_item_consumer, group: group) } it_behaves_like 'Ai::Catalog::ItemConsumers::UpdateService' + + context 'when user has permission' do + let(:user) { maintainer } + + it 'tracks internal event with group namespace' do + expect(Gitlab::InternalEvents).to receive(:track_event).with( + 'update_ai_catalog_item_consumer', + hash_including( + project: nil, + namespace: group + ) + ) + + response + end + end end end end -- GitLab From 296bf35456b81b81310fc092f553e7bd5643ed23 Mon Sep 17 00:00:00 2001 From: Keeyan Nejad Date: Fri, 1 Aug 2025 12:00:57 +0100 Subject: [PATCH 3/9] Refactor specs to use composable matchers for internal events Replace direct Gitlab::InternalEvents.track_event expectations with the recommended trigger_internal_events composable matcher pattern for better readability and integration with GitLab's testing framework. Related to https://gitlab.com/gitlab-org/gitlab/-/issues/536223 --- .../item_consumers/create_service_spec.rb | 48 +++++++------------ .../item_consumers/destroy_service_spec.rb | 36 ++++---------- .../item_consumers/update_service_spec.rb | 48 ++++++------------- 3 files changed, 41 insertions(+), 91 deletions(-) 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 634f05addbd8f7..ddcd8833f9b358 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 @@ -47,21 +47,15 @@ end it 'tracks internal event on successful creation' do - expect(Gitlab::InternalEvents).to receive(:track_event).with( - 'create_ai_catalog_item_consumer', - hash_including( - category: 'Ai::Catalog::ItemConsumers::CreateService', - user: user, - project: consumer_project, - namespace: nil, - additional_properties: { - label: 'true', - property: 'true' - } - ) + 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' + } ) - - execute end context 'when the item is already configured in the project' do @@ -72,9 +66,7 @@ it_behaves_like 'a failure', 'Item already configured' it 'does not track internal event on failure' do - expect(Gitlab::InternalEvents).not_to receive(:track_event) - - execute + expect { execute }.not_to trigger_internal_events('create_ai_catalog_item_consumer') end end @@ -94,21 +86,15 @@ end it 'tracks internal event with group namespace' do - expect(Gitlab::InternalEvents).to receive(:track_event).with( - 'create_ai_catalog_item_consumer', - hash_including( - category: 'Ai::Catalog::ItemConsumers::CreateService', - user: user, - project: nil, - namespace: consumer_group, - additional_properties: { - label: 'true', - property: 'true' - } - ) + 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' + } ) - - execute end context 'when the item is already configured in the group' do 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 27d9afdaa2be8f..a0206afb28f4ac 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 @@ -23,9 +23,7 @@ end it 'does not track internal event on failure' do - expect(Gitlab::InternalEvents).not_to receive(:track_event) - - response + expect { response }.not_to trigger_internal_events('delete_ai_catalog_item_consumer') end end @@ -38,17 +36,11 @@ end it 'tracks internal event on successful deletion' do - expect(Gitlab::InternalEvents).to receive(:track_event).with( - 'delete_ai_catalog_item_consumer', - hash_including( - category: 'Ai::Catalog::ItemConsumers::DestroyService', - user: maintainer, - project: project, - namespace: nil - ) + expect { response }.to trigger_internal_events('delete_ai_catalog_item_consumer').with( + user: maintainer, + project: project, + namespace: nil ) - - response end context 'when destroy fails' do @@ -66,9 +58,7 @@ end it 'does not track internal event on failure' do - expect(Gitlab::InternalEvents).not_to receive(:track_event) - - response + expect { response }.not_to trigger_internal_events('delete_ai_catalog_item_consumer') end end end @@ -95,17 +85,11 @@ end it 'tracks internal event with group namespace' do - expect(Gitlab::InternalEvents).to receive(:track_event).with( - 'delete_ai_catalog_item_consumer', - hash_including( - category: 'Ai::Catalog::ItemConsumers::DestroyService', - user: maintainer, - project: nil, - namespace: group - ) + expect { response }.to trigger_internal_events('delete_ai_catalog_item_consumer').with( + user: maintainer, + project: nil, + namespace: group ) - - response 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 db78f00342b211..2c304744d00040 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 @@ -23,9 +23,7 @@ end it 'does not track internal event on failure' do - expect(Gitlab::InternalEvents).not_to receive(:track_event) - - response + expect { response }.not_to trigger_internal_events('update_ai_catalog_item_consumer') end end @@ -43,19 +41,13 @@ end it 'tracks internal event on successful update' do - expect(Gitlab::InternalEvents).to receive(:track_event).with( - 'update_ai_catalog_item_consumer', - hash_including( - category: 'Ai::Catalog::ItemConsumers::UpdateService', - user: maintainer, - additional_properties: { - label: 'false', - property: 'false' - } - ) + expect { response }.to trigger_internal_events('update_ai_catalog_item_consumer').with( + user: maintainer, + additional_properties: { + label: 'false', + property: 'false' + } ) - - response end context 'when the item consumer cannot be updated' do @@ -67,9 +59,7 @@ end it 'does not track internal event on failure' do - expect(Gitlab::InternalEvents).not_to receive(:track_event) - - response + expect { response }.not_to trigger_internal_events('update_ai_catalog_item_consumer') end end end @@ -84,15 +74,10 @@ let(:user) { maintainer } it 'tracks internal event with project context' do - expect(Gitlab::InternalEvents).to receive(:track_event).with( - 'update_ai_catalog_item_consumer', - hash_including( - project: project, - namespace: nil - ) + expect { response }.to trigger_internal_events('update_ai_catalog_item_consumer').with( + project: project, + namespace: nil ) - - response end end end @@ -106,15 +91,10 @@ let(:user) { maintainer } it 'tracks internal event with group namespace' do - expect(Gitlab::InternalEvents).to receive(:track_event).with( - 'update_ai_catalog_item_consumer', - hash_including( - project: nil, - namespace: group - ) + expect { response }.to trigger_internal_events('update_ai_catalog_item_consumer').with( + project: nil, + namespace: group ) - - response end end end -- GitLab From 9eccfba049c1575676e09b7c6f41a1d23e715f1c Mon Sep 17 00:00:00 2001 From: Keeyan Nejad Date: Fri, 1 Aug 2025 12:19:49 +0100 Subject: [PATCH 4/9] Add event definitions for AI Catalog ItemConsumer tracking Register the three internal events for AI Catalog ItemConsumer operations: - create_ai_catalog_item_consumer - update_ai_catalog_item_consumer - delete_ai_catalog_item_consumer These event definitions are required for the internal event tracking to work properly in the specs and production code. --- .../create_ai_catalog_item_consumer.yml | 19 +++++++++++++++++++ .../delete_ai_catalog_item_consumer.yml | 14 ++++++++++++++ .../update_ai_catalog_item_consumer.yml | 19 +++++++++++++++++++ 3 files changed, 52 insertions(+) create mode 100644 ee/config/events/create_ai_catalog_item_consumer.yml create mode 100644 ee/config/events/delete_ai_catalog_item_consumer.yml create mode 100644 ee/config/events/update_ai_catalog_item_consumer.yml 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 00000000000000..0e65e137538457 --- /dev/null +++ b/ee/config/events/create_ai_catalog_item_consumer.yml @@ -0,0 +1,19 @@ +--- +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: +- ai_powered +milestone: '18.0' +introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/200024 +tiers: +- 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 00000000000000..953aee395b23e3 --- /dev/null +++ b/ee/config/events/delete_ai_catalog_item_consumer.yml @@ -0,0 +1,14 @@ +--- +description: AI Catalog item consumer deleted +internal_events: true +action: delete_ai_catalog_item_consumer +identifiers: +- project +- namespace +product_group: workflow_catalog +product_categories: +- ai_powered +milestone: '18.0' +introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/200024 +tiers: +- 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 00000000000000..8b88ed3d8ec5fb --- /dev/null +++ b/ee/config/events/update_ai_catalog_item_consumer.yml @@ -0,0 +1,19 @@ +--- +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: +- ai_powered +milestone: '18.0' +introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/200024 +tiers: +- ultimate -- GitLab From 9851b06250842b4afa53219b5a6be5186b8d925a Mon Sep 17 00:00:00 2001 From: Keeyan Nejad Date: Fri, 1 Aug 2025 14:23:27 +0100 Subject: [PATCH 5/9] Fix failing specs and update event milestones --- .../ai/catalog/item_consumers/create_service.rb | 2 +- .../events/create_ai_catalog_item_consumer.yml | 2 +- .../events/delete_ai_catalog_item_consumer.yml | 2 +- .../events/update_ai_catalog_item_consumer.yml | 2 +- .../item_consumers/update_service_spec.rb | 16 ++++++++++++++-- 5 files changed, 18 insertions(+), 6 deletions(-) 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 b9ba0c7d95a410..fb0bdebb9e9d66 100644 --- a/ee/app/services/ai/catalog/item_consumers/create_service.rb +++ b/ee/app/services/ai/catalog/item_consumers/create_service.rb @@ -12,7 +12,7 @@ def execute params.merge!(project: project, group: group) item_consumer = ::Ai::Catalog::ItemConsumer.create(params) - + if item_consumer.save track_create_event(item_consumer) ServiceResponse.success(payload: { item_consumer: item_consumer }) diff --git a/ee/config/events/create_ai_catalog_item_consumer.yml b/ee/config/events/create_ai_catalog_item_consumer.yml index 0e65e137538457..2f2e6f6d1ca700 100644 --- a/ee/config/events/create_ai_catalog_item_consumer.yml +++ b/ee/config/events/create_ai_catalog_item_consumer.yml @@ -13,7 +13,7 @@ additional_properties: product_group: workflow_catalog product_categories: - ai_powered -milestone: '18.0' +milestone: '18.3' introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/200024 tiers: - ultimate diff --git a/ee/config/events/delete_ai_catalog_item_consumer.yml b/ee/config/events/delete_ai_catalog_item_consumer.yml index 953aee395b23e3..e2a2d58af98f5d 100644 --- a/ee/config/events/delete_ai_catalog_item_consumer.yml +++ b/ee/config/events/delete_ai_catalog_item_consumer.yml @@ -8,7 +8,7 @@ identifiers: product_group: workflow_catalog product_categories: - ai_powered -milestone: '18.0' +milestone: '18.3' introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/200024 tiers: - ultimate diff --git a/ee/config/events/update_ai_catalog_item_consumer.yml b/ee/config/events/update_ai_catalog_item_consumer.yml index 8b88ed3d8ec5fb..c8b1d243e37c3a 100644 --- a/ee/config/events/update_ai_catalog_item_consumer.yml +++ b/ee/config/events/update_ai_catalog_item_consumer.yml @@ -13,7 +13,7 @@ additional_properties: product_group: workflow_catalog product_categories: - ai_powered -milestone: '18.0' +milestone: '18.3' introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/200024 tiers: - ultimate 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 2c304744d00040..4ce680acee5e14 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 @@ -43,6 +43,8 @@ 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' @@ -75,8 +77,13 @@ it 'tracks internal event with project context' do expect { response }.to trigger_internal_events('update_ai_catalog_item_consumer').with( + user: user, project: project, - namespace: nil + namespace: nil, + additional_properties: { + label: 'false', + property: 'false' + } ) end end @@ -92,8 +99,13 @@ it 'tracks internal event with group namespace' do expect { response }.to trigger_internal_events('update_ai_catalog_item_consumer').with( + user: user, project: nil, - namespace: group + namespace: group, + additional_properties: { + label: 'false', + property: 'false' + } ) end end -- GitLab From c63cd2b32e6095aa9e22f513cbcee516c107c3ab Mon Sep 17 00:00:00 2001 From: Keeyan Nejad Date: Fri, 1 Aug 2025 14:54:18 +0100 Subject: [PATCH 6/9] Fix event definitions --- ee/config/events/create_ai_catalog_item_consumer.yml | 3 ++- ee/config/events/delete_ai_catalog_item_consumer.yml | 3 ++- ee/config/events/update_ai_catalog_item_consumer.yml | 8 +++++++- 3 files changed, 11 insertions(+), 3 deletions(-) diff --git a/ee/config/events/create_ai_catalog_item_consumer.yml b/ee/config/events/create_ai_catalog_item_consumer.yml index 2f2e6f6d1ca700..9c9f3004b6e423 100644 --- a/ee/config/events/create_ai_catalog_item_consumer.yml +++ b/ee/config/events/create_ai_catalog_item_consumer.yml @@ -12,8 +12,9 @@ additional_properties: description: Locked state of the item consumer product_group: workflow_catalog product_categories: -- ai_powered +- 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 index e2a2d58af98f5d..0fc3c80369a582 100644 --- a/ee/config/events/delete_ai_catalog_item_consumer.yml +++ b/ee/config/events/delete_ai_catalog_item_consumer.yml @@ -7,8 +7,9 @@ identifiers: - namespace product_group: workflow_catalog product_categories: -- ai_powered +- 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 index c8b1d243e37c3a..a6cbf96ffc6948 100644 --- a/ee/config/events/update_ai_catalog_item_consumer.yml +++ b/ee/config/events/update_ai_catalog_item_consumer.yml @@ -12,8 +12,14 @@ additional_properties: description: Locked state of the item consumer product_group: workflow_catalog product_categories: -- ai_powered +- workflow_catalog milestone: '18.3' introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/200024 tiers: +- premium - ultimate +additional_properties: + label: + description: Enabled state of the item consumer + property: + description: Locked state of the item consumer -- GitLab From 6db81ce03069527f8c4a678aa84c4fa7a15a055e Mon Sep 17 00:00:00 2001 From: Keeyan Nejad Date: Wed, 6 Aug 2025 15:52:39 +0100 Subject: [PATCH 7/9] Remove duplicate additional_properties --- ee/config/events/update_ai_catalog_item_consumer.yml | 5 ----- 1 file changed, 5 deletions(-) diff --git a/ee/config/events/update_ai_catalog_item_consumer.yml b/ee/config/events/update_ai_catalog_item_consumer.yml index a6cbf96ffc6948..8cc18c034690d3 100644 --- a/ee/config/events/update_ai_catalog_item_consumer.yml +++ b/ee/config/events/update_ai_catalog_item_consumer.yml @@ -18,8 +18,3 @@ introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/200024 tiers: - premium - ultimate -additional_properties: - label: - description: Enabled state of the item consumer - property: - description: Locked state of the item consumer -- GitLab From 38fbb6f671e5ca7fcbf6567c0f38ba459f407cf5 Mon Sep 17 00:00:00 2001 From: Keeyan Nejad Date: Thu, 7 Aug 2025 08:55:47 +0100 Subject: [PATCH 8/9] Clean up specs --- .../item_consumers/create_service_spec.rb | 8 ++--- .../item_consumers/destroy_service_spec.rb | 4 +++ .../item_consumers/update_service_spec.rb | 32 ------------------- 3 files changed, 8 insertions(+), 36 deletions(-) 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 ddcd8833f9b358..32dd83fabf6388 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 @@ -32,6 +32,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 @@ -64,10 +68,6 @@ end it_behaves_like 'a failure', 'Item already configured' - - it 'does not track internal event on failure' do - expect { execute }.not_to trigger_internal_events('create_ai_catalog_item_consumer') - end end context 'when the consumer is a group' do 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 a0206afb28f4ac..451f96c3c5fbe9 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 @@ -74,6 +74,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 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 4ce680acee5e14..27ab093ecb9f2e 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 @@ -71,44 +71,12 @@ let_it_be_with_reload(:item_consumer) { create(:ai_catalog_item_consumer, project: project) } it_behaves_like 'Ai::Catalog::ItemConsumers::UpdateService' - - context 'when user has permission' do - let(:user) { maintainer } - - it 'tracks internal event with project context' do - expect { response }.to trigger_internal_events('update_ai_catalog_item_consumer').with( - user: user, - project: project, - namespace: nil, - additional_properties: { - label: 'false', - property: 'false' - } - ) - end - end end context 'with a group level item consumer' do let_it_be_with_reload(:item_consumer) { create(:ai_catalog_item_consumer, group: group) } it_behaves_like 'Ai::Catalog::ItemConsumers::UpdateService' - - context 'when user has permission' do - let(:user) { maintainer } - - it 'tracks internal event with group namespace' do - expect { response }.to trigger_internal_events('update_ai_catalog_item_consumer').with( - user: user, - project: nil, - namespace: group, - additional_properties: { - label: 'false', - property: 'false' - } - ) - end - end end end end -- GitLab From 9a00c39ad67f570a62151023fb94f2e998ed8630 Mon Sep 17 00:00:00 2001 From: Keeyan Nejad Date: Thu, 7 Aug 2025 10:20:20 +0100 Subject: [PATCH 9/9] Extract item consumer events to module --- .../catalog/item_consumers/create_service.rb | 17 +------ .../catalog/item_consumers/destroy_service.rb | 13 +----- .../internal_events_tracking.rb | 26 +++++++++++ .../catalog/item_consumers/update_service.rb | 17 +------ .../item_consumers/create_service_spec.rb | 5 +++ .../item_consumers/destroy_service_spec.rb | 5 +++ .../internal_events_tracking.rb | 44 +++++++++++++++++++ .../item_consumers/update_service_spec.rb | 5 +++ 8 files changed, 91 insertions(+), 41 deletions(-) create mode 100644 ee/app/services/ai/catalog/item_consumers/internal_events_tracking.rb create mode 100644 ee/spec/services/ai/catalog/item_consumers/shared_examples/internal_events_tracking.rb 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 fb0bdebb9e9d66..04c590619c4ef9 100644 --- a/ee/app/services/ai/catalog/item_consumers/create_service.rb +++ b/ee/app/services/ai/catalog/item_consumers/create_service.rb @@ -4,7 +4,7 @@ module Ai module Catalog module ItemConsumers class CreateService < ::BaseContainerService - include Gitlab::InternalEventsTracking + include InternalEventsTracking def execute return error_no_permissions unless allowed? @@ -14,7 +14,7 @@ def execute item_consumer = ::Ai::Catalog::ItemConsumer.create(params) if item_consumer.save - track_create_event(item_consumer) + track_item_consumer_event(item_consumer, 'create_ai_catalog_item_consumer') ServiceResponse.success(payload: { item_consumer: item_consumer }) else error_creating(item_consumer) @@ -43,19 +43,6 @@ def error(message) def error_no_permissions error('Item does not exist, or you have insufficient permissions') end - - def track_create_event(item_consumer) - track_internal_event( - 'create_ai_catalog_item_consumer', - 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 - } - ) - end end end end 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 4664a7fb066b9b..0dd890bceeb5d8 100644 --- a/ee/app/services/ai/catalog/item_consumers/destroy_service.rb +++ b/ee/app/services/ai/catalog/item_consumers/destroy_service.rb @@ -4,7 +4,7 @@ module Ai module Catalog module ItemConsumers class DestroyService - include Gitlab::InternalEventsTracking + include InternalEventsTracking def initialize(item_consumer, current_user) @current_user = current_user @@ -15,7 +15,7 @@ def execute return error_no_permissions unless allowed? if item_consumer.destroy - track_delete_event(item_consumer) + 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) @@ -37,15 +37,6 @@ def error_no_permissions def error(message) ServiceResponse.error(message: Array(message)) end - - def track_delete_event(item_consumer) - track_internal_event( - 'delete_ai_catalog_item_consumer', - user: current_user, - project: item_consumer.project, - namespace: item_consumer.group - ) - end end end end 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 00000000000000..66d83de7fc87a5 --- /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 39632af68a4224..abbbcc29674d32 100644 --- a/ee/app/services/ai/catalog/item_consumers/update_service.rb +++ b/ee/app/services/ai/catalog/item_consumers/update_service.rb @@ -4,7 +4,7 @@ module Ai module Catalog module ItemConsumers class UpdateService - include Gitlab::InternalEventsTracking + include InternalEventsTracking def initialize(item_consumer, current_user, params) @current_user = current_user @@ -16,7 +16,7 @@ def execute return error_no_permissions unless allowed? if item_consumer.update(params) - track_update_event(item_consumer) + track_item_consumer_event(item_consumer, 'update_ai_catalog_item_consumer') ServiceResponse.success(payload: { item_consumer: item_consumer }) else error_updating @@ -42,19 +42,6 @@ def error(message) def error_updating error(item_consumer.errors.full_messages.presence || 'Failed to update item consumer') end - - def track_update_event(item_consumer) - track_internal_event( - 'update_ai_catalog_item_consumer', - 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 - } - ) - end end end end 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 32dd83fabf6388..c4de5ece66e0eb 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 } 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 451f96c3c5fbe9..faa5bf15316427 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) } 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 00000000000000..c24aa9132e0df2 --- /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 27ab093ecb9f2e..ef08aab404c4ac 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) } -- GitLab