[Ingest Pipelines] Add license checks for processors (#154525)

This commit is contained in:
Ignacio Rivas 2023-04-12 14:28:51 +02:00 committed by GitHub
parent dd083a351a
commit aefc949911
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
8 changed files with 89 additions and 16 deletions

View file

@ -11,6 +11,7 @@
"ingest_pipelines"
],
"requiredPlugins": [
"licensing",
"management",
"features",
"share",

View file

@ -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<Pipeline, 'processors'> = {
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

View file

@ -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<string> = {
export const ProcessorTypeField: FunctionComponent<Props> = ({ 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 (
<UseField<string> config={typeConfig} defaultValue={initialType} path="type">
{(typeField) => {
@ -128,7 +141,7 @@ export const ProcessorTypeField: FunctionComponent<Props> = ({ initialType }) =>
defaultMessage: 'Type and then hit "ENTER"',
}
)}
options={processorTypesAndLabels}
options={processorOptions}
selectedOptions={selectedOptions}
onCreateOption={onCreateComboOption}
onChange={(options: Array<EuiComboBoxOptionOption<string>>) => {

View file

@ -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, any>) => string | undefined;
/**
* Some processors are only available for certain license types
*/
forLicenseAtLeast?: LicenseType;
}
type MapProcessorTypeToDescriptor = Record<string, FieldDescriptor>;
@ -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',

View file

@ -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 {

View file

@ -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<StartDependencies>,
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$ });

View file

@ -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<void, void, SetupDependencies, StartDependencies>
{
private license: ILicense | null = null;
private licensingSubscription?: Subscription;
public setup(coreSetup: CoreSetup<StartDependencies>, 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();
}
}

View file

@ -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;
}