diff --git a/ee/app/assets/javascripts/ai/catalog/components/ai_catalog_list_item.vue b/ee/app/assets/javascripts/ai/catalog/components/ai_catalog_list_item.vue index ce84b23ea08c524fb77fe0ea5633106038604573..527e38a3dac64c91a2156bee92400293581a7c2b 100644 --- a/ee/app/assets/javascripts/ai/catalog/components/ai_catalog_list_item.vue +++ b/ee/app/assets/javascripts/ai/catalog/components/ai_catalog_list_item.vue @@ -8,7 +8,7 @@ import { GlIcon, GlTooltipDirective, } from '@gitlab/ui'; -import { __, s__ } from '~/locale'; +import { __, s__, sprintf } from '~/locale'; import { getIdFromGraphQLId } from '~/graphql_shared/utils'; import { VISIBILITY_TYPE_ICON, @@ -17,7 +17,13 @@ import { VISIBILITY_LEVEL_PRIVATE_STRING, } from '~/visibility_level/constants'; import FoundationalIcon from 'ee/ai/components/foundational_icon.vue'; -import { AI_CATALOG_TYPE_THIRD_PARTY_FLOW } from '../constants'; +import { + AI_CATALOG_TYPE_THIRD_PARTY_FLOW, + AI_CATALOG_ITEM_LABELS, + AI_CATALOG_CONSUMER_TYPE_PROJECT, + AI_CATALOG_CONSUMER_TYPE_GROUP, + AI_CATALOG_CONSUMER_LABELS, +} from '../constants'; export default { name: 'AiCatalogListItem', @@ -33,6 +39,11 @@ export default { directives: { GlTooltip: GlTooltipDirective, }, + inject: { + projectId: { + default: null, + }, + }, props: { item: { type: Object, @@ -44,6 +55,20 @@ export default { }, }, computed: { + isProjectNamespace() { + return Boolean(this.projectId); + }, + itemTypeLabel() { + return AI_CATALOG_ITEM_LABELS[this.item.itemType]; + }, + targetType() { + return this.isProjectNamespace + ? AI_CATALOG_CONSUMER_TYPE_PROJECT + : AI_CATALOG_CONSUMER_TYPE_GROUP; + }, + targetTypeLabel() { + return AI_CATALOG_CONSUMER_LABELS[this.targetType]; + }, actionItems() { return this.itemTypeConfig.actionItems?.(this.item) || []; }, @@ -96,6 +121,17 @@ export default { isThirdPartyFlow() { return this.item.itemType === AI_CATALOG_TYPE_THIRD_PARTY_FLOW; }, + softDeletedTooltipText() { + return sprintf( + s__( + 'AICatalog|This %{itemType} was removed from the AI Catalog. You can still use it in this %{targetType}.', + ), + { + itemType: this.itemTypeLabel, + targetType: this.targetTypeLabel, + }, + ); + }, }, }; @@ -176,6 +212,15 @@ export default { {{ s__('AICatalog|Update available') }} +
+ {{ s__('AICatalog|Unlisted') }} +
{ const findDisclosureDropdownItems = () => wrapper.findAllComponents(GlDisclosureDropdownItem); const findFoundationalIcon = () => wrapper.findComponent(FoundationalIcon); const findUpdateAvailableLabel = () => wrapper.findByTestId('ai-catalog-item-update'); + const findUpdateUnlistedBadge = () => wrapper.findByTestId('ai-catalog-item-unlisted'); beforeEach(() => { createComponent(); @@ -334,4 +335,34 @@ describe('AiCatalogListItem', () => { }); }); }); + + describe('unlisted badge', () => { + describe('when item is soft deleted', () => { + beforeEach(() => { + createComponent({ + item: { ...mockItem, softDeleted: true }, + }); + }); + it('renders Unlisted badge when softDeleted is true', () => { + expect(findUpdateUnlistedBadge().findComponent(GlBadge).text()).toBe('Unlisted'); + }); + + it('renders tooltip with correct text', () => { + expect(findUpdateUnlistedBadge().attributes('title')).toBe( + 'This agent was removed from the AI Catalog. You can still use it in this group.', + ); + }); + }); + + describe('when item is not soft deleted', () => { + beforeEach(() => { + createComponent({ + item: { ...mockItem, isUpdateAvailable: false }, + }); + }); + it('does not render Unlisted badge when softDeleted is false', () => { + expect(findUpdateUnlistedBadge().exists()).toBe(false); + }); + }); + }); }); diff --git a/ee/spec/frontend/ai/catalog/mock_data.js b/ee/spec/frontend/ai/catalog/mock_data.js index 1b1c439c4cf04307a0433846f4a0454da27b41d2..0c49947d3e33818b2665884523108b90ba046046 100644 --- a/ee/spec/frontend/ai/catalog/mock_data.js +++ b/ee/spec/frontend/ai/catalog/mock_data.js @@ -175,6 +175,7 @@ const mockAgentFactory = (overrides = {}) => ({ itemType: 'AGENT', description: 'A helpful AI assistant for testing purposes', createdAt: '2024-01-15T10:30:00Z', + softDeleted: false, public: true, updatedAt: '2024-08-21T14:30:00Z', latestVersion: mockBaseVersion, @@ -468,6 +469,7 @@ const mockFlowFactory = (overrides = {}) => ({ createdAt: '2024-01-15T10:30:00Z', public: true, updatedAt: '2024-08-21T14:30:00Z', + softDeleted: false, foundational: false, latestVersion: mockBaseVersion, userPermissions: mockUserPermissions, diff --git a/locale/gitlab.pot b/locale/gitlab.pot index ae50aa33f5dc372fda52b8290ef95526b1d9b1c5..8006b79559e121adac46d0e2202a8f408c6ee724 100644 --- a/locale/gitlab.pot +++ b/locale/gitlab.pot @@ -3073,6 +3073,9 @@ msgstr "" msgid "AICatalog|This %{itemType} requires approval from your parent group owner before it can be used" msgstr "" +msgid "AICatalog|This %{itemType} was removed from the AI Catalog. You can still use it in this %{targetType}." +msgstr "" + msgid "AICatalog|This YAML configuration file determines the prompts, tools, and capabilities of your flow. Required properties: injectGatewayToken, image, commands" msgstr "" @@ -3115,6 +3118,9 @@ msgstr "" msgid "AICatalog|Type" msgstr "" +msgid "AICatalog|Unlisted" +msgstr "" + msgid "AICatalog|Update available" msgstr ""