diff --git a/x-pack/plugins/fleet/common/services/index.ts b/x-pack/plugins/fleet/common/services/index.ts index 4443878617796..7061d6d3028d8 100644 --- a/x-pack/plugins/fleet/common/services/index.ts +++ b/x-pack/plugins/fleet/common/services/index.ts @@ -33,6 +33,7 @@ export { isIntegrationPolicyTemplate, getNormalizedInputs, getNormalizedDataStreams, + filterPolicyTemplatesTiles, } from './policy_template'; export { doesPackageHaveIntegrations } from './packages_with_integrations'; export type { diff --git a/x-pack/plugins/fleet/common/services/policy_template.test.ts b/x-pack/plugins/fleet/common/services/policy_template.test.ts index 87ce2121b8b59..b0cba311fe70c 100644 --- a/x-pack/plugins/fleet/common/services/policy_template.test.ts +++ b/x-pack/plugins/fleet/common/services/policy_template.test.ts @@ -10,6 +10,7 @@ import type { RegistryPolicyIntegrationTemplate, PackageInfo, RegistryVarType, + PackageListItem, } from '../types'; import { @@ -17,6 +18,7 @@ import { isIntegrationPolicyTemplate, getNormalizedInputs, getNormalizedDataStreams, + filterPolicyTemplatesTiles, } from './policy_template'; describe('isInputOnlyPolicyTemplate', () => { @@ -280,3 +282,125 @@ describe('getNormalizedDataStreams', () => { expect(result?.[0].streams?.[0]?.vars).toEqual([datasetVar]); }); }); + +describe('filterPolicyTemplatesTiles', () => { + const topPackagePolicy: PackageListItem = { + id: 'nginx', + integration: 'nginx', + title: 'Nginx', + name: 'nginx', + version: '0.0.1', + status: 'not_installed', + }; + + const childPolicyTemplates: PackageListItem[] = [ + { + id: 'nginx-template1', + integration: 'nginx-template-1', + title: 'Nginx Template 1', + name: 'nginx', + version: '0.0.1', + status: 'not_installed', + }, + { + id: 'nginx-template2', + integration: 'nginx-template-2', + title: 'Nginx Template 2', + name: 'nginx', + version: '0.0.1', + status: 'not_installed', + }, + ]; + it('should return all tiles as undefined value', () => { + expect(filterPolicyTemplatesTiles(undefined, topPackagePolicy, childPolicyTemplates)).toEqual([ + { + id: 'nginx', + integration: 'nginx', + title: 'Nginx', + name: 'nginx', + version: '0.0.1', + status: 'not_installed', + }, + { + id: 'nginx-template1', + integration: 'nginx-template-1', + title: 'Nginx Template 1', + name: 'nginx', + version: '0.0.1', + status: 'not_installed', + }, + { + id: 'nginx-template2', + integration: 'nginx-template-2', + title: 'Nginx Template 2', + name: 'nginx', + version: '0.0.1', + status: 'not_installed', + }, + ]); + }); + it('should return all tiles', () => { + expect(filterPolicyTemplatesTiles('all', topPackagePolicy, childPolicyTemplates)).toEqual([ + { + id: 'nginx', + integration: 'nginx', + title: 'Nginx', + name: 'nginx', + version: '0.0.1', + status: 'not_installed', + }, + { + id: 'nginx-template1', + integration: 'nginx-template-1', + title: 'Nginx Template 1', + name: 'nginx', + version: '0.0.1', + status: 'not_installed', + }, + { + id: 'nginx-template2', + integration: 'nginx-template-2', + title: 'Nginx Template 2', + name: 'nginx', + version: '0.0.1', + status: 'not_installed', + }, + ]); + }); + it('should return just the combined policy tile', () => { + expect( + filterPolicyTemplatesTiles('combined_policy', topPackagePolicy, childPolicyTemplates) + ).toEqual([ + { + id: 'nginx', + integration: 'nginx', + title: 'Nginx', + name: 'nginx', + version: '0.0.1', + status: 'not_installed', + }, + ]); + }); + it('should return just the individual policies (tiles)', () => { + expect( + filterPolicyTemplatesTiles('individual_policies', topPackagePolicy, childPolicyTemplates) + ).toEqual([ + { + id: 'nginx-template1', + integration: 'nginx-template-1', + title: 'Nginx Template 1', + name: 'nginx', + version: '0.0.1', + status: 'not_installed', + }, + { + id: 'nginx-template2', + integration: 'nginx-template-2', + title: 'Nginx Template 2', + name: 'nginx', + version: '0.0.1', + status: 'not_installed', + }, + ]); + }); +}); diff --git a/x-pack/plugins/fleet/common/services/policy_template.ts b/x-pack/plugins/fleet/common/services/policy_template.ts index ed390e0c6b45d..efa65a880576a 100644 --- a/x-pack/plugins/fleet/common/services/policy_template.ts +++ b/x-pack/plugins/fleet/common/services/policy_template.ts @@ -39,6 +39,7 @@ export function packageHasNoPolicyTemplates(packageInfo: PackageInfo): boolean { ) ); } + export function isInputOnlyPolicyTemplate( policyTemplate: RegistryPolicyTemplate ): policyTemplate is RegistryPolicyInputOnlyTemplate { @@ -142,3 +143,27 @@ const createDefaultDatasetName = ( packageInfo: { name: string }, policyTemplate: { name: string } ): string => packageInfo.name + '.' + policyTemplate.name; + +export function filterPolicyTemplatesTiles( + templatesBehavior: string | undefined, + packagePolicy: T, + packagePolicyTemplates: T[] +): T[] { + switch (templatesBehavior || 'all') { + case 'combined_policy': + return [packagePolicy]; + case 'individual_policies': + return [ + ...(packagePolicyTemplates && packagePolicyTemplates.length > 1 + ? packagePolicyTemplates + : []), + ]; + default: + return [ + packagePolicy, + ...(packagePolicyTemplates && packagePolicyTemplates.length > 1 + ? packagePolicyTemplates + : []), + ]; + } +} diff --git a/x-pack/plugins/fleet/common/types/models/epm.ts b/x-pack/plugins/fleet/common/types/models/epm.ts index f1cd9e5ee4a7f..36a83c1c0a09e 100644 --- a/x-pack/plugins/fleet/common/types/models/epm.ts +++ b/x-pack/plugins/fleet/common/types/models/epm.ts @@ -311,6 +311,7 @@ export type RegistrySearchResult = Pick< | 'icons' | 'internal' | 'data_streams' + | 'policy_templates_behavior' | 'policy_templates' | 'categories' >; diff --git a/x-pack/plugins/fleet/common/types/models/package_spec.ts b/x-pack/plugins/fleet/common/types/models/package_spec.ts index 18c10e4617417..6ae8ba984f5f6 100644 --- a/x-pack/plugins/fleet/common/types/models/package_spec.ts +++ b/x-pack/plugins/fleet/common/types/models/package_spec.ts @@ -24,6 +24,7 @@ export interface PackageSpecManifest { conditions?: PackageSpecConditions; icons?: PackageSpecIcon[]; screenshots?: PackageSpecScreenshot[]; + policy_templates_behavior?: 'all' | 'combined_policy' | 'individual_policies'; policy_templates?: RegistryPolicyTemplate[]; vars?: RegistryVarsEntry[]; owner: { github?: string; type?: 'elastic' | 'partner' | 'community' }; diff --git a/x-pack/plugins/fleet/public/applications/integrations/sections/epm/screens/home/hooks/use_available_packages.tsx b/x-pack/plugins/fleet/public/applications/integrations/sections/epm/screens/home/hooks/use_available_packages.tsx index 2f506b30b2626..c399a0241c22a 100644 --- a/x-pack/plugins/fleet/public/applications/integrations/sections/epm/screens/home/hooks/use_available_packages.tsx +++ b/x-pack/plugins/fleet/public/applications/integrations/sections/epm/screens/home/hooks/use_available_packages.tsx @@ -28,6 +28,7 @@ import { doesPackageHaveIntegrations, ExperimentalFeaturesService } from '../../ import { isInputOnlyPolicyTemplate, isIntegrationPolicyTemplate, + filterPolicyTemplatesTiles, } from '../../../../../../../../common/services'; import { @@ -83,30 +84,33 @@ const packageListToIntegrationsList = (packages: PackageList): PackageList => { categories: getAllCategoriesFromIntegrations(pkg), }; - return [ - ...acc, + const integrationsPolicyTemplates = doesPackageHaveIntegrations(pkg) + ? policyTemplates.map((policyTemplate) => { + const { name, title, description, icons } = policyTemplate; + + const categories = + isIntegrationPolicyTemplate(policyTemplate) && policyTemplate.categories + ? policyTemplate.categories + : []; + const allCategories = [...topCategories, ...categories]; + return { + ...restOfPackage, + id: `${restOfPackage.id}-${name}`, + integration: name, + title, + description, + icons: icons || restOfPackage.icons, + categories: uniq(allCategories), + }; + }) + : []; + + const tiles = filterPolicyTemplatesTiles( + pkg.policy_templates_behavior, topPackage, - ...(doesPackageHaveIntegrations(pkg) - ? policyTemplates.map((policyTemplate) => { - const { name, title, description, icons } = policyTemplate; - - const categories = - isIntegrationPolicyTemplate(policyTemplate) && policyTemplate.categories - ? policyTemplate.categories - : []; - const allCategories = [...topCategories, ...categories]; - return { - ...restOfPackage, - id: `${restOfPackage.id}-${name}`, - integration: name, - title, - description, - icons: icons || restOfPackage.icons, - categories: uniq(allCategories), - }; - }) - : []), - ]; + integrationsPolicyTemplates + ); + return [...acc, ...tiles]; }, []); }; diff --git a/x-pack/plugins/fleet/public/search_provider.test.ts b/x-pack/plugins/fleet/public/search_provider.test.ts index 68ba3042a8e76..4ff04862c756f 100644 --- a/x-pack/plugins/fleet/public/search_provider.test.ts +++ b/x-pack/plugins/fleet/public/search_provider.test.ts @@ -112,6 +112,123 @@ const testResponse: GetPackagesResponse['items'] = [ }, ]; +const testResponseBehaviorField: GetPackagesResponse['items'] = [ + { + description: 'testWithPolicyTemplateBehaviorAll', + download: 'testWithPolicyTemplateBehaviorAll', + id: 'testWithPolicyTemplateBehaviorAll', + name: 'testWithPolicyTemplateBehaviorAll', + path: 'testWithPolicyTemplateBehaviorAll', + release: 'ga', + status: 'not_installed', + title: 'testWithPolicyTemplateBehaviorAll', + version: 'testWithPolicyTemplateBehaviorAll', + policy_templates_behavior: 'all', + policy_templates: [ + { + description: 'testPolicyTemplate1BehaviorAll', + name: 'testPolicyTemplate1BehaviorAll', + icons: [ + { + src: 'testPolicyTemplate1BehaviorAll', + path: 'testPolicyTemplate1BehaviorAll', + }, + ], + title: 'testPolicyTemplate1BehaviorAll', + type: 'testPolicyTemplate1BehaviorAll', + }, + { + description: 'testPolicyTemplate2BehaviorAll', + name: 'testPolicyTemplate2BehaviorAll', + icons: [ + { + src: 'testPolicyTemplate2BehaviorAll', + path: 'testPolicyTemplate2BehaviorAll', + }, + ], + title: 'testPolicyTemplate2BehaviorAll', + type: 'testPolicyTemplate2BehaviorAll', + }, + ], + }, + { + description: 'testWithPolicyTemplateBehaviorCombined', + download: 'testWithPolicyTemplateBehaviorCombined', + id: 'testWithPolicyTemplateBehaviorCombined', + name: 'testWithPolicyTemplateBehaviorCombined', + path: 'testWithPolicyTemplateBehaviorCombined', + release: 'ga', + status: 'not_installed', + title: 'testWithPolicyTemplateBehaviorCombined', + version: 'testWithPolicyTemplateBehaviorCombined', + policy_templates_behavior: 'combined_policy', + policy_templates: [ + { + description: 'testPolicyTemplate1BehaviorCombined', + name: 'testPolicyTemplate1BehaviorCombined', + icons: [ + { + src: 'testPolicyTemplate1BehaviorCombined', + path: 'testPolicyTemplate1BehaviorCombined', + }, + ], + title: 'testPolicyTemplate1BehaviorCombined', + type: 'testPolicyTemplate1BehaviorCombined', + }, + { + description: 'testPolicyTemplate2BehaviorCombined', + name: 'testPolicyTemplate2BehaviorCombined', + icons: [ + { + src: 'testPolicyTemplate2BehaviorCombined', + path: 'testPolicyTemplate2BehaviorCombined', + }, + ], + title: 'testPolicyTemplate2BehaviorCombined', + type: 'testPolicyTemplate2BehaviorCombined', + }, + ], + }, + { + description: 'testWithPolicyTemplateBehaviorIndividual', + download: 'testWithPolicyTemplateBehaviorIndividual', + id: 'testWithPolicyTemplateBehaviorIndividual', + name: 'testWithPolicyTemplateBehaviorIndividual', + path: 'testWithPolicyTemplateBehaviorIndividual', + release: 'ga', + status: 'not_installed', + title: 'testWithPolicyTemplateBehaviorIndividual', + version: 'testWithPolicyTemplateBehaviorIndividual', + policy_templates_behavior: 'individual_policies', + policy_templates: [ + { + description: 'testPolicyTemplate1BehaviorIndividual', + name: 'testPolicyTemplate1BehaviorIndividual', + icons: [ + { + src: 'testPolicyTemplate1BehaviorIndividual', + path: 'testPolicyTemplate1BehaviorIndividual', + }, + ], + title: 'testPolicyTemplate1BehaviorIndividual', + type: 'testPolicyTemplate1BehaviorIndividual', + }, + { + description: 'testPolicyTemplate2BehaviorIndividual', + name: 'testPolicyTemplate2BehaviorIndividual', + icons: [ + { + src: 'testPolicyTemplate2BehaviorIndividual', + path: 'testPolicyTemplate2BehaviorIndividual', + }, + ], + title: 'testPolicyTemplate2BehaviorIndividual', + type: 'testPolicyTemplate2BehaviorIndividual', + }, + ], + }, +]; + const getTestScheduler = () => { return new TestScheduler((actual, expected) => { return expect(actual).toEqual(expected); @@ -394,6 +511,89 @@ describe('Package search provider', () => { expect(sendGetPackages).toHaveBeenCalledTimes(1); }); + test('with integration tag, with policy_templates_behavior field', () => { + getTestScheduler().run(({ hot, expectObservable }) => { + mockSendGetPackages.mockReturnValue( + hot('--(a|)', { a: { data: { items: testResponseBehaviorField } } }) + ); + setupMock.getStartServices.mockReturnValue( + hot('--(a|)', { a: [coreMock.createStart()] }) as any + ); + const packageSearchProvider = createPackageSearchProvider(setupMock); + expectObservable( + packageSearchProvider.find( + { types: ['integration'] }, + { aborted$: NEVER, maxResults: 100, preference: '' } + ) + ).toBe('--(a|)', { + a: [ + { + id: 'testWithPolicyTemplateBehaviorAll', + score: 80, + title: 'testWithPolicyTemplateBehaviorAll', + type: 'integration', + url: { + path: 'undefined/detail/testWithPolicyTemplateBehaviorAll/overview', + prependBasePath: false, + }, + }, + { + id: 'testPolicyTemplate1BehaviorAll', + score: 80, + title: 'testPolicyTemplate1BehaviorAll', + type: 'integration', + url: { + path: 'undefined/detail/testWithPolicyTemplateBehaviorAll/overview?integration=testPolicyTemplate1BehaviorAll', + prependBasePath: false, + }, + }, + { + id: 'testPolicyTemplate2BehaviorAll', + score: 80, + title: 'testPolicyTemplate2BehaviorAll', + type: 'integration', + url: { + path: 'undefined/detail/testWithPolicyTemplateBehaviorAll/overview?integration=testPolicyTemplate2BehaviorAll', + prependBasePath: false, + }, + }, + { + id: 'testWithPolicyTemplateBehaviorCombined', + score: 80, + title: 'testWithPolicyTemplateBehaviorCombined', + type: 'integration', + url: { + path: 'undefined/detail/testWithPolicyTemplateBehaviorCombined/overview', + prependBasePath: false, + }, + }, + { + id: 'testPolicyTemplate1BehaviorIndividual', + score: 80, + title: 'testPolicyTemplate1BehaviorIndividual', + type: 'integration', + url: { + path: 'undefined/detail/testWithPolicyTemplateBehaviorIndividual/overview?integration=testPolicyTemplate1BehaviorIndividual', + prependBasePath: false, + }, + }, + { + id: 'testPolicyTemplate2BehaviorIndividual', + score: 80, + title: 'testPolicyTemplate2BehaviorIndividual', + type: 'integration', + url: { + path: 'undefined/detail/testWithPolicyTemplateBehaviorIndividual/overview?integration=testPolicyTemplate2BehaviorIndividual', + prependBasePath: false, + }, + }, + ], + }); + }); + + expect(sendGetPackages).toHaveBeenCalledTimes(1); + }); + test('with integration tag, with search term', () => { getTestScheduler().run(({ hot, expectObservable }) => { mockSendGetPackages.mockReturnValue( diff --git a/x-pack/plugins/fleet/public/search_provider.ts b/x-pack/plugins/fleet/public/search_provider.ts index a6810633c428e..c329443288e4b 100644 --- a/x-pack/plugins/fleet/public/search_provider.ts +++ b/x-pack/plugins/fleet/public/search_provider.ts @@ -16,6 +16,7 @@ import type { } from '@kbn/global-search-plugin/public'; import { INTEGRATIONS_PLUGIN_ID } from '../common'; +import { filterPolicyTemplatesTiles } from '../common/services'; import { sendGetPackages } from './hooks'; import type { GetPackagesResponse, PackageListItem } from './types'; @@ -74,10 +75,12 @@ export const toSearchResult = ( }) ); - return [ + const tiles = filterPolicyTemplatesTiles( + pkg.policy_templates_behavior, packageResult, - ...(policyTemplateResults && policyTemplateResults.length > 1 ? policyTemplateResults : []), - ]; + policyTemplateResults || [] + ); + return [...tiles]; }; export const createPackageSearchProvider = (core: CoreSetup): GlobalSearchResultProvider => {