diff --git a/x-pack/plugins/ingest_pipelines/kibana.jsonc b/x-pack/plugins/ingest_pipelines/kibana.jsonc index 29ce5892764b..41e6eb32ef77 100644 --- a/x-pack/plugins/ingest_pipelines/kibana.jsonc +++ b/x-pack/plugins/ingest_pipelines/kibana.jsonc @@ -11,6 +11,7 @@ "ingest_pipelines" ], "requiredPlugins": [ + "licensing", "management", "features", "share", diff --git a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_editor/__jest__/pipeline_processors_editor.test.tsx b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_editor/__jest__/pipeline_processors_editor.test.tsx index 86acbec567ee..044121d1307d 100644 --- a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_editor/__jest__/pipeline_processors_editor.test.tsx +++ b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_editor/__jest__/pipeline_processors_editor.test.tsx @@ -6,8 +6,14 @@ */ import { act } from 'react-dom/test-utils'; +import { licensingMock } from '@kbn/licensing-plugin/server/mocks'; import { setup, SetupResult } from './pipeline_processors_editor.helpers'; import { Pipeline } from '../../../../../common/types'; +import { + extractProcessorDetails, + getProcessorTypesAndLabels, +} from '../components/processor_form/processors/common_fields/processor_type_field'; +import { mapProcessorTypeToDescriptor } from '../components/shared/map_processor_type_to_form'; const testProcessors: Pick = { processors: [ @@ -96,6 +102,28 @@ describe('Pipeline Editor', () => { expect(d).toEqual({ test: { if: '1 == 1' } }); }); + it('Shows inference and redact processors for licenses > platinum', async () => { + const basicLicense = licensingMock.createLicense({ + license: { status: 'active', type: 'basic' }, + }); + const platinumLicense = licensingMock.createLicense({ + license: { status: 'active', type: 'platinum' }, + }); + + // Get the list of processors that are only available for platinum licenses + const processorsForPlatinumLicense = extractProcessorDetails(mapProcessorTypeToDescriptor) + .filter((processor) => processor.forLicenseAtLeast === 'platinum') + .map(({ value, label }) => ({ label, value })); + + // Check that the list of processors for platinum licenses is not included in the list of processors for basic licenses + expect(getProcessorTypesAndLabels(basicLicense)).toEqual( + expect.not.arrayContaining(processorsForPlatinumLicense) + ); + expect(getProcessorTypesAndLabels(platinumLicense)).toEqual( + expect.arrayContaining(processorsForPlatinumLicense) + ); + }); + it('edits a processor without removing unknown processor.options', async () => { const { actions, exists, form } = testBed; // Open the edit processor form for the set processor diff --git a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_editor/components/processor_form/processors/common_fields/processor_type_field.tsx b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_editor/components/processor_form/processors/common_fields/processor_type_field.tsx index 756368d3b76e..a93836e8fdf7 100644 --- a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_editor/components/processor_form/processors/common_fields/processor_type_field.tsx +++ b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_editor/components/processor_form/processors/common_fields/processor_type_field.tsx @@ -7,7 +7,7 @@ import { EuiComboBox, EuiComboBoxOptionOption, EuiFormRow } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; -import React, { FunctionComponent, ReactNode } from 'react'; +import React, { FunctionComponent, ReactNode, useMemo } from 'react'; import { flow } from 'fp-ts/lib/function'; import { map } from 'fp-ts/lib/Array'; @@ -15,6 +15,7 @@ import { FieldValidateResponse, VALIDATION_TYPES, } from '@kbn/es-ui-shared-plugin/static/forms/hook_form_lib'; +import { ILicense } from '../../../../../../../types'; import { FIELD_TYPES, FieldConfig, @@ -25,11 +26,12 @@ import { import { getProcessorDescriptor, mapProcessorTypeToDescriptor } from '../../../shared'; -const extractProcessorTypesAndLabels = flow( +export const extractProcessorDetails = flow( Object.entries, - map(([type, { label }]) => ({ + map(([type, { label, forLicenseAtLeast }]) => ({ label, value: type, + ...(forLicenseAtLeast ? { forLicenseAtLeast } : {}), })), (arr) => arr.sort((a, b) => a.label.localeCompare(b.label)) ); @@ -39,9 +41,17 @@ interface ProcessorTypeAndLabel { label: string; } -const processorTypesAndLabels: ProcessorTypeAndLabel[] = extractProcessorTypesAndLabels( - mapProcessorTypeToDescriptor -); +export const getProcessorTypesAndLabels = (license: ILicense | null) => { + return ( + extractProcessorDetails(mapProcessorTypeToDescriptor) + // Filter out any processors that are not available for the current license type + .filter((option) => { + return option.forLicenseAtLeast ? license?.hasAtLeast(option.forLicenseAtLeast) : true; + }) + // Convert to EuiComboBox options + .map(({ value, label }) => ({ label, value })) + ); +}; interface Props { initialType?: string; @@ -68,9 +78,12 @@ const typeConfig: FieldConfig = { export const ProcessorTypeField: FunctionComponent = ({ initialType }) => { const { - services: { documentation }, + services: { documentation, license }, } = useKibana(); const esDocUrl = documentation.getEsDocsBasePath(); + // Some processors are only available for certain license types + const processorOptions = useMemo(() => getProcessorTypesAndLabels(license), [license]); + return ( config={typeConfig} defaultValue={initialType} path="type"> {(typeField) => { @@ -128,7 +141,7 @@ export const ProcessorTypeField: FunctionComponent = ({ initialType }) => defaultMessage: 'Type and then hit "ENTER"', } )} - options={processorTypesAndLabels} + options={processorOptions} selectedOptions={selectedOptions} onCreateOption={onCreateComboOption} onChange={(options: Array>) => { diff --git a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_editor/components/shared/map_processor_type_to_form.tsx b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_editor/components/shared/map_processor_type_to_form.tsx index cd53afedcc34..3218ca456f95 100644 --- a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_editor/components/shared/map_processor_type_to_form.tsx +++ b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_editor/components/shared/map_processor_type_to_form.tsx @@ -10,6 +10,8 @@ import React, { ReactNode } from 'react'; import { FormattedMessage } from '@kbn/i18n-react'; import { EuiCode, EuiLink } from '@elastic/eui'; +import { LicenseType } from '../../../../../types'; + import { Append, Bytes, @@ -69,6 +71,10 @@ interface FieldDescriptor { * Default */ getDefaultDescription: (processorOptions: Record) => string | undefined; + /** + * Some processors are only available for certain license types + */ + forLicenseAtLeast?: LicenseType; } type MapProcessorTypeToDescriptor = Record; @@ -453,6 +459,7 @@ export const mapProcessorTypeToDescriptor: MapProcessorTypeToDescriptor = { }, inference: { FieldsComponent: Inference, + forLicenseAtLeast: 'platinum', docLinkPath: '/inference-processor.html', label: i18n.translate('xpack.ingestPipelines.processors.label.inference', { defaultMessage: 'Inference', @@ -580,6 +587,7 @@ export const mapProcessorTypeToDescriptor: MapProcessorTypeToDescriptor = { }, redact: { FieldsComponent: Redact, + forLicenseAtLeast: 'platinum', docLinkPath: '/redact-processor.html', label: i18n.translate('xpack.ingestPipelines.processors.label.redact', { defaultMessage: 'Redact', diff --git a/x-pack/plugins/ingest_pipelines/public/application/index.tsx b/x-pack/plugins/ingest_pipelines/public/application/index.tsx index 56ae3e8da6d9..71006851e0ca 100644 --- a/x-pack/plugins/ingest_pipelines/public/application/index.tsx +++ b/x-pack/plugins/ingest_pipelines/public/application/index.tsx @@ -16,6 +16,7 @@ import { ManagementAppMountParams } from '@kbn/management-plugin/public'; import type { SharePluginStart } from '@kbn/share-plugin/public'; import type { FileUploadPluginStart } from '@kbn/file-upload-plugin/public'; import { KibanaContextProvider, KibanaThemeProvider } from '../shared_imports'; +import { ILicense } from '../types'; import { API_BASE_PATH } from '../../common/constants'; @@ -42,6 +43,7 @@ export interface AppServices { share: SharePluginStart; fileUpload: FileUploadPluginStart; application: ApplicationStart; + license: ILicense | null; } export interface CoreServices { diff --git a/x-pack/plugins/ingest_pipelines/public/application/mount_management_section.ts b/x-pack/plugins/ingest_pipelines/public/application/mount_management_section.ts index bc6077cc77d7..09bd6366f234 100644 --- a/x-pack/plugins/ingest_pipelines/public/application/mount_management_section.ts +++ b/x-pack/plugins/ingest_pipelines/public/application/mount_management_section.ts @@ -8,7 +8,7 @@ import { CoreSetup } from '@kbn/core/public'; import { ManagementAppMountParams } from '@kbn/management-plugin/public'; -import { StartDependencies } from '../types'; +import { StartDependencies, ILicense } from '../types'; import { documentationService, uiMetricService, @@ -18,11 +18,15 @@ import { } from './services'; import { renderApp } from '.'; +export interface AppParams extends ManagementAppMountParams { + license: ILicense | null; +} + export async function mountManagementSection( { http, getStartServices, notifications }: CoreSetup, - params: ManagementAppMountParams + params: AppParams ) { - const { element, setBreadcrumbs, history, theme$ } = params; + const { element, setBreadcrumbs, history, theme$, license } = params; const [coreStart, depsStart] = await getStartServices(); const { docLinks, @@ -47,6 +51,7 @@ export async function mountManagementSection( fileUpload: depsStart.fileUpload, application, executionContext, + license, }; return renderApp(element, I18nContext, services, { http }, { theme$ }); diff --git a/x-pack/plugins/ingest_pipelines/public/plugin.ts b/x-pack/plugins/ingest_pipelines/public/plugin.ts index 674c549e0f34..ae180b8378af 100644 --- a/x-pack/plugins/ingest_pipelines/public/plugin.ts +++ b/x-pack/plugins/ingest_pipelines/public/plugin.ts @@ -6,16 +6,20 @@ */ import { i18n } from '@kbn/i18n'; -import { CoreSetup, Plugin } from '@kbn/core/public'; +import { Subscription } from 'rxjs'; +import { CoreStart, CoreSetup, Plugin } from '@kbn/core/public'; import { PLUGIN_ID } from '../common/constants'; import { uiMetricService, apiService } from './application/services'; -import { SetupDependencies, StartDependencies } from './types'; +import { SetupDependencies, StartDependencies, ILicense } from './types'; import { IngestPipelinesLocatorDefinition } from './locator'; export class IngestPipelinesPlugin implements Plugin { + private license: ILicense | null = null; + private licensingSubscription?: Subscription; + public setup(coreSetup: CoreSetup, plugins: SetupDependencies): void { const { management, usageCollection, share } = plugins; const { http, getStartServices } = coreSetup; @@ -42,7 +46,10 @@ export class IngestPipelinesPlugin docTitle.change(pluginName); const { mountManagementSection } = await import('./application/mount_management_section'); - const unmountAppCallback = await mountManagementSection(coreSetup, params); + const unmountAppCallback = await mountManagementSection(coreSetup, { + ...params, + license: this.license, + }); return () => { docTitle.reset(); @@ -58,7 +65,13 @@ export class IngestPipelinesPlugin ); } - public start() {} + public start(core: CoreStart, { licensing }: StartDependencies) { + this.licensingSubscription = licensing?.license$.subscribe((license) => { + this.license = license; + }); + } - public stop() {} + public stop() { + this.licensingSubscription?.unsubscribe(); + } } diff --git a/x-pack/plugins/ingest_pipelines/public/types.ts b/x-pack/plugins/ingest_pipelines/public/types.ts index e937051e656d..d6c249c0b041 100644 --- a/x-pack/plugins/ingest_pipelines/public/types.ts +++ b/x-pack/plugins/ingest_pipelines/public/types.ts @@ -9,6 +9,8 @@ import { ManagementSetup } from '@kbn/management-plugin/public'; import { UsageCollectionSetup } from '@kbn/usage-collection-plugin/public'; import { SharePluginStart, SharePluginSetup } from '@kbn/share-plugin/public'; import type { FileUploadPluginStart } from '@kbn/file-upload-plugin/public'; +import { LicensingPluginStart } from '@kbn/licensing-plugin/public'; +export type { LicenseType, ILicense } from '@kbn/licensing-plugin/public'; export interface SetupDependencies { management: ManagementSetup; @@ -19,4 +21,5 @@ export interface SetupDependencies { export interface StartDependencies { share: SharePluginStart; fileUpload: FileUploadPluginStart; + licensing?: LicensingPluginStart; }