diff --git a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_creation/components/runtime_mappings/runtime_mappings.tsx b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_creation/components/runtime_mappings/runtime_mappings.tsx index d9f1d78c302f..d21bf67a1f51 100644 --- a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_creation/components/runtime_mappings/runtime_mappings.tsx +++ b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_creation/components/runtime_mappings/runtime_mappings.tsx @@ -38,6 +38,7 @@ const COPY_TO_CLIPBOARD_RUNTIME_MAPPINGS = i18n.translate( const { useXJsonMode } = XJson; const xJsonMode = new XJsonMode(); +export type XJsonModeType = ReturnType; interface Props { actions: CreateAnalyticsFormProps['actions']; diff --git a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_creation/components/runtime_mappings/runtime_mappings_editor.tsx b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_creation/components/runtime_mappings/runtime_mappings_editor.tsx index 70544cc14ba0..66a96e7316e8 100644 --- a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_creation/components/runtime_mappings/runtime_mappings_editor.tsx +++ b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_creation/components/runtime_mappings/runtime_mappings_editor.tsx @@ -10,6 +10,7 @@ import React, { memo, FC } from 'react'; import { EuiCodeEditor } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import { isRuntimeMappings } from '../../../../../../../common/util/runtime_field_utils'; +import { XJsonModeType } from './runtime_mappings'; interface Props { convertToJson: (data: string) => string; @@ -17,7 +18,7 @@ interface Props { setIsRuntimeMappingsEditorApplyButtonEnabled: React.Dispatch>; advancedEditorRuntimeMappingsLastApplied: string | undefined; advancedRuntimeMappingsConfig: string; - xJsonMode: any; + xJsonMode: XJsonModeType; } export const RuntimeMappingsEditor: FC = memo( diff --git a/x-pack/test/functional/apps/ml/data_frame_analytics/classification_creation.ts b/x-pack/test/functional/apps/ml/data_frame_analytics/classification_creation.ts index 615b55c6ce56..5e6a08751c93 100644 --- a/x-pack/test/functional/apps/ml/data_frame_analytics/classification_creation.ts +++ b/x-pack/test/functional/apps/ml/data_frame_analytics/classification_creation.ts @@ -36,6 +36,12 @@ export default function ({ getService }: FtrProviderContext) { get destinationIndex(): string { return `user-${this.jobId}`; }, + runtimeFields: { + uppercase_y: { + type: 'keyword', + script: 'emit(params._source.y.toUpperCase())', + }, + }, dependentVariable: 'y', trainingPercent: 20, modelMemory: '60mb', @@ -95,6 +101,22 @@ export default function ({ getService }: FtrProviderContext) { await ml.dataFrameAnalyticsCreation.assertJobTypeSelectExists(); await ml.dataFrameAnalyticsCreation.selectJobType(testData.jobType); + await ml.testExecution.logTestStep('displays the runtime mappings editor switch'); + await ml.dataFrameAnalyticsCreation.assertRuntimeMappingSwitchExists(); + + await ml.testExecution.logTestStep('enables the runtime mappings editor'); + await ml.dataFrameAnalyticsCreation.toggleRuntimeMappingsEditorSwitch(true); + await ml.dataFrameAnalyticsCreation.assertRuntimeMappingsEditorContent(['']); + + await ml.testExecution.logTestStep('sets runtime mappings'); + await ml.dataFrameAnalyticsCreation.setRuntimeMappingsEditorContent( + JSON.stringify(testData.runtimeFields) + ); + await ml.dataFrameAnalyticsCreation.applyRuntimeMappings(); + await ml.dataFrameAnalyticsCreation.assertRuntimeMappingsEditorContent([ + '{"uppercase_y":{"type":"keyword","script":"emit(params._source.y.toUpperCase())"}}', + ]); + await ml.testExecution.logTestStep('inputs the dependent variable'); await ml.dataFrameAnalyticsCreation.assertDependentVariableInputExists(); await ml.dataFrameAnalyticsCreation.selectDependentVariable(testData.dependentVariable); diff --git a/x-pack/test/functional/apps/ml/data_frame_analytics/outlier_detection_creation.ts b/x-pack/test/functional/apps/ml/data_frame_analytics/outlier_detection_creation.ts index d72ee4fa0fd2..e73a477d21b1 100644 --- a/x-pack/test/functional/apps/ml/data_frame_analytics/outlier_detection_creation.ts +++ b/x-pack/test/functional/apps/ml/data_frame_analytics/outlier_detection_creation.ts @@ -35,6 +35,12 @@ export default function ({ getService }: FtrProviderContext) { get destinationIndex(): string { return `user-${this.jobId}`; }, + runtimeFields: { + lowercase_central_air: { + type: 'keyword', + script: 'emit(params._source.CentralAir.toLowerCase())', + }, + }, modelMemory: '5mb', createIndexPattern: true, expected: { @@ -106,6 +112,22 @@ export default function ({ getService }: FtrProviderContext) { await ml.dataFrameAnalyticsCreation.assertJobTypeSelectExists(); await ml.dataFrameAnalyticsCreation.selectJobType(testData.jobType); + await ml.testExecution.logTestStep('displays the runtime mappings editor switch'); + await ml.dataFrameAnalyticsCreation.assertRuntimeMappingSwitchExists(); + + await ml.testExecution.logTestStep('enables the runtime mappings editor'); + await ml.dataFrameAnalyticsCreation.toggleRuntimeMappingsEditorSwitch(true); + await ml.dataFrameAnalyticsCreation.assertRuntimeMappingsEditorContent(['']); + + await ml.testExecution.logTestStep('sets runtime mappings'); + await ml.dataFrameAnalyticsCreation.setRuntimeMappingsEditorContent( + JSON.stringify(testData.runtimeFields) + ); + await ml.dataFrameAnalyticsCreation.applyRuntimeMappings(); + await ml.dataFrameAnalyticsCreation.assertRuntimeMappingsEditorContent([ + '{"lowercase_central_air":{"type":"keyword","script":"emit(params._source.CentralAir.toLowerCase())"}}', + ]); + await ml.testExecution.logTestStep('does not display the dependent variable input'); await ml.dataFrameAnalyticsCreation.assertDependentVariableInputMissing(); diff --git a/x-pack/test/functional/apps/ml/data_frame_analytics/regression_creation.ts b/x-pack/test/functional/apps/ml/data_frame_analytics/regression_creation.ts index 8e28a9933cda..540fbc10fa0f 100644 --- a/x-pack/test/functional/apps/ml/data_frame_analytics/regression_creation.ts +++ b/x-pack/test/functional/apps/ml/data_frame_analytics/regression_creation.ts @@ -35,6 +35,12 @@ export default function ({ getService }: FtrProviderContext) { get destinationIndex(): string { return `user-${this.jobId}`; }, + runtimeFields: { + uppercase_stab: { + type: 'keyword', + script: 'emit(params._source.stabf.toUpperCase())', + }, + }, dependentVariable: 'stab', trainingPercent: 20, modelMemory: '20mb', @@ -89,6 +95,22 @@ export default function ({ getService }: FtrProviderContext) { await ml.dataFrameAnalyticsCreation.assertJobTypeSelectExists(); await ml.dataFrameAnalyticsCreation.selectJobType(testData.jobType); + await ml.testExecution.logTestStep('displays the runtime mappings editor switch'); + await ml.dataFrameAnalyticsCreation.assertRuntimeMappingSwitchExists(); + + await ml.testExecution.logTestStep('enables the runtime mappings editor'); + await ml.dataFrameAnalyticsCreation.toggleRuntimeMappingsEditorSwitch(true); + await ml.dataFrameAnalyticsCreation.assertRuntimeMappingsEditorContent(['']); + + await ml.testExecution.logTestStep('sets runtime mappings'); + await ml.dataFrameAnalyticsCreation.setRuntimeMappingsEditorContent( + JSON.stringify(testData.runtimeFields) + ); + await ml.dataFrameAnalyticsCreation.applyRuntimeMappings(); + await ml.dataFrameAnalyticsCreation.assertRuntimeMappingsEditorContent([ + '{"uppercase_stab":{"type":"keyword","script":"emit(params._source.stabf.toUpperCase())"}}', + ]); + await ml.testExecution.logTestStep('inputs the dependent variable'); await ml.dataFrameAnalyticsCreation.assertDependentVariableInputExists(); await ml.dataFrameAnalyticsCreation.selectDependentVariable(testData.dependentVariable); diff --git a/x-pack/test/functional/services/ml/data_frame_analytics_creation.ts b/x-pack/test/functional/services/ml/data_frame_analytics_creation.ts index 23bdc17919a7..b22748608589 100644 --- a/x-pack/test/functional/services/ml/data_frame_analytics_creation.ts +++ b/x-pack/test/functional/services/ml/data_frame_analytics_creation.ts @@ -25,6 +25,7 @@ export function MachineLearningDataFrameAnalyticsCreationProvider( const testSubjects = getService('testSubjects'); const comboBox = getService('comboBox'); const retry = getService('retry'); + const aceEditor = getService('aceEditor'); return { async assertJobTypeSelectExists() { @@ -237,6 +238,69 @@ export function MachineLearningDataFrameAnalyticsCreationProvider( ); }, + async assertRuntimeMappingSwitchExists() { + await testSubjects.existOrFail('mlDataFrameAnalyticsRuntimeMappingsEditorSwitch'); + }, + + async assertRuntimeMappingEditorExists() { + await testSubjects.existOrFail('mlDataFrameAnalyticsAdvancedRuntimeMappingsEditor'); + }, + + async assertRuntimeMappingsEditorSwitchCheckState(expectedCheckState: boolean) { + const actualCheckState = await this.getRuntimeMappingsEditorSwitchCheckedState(); + expect(actualCheckState).to.eql( + expectedCheckState, + `Advanced runtime mappings editor switch check state should be '${expectedCheckState}' (got '${actualCheckState}')` + ); + }, + + async getRuntimeMappingsEditorSwitchCheckedState(): Promise { + const subj = 'mlDataFrameAnalyticsRuntimeMappingsEditorSwitch'; + const isSelected = await testSubjects.getAttribute(subj, 'aria-checked'); + return isSelected === 'true'; + }, + + async toggleRuntimeMappingsEditorSwitch(toggle: boolean) { + const subj = 'mlDataFrameAnalyticsRuntimeMappingsEditorSwitch'; + if ((await this.getRuntimeMappingsEditorSwitchCheckedState()) !== toggle) { + await retry.tryForTime(5 * 1000, async () => { + await testSubjects.clickWhenNotDisabled(subj); + await this.assertRuntimeMappingsEditorSwitchCheckState(toggle); + }); + } + }, + + async setRuntimeMappingsEditorContent(input: string) { + await aceEditor.setValue('mlDataFrameAnalyticsAdvancedRuntimeMappingsEditor', input); + }, + + async assertRuntimeMappingsEditorContent(expectedContent: string[]) { + await this.assertRuntimeMappingEditorExists(); + + const runtimeMappingsEditorString = await aceEditor.getValue( + 'mlDataFrameAnalyticsAdvancedRuntimeMappingsEditor' + ); + // Not all lines may be visible in the editor and thus aceEditor may not return all lines. + // This means we might not get back valid JSON so we only test against the first few lines + // and see if the string matches. + const splicedAdvancedEditorValue = runtimeMappingsEditorString.split('\n').splice(0, 3); + expect(splicedAdvancedEditorValue).to.eql( + expectedContent, + `Expected the first editor lines to be '${expectedContent}' (got '${splicedAdvancedEditorValue}')` + ); + }, + + async applyRuntimeMappings() { + const subj = 'mlDataFrameAnalyticsRuntimeMappingsApplyButton'; + await testSubjects.existOrFail(subj); + await testSubjects.clickWhenNotDisabled(subj); + const isEnabled = await testSubjects.isEnabled(subj); + expect(isEnabled).to.eql( + false, + `Expected runtime mappings 'Apply changes' button to be disabled, got enabled.` + ); + }, + async assertDependentVariableSelection(expectedSelection: string[]) { await this.waitForDependentVariableInputLoaded(); const actualSelection = await comboBox.getComboBoxSelectedOptions(