From 49fdd24f80fb46af2967fd5c705e4387b63e6526 Mon Sep 17 00:00:00 2001 From: Walter Rafelsberger Date: Wed, 4 Sep 2019 06:54:33 -0700 Subject: [PATCH] [ML] Data frame analytics: Split comma-separated Kibana index patterns to array of indices. (#44757) Kibana index patterns can be comma-separated whereas the create analytics API takes an array of indices. This fixes the analytics creation UI to convert Kibana index patterns to ES index patterns. --- .../data_frame_analytics/common/analytics.ts | 2 +- .../use_create_analytics_form/reducer.test.ts | 48 ++++++++++++++++++- .../use_create_analytics_form/reducer.ts | 36 ++++++++++++-- .../use_create_analytics_form/state.test.ts | 8 ++++ .../hooks/use_create_analytics_form/state.ts | 7 ++- 5 files changed, 95 insertions(+), 6 deletions(-) diff --git a/x-pack/legacy/plugins/ml/public/data_frame_analytics/common/analytics.ts b/x-pack/legacy/plugins/ml/public/data_frame_analytics/common/analytics.ts index 5faf20991056a..95310b414d6f9 100644 --- a/x-pack/legacy/plugins/ml/public/data_frame_analytics/common/analytics.ts +++ b/x-pack/legacy/plugins/ml/public/data_frame_analytics/common/analytics.ts @@ -65,7 +65,7 @@ export interface DataFrameAnalyticsConfig { results_field: string; }; source: { - index: IndexName; + index: IndexName | IndexName[]; }; analysis: AnalysisConfig; analyzed_fields: { diff --git a/x-pack/legacy/plugins/ml/public/data_frame_analytics/pages/analytics_management/hooks/use_create_analytics_form/reducer.test.ts b/x-pack/legacy/plugins/ml/public/data_frame_analytics/pages/analytics_management/hooks/use_create_analytics_form/reducer.test.ts index b74611fe9c77b..46cf833610073 100644 --- a/x-pack/legacy/plugins/ml/public/data_frame_analytics/pages/analytics_management/hooks/use_create_analytics_form/reducer.test.ts +++ b/x-pack/legacy/plugins/ml/public/data_frame_analytics/pages/analytics_management/hooks/use_create_analytics_form/reducer.test.ts @@ -4,14 +4,34 @@ * you may not use this file except in compliance with the Elastic License. */ +import { merge } from 'lodash'; + +import { DataFrameAnalyticsConfig } from '../../../../common'; + import { ACTION } from './actions'; -import { reducer } from './reducer'; +import { reducer, validateAdvancedEditor } from './reducer'; import { getInitialState } from './state'; jest.mock('ui/index_patterns', () => ({ validateIndexPattern: () => true, })); +type SourceIndex = DataFrameAnalyticsConfig['source']['index']; + +const getMockState = (index: SourceIndex) => + merge(getInitialState(), { + form: { + jobIdEmpty: false, + jobIdValid: true, + jobIdExists: false, + createIndexPattern: false, + }, + jobConfig: { + source: { index }, + dest: { index: 'the-destination-index' }, + }, + }); + describe('useCreateAnalyticsForm', () => { test('reducer(): provide a minimum required valid job config, then reset.', () => { const initialState = getInitialState(); @@ -65,4 +85,30 @@ describe('useCreateAnalyticsForm', () => { }); expect(resetMessageState.requestMessages).toHaveLength(0); }); + + test('validateAdvancedEditor(): check index pattern variations', () => { + // valid single index pattern + expect(validateAdvancedEditor(getMockState('the-source-index')).isValid).toBe(true); + // valid array with one ES index pattern + expect(validateAdvancedEditor(getMockState(['the-source-index'])).isValid).toBe(true); + // valid array with two ES index patterns + expect( + validateAdvancedEditor(getMockState(['the-source-index-1', 'the-source-index-2'])).isValid + ).toBe(true); + // invalid comma-separated index pattern, this is only allowed in the simple form + // but not the advanced editor. + expect( + validateAdvancedEditor(getMockState('the-source-index-1,the-source-index-2')).isValid + ).toBe(false); + expect( + validateAdvancedEditor( + getMockState(['the-source-index-1,the-source-index-2', 'the-source-index-3']) + ).isValid + ).toBe(false); + // invalid formats ("fake" TS casting to get valid TS and be able to run the tests) + expect(validateAdvancedEditor(getMockState({} as SourceIndex)).isValid).toBe(false); + expect( + validateAdvancedEditor(getMockState((undefined as unknown) as SourceIndex)).isValid + ).toBe(false); + }); }); diff --git a/x-pack/legacy/plugins/ml/public/data_frame_analytics/pages/analytics_management/hooks/use_create_analytics_form/reducer.ts b/x-pack/legacy/plugins/ml/public/data_frame_analytics/pages/analytics_management/hooks/use_create_analytics_form/reducer.ts index 1c4aefcd26ced..a52d88365d7ac 100644 --- a/x-pack/legacy/plugins/ml/public/data_frame_analytics/pages/analytics_management/hooks/use_create_analytics_form/reducer.ts +++ b/x-pack/legacy/plugins/ml/public/data_frame_analytics/pages/analytics_management/hooks/use_create_analytics_form/reducer.ts @@ -16,15 +16,45 @@ import { isAnalyticsIdValid } from '../../../../common'; import { Action, ACTION } from './actions'; import { getInitialState, getJobConfigFromFormState, State } from './state'; -const validateAdvancedEditor = (state: State): State => { +const getSourceIndexString = (state: State) => { + const { jobConfig } = state; + + const sourceIndex = idx(jobConfig, _ => _.source.index); + + if (typeof sourceIndex === 'string') { + return sourceIndex; + } + + if (Array.isArray(sourceIndex)) { + return sourceIndex.join(','); + } + + return ''; +}; + +export const validateAdvancedEditor = (state: State): State => { const { jobIdEmpty, jobIdValid, jobIdExists, createIndexPattern } = state.form; const { jobConfig } = state; state.advancedEditorMessages = []; - const sourceIndexName = idx(jobConfig, _ => _.source.index) || ''; + const sourceIndexName = getSourceIndexString(state); const sourceIndexNameEmpty = sourceIndexName === ''; - const sourceIndexNameValid = validateIndexPattern(sourceIndexName); + // general check against Kibana index pattern names, but since this is about the advanced editor + // with support for arrays in the job config, we also need to check that each individual name + // doesn't include a comma if index names are supplied as an array. + // `validateIndexPattern()` returns a map of messages, we're only interested here if it's valid or not. + // If there are no messages, it means the index pattern is valid. + let sourceIndexNameValid = Object.keys(validateIndexPattern(sourceIndexName)).length === 0; + const sourceIndex = idx(jobConfig, _ => _.source.index); + if (sourceIndexNameValid) { + if (typeof sourceIndex === 'string') { + sourceIndexNameValid = !sourceIndex.includes(','); + } + if (Array.isArray(sourceIndex)) { + sourceIndexNameValid = !sourceIndex.some(d => d.includes(',')); + } + } const destinationIndexName = idx(jobConfig, _ => _.dest.index) || ''; const destinationIndexNameEmpty = destinationIndexName === ''; diff --git a/x-pack/legacy/plugins/ml/public/data_frame_analytics/pages/analytics_management/hooks/use_create_analytics_form/state.test.ts b/x-pack/legacy/plugins/ml/public/data_frame_analytics/pages/analytics_management/hooks/use_create_analytics_form/state.test.ts index f0d3bdf186994..ec70c54892a0e 100644 --- a/x-pack/legacy/plugins/ml/public/data_frame_analytics/pages/analytics_management/hooks/use_create_analytics_form/state.test.ts +++ b/x-pack/legacy/plugins/ml/public/data_frame_analytics/pages/analytics_management/hooks/use_create_analytics_form/state.test.ts @@ -21,5 +21,13 @@ describe('useCreateAnalyticsForm', () => { expect(idx(jobConfig, _ => _.source.index)).toBe('the-source-index'); expect(idx(jobConfig, _ => _.analyzed_fields.excludes)).toStrictEqual([]); expect(typeof idx(jobConfig, _ => _.analyzed_fields.includes)).toBe('undefined'); + + // test the conversion of comma-separated Kibana index patterns to ES array based index patterns + state.form.sourceIndex = 'the-source-index-1,the-source-index-2'; + const jobConfigSourceIndexArray = getJobConfigFromFormState(state.form); + expect(idx(jobConfigSourceIndexArray, _ => _.source.index)).toStrictEqual([ + 'the-source-index-1', + 'the-source-index-2', + ]); }); }); diff --git a/x-pack/legacy/plugins/ml/public/data_frame_analytics/pages/analytics_management/hooks/use_create_analytics_form/state.ts b/x-pack/legacy/plugins/ml/public/data_frame_analytics/pages/analytics_management/hooks/use_create_analytics_form/state.ts index 1aacab853f522..a4a0087c9d869 100644 --- a/x-pack/legacy/plugins/ml/public/data_frame_analytics/pages/analytics_management/hooks/use_create_analytics_form/state.ts +++ b/x-pack/legacy/plugins/ml/public/data_frame_analytics/pages/analytics_management/hooks/use_create_analytics_form/state.ts @@ -92,7 +92,12 @@ export const getJobConfigFromFormState = ( ): DeepPartial => { return { source: { - index: formState.sourceIndex, + // If a Kibana index patterns includes commas, we need to split + // the into an array of indices to be in the correct format for + // the data frame analytics API. + index: formState.sourceIndex.includes(',') + ? formState.sourceIndex.split(',').map(d => d.trim()) + : formState.sourceIndex, }, dest: { index: formState.destinationIndex,