diff --git a/doc/api/graphql/reference/_index.md b/doc/api/graphql/reference/_index.md index ff8013243a2a2e3b42add2978b53cbd936c0d467..25680cea828797910194c2af9a6aa287b7d41199 100644 --- a/doc/api/graphql/reference/_index.md +++ b/doc/api/graphql/reference/_index.md @@ -2498,6 +2498,7 @@ Input type: `AiCatalogAgentUpdateInput` | `systemPrompt` | [`String`](#string) | System prompt for the agent. | | `tools` | [`[AiCatalogBuiltInToolID!]`](#aicatalogbuiltintoolid) | List of GitLab tools enabled for the agent. | | `userPrompt` | [`String`](#string) | User prompt for the agent. | +| `versionBump` | [`AiCatalogVersionBump`](#aicatalogversionbump) | Bump version, calculated from the last released version name. | #### Fields @@ -46171,6 +46172,16 @@ Possible item types for AI items. | `AGENT` | Agent. | | `FLOW` | Flow. | +### `AiCatalogVersionBump` + +Possible version bumps for AI catalog items. + +| Value | Description | +| ----- | ----------- | +| `MAJOR` | Major version bump. | +| `MINOR` | Minor version bump. | +| `PATCH` | Patch version bump. | + ### `AiConversationsThreadsConversationType` Conversation type of the thread. diff --git a/ee/app/graphql/mutations/ai/catalog/agent/update.rb b/ee/app/graphql/mutations/ai/catalog/agent/update.rb index 5302e98e8f9e9d70a933d459e8fe65202202b6dc..b0232ac45571c43ffdfc8597353b5339f3f8e230 100644 --- a/ee/app/graphql/mutations/ai/catalog/agent/update.rb +++ b/ee/app/graphql/mutations/ai/catalog/agent/update.rb @@ -45,6 +45,10 @@ class Update < BaseMutation required: false, description: 'User prompt for the agent.' + argument :version_bump, Types::Ai::Catalog::VersionBumpEnum, + required: false, + description: 'Bump version, calculated from the last released version name.' + authorize :admin_ai_catalog_item def resolve(args) diff --git a/ee/app/graphql/types/ai/catalog/version_bump_enum.rb b/ee/app/graphql/types/ai/catalog/version_bump_enum.rb new file mode 100644 index 0000000000000000000000000000000000000000..19aaea7f0096c003dba605a9771acc2af54c2906 --- /dev/null +++ b/ee/app/graphql/types/ai/catalog/version_bump_enum.rb @@ -0,0 +1,16 @@ +# frozen_string_literal: true + +module Types + module Ai + module Catalog + class VersionBumpEnum < BaseEnum + graphql_name 'AiCatalogVersionBump' + description 'Possible version bumps for AI catalog items.' + + ::Ai::Catalog::ItemVersion::VERSION_BUMP_OPTIONS.each do |version_bump| + value version_bump.upcase, description: "#{version_bump.to_s.capitalize} version bump.", value: version_bump + end + end + end + end +end diff --git a/ee/app/models/ai/catalog/item_version.rb b/ee/app/models/ai/catalog/item_version.rb index a89026b369db10fb313b7df8ca6b3e012a7205e1..a043be4e70028012c5bae5f099213e013fe55f0c 100644 --- a/ee/app/models/ai/catalog/item_version.rb +++ b/ee/app/models/ai/catalog/item_version.rb @@ -12,6 +12,7 @@ class ItemVersion < ApplicationRecord VERSION_BUMP_MAJOR = :major VERSION_BUMP_MINOR = :minor VERSION_BUMP_PATCH = :patch + VERSION_BUMP_OPTIONS = [VERSION_BUMP_MAJOR, VERSION_BUMP_MINOR, VERSION_BUMP_PATCH].freeze self.table_name = "ai_catalog_item_versions" @@ -55,6 +56,7 @@ def draft? def version_bump(bump_level) return if version.nil? + raise ArgumentError, "unknown bump_level: #{bump_level}" unless bump_level.in?(VERSION_BUMP_OPTIONS) old_version = Gitlab::VersionInfo.parse(version) @@ -65,8 +67,6 @@ def version_bump(bump_level) [old_version.major, old_version.minor + 1, 0] when VERSION_BUMP_PATCH [old_version.major, old_version.minor, old_version.patch + 1] - else - raise ArgumentError, "unknown bump_level: #{bump_level}" end Gitlab::VersionInfo.new(*new_version).to_s diff --git a/ee/app/services/ai/catalog/agents/update_service.rb b/ee/app/services/ai/catalog/agents/update_service.rb index 9364263fbc6f82d1412789d0eda35d82f86ed118..1b57bcff623bec16a34d5277eb8672fdf79a1e80 100644 --- a/ee/app/services/ai/catalog/agents/update_service.rb +++ b/ee/app/services/ai/catalog/agents/update_service.rb @@ -51,6 +51,7 @@ def prepare_version_to_update version_to_update.schema_version = Ai::Catalog::ItemVersion::AGENT_SCHEMA_VERSION end + version_to_update.version = calculate_next_version if should_calculate_version?(version_to_update) version_to_update.release_date ||= Time.zone.now if params[:release] == true version_to_update end @@ -60,10 +61,9 @@ def determine_version_to_update version_params = build_version_params(latest_version) latest_version.assign_attributes(version_params) - return latest_version unless latest_version.changed? return latest_version unless should_create_new_version?(latest_version) - build_new_version(latest_version, version_params) + build_new_version(version_params) end def build_version_params(latest_version) @@ -78,21 +78,26 @@ def build_version_params(latest_version) end def should_create_new_version?(version) - version.released? && version.enforce_readonly_versions? + version.definition_changed? && version.released? && version.enforce_readonly_versions? end - def build_new_version(latest_version, version_params) - new_version_params = version_params.merge( - version: calculate_next_version(latest_version) - ) + def build_new_version(version_params) + agent.build_new_version(version_params) + end - agent.build_new_version(new_version_params) + def should_calculate_version?(version) + version.new_record? || params[:version_bump].present? end - def calculate_next_version(latest_version) - # TODO: Support params[:version_bump] parameter. - # For now, always make a major version bump. - latest_version.version_bump(Ai::Catalog::ItemVersion::VERSION_BUMP_MAJOR) + def calculate_next_version + # TODO replace with agent.latest_released_version once + # https://gitlab.com/gitlab-org/gitlab/-/issues/554673 is completed + latest_released_version = agent.versions.where.not(release_date: nil).order(id: :desc).take # rubocop:disable CodeReuse/ActiveRecord -- Will be fixed after https://gitlab.com/gitlab-org/gitlab/-/issues/554673 + + return BaseService::DEFAULT_VERSION unless latest_released_version + + bump_level = params[:version_bump] || Ai::Catalog::ItemVersion::VERSION_BUMP_MAJOR + latest_released_version.version_bump(bump_level) end end end diff --git a/ee/spec/factories/ai/catalog/item_versions.rb b/ee/spec/factories/ai/catalog/item_versions.rb index eb14b04da0217df88c0286be3ed9eca470ac4c5c..6b1bda42c61bfc2b00c5798e3feb8b2a79c8da0a 100644 --- a/ee/spec/factories/ai/catalog/item_versions.rb +++ b/ee/spec/factories/ai/catalog/item_versions.rb @@ -2,7 +2,7 @@ FactoryBot.define do factory :ai_catalog_item_version, class: 'Ai::Catalog::ItemVersion' do - version { FFaker::Number.unique.number(digits: 6).to_s.scan(/.{2}/).join('.') } + sequence(:version) { |n| "#{n}.0.99" } schema_version { 1 } for_agent release_date { nil } diff --git a/ee/spec/graphql/mutations/ai/catalog/agent/update_spec.rb b/ee/spec/graphql/mutations/ai/catalog/agent/update_spec.rb index 65303af94c54b4295a2481553e566fc623c6314d..b7bbfff56a52c9323059a925b8063dcdb5d4995d 100644 --- a/ee/spec/graphql/mutations/ai/catalog/agent/update_spec.rb +++ b/ee/spec/graphql/mutations/ai/catalog/agent/update_spec.rb @@ -23,6 +23,7 @@ :system_prompt, :user_prompt, :tools, + :version_bump, :client_mutation_id ) end diff --git a/ee/spec/graphql/types/ai/catalog/version_bump_enum_spec.rb b/ee/spec/graphql/types/ai/catalog/version_bump_enum_spec.rb new file mode 100644 index 0000000000000000000000000000000000000000..b3703b5228d9f69e4943980446d5d4ec6482b33f --- /dev/null +++ b/ee/spec/graphql/types/ai/catalog/version_bump_enum_spec.rb @@ -0,0 +1,11 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe GitlabSchema.types['AiCatalogVersionBump'], feature_category: :workflow_catalog do + it 'exposes all version bump options' do + expect(described_class.values.keys).to match_array( + Ai::Catalog::ItemVersion::VERSION_BUMP_OPTIONS.map { |o| o.to_s.upcase } + ) + end +end diff --git a/ee/spec/requests/api/graphql/mutations/ai/catalog/agent/update_spec.rb b/ee/spec/requests/api/graphql/mutations/ai/catalog/agent/update_spec.rb index cdddba13446b5c6ae71ee39b85b27cfe03a6ea4e..8d15f8b7c5efd637f7b3d4c0fb387f282799e878 100644 --- a/ee/spec/requests/api/graphql/mutations/ai/catalog/agent/update_spec.rb +++ b/ee/spec/requests/api/graphql/mutations/ai/catalog/agent/update_spec.rb @@ -8,6 +8,10 @@ let_it_be(:maintainer) { create(:user) } let_it_be(:project) { create(:project, maintainers: maintainer) } let_it_be_with_reload(:agent) { create(:ai_catalog_item, project: project) } + let_it_be_with_reload(:latest_released_version) do + create(:ai_catalog_item_version, :released, version: '1.0.0', item: agent) + end + let_it_be_with_reload(:latest_version) { create(:ai_catalog_item_version, version: '1.1.0', item: agent) } let(:current_user) { maintainer } @@ -49,7 +53,8 @@ release: true, system_prompt: 'New system prompt', tools: tools.map { |tool| global_id_of(tool) }, - user_prompt: 'New user prompt' + user_prompt: 'New user prompt', + version_bump: 'PATCH' } end @@ -130,7 +135,7 @@ expect(latest_version.reload).to have_attributes( schema_version: 1, - version: '1.1.0', + version: '1.0.1', release_date: Time.zone.now, definition: { system_prompt: 'New system prompt', diff --git a/ee/spec/services/ai/catalog/agents/update_service_spec.rb b/ee/spec/services/ai/catalog/agents/update_service_spec.rb index df6889a21fbbb26eaef8c0fda9c343f336b4bf40..b1d885bdb0a93e5b754624bca35eb5c7ee5d4c0b 100644 --- a/ee/spec/services/ai/catalog/agents/update_service_spec.rb +++ b/ee/spec/services/ai/catalog/agents/update_service_spec.rb @@ -6,6 +6,10 @@ let_it_be(:project) { create(:project) } let_it_be(:user) { create(:user) } let_it_be_with_reload(:agent) { create(:ai_catalog_item, public: false, project: project) } + let_it_be_with_reload(:latest_released_version) do + create(:ai_catalog_item_version, :released, version: '1.0.0', item: agent) + end + let_it_be_with_reload(:latest_version) { create(:ai_catalog_item_version, version: '1.1.0', item: agent) } let(:tools) { Ai::Catalog::BuiltInTool.where(id: [1, 9]) } @@ -84,6 +88,46 @@ ) end + context 'when version_bump is provided' do + let(:params) { super().merge(version_bump: :patch) } + + it 'sets the version correctly' do + execute_service + + expect(latest_version.reload).to have_attributes( + schema_version: 1, + version: '1.0.1', + release_date: Time.zone.now, + definition: { + user_prompt: 'New user prompt', + tools: [1, 9], + system_prompt: 'New system prompt' + }.stringify_keys + ) + end + + context 'when there is no previous released version' do + before do + latest_released_version.destroy! + end + + it 'sets the version to the default version' do + execute_service + + expect(latest_version.reload).to have_attributes( + schema_version: 1, + version: '1.0.0', + release_date: Time.zone.now, + definition: { + user_prompt: 'New user prompt', + tools: [1, 9], + system_prompt: 'New system prompt' + }.stringify_keys + ) + end + end + end + it 'returns success response with item in payload', :aggregate_failures do result = execute_service @@ -122,6 +166,25 @@ expect { execute_service }.not_to change { latest_version.reload.attributes } end + context 'when version_bump is provided' do + let(:params) { super().merge(version_bump: :patch) } + + it 'sets the version correctly' do + execute_service + + expect(agent.reload.latest_version).to have_attributes( + schema_version: 1, + version: '1.1.1', + release_date: Time.zone.now, + definition: { + user_prompt: 'New user prompt', + tools: [1, 9], + system_prompt: 'New system prompt' + }.stringify_keys + ) + end + end + context 'when the `ai_catalog_enforce_readonly_versions` flag is disabled' do before do stub_feature_flags(ai_catalog_enforce_readonly_versions: false) diff --git a/ee/spec/services/ai/catalog/flows/update_service_spec.rb b/ee/spec/services/ai/catalog/flows/update_service_spec.rb index 1ab99319d8f0fa4c36c365806e107e935b99d448..b1bbcaebc26684ec6b26aeafe9b9f6f5ad251a4b 100644 --- a/ee/spec/services/ai/catalog/flows/update_service_spec.rb +++ b/ee/spec/services/ai/catalog/flows/update_service_spec.rb @@ -97,9 +97,9 @@ end context 'when the prefix is not valid' do - let(:params) { super().merge(steps: [{ agent: agent, pinned_version_prefix: '2' }]) } + let(:params) { super().merge(steps: [{ agent: agent, pinned_version_prefix: '2.2' }]) } - it_behaves_like 'an error response', 'Step 1: Unable to resolve version with prefix 2' + it_behaves_like 'an error response', 'Step 1: Unable to resolve version with prefix 2.2' end end