diff --git a/ee/app/models/ai/catalog/concerns/flow_version.rb b/ee/app/models/ai/catalog/concerns/flow_version.rb index 7eb32477dec7407e395f0b6c02b2258a841db7c2..e7c9bb89b4a7d29d897975c315ce966e6b7ddac6 100644 --- a/ee/app/models/ai/catalog/concerns/flow_version.rb +++ b/ee/app/models/ai/catalog/concerns/flow_version.rb @@ -7,16 +7,28 @@ module FlowVersion extend ActiveSupport::Concern included do + validate :validate_released_version_has_steps, if: -> { released? && definition_for_flow? } + def delete_no_longer_used_dependencies dependencies.where.not(dependency_id: dependency_ids).delete_all end def dependency_ids - # Check to ensure item is a flow and not an agent, so we don't have to load item for item.flow? - return unless definition['steps'] + return unless definition_for_flow? && definition['steps'].present? definition['steps'].pluck('agent_id').uniq.compact # rubocop:disable Database/AvoidUsingPluckWithoutLimit -- Not ActiveRecord end + + def validate_released_version_has_steps + return unless definition['steps'].empty? + + errors.add(:definition, s_('AICatalog|must have at least one node')) + end + + # A shortcut to avoid checking item.flow? which would require loading `item` + def definition_for_flow? + definition.key?('steps') + end end end end diff --git a/ee/spec/factories/ai/catalog/item_versions.rb b/ee/spec/factories/ai/catalog/item_versions.rb index 6b1bda42c61bfc2b00c5798e3feb8b2a79c8da0a..e48a9ee23a653109bbe0a688a52b3bab06a538df 100644 --- a/ee/spec/factories/ai/catalog/item_versions.rb +++ b/ee/spec/factories/ai/catalog/item_versions.rb @@ -28,9 +28,13 @@ trait :for_flow do item { association :ai_catalog_flow } definition do + agent = Ai::Catalog::Item.find_by(item_type: :agent) || create(:ai_catalog_agent) # rubocop:disable RSpec/FactoryBot/InlineAssociation -- Not used for an association + { 'triggers' => [1], - 'steps' => [] + 'steps' => [ + { 'agent_id' => agent.id, 'current_version_id' => agent.latest_version.id, 'pinned_version_prefix' => nil } + ] } end end diff --git a/ee/spec/lib/ai/catalog/duo_workflow_payload_builder/experimental_spec.rb b/ee/spec/lib/ai/catalog/duo_workflow_payload_builder/experimental_spec.rb index f1ecfc331b69eab95d5adc91e33d7beec93a8994..85c38e6fd14af8792f3c87f99d1ab8dac6bba976 100644 --- a/ee/spec/lib/ai/catalog/duo_workflow_payload_builder/experimental_spec.rb +++ b/ee/spec/lib/ai/catalog/duo_workflow_payload_builder/experimental_spec.rb @@ -70,13 +70,6 @@ end describe '#build' do - context 'when flow has no versions' do - let_it_be(:empty_flow) { create(:ai_catalog_flow, project: project) } - let_it_be(:builder) { described_class.new(empty_flow) } - - it_behaves_like 'invalid flow configuration' - end - context 'when flow has no agents' do let_it_be(:empty_steps_definition) { { 'triggers' => [1], 'steps' => [] } } let_it_be(:empty_steps_flow) { create(:ai_catalog_flow, project: project) } diff --git a/ee/spec/support/shared_examples/models/ai/catalog/flow_version.rb b/ee/spec/support/shared_examples/models/ai/catalog/flow_version.rb index ce1366458142ddc2314cbdfd3bb04bab831ccddd..82d07cccfdd9ffeeeba5b102de1db07097e17f56 100644 --- a/ee/spec/support/shared_examples/models/ai/catalog/flow_version.rb +++ b/ee/spec/support/shared_examples/models/ai/catalog/flow_version.rb @@ -5,6 +5,42 @@ let_it_be(:agent2) { create(:ai_catalog_item, :agent) } let_it_be(:agent3) { create(:ai_catalog_item, :agent) } + describe 'validations' do + it 'does not allow a released version without steps' do + version = build( + :ai_catalog_item_version, + :released, + :for_flow, + 'definition' => { 'steps' => [], 'triggers' => [] } + ) + + expect(version).not_to be_valid + expect(version.errors[:definition]).to include(s_('AICatalog|must have at least one node')) + end + + it 'allow a released version with steps' do + version = build(:ai_catalog_item_version, :released, :for_flow) + + expect(version).to be_valid + end + + it 'allows an unreleased version to have no steps' do + version = build( + :ai_catalog_item_version, + :for_flow, + 'definition' => { 'steps' => [], 'triggers' => [] } + ) + + expect(version).to be_valid + end + + it 'allows a released agent version to not have steps' do + version = build(:ai_catalog_agent_version, :for_agent, :released) + + expect(version).to be_valid + end + end + describe '#delete_no_longer_used_dependencies' do let_it_be(:flow_version) do create( diff --git a/locale/gitlab.pot b/locale/gitlab.pot index ba0835f58f9537901466bcfdbbdcdfa49316e246..8053685915e9240adb1d00ca97081975fc52efa2 100644 --- a/locale/gitlab.pot +++ b/locale/gitlab.pot @@ -2811,6 +2811,9 @@ msgstr "" msgid "AICatalog|is private to another project" msgstr "" +msgid "AICatalog|must have at least one node" +msgstr "" + msgid "AICatalog|organization must match the item consumer's organization" msgstr ""