diff --git a/x-pack/plugins/transform/common/api_schemas/type_guards.ts b/x-pack/plugins/transform/common/api_schemas/type_guards.ts index 6c572a195b65f4c..c9b9f98e21dc30e 100644 --- a/x-pack/plugins/transform/common/api_schemas/type_guards.ts +++ b/x-pack/plugins/transform/common/api_schemas/type_guards.ts @@ -8,6 +8,7 @@ import type * as estypes from '@elastic/elasticsearch/lib/api/typesWithBodyKey'; import type { EsIndex } from '../types/es_index'; +import type { EsIngestPipeline } from '../types/es_ingest_pipeline'; import { isPopulatedObject } from '../shared_imports'; // To be able to use the type guards on the client side, we need to make sure we don't import @@ -60,6 +61,10 @@ export const isEsIndices = (arg: unknown): arg is EsIndex[] => { return Array.isArray(arg); }; +export const isEsIngestPipelines = (arg: unknown): arg is EsIngestPipeline[] => { + return Array.isArray(arg) && arg.every((d) => isPopulatedObject(d, ['name'])); +}; + export const isEsSearchResponse = (arg: unknown): arg is estypes.SearchResponse => { return isPopulatedObject(arg, ['hits']); }; diff --git a/x-pack/plugins/transform/common/types/es_ingest_pipeline.ts b/x-pack/plugins/transform/common/types/es_ingest_pipeline.ts new file mode 100644 index 000000000000000..61dc8effa3657a8 --- /dev/null +++ b/x-pack/plugins/transform/common/types/es_ingest_pipeline.ts @@ -0,0 +1,13 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +// This interface doesn't cover a full ingest pipeline spec, +// just what's necessary to make it work in the transform creation wizard. +// The full interface can be found in x-pack/plugins/ingest_pipelines/common/types.ts +export interface EsIngestPipeline { + name: string; +} diff --git a/x-pack/plugins/transform/public/app/common/request.ts b/x-pack/plugins/transform/public/app/common/request.ts index 463723051d57527..36776759eb47a59 100644 --- a/x-pack/plugins/transform/public/app/common/request.ts +++ b/x-pack/plugins/transform/public/app/common/request.ts @@ -219,6 +219,10 @@ export const getCreateTransformRequestBody = ( : {}), dest: { index: transformDetailsState.destinationIndex, + // conditionally add optional ingest pipeline + ...(transformDetailsState.destinationIngestPipeline !== '' + ? { pipeline: transformDetailsState.destinationIngestPipeline } + : {}), }, // conditionally add continuous mode config ...(transformDetailsState.isContinuousModeEnabled diff --git a/x-pack/plugins/transform/public/app/hooks/use_api.ts b/x-pack/plugins/transform/public/app/hooks/use_api.ts index 21e37ca16c4de70..bc3f990ab2c4b34 100644 --- a/x-pack/plugins/transform/public/app/hooks/use_api.ts +++ b/x-pack/plugins/transform/public/app/hooks/use_api.ts @@ -46,6 +46,7 @@ import type { GetTransformsStatsResponseSchema } from '../../../common/api_schem import { TransformId } from '../../../common/types/transform'; import { API_BASE_PATH } from '../../../common/constants'; import { EsIndex } from '../../../common/types/es_index'; +import { EsIngestPipeline } from '../../../common/types/es_ingest_pipeline'; import { useAppDependencies } from '../app_dependencies'; @@ -202,6 +203,13 @@ export const useApi = () => { return e; } }, + async getEsIngestPipelines(): Promise { + try { + return await http.get('/api/ingest_pipelines'); + } catch (e) { + return e; + } + }, async getHistogramsForFields( indexPatternTitle: string, fields: FieldHistogramRequestConfig[], diff --git a/x-pack/plugins/transform/public/app/sections/create_transform/components/step_details/common.ts b/x-pack/plugins/transform/public/app/sections/create_transform/components/step_details/common.ts index 21e6bce204ec85e..e5122c53422ce55 100644 --- a/x-pack/plugins/transform/public/app/sections/create_transform/components/step_details/common.ts +++ b/x-pack/plugins/transform/public/app/sections/create_transform/components/step_details/common.ts @@ -8,6 +8,7 @@ import type { TransformConfigUnion, TransformId } from '../../../../../../common/types/transform'; export type EsIndexName = string; +export type EsIngestPipelineName = string; export type IndexPatternTitle = string; export interface StepDetailsExposedState { @@ -15,6 +16,7 @@ export interface StepDetailsExposedState { continuousModeDelay: string; createIndexPattern: boolean; destinationIndex: EsIndexName; + destinationIngestPipeline: EsIngestPipelineName; isContinuousModeEnabled: boolean; isRetentionPolicyEnabled: boolean; retentionPolicyDateField: string; @@ -48,6 +50,7 @@ export function getDefaultStepDetailsState(): StepDetailsExposedState { transformFrequency: defaultTransformFrequency, transformSettingsMaxPageSearchSize: defaultTransformSettingsMaxPageSearchSize, destinationIndex: '', + destinationIngestPipeline: '', touched: false, valid: false, indexPatternTimeField: undefined, @@ -73,6 +76,11 @@ export function applyTransformConfigToDetailsState( state.transformDescription = transformConfig.description; } + // Ingest Pipeline + if (transformConfig.dest.pipeline !== undefined) { + state.destinationIngestPipeline = transformConfig.dest.pipeline; + } + // Frequency if (transformConfig.frequency !== undefined) { state.transformFrequency = transformConfig.frequency; diff --git a/x-pack/plugins/transform/public/app/sections/create_transform/components/step_details/step_details_form.tsx b/x-pack/plugins/transform/public/app/sections/create_transform/components/step_details/step_details_form.tsx index 4f1c99e0f7f0568..c3fa4e942ced327 100644 --- a/x-pack/plugins/transform/public/app/sections/create_transform/components/step_details/step_details_form.tsx +++ b/x-pack/plugins/transform/public/app/sections/create_transform/components/step_details/step_details_form.tsx @@ -28,6 +28,7 @@ import { toMountPoint } from '../../../../../../../../../src/plugins/kibana_reac import { isEsIndices, + isEsIngestPipelines, isPostTransformsPreviewResponseSchema, } from '../../../../../../common/api_schemas/type_guards'; import { TransformId } from '../../../../../../common/types/transform'; @@ -82,8 +83,12 @@ export const StepDetailsForm: FC = React.memo( const [destinationIndex, setDestinationIndex] = useState( defaults.destinationIndex ); + const [destinationIngestPipeline, setDestinationIngestPipeline] = useState( + defaults.destinationIngestPipeline + ); const [transformIds, setTransformIds] = useState([]); const [indexNames, setIndexNames] = useState([]); + const [ingestPipelineNames, setIngestPipelineNames] = useState([]); const canCreateDataView = useMemo( () => @@ -180,7 +185,10 @@ export const StepDetailsForm: FC = React.memo( setTransformIds(resp.transforms.map((transform) => transform.id)); } - const indices = await api.getEsIndices(); + const [indices, ingestPipelines] = await Promise.all([ + api.getEsIndices(), + api.getEsIngestPipelines(), + ]); if (isEsIndices(indices)) { setIndexNames(indices.map((index) => index.name)); @@ -200,6 +208,24 @@ export const StepDetailsForm: FC = React.memo( }); } + if (isEsIngestPipelines(ingestPipelines)) { + setIngestPipelineNames(ingestPipelines.map(({ name }) => name)); + } else { + toastNotifications.addDanger({ + title: i18n.translate('xpack.transform.stepDetailsForm.errorGettingIngestPipelines', { + defaultMessage: 'An error occurred getting the existing ingest pipeline names:', + }), + text: toMountPoint( + , + { theme$: theme.theme$ } + ), + }); + } + try { setIndexPatternTitles(await deps.data.indexPatterns.getTitles()); } catch (e) { @@ -311,6 +337,7 @@ export const StepDetailsForm: FC = React.memo( transformSettingsMaxPageSearchSize, transformSettingsDocsPerSecond, destinationIndex, + destinationIngestPipeline, touched: true, valid, indexPatternTimeField, @@ -443,6 +470,25 @@ export const StepDetailsForm: FC = React.memo( /> + {ingestPipelineNames.length > 0 && ( + + ({ text, value: text }))} + value={destinationIngestPipeline} + onChange={(e) => setDestinationIngestPipeline(e.target.value)} + hasNoInitialSelection={defaults.destinationIngestPipeline === ''} + data-test-subj="transformDestinationPipelineSelect" + /> + + )} + {stepDefineState.transformFunction === TRANSFORM_FUNCTION.LATEST ? ( <>