Skip to content

Commit

Permalink
[ML] Data frame analytics: Split comma-separated Kibana index pattern…
Browse files Browse the repository at this point in the history
…s 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.
  • Loading branch information
walterra authored Sep 4, 2019
1 parent 9155bfd commit 49fdd24
Show file tree
Hide file tree
Showing 5 changed files with 95 additions and 6 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@ export interface DataFrameAnalyticsConfig {
results_field: string;
};
source: {
index: IndexName;
index: IndexName | IndexName[];
};
analysis: AnalysisConfig;
analyzed_fields: {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Expand Down Expand Up @@ -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);
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -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 === '';
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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',
]);
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -92,7 +92,12 @@ export const getJobConfigFromFormState = (
): DeepPartial<DataFrameAnalyticsConfig> => {
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,
Expand Down

0 comments on commit 49fdd24

Please sign in to comment.