From 9a695e5a1255397ba50fff16d2a1d02ed90d09f9 Mon Sep 17 00:00:00 2001 From: Melissa Alvarez Date: Tue, 9 Jun 2020 18:38:09 -0400 Subject: [PATCH] [ML] DF Analytics: Creation wizard part 2 (#68462) (#68707) * Add ability to clone job * remove deprecated creation form code * ensure excludes cloned correctly and update clone action jest test * remove unused translations * update helper function name --- .../data_frame_analytics/_index.scss | 2 - .../analysis_fields_table.tsx | 2 +- .../configuration_step_form.tsx | 38 +- .../form_options_validation.ts | 2 +- .../supported_fields_message.tsx | 5 +- .../create_analytics_advanced_editor.tsx | 7 +- .../create_analytics_advanced_editor/index.ts | 0 .../components/create_step/create_step.tsx | 13 +- .../components/shared}/index.ts | 2 +- .../components/shared}/messages.tsx | 2 +- .../pages/analytics_creation/page.tsx | 54 +- .../analytics_list/action_clone.test.ts | 38 +- .../analytics_list/action_clone.tsx | 74 +- .../components/analytics_list/actions.tsx | 14 +- .../analytics_list/analytics_list.tsx | 7 - .../_create_analytics_flyout.scss | 3 - .../create_analytics_flyout/_index.scss | 1 - .../create_analytics_flyout.test.tsx | 42 - .../create_analytics_flyout.tsx | 108 --- .../create_analytics_flyout/index.ts | 7 - .../create_analytics_flyout_wrapper.tsx | 28 - .../create_analytics_flyout_wrapper/index.ts | 7 - .../_create_analytics_form.scss | 31 - .../create_analytics_form/_index.scss | 1 - .../create_analytics_form.test.tsx | 69 -- .../create_analytics_form.tsx | 850 ------------------ .../create_analytics_form/job_description.tsx | 37 - .../create_analytics_form/job_type.tsx | 80 -- .../use_create_analytics_form/actions.ts | 7 - .../use_create_analytics_form/reducer.test.ts | 15 - .../use_create_analytics_form/reducer.ts | 12 - .../use_create_analytics_form/state.test.ts | 54 +- .../hooks/use_create_analytics_form/state.ts | 27 +- .../use_create_analytics_form.test.tsx | 28 +- .../use_create_analytics_form.ts | 21 - .../analytics_job_creation.tsx | 7 +- .../translations/translations/ja-JP.json | 17 - .../translations/translations/zh-CN.json | 17 - 38 files changed, 236 insertions(+), 1493 deletions(-) rename x-pack/plugins/ml/public/application/data_frame_analytics/pages/{analytics_management/components/create_analytics_form => analytics_creation/components/configuration_step}/form_options_validation.ts (93%) rename x-pack/plugins/ml/public/application/data_frame_analytics/pages/{analytics_management => analytics_creation}/components/create_analytics_advanced_editor/create_analytics_advanced_editor.tsx (95%) rename x-pack/plugins/ml/public/application/data_frame_analytics/pages/{analytics_management => analytics_creation}/components/create_analytics_advanced_editor/index.ts (100%) rename x-pack/plugins/ml/public/application/data_frame_analytics/pages/{analytics_management/components/create_analytics_form => analytics_creation/components/shared}/index.ts (79%) rename x-pack/plugins/ml/public/application/data_frame_analytics/pages/{analytics_management/components/create_analytics_form => analytics_creation/components/shared}/messages.tsx (92%) delete mode 100644 x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/create_analytics_flyout/_create_analytics_flyout.scss delete mode 100644 x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/create_analytics_flyout/_index.scss delete mode 100644 x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/create_analytics_flyout/create_analytics_flyout.test.tsx delete mode 100644 x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/create_analytics_flyout/create_analytics_flyout.tsx delete mode 100644 x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/create_analytics_flyout/index.ts delete mode 100644 x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/create_analytics_flyout_wrapper/create_analytics_flyout_wrapper.tsx delete mode 100644 x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/create_analytics_flyout_wrapper/index.ts delete mode 100644 x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/create_analytics_form/_create_analytics_form.scss delete mode 100644 x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/create_analytics_form/_index.scss delete mode 100644 x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/create_analytics_form/create_analytics_form.test.tsx delete mode 100644 x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/create_analytics_form/create_analytics_form.tsx delete mode 100644 x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/create_analytics_form/job_description.tsx delete mode 100644 x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/create_analytics_form/job_type.tsx diff --git a/x-pack/plugins/ml/public/application/data_frame_analytics/_index.scss b/x-pack/plugins/ml/public/application/data_frame_analytics/_index.scss index 5508c021d3313..140593cb17f6e 100644 --- a/x-pack/plugins/ml/public/application/data_frame_analytics/_index.scss +++ b/x-pack/plugins/ml/public/application/data_frame_analytics/_index.scss @@ -1,5 +1,3 @@ @import 'pages/analytics_exploration/components/regression_exploration/index'; @import 'pages/analytics_management/components/analytics_list/index'; -@import 'pages/analytics_management/components/create_analytics_form/index'; -@import 'pages/analytics_management/components/create_analytics_flyout/index'; @import 'pages/analytics_management/components/create_analytics_button/index'; diff --git a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_creation/components/configuration_step/analysis_fields_table.tsx b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_creation/components/configuration_step/analysis_fields_table.tsx index ad540285e49f0..09aea596d81e9 100644 --- a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_creation/components/configuration_step/analysis_fields_table.tsx +++ b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_creation/components/configuration_step/analysis_fields_table.tsx @@ -85,7 +85,7 @@ export const MemoizedAnalysisFieldsTable: FC<{ if (excludes.length > 0) { setCurrentSelection(excludes); } - }, []); + }, [tableItems]); // Only set form state on unmount to prevent re-renders due to props changing if exludes was updated on each selection useEffect(() => { diff --git a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_creation/components/configuration_step/configuration_step_form.tsx b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_creation/components/configuration_step/configuration_step_form.tsx index 9446dfd4ed525..e63756686a4ba 100644 --- a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_creation/components/configuration_step/configuration_step_form.tsx +++ b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_creation/components/configuration_step/configuration_step_form.tsx @@ -20,13 +20,13 @@ import { TRAINING_PERCENT_MAX, } from '../../../../common/analytics'; import { CreateAnalyticsStepProps } from '../../../analytics_management/hooks/use_create_analytics_form'; -import { Messages } from '../../../analytics_management/components/create_analytics_form/messages'; +import { Messages } from '../shared'; import { DEFAULT_MODEL_MEMORY_LIMIT, getJobConfigFromFormState, State, } from '../../../analytics_management/hooks/use_create_analytics_form/state'; -import { shouldAddAsDepVarOption } from '../../../analytics_management/components/create_analytics_form/form_options_validation'; +import { shouldAddAsDepVarOption } from './form_options_validation'; import { ml } from '../../../../../services/ml_api_service'; import { getToastNotifications } from '../../../../../util/dependency_cache'; @@ -56,7 +56,7 @@ export const ConfigurationStepForm: FC = ({ const { currentSavedSearch, currentIndexPattern } = mlContext; const { savedSearchQuery, savedSearchQueryStr } = useSavedSearch(); - const { initiateWizard, setEstimatedModelMemoryLimit, setFormState } = actions; + const { setEstimatedModelMemoryLimit, setFormState } = actions; const { estimatedModelMemoryLimit, form, isJobCreated, requestMessages } = state; const firstUpdate = useRef(true); const { @@ -75,9 +75,12 @@ export const ConfigurationStepForm: FC = ({ modelMemoryLimit, previousJobType, requiredFieldsError, + sourceIndex, trainingPercent, } = form; + const toastNotifications = getToastNotifications(); + const setJobConfigQuery = ({ query, queryString }: { query: any; queryString: string }) => { setFormState({ jobConfigQuery: query, jobConfigQueryString: queryString }); }; @@ -90,7 +93,7 @@ export const ConfigurationStepForm: FC = ({ const indexPreviewProps = { ...indexData, dataTestSubj: 'mlAnalyticsCreationDataGrid', - toastNotifications: getToastNotifications(), + toastNotifications, }; const isJobTypeWithDepVar = @@ -209,7 +212,8 @@ export const ConfigurationStepForm: FC = ({ }); } } catch (e) { - let errorMessage; + let maxDistinctValuesErrorMessage; + if ( jobType === ANALYSIS_CONFIG_TYPE.CLASSIFICATION && e.body && @@ -218,7 +222,23 @@ export const ConfigurationStepForm: FC = ({ (e.body.message.includes('must have at most') || e.body.message.includes('must have at least')) ) { - errorMessage = e.body.message; + maxDistinctValuesErrorMessage = e.body.message; + } + + if ( + e.body && + e.body.message !== undefined && + e.body.message.includes('status_exception') && + e.body.message.includes('Unable to estimate memory usage as no documents') + ) { + toastNotifications.addWarning( + i18n.translate('xpack.ml.dataframe.analytics.create.allDocsMissingFieldsErrorMessage', { + defaultMessage: `Unable to estimate memory usage. There are mapped fields for source index [{index}] that do not exist in any indexed documents. You will have to switch to the JSON editor for explicit field selection and include only fields that exist in indexed documents.`, + values: { + index: sourceIndex, + }, + }) + ); } const fallbackModelMemoryLimit = jobType !== undefined @@ -227,17 +247,13 @@ export const ConfigurationStepForm: FC = ({ setEstimatedModelMemoryLimit(fallbackModelMemoryLimit); setFormState({ fieldOptionsFetchFail: true, - maxDistinctValuesError: errorMessage, + maxDistinctValuesError: maxDistinctValuesErrorMessage, loadingFieldOptions: false, ...(shouldUpdateModelMemoryLimit ? { modelMemoryLimit: fallbackModelMemoryLimit } : {}), }); } }, 300); - useEffect(() => { - initiateWizard(); - }, []); - useEffect(() => { setFormState({ sourceIndex: currentIndexPattern.title }); }, []); diff --git a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/create_analytics_form/form_options_validation.ts b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_creation/components/configuration_step/form_options_validation.ts similarity index 93% rename from x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/create_analytics_form/form_options_validation.ts rename to x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_creation/components/configuration_step/form_options_validation.ts index 0579283c97d61..bf3ab01549139 100644 --- a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/create_analytics_form/form_options_validation.ts +++ b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_creation/components/configuration_step/form_options_validation.ts @@ -7,7 +7,7 @@ import { ES_FIELD_TYPES } from '../../../../../../../../../../src/plugins/data/public'; import { Field, EVENT_RATE_FIELD_ID } from '../../../../../../../common/types/fields'; import { ANALYSIS_CONFIG_TYPE } from '../../../../common/analytics'; -import { AnalyticsJobType } from '../../hooks/use_create_analytics_form/state'; +import { AnalyticsJobType } from '../../../analytics_management/hooks/use_create_analytics_form/state'; import { BASIC_NUMERICAL_TYPES, EXTENDED_NUMERICAL_TYPES } from '../../../../common/fields'; export const CATEGORICAL_TYPES = new Set(['ip', 'keyword']); diff --git a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_creation/components/configuration_step/supported_fields_message.tsx b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_creation/components/configuration_step/supported_fields_message.tsx index fe13cc1d6edfc..0a4ba67831818 100644 --- a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_creation/components/configuration_step/supported_fields_message.tsx +++ b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_creation/components/configuration_step/supported_fields_message.tsx @@ -12,10 +12,7 @@ import { AnalyticsJobType } from '../../../analytics_management/hooks/use_create import { ANALYSIS_CONFIG_TYPE } from '../../../../common/analytics'; import { Field, EVENT_RATE_FIELD_ID } from '../../../../../../../common/types/fields'; import { BASIC_NUMERICAL_TYPES, EXTENDED_NUMERICAL_TYPES } from '../../../../common/fields'; -import { - OMIT_FIELDS, - CATEGORICAL_TYPES, -} from '../../../analytics_management/components/create_analytics_form/form_options_validation'; +import { OMIT_FIELDS, CATEGORICAL_TYPES } from './form_options_validation'; import { ES_FIELD_TYPES } from '../../../../../../../../../../src/plugins/data/public'; import { newJobCapsService } from '../../../../../services/new_job_capabilities_service'; diff --git a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/create_analytics_advanced_editor/create_analytics_advanced_editor.tsx b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_creation/components/create_analytics_advanced_editor/create_analytics_advanced_editor.tsx similarity index 95% rename from x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/create_analytics_advanced_editor/create_analytics_advanced_editor.tsx rename to x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_creation/components/create_analytics_advanced_editor/create_analytics_advanced_editor.tsx index 17b905cab135b..a35a314bec985 100644 --- a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/create_analytics_advanced_editor/create_analytics_advanced_editor.tsx +++ b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_creation/components/create_analytics_advanced_editor/create_analytics_advanced_editor.tsx @@ -22,9 +22,9 @@ import { XJsonMode } from '../../../../../../../shared_imports'; const xJsonMode = new XJsonMode(); -import { CreateAnalyticsFormProps } from '../../hooks/use_create_analytics_form'; -import { CreateStep } from '../../../analytics_creation/components/create_step'; -import { ANALYTICS_STEPS } from '../../../analytics_creation/page'; +import { CreateAnalyticsFormProps } from '../../../analytics_management/hooks/use_create_analytics_form'; +import { CreateStep } from '../create_step'; +import { ANALYTICS_STEPS } from '../../page'; export const CreateAnalyticsAdvancedEditor: FC = (props) => { const { actions, state } = props; @@ -125,7 +125,6 @@ export const CreateAnalyticsAdvancedEditor: FC = (prop onChange={onChange} setOptions={{ fontSize: '12px', - maxLines: 20, }} theme="textmate" aria-label={i18n.translate( diff --git a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/create_analytics_advanced_editor/index.ts b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_creation/components/create_analytics_advanced_editor/index.ts similarity index 100% rename from x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/create_analytics_advanced_editor/index.ts rename to x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_creation/components/create_analytics_advanced_editor/index.ts diff --git a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_creation/components/create_step/create_step.tsx b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_creation/components/create_step/create_step.tsx index 2dda5f5d819b7..8d51848a25f50 100644 --- a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_creation/components/create_step/create_step.tsx +++ b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_creation/components/create_step/create_step.tsx @@ -16,7 +16,7 @@ import { import { i18n } from '@kbn/i18n'; import { CreateAnalyticsFormProps } from '../../../analytics_management/hooks/use_create_analytics_form'; -import { Messages } from '../../../analytics_management/components/create_analytics_form/messages'; +import { Messages } from '../shared'; import { ANALYTICS_STEPS } from '../../page'; import { BackToListPanel } from '../back_to_list_panel'; @@ -26,14 +26,7 @@ interface Props extends CreateAnalyticsFormProps { export const CreateStep: FC = ({ actions, state, step }) => { const { createAnalyticsJob, startAnalyticsJob } = actions; - const { - isAdvancedEditorValidJson, - isJobCreated, - isJobStarted, - isModalButtonDisabled, - isValid, - requestMessages, - } = state; + const { isAdvancedEditorValidJson, isJobCreated, isJobStarted, isValid, requestMessages } = state; const [checked, setChecked] = useState(true); @@ -75,7 +68,7 @@ export const CreateStep: FC = ({ actions, state, step }) => { { +interface Props { + jobId?: DataFrameAnalyticsId; +} + +export const Page: FC = ({ jobId }) => { const [currentStep, setCurrentStep] = useState(ANALYTICS_STEPS.CONFIGURATION); const [activatedSteps, setActivatedSteps] = useState([true, false, false, false]); @@ -44,23 +50,36 @@ export const Page: FC = () => { const createAnalyticsForm = useCreateAnalyticsForm(); const { isAdvancedEditorEnabled } = createAnalyticsForm.state; const { jobType } = createAnalyticsForm.state.form; - const { switchToAdvancedEditor } = createAnalyticsForm.actions; + const { initiateWizard, setJobClone, switchToAdvancedEditor } = createAnalyticsForm.actions; useEffect(() => { - if (activatedSteps[currentStep] === false) { - activatedSteps.splice(currentStep, 1, true); - setActivatedSteps(activatedSteps); - } - }, [currentStep]); + initiateWizard(); - useEffect(() => { if (currentIndexPattern) { (async function () { await newJobCapsService.initializeFromIndexPattern(currentIndexPattern, false, false); + + if (jobId !== undefined) { + const analyticsConfigs = await ml.dataFrameAnalytics.getDataFrameAnalytics(jobId); + if ( + Array.isArray(analyticsConfigs.data_frame_analytics) && + analyticsConfigs.data_frame_analytics.length > 0 + ) { + const clonedJobConfig: any = analyticsConfigs.data_frame_analytics[0]; + await setJobClone(clonedJobConfig); + } + } })(); } }, []); + useEffect(() => { + if (activatedSteps[currentStep] === false) { + activatedSteps.splice(currentStep, 1, true); + setActivatedSteps(activatedSteps); + } + }, [currentStep]); + const analyticsWizardSteps = [ { title: i18n.translate('xpack.ml.dataframe.analytics.creation.configurationStepTitle', { @@ -127,10 +146,19 @@ export const Page: FC = () => {

- + {jobId === undefined && ( + + )} + {jobId !== undefined && ( + + )}

diff --git a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/analytics_list/action_clone.test.ts b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/analytics_list/action_clone.test.ts index 9221f8c500326..01d92d8e192c1 100644 --- a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/analytics_list/action_clone.test.ts +++ b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/analytics_list/action_clone.test.ts @@ -140,8 +140,8 @@ describe('Analytics job clone action', () => { expect(isAdvancedConfig(advancedClassificationJob)).toBe(true); }); - test('should detect advanced outlier_detection job', () => { - const advancedOutlierDetectionJob = { + test('should detect advanced regression job', () => { + const advancedRegressionJob = { description: "Outlier detection job with 'glass' dataset", source: { index: ['glass_withoutdupl_norm'], @@ -155,10 +155,8 @@ describe('Analytics job clone action', () => { results_field: 'ml', }, analysis: { - outlier_detection: { - compute_feature_influence: false, - outlier_fraction: 0.05, - standardization_enabled: true, + regression: { + loss_function: 'msle', }, }, analyzed_fields: { @@ -168,7 +166,7 @@ describe('Analytics job clone action', () => { model_memory_limit: '1mb', allow_lazy_start: false, }; - expect(isAdvancedConfig(advancedOutlierDetectionJob)).toBe(true); + expect(isAdvancedConfig(advancedRegressionJob)).toBe(true); }); test('should detect a custom query', () => { @@ -207,32 +205,6 @@ describe('Analytics job clone action', () => { expect(isAdvancedConfig(advancedRegressionJob)).toBe(true); }); - test('should detect custom analysis settings', () => { - const config = { - description: "Classification clone with 'bank-marketing' dataset", - source: { - index: 'bank-marketing', - }, - dest: { - index: 'bank_classification4', - }, - analyzed_fields: { - excludes: [], - }, - analysis: { - classification: { - dependent_variable: 'y', - training_percent: 71, - max_trees: 1500, - num_top_feature_importance_values: 4, - }, - }, - model_memory_limit: '400mb', - }; - - expect(isAdvancedConfig(config)).toBe(true); - }); - test('should detect as advanced if the prop is unknown', () => { const config = { description: "Classification clone with 'bank-marketing' dataset", diff --git a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/analytics_list/action_clone.tsx b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/analytics_list/action_clone.tsx index cfb11856670c4..a1f0448b819d1 100644 --- a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/analytics_list/action_clone.tsx +++ b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/analytics_list/action_clone.tsx @@ -8,10 +8,12 @@ import { EuiButtonEmpty } from '@elastic/eui'; import React, { FC } from 'react'; import { isEqual, cloneDeep } from 'lodash'; import { i18n } from '@kbn/i18n'; +import { IIndexPattern } from 'src/plugins/data/common'; import { DeepReadonly } from '../../../../../../../common/types/common'; import { DataFrameAnalyticsConfig, isOutlierAnalysis } from '../../../../common'; import { isClassificationAnalysis, isRegressionAnalysis } from '../../../../common/analytics'; import { DEFAULT_RESULTS_FIELD } from '../../../../common/constants'; +import { useMlKibana } from '../../../../../contexts/kibana'; import { CreateAnalyticsFormProps, DEFAULT_NUM_TOP_FEATURE_IMPORTANCE_VALUES, @@ -19,6 +21,7 @@ import { import { State } from '../../hooks/use_create_analytics_form/state'; import { DataFrameAnalyticsListRow } from './common'; import { checkPermission } from '../../../../../capabilities/check_capabilities'; +import { extractErrorMessage } from '../../../../../util/error_utils'; interface PropDefinition { /** @@ -74,31 +77,39 @@ const getAnalyticsJobMeta = (config: CloneDataFrameAnalyticsConfig): AnalyticsJo }, eta: { optional: true, + formKey: 'eta', }, feature_bag_fraction: { optional: true, + formKey: 'featureBagFraction', }, max_trees: { optional: true, + formKey: 'maxTrees', }, gamma: { optional: true, + formKey: 'gamma', }, lambda: { optional: true, + formKey: 'lambda', }, num_top_classes: { optional: true, defaultValue: 2, + formKey: 'numTopClasses', }, prediction_field_name: { optional: true, defaultValue: `${config.analysis.classification.dependent_variable}_prediction`, + formKey: 'predictionFieldName', }, randomize_seed: { optional: true, // By default it is randomly generated ignore: true, + formKey: 'randomizeSeed', }, num_top_feature_importance_values: { optional: true, @@ -118,23 +129,29 @@ const getAnalyticsJobMeta = (config: CloneDataFrameAnalyticsConfig): AnalyticsJo standardization_enabled: { defaultValue: true, optional: true, + formKey: 'standardizationEnabled', }, compute_feature_influence: { defaultValue: true, optional: true, + formKey: 'computeFeatureInfluence', }, outlier_fraction: { defaultValue: 0.05, optional: true, + formKey: 'outlierFraction', }, feature_influence_threshold: { optional: true, + formKey: 'featureInfluenceThreshold', }, method: { optional: true, + formKey: 'method', }, n_neighbors: { optional: true, + formKey: 'nNeighbors', }, }, } @@ -152,22 +169,28 @@ const getAnalyticsJobMeta = (config: CloneDataFrameAnalyticsConfig): AnalyticsJo }, eta: { optional: true, + formKey: 'eta', }, feature_bag_fraction: { optional: true, + formKey: 'featureBagFraction', }, max_trees: { optional: true, + formKey: 'maxTrees', }, gamma: { optional: true, + formKey: 'gamma', }, lambda: { optional: true, + formKey: 'lambda', }, prediction_field_name: { optional: true, defaultValue: `${config.analysis.regression.dependent_variable}_prediction`, + formKey: 'predictionFieldName', }, num_top_feature_importance_values: { optional: true, @@ -178,11 +201,15 @@ const getAnalyticsJobMeta = (config: CloneDataFrameAnalyticsConfig): AnalyticsJo optional: true, // By default it is randomly generated ignore: true, + formKey: 'randomizeSeed', }, loss_function: { optional: true, defaultValue: 'mse', }, + loss_function_parameter: { + optional: true, + }, }, } : {}), @@ -332,9 +359,52 @@ export const CloneAction: FC = ({ createAnalyticsForm, item }) const buttonText = i18n.translate('xpack.ml.dataframe.analyticsList.cloneJobButtonLabel', { defaultMessage: 'Clone job', }); - const { actions } = createAnalyticsForm; + + const { notifications, savedObjects } = useMlKibana().services; + const savedObjectsClient = savedObjects.client; + const onClick = async () => { - await actions.setJobClone(item.config); + const sourceIndex = Array.isArray(item.config.source.index) + ? item.config.source.index[0] + : item.config.source.index; + let sourceIndexId; + + try { + const response = await savedObjectsClient.find({ + type: 'index-pattern', + perPage: 10, + search: `"${sourceIndex}"`, + searchFields: ['title'], + fields: ['title'], + }); + + const ip = response.savedObjects.find( + (obj) => obj.attributes.title.toLowerCase() === sourceIndex.toLowerCase() + ); + if (ip !== undefined) { + sourceIndexId = ip.id; + } + } catch (e) { + const { toasts } = notifications; + const error = extractErrorMessage(e); + + toasts.addDanger( + i18n.translate( + 'xpack.ml.dataframe.analyticsList.fetchSourceIndexPatternForCloneErrorMessage', + { + defaultMessage: + 'An error occurred checking if index pattern {indexPattern} exists: {error}', + values: { indexPattern: sourceIndex, error }, + } + ) + ); + } + + if (sourceIndexId) { + window.location.href = `ml#/data_frame_analytics/new_job?index=${encodeURIComponent( + sourceIndexId + )}&jobId=${item.config.id}`; + } }; return ( diff --git a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/analytics_list/actions.tsx b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/analytics_list/actions.tsx index 295a3988e1b58..72514c91ff58b 100644 --- a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/analytics_list/actions.tsx +++ b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/analytics_list/actions.tsx @@ -7,7 +7,7 @@ import React from 'react'; import { i18n } from '@kbn/i18n'; import { EuiButtonEmpty, EuiToolTip } from '@elastic/eui'; -// import { DeepReadonly } from '../../../../../../../common/types/common'; +import { DeepReadonly } from '../../../../../../../common/types/common'; import { checkPermission, @@ -21,7 +21,7 @@ import { isClassificationAnalysis, } from '../../../../common/analytics'; import { CreateAnalyticsFormProps } from '../../hooks/use_create_analytics_form'; -// import { CloneAction } from './action_clone'; +import { CloneAction } from './action_clone'; import { getResultsUrl, isDataFrameAnalyticsRunning, DataFrameAnalyticsListRow } from './common'; import { stopAnalytics } from '../../services/analytics_service'; @@ -106,10 +106,10 @@ export const getActions = (createAnalyticsForm: CreateAnalyticsFormProps) => { return ; }, }, - // { - // render: (item: DeepReadonly) => { - // return ; - // }, - // }, + { + render: (item: DeepReadonly) => { + return ; + }, + }, ]; }; diff --git a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/analytics_list/analytics_list.tsx b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/analytics_list/analytics_list.tsx index bb012a2190859..25e3a2808fc61 100644 --- a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/analytics_list/analytics_list.tsx +++ b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/analytics_list/analytics_list.tsx @@ -51,7 +51,6 @@ import { AnalyticStatsBarStats, StatsBar } from '../../../../../components/stats import { RefreshAnalyticsListButton } from '../refresh_analytics_list_button'; import { CreateAnalyticsButton } from '../create_analytics_button'; import { CreateAnalyticsFormProps } from '../../hooks/use_create_analytics_form'; -import { CreateAnalyticsFlyoutWrapper } from '../create_analytics_flyout_wrapper'; import { getSelectedJobIdFromUrl } from '../../../../../jobs/jobs_list/components/utils'; import { SourceSelection } from '../source_selection'; @@ -286,9 +285,6 @@ export const DataFrameAnalyticsList: FC = ({ } data-test-subj="mlNoDataFrameAnalyticsFound" /> - {!isManagementTable && createAnalyticsForm && ( - - )} {isSourceIndexModalVisible === true && ( setIsSourceIndexModalVisible(false)} /> )} @@ -440,9 +436,6 @@ export const DataFrameAnalyticsList: FC = ({ /> - {!isManagementTable && createAnalyticsForm?.state.isModalVisible && ( - - )} {isSourceIndexModalVisible === true && ( setIsSourceIndexModalVisible(false)} /> )} diff --git a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/create_analytics_flyout/_create_analytics_flyout.scss b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/create_analytics_flyout/_create_analytics_flyout.scss deleted file mode 100644 index e6c6ffafc446a..0000000000000 --- a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/create_analytics_flyout/_create_analytics_flyout.scss +++ /dev/null @@ -1,3 +0,0 @@ -.mlAnalyticsCreateFlyout__footerButton { - float: right; -} diff --git a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/create_analytics_flyout/_index.scss b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/create_analytics_flyout/_index.scss deleted file mode 100644 index 668b35f8370d2..0000000000000 --- a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/create_analytics_flyout/_index.scss +++ /dev/null @@ -1 +0,0 @@ -@import 'create_analytics_flyout'; diff --git a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/create_analytics_flyout/create_analytics_flyout.test.tsx b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/create_analytics_flyout/create_analytics_flyout.test.tsx deleted file mode 100644 index dc91c955184b0..0000000000000 --- a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/create_analytics_flyout/create_analytics_flyout.test.tsx +++ /dev/null @@ -1,42 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import { mount } from 'enzyme'; -import React from 'react'; -import { mountHook } from 'test_utils/enzyme_helpers'; - -import { CreateAnalyticsFlyout } from './create_analytics_flyout'; - -import { MlContext } from '../../../../../contexts/ml'; -import { kibanaContextValueMock } from '../../../../../contexts/ml/__mocks__/kibana_context_value'; - -import { useCreateAnalyticsForm } from '../../hooks/use_create_analytics_form'; - -const getMountedHook = () => - mountHook( - () => useCreateAnalyticsForm(), - ({ children }) => ( - {children} - ) - ); - -// workaround to make React.memo() work with enzyme -jest.mock('react', () => { - const r = jest.requireActual('react'); - return { ...r, memo: (x: any) => x }; -}); - -describe('Data Frame Analytics: ', () => { - test('Minimal initialization', () => { - const { getLastHookValue } = getMountedHook(); - const props = getLastHookValue(); - const wrapper = mount(); - - expect(wrapper.find('[data-test-subj="mlDataFrameAnalyticsFlyoutHeaderTitle"]').text()).toBe( - 'Create analytics job' - ); - }); -}); diff --git a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/create_analytics_flyout/create_analytics_flyout.tsx b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/create_analytics_flyout/create_analytics_flyout.tsx deleted file mode 100644 index b0f13e398cc50..0000000000000 --- a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/create_analytics_flyout/create_analytics_flyout.tsx +++ /dev/null @@ -1,108 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import React, { FC } from 'react'; - -import { - EuiButton, - EuiButtonEmpty, - EuiFlyout, - EuiFlyoutBody, - EuiFlyoutHeader, - EuiFlyoutFooter, - EuiTitle, -} from '@elastic/eui'; - -import { i18n } from '@kbn/i18n'; - -import { CreateAnalyticsFormProps } from '../../hooks/use_create_analytics_form'; - -export const CreateAnalyticsFlyout: FC = ({ - actions, - children, - state, -}) => { - const { closeModal, createAnalyticsJob, startAnalyticsJob } = actions; - const { - isJobCreated, - isJobStarted, - isModalButtonDisabled, - isValid, - isAdvancedEditorValidJson, - cloneJob, - } = state; - - const headerText = !!cloneJob - ? i18n.translate('xpack.ml.dataframe.analytics.clone.flyoutHeaderTitle', { - defaultMessage: 'Clone job from {job_id}', - values: { job_id: cloneJob.id }, - }) - : i18n.translate('xpack.ml.dataframe.analytics.create.flyoutHeaderTitle', { - defaultMessage: 'Create analytics job', - }); - - return ( - - - -

{headerText}

-
-
- {children} - - {(!isJobCreated || !isJobStarted) && ( - - {isJobCreated === true - ? i18n.translate('xpack.ml.dataframe.analytics.create.flyoutCloseButton', { - defaultMessage: 'Close', - }) - : i18n.translate('xpack.ml.dataframe.analytics.create.flyoutCancelButton', { - defaultMessage: 'Cancel', - })} - - )} - - {!isJobCreated && !isJobStarted && ( - - {i18n.translate('xpack.ml.dataframe.analytics.create.flyoutCreateButton', { - defaultMessage: 'Create', - })} - - )} - {isJobCreated && !isJobStarted && ( - - {i18n.translate('xpack.ml.dataframe.analytics.create.flyoutStartButton', { - defaultMessage: 'Start', - })} - - )} - {isJobCreated && isJobStarted && ( - - {i18n.translate('xpack.ml.dataframe.analytics.create.flyoutCloseButton', { - defaultMessage: 'Close', - })} - - )} - -
- ); -}; diff --git a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/create_analytics_flyout/index.ts b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/create_analytics_flyout/index.ts deleted file mode 100644 index 3a25359373aa6..0000000000000 --- a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/create_analytics_flyout/index.ts +++ /dev/null @@ -1,7 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -export { CreateAnalyticsFlyout } from './create_analytics_flyout'; diff --git a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/create_analytics_flyout_wrapper/create_analytics_flyout_wrapper.tsx b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/create_analytics_flyout_wrapper/create_analytics_flyout_wrapper.tsx deleted file mode 100644 index 2f3c38b6ffe4e..0000000000000 --- a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/create_analytics_flyout_wrapper/create_analytics_flyout_wrapper.tsx +++ /dev/null @@ -1,28 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import React, { FC } from 'react'; - -import { CreateAnalyticsFormProps } from '../../hooks/use_create_analytics_form'; - -import { CreateAnalyticsAdvancedEditor } from '../create_analytics_advanced_editor'; -import { CreateAnalyticsForm } from '../create_analytics_form'; -import { CreateAnalyticsFlyout } from '../create_analytics_flyout'; - -export const CreateAnalyticsFlyoutWrapper: FC = (props) => { - const { isAdvancedEditorEnabled, isModalVisible } = props.state; - - if (isModalVisible === false) { - return null; - } - - return ( - - {isAdvancedEditorEnabled === false && } - {isAdvancedEditorEnabled === true && } - - ); -}; diff --git a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/create_analytics_flyout_wrapper/index.ts b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/create_analytics_flyout_wrapper/index.ts deleted file mode 100644 index c8e7a958f6d42..0000000000000 --- a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/create_analytics_flyout_wrapper/index.ts +++ /dev/null @@ -1,7 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -export { CreateAnalyticsFlyoutWrapper } from './create_analytics_flyout_wrapper'; diff --git a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/create_analytics_form/_create_analytics_form.scss b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/create_analytics_form/_create_analytics_form.scss deleted file mode 100644 index 9b4559f9e2cb2..0000000000000 --- a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/create_analytics_form/_create_analytics_form.scss +++ /dev/null @@ -1,31 +0,0 @@ -/* - The job creation form displayed in the modal adapts its height dynamically - if the content changes. If a form element changes to show/hide error messages, - this results in a changing height of the modal. If you type quickly e.g. - in the job ID input field and type chars which are invalid only for example - at the end of the string, this will result in an unwanted height toggling - effect. The following CSS avoids this by 1) delaying the visilibity of the - error message by 500ms and 2) animating the height and opacity to create - a fade-in effect after that so the modal grows smoothly and doesn't - toggle its height. - */ - -@keyframes mlDelayedShow { - 0%, 50% { - max-height: 0; - opacity: 0; - padding: 0; - visibility: hidden; - } - 100% { - max-height: 300px; - opacity: 1; - padding-top: $euiSizeS; - } -} - -.mlDataFrameAnalyticsCreateForm { - .euiFormErrorText { - animation: mlDelayedShow 1s; - } -} diff --git a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/create_analytics_form/_index.scss b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/create_analytics_form/_index.scss deleted file mode 100644 index 66fa2c02e60f5..0000000000000 --- a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/create_analytics_form/_index.scss +++ /dev/null @@ -1 +0,0 @@ -@import 'create_analytics_form'; diff --git a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/create_analytics_form/create_analytics_form.test.tsx b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/create_analytics_form/create_analytics_form.test.tsx deleted file mode 100644 index 85cd70912b41f..0000000000000 --- a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/create_analytics_form/create_analytics_form.test.tsx +++ /dev/null @@ -1,69 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import { mount } from 'enzyme'; -import React from 'react'; -import { mountHook } from 'test_utils/enzyme_helpers'; - -import { CreateAnalyticsForm } from './create_analytics_form'; - -import { MlContext } from '../../../../../contexts/ml'; -import { kibanaContextValueMock } from '../../../../../contexts/ml/__mocks__/kibana_context_value'; - -import { useCreateAnalyticsForm } from '../../hooks/use_create_analytics_form'; - -const getMountedHook = () => - mountHook( - () => useCreateAnalyticsForm(), - ({ children }) => ( - {children} - ) - ); - -// workaround to make React.memo() work with enzyme -jest.mock('react', () => { - const r = jest.requireActual('react'); - return { ...r, memo: (x: any) => x }; -}); - -jest.mock('../../../../../contexts/kibana', () => ({ - useMlKibana: () => { - return { - services: { - docLinks: () => ({ - ELASTIC_WEBSITE_URL: 'https://www.elastic.co/', - DOC_LINK_VERSION: 'jest-metadata-mock-branch', - }), - }, - }; - }, -})); - -describe('Data Frame Analytics: ', () => { - test('Minimal initialization', () => { - const { getLastHookValue } = getMountedHook(); - const props = getLastHookValue(); - const wrapper = mount( - - - - ); - - const euiFormRows = wrapper.find('EuiFormRow'); - expect(euiFormRows.length).toBe(10); - - const row1 = euiFormRows.at(0); - expect(row1.find('label').text()).toBe('Job type'); - - const options = row1.find('option'); - expect(options.at(0).props().value).toBe(''); - expect(options.at(1).props().value).toBe('outlier_detection'); - expect(options.at(2).props().value).toBe('regression'); - - const row2 = euiFormRows.at(1); - expect(row2.find('EuiSwitch').text()).toBe('Enable advanced editor'); - }); -}); diff --git a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/create_analytics_form/create_analytics_form.tsx b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/create_analytics_form/create_analytics_form.tsx deleted file mode 100644 index 64fe736e67b17..0000000000000 --- a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/create_analytics_form/create_analytics_form.tsx +++ /dev/null @@ -1,850 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import React, { Fragment, FC, useEffect, useMemo, useRef } from 'react'; - -import { - EuiComboBox, - EuiComboBoxOptionOption, - EuiForm, - EuiFieldNumber, - EuiFieldText, - EuiFormRow, - EuiLink, - EuiRange, - EuiSwitch, -} from '@elastic/eui'; -import { debounce } from 'lodash'; - -import { i18n } from '@kbn/i18n'; -import { FormattedMessage } from '@kbn/i18n/react'; - -import { useMlKibana } from '../../../../../contexts/kibana'; -import { ml } from '../../../../../services/ml_api_service'; -import { newJobCapsService } from '../../../../../services/new_job_capabilities_service'; -import { useMlContext } from '../../../../../contexts/ml'; -import { CreateAnalyticsFormProps } from '../../hooks/use_create_analytics_form'; -import { - DEFAULT_MODEL_MEMORY_LIMIT, - getJobConfigFromFormState, - State, -} from '../../hooks/use_create_analytics_form/state'; -import { JOB_ID_MAX_LENGTH } from '../../../../../../../common/constants/validation'; -import { Messages } from './messages'; -import { JobType } from './job_type'; -import { JobDescriptionInput } from './job_description'; -import { getModelMemoryLimitErrors } from '../../hooks/use_create_analytics_form/reducer'; -import { IndexPattern, indexPatterns } from '../../../../../../../../../../src/plugins/data/public'; -import { - ANALYSIS_CONFIG_TYPE, - DfAnalyticsExplainResponse, - FieldSelectionItem, - NUM_TOP_FEATURE_IMPORTANCE_VALUES_MIN, - TRAINING_PERCENT_MIN, - TRAINING_PERCENT_MAX, -} from '../../../../common/analytics'; -import { shouldAddAsDepVarOption, OMIT_FIELDS } from './form_options_validation'; - -const requiredFieldsErrorText = i18n.translate( - 'xpack.ml.dataframe.analytics.create.requiredFieldsErrorMessage', - { - defaultMessage: 'At least one field must be included in the analysis.', - } -); - -export const CreateAnalyticsForm: FC = ({ actions, state }) => { - const { - services: { docLinks }, - } = useMlKibana(); - const { ELASTIC_WEBSITE_URL, DOC_LINK_VERSION } = docLinks; - const { setFormState, setEstimatedModelMemoryLimit } = actions; - const mlContext = useMlContext(); - const { - estimatedModelMemoryLimit, - form, - indexPatternsMap, - isAdvancedEditorEnabled, - isJobCreated, - requestMessages, - } = state; - - const forceInput = useRef(null); - const firstUpdate = useRef(true); - - const { - createIndexPattern, - dependentVariable, - dependentVariableFetchFail, - dependentVariableOptions, - description, - destinationIndex, - destinationIndexNameEmpty, - destinationIndexNameExists, - destinationIndexNameValid, - destinationIndexPatternTitleExists, - excludes, - excludesOptions, - fieldOptionsFetchFail, - jobId, - jobIdEmpty, - jobIdExists, - jobIdValid, - jobIdInvalidMaxLength, - jobType, - loadingDepVarOptions, - loadingFieldOptions, - maxDistinctValuesError, - modelMemoryLimit, - modelMemoryLimitValidationResult, - numTopFeatureImportanceValues, - numTopFeatureImportanceValuesValid, - previousJobType, - previousSourceIndex, - requiredFieldsError, - sourceIndex, - sourceIndexNameEmpty, - sourceIndexNameValid, - sourceIndexContainsNumericalFields, - sourceIndexFieldsCheckFailed, - trainingPercent, - } = form; - const characterList = indexPatterns.ILLEGAL_CHARACTERS_VISIBLE.join(', '); - - const mmlErrors = useMemo(() => getModelMemoryLimitErrors(modelMemoryLimitValidationResult), [ - modelMemoryLimitValidationResult, - ]); - - const isJobTypeWithDepVar = - jobType === ANALYSIS_CONFIG_TYPE.REGRESSION || jobType === ANALYSIS_CONFIG_TYPE.CLASSIFICATION; - - // Find out if index pattern contain numeric fields. Provides a hint in the form - // that an analytics jobs is not able to identify outliers if there are no numeric fields present. - const validateSourceIndexFields = async () => { - try { - const indexPattern: IndexPattern = await mlContext.indexPatterns.get( - indexPatternsMap[sourceIndex].value - ); - const containsNumericalFields: boolean = indexPattern.fields.some( - ({ name, type }) => !OMIT_FIELDS.includes(name) && type === 'number' - ); - - setFormState({ - sourceIndexContainsNumericalFields: containsNumericalFields, - sourceIndexFieldsCheckFailed: false, - }); - } catch (e) { - setFormState({ - sourceIndexFieldsCheckFailed: true, - }); - } - }; - - const onCreateOption = (searchValue: string, flattenedOptions: EuiComboBoxOptionOption[]) => { - const normalizedSearchValue = searchValue.trim().toLowerCase(); - - if (!normalizedSearchValue) { - return; - } - - const newOption = { - label: searchValue, - }; - - // Create the option if it doesn't exist. - if ( - !flattenedOptions.some( - (option: EuiComboBoxOptionOption) => - option.label.trim().toLowerCase() === normalizedSearchValue - ) - ) { - excludesOptions.push(newOption); - setFormState({ excludes: [...excludes, newOption.label] }); - } - }; - - const debouncedGetExplainData = debounce(async () => { - const jobTypeOrIndexChanged = - previousSourceIndex !== sourceIndex || previousJobType !== jobType; - const shouldUpdateModelMemoryLimit = !firstUpdate.current || !modelMemoryLimit; - const shouldUpdateEstimatedMml = - !firstUpdate.current || !modelMemoryLimit || estimatedModelMemoryLimit === ''; - - if (firstUpdate.current) { - firstUpdate.current = false; - } - // Reset if sourceIndex or jobType changes (jobType requires dependent_variable to be set - - // which won't be the case if switching from outlier detection) - if (jobTypeOrIndexChanged) { - setFormState({ - loadingFieldOptions: true, - }); - } - - try { - const jobConfig = getJobConfigFromFormState(form); - delete jobConfig.dest; - delete jobConfig.model_memory_limit; - const resp: DfAnalyticsExplainResponse = await ml.dataFrameAnalytics.explainDataFrameAnalytics( - jobConfig - ); - const expectedMemoryWithoutDisk = resp.memory_estimation?.expected_memory_without_disk; - - if (shouldUpdateEstimatedMml) { - setEstimatedModelMemoryLimit(expectedMemoryWithoutDisk); - } - - const fieldSelection: FieldSelectionItem[] | undefined = resp.field_selection; - - let hasRequiredFields = false; - if (fieldSelection) { - for (let i = 0; i < fieldSelection.length; i++) { - const field = fieldSelection[i]; - if (field.is_included === true && field.is_required === false) { - hasRequiredFields = true; - break; - } - } - } - - // If sourceIndex has changed load analysis field options again - if (jobTypeOrIndexChanged) { - const analyzedFieldsOptions: EuiComboBoxOptionOption[] = []; - - if (resp.field_selection) { - resp.field_selection.forEach((selectedField: FieldSelectionItem) => { - if (selectedField.is_included === true && selectedField.name !== dependentVariable) { - analyzedFieldsOptions.push({ label: selectedField.name }); - } - }); - } - - setFormState({ - ...(shouldUpdateModelMemoryLimit ? { modelMemoryLimit: expectedMemoryWithoutDisk } : {}), - excludesOptions: analyzedFieldsOptions, - loadingFieldOptions: false, - fieldOptionsFetchFail: false, - maxDistinctValuesError: undefined, - requiredFieldsError: !hasRequiredFields ? requiredFieldsErrorText : undefined, - }); - } else { - setFormState({ - ...(shouldUpdateModelMemoryLimit ? { modelMemoryLimit: expectedMemoryWithoutDisk } : {}), - requiredFieldsError: !hasRequiredFields ? requiredFieldsErrorText : undefined, - }); - } - } catch (e) { - let errorMessage; - if ( - jobType === ANALYSIS_CONFIG_TYPE.CLASSIFICATION && - e.body && - e.body.message !== undefined && - e.body.message.includes('status_exception') && - e.body.message.includes('must have at most') - ) { - errorMessage = e.body.message; - } - const fallbackModelMemoryLimit = - jobType !== undefined - ? DEFAULT_MODEL_MEMORY_LIMIT[jobType] - : DEFAULT_MODEL_MEMORY_LIMIT.outlier_detection; - setEstimatedModelMemoryLimit(fallbackModelMemoryLimit); - setFormState({ - fieldOptionsFetchFail: true, - maxDistinctValuesError: errorMessage, - loadingFieldOptions: false, - ...(shouldUpdateModelMemoryLimit ? { modelMemoryLimit: fallbackModelMemoryLimit } : {}), - }); - } - }, 400); - - const loadDepVarOptions = async (formState: State['form']) => { - setFormState({ - loadingDepVarOptions: true, - // clear when the source index changes - maxDistinctValuesError: undefined, - sourceIndexFieldsCheckFailed: false, - sourceIndexContainsNumericalFields: true, - }); - try { - const indexPattern: IndexPattern = await mlContext.indexPatterns.get( - indexPatternsMap[sourceIndex].value - ); - - if (indexPattern !== undefined) { - const formStateUpdate: { - loadingDepVarOptions: boolean; - dependentVariableFetchFail: boolean; - dependentVariableOptions: State['form']['dependentVariableOptions']; - dependentVariable?: State['form']['dependentVariable']; - } = { - loadingDepVarOptions: false, - dependentVariableFetchFail: false, - dependentVariableOptions: [] as State['form']['dependentVariableOptions'], - }; - - await newJobCapsService.initializeFromIndexPattern(indexPattern, false, false); - // Get fields and filter for supported types for job type - const { fields } = newJobCapsService; - - let resetDependentVariable = true; - for (const field of fields) { - if (shouldAddAsDepVarOption(field, jobType)) { - formStateUpdate.dependentVariableOptions.push({ - label: field.id, - }); - - if (formState.dependentVariable === field.id) { - resetDependentVariable = false; - } - } - } - - if (resetDependentVariable) { - formStateUpdate.dependentVariable = ''; - } - - setFormState(formStateUpdate); - } - } catch (e) { - setFormState({ loadingDepVarOptions: false, dependentVariableFetchFail: true }); - } - }; - - const getSourceIndexErrorMessages = () => { - const errors = []; - if (!sourceIndexNameEmpty && !sourceIndexNameValid) { - errors.push( - - - - ); - } - - if (sourceIndexFieldsCheckFailed === true) { - errors.push( - - - - ); - } - - return errors; - }; - - const onSourceIndexChange = (selectedOptions: EuiComboBoxOptionOption[]) => { - setFormState({ - excludes: [], - excludesOptions: [], - previousSourceIndex: sourceIndex, - sourceIndex: selectedOptions[0].label || '', - requiredFieldsError: undefined, - }); - }; - - useEffect(() => { - if (isJobTypeWithDepVar && sourceIndexNameEmpty === false) { - loadDepVarOptions(form); - } - - if (jobType === ANALYSIS_CONFIG_TYPE.OUTLIER_DETECTION && sourceIndexNameEmpty === false) { - validateSourceIndexFields(); - } - }, [sourceIndex, jobType, sourceIndexNameEmpty]); - - useEffect(() => { - const hasBasicRequiredFields = - jobType !== undefined && sourceIndex !== '' && sourceIndexNameValid === true; - - const hasRequiredAnalysisFields = - (isJobTypeWithDepVar && dependentVariable !== '') || - jobType === ANALYSIS_CONFIG_TYPE.OUTLIER_DETECTION; - - if (hasBasicRequiredFields && hasRequiredAnalysisFields) { - debouncedGetExplainData(); - } - - return () => { - debouncedGetExplainData.cancel(); - }; - }, [ - jobType, - sourceIndex, - sourceIndexNameEmpty, - dependentVariable, - trainingPercent, - JSON.stringify(excludes), - ]); - - // Temp effect to close the context menu popover on Clone button click - useEffect(() => { - if (forceInput.current === null) { - return; - } - const evt = document.createEvent('MouseEvents'); - evt.initEvent('mouseup', true, true); - forceInput.current.dispatchEvent(evt); - }, []); - - const noSupportetdAnalysisFields = - excludesOptions.length === 0 && fieldOptionsFetchFail === false && !sourceIndexNameEmpty; - - return ( - - - {!isJobCreated && ( - - - - - - - { - if (input) { - forceInput.current = input; - } - }} - disabled={isJobCreated} - placeholder={i18n.translate('xpack.ml.dataframe.analytics.create.jobIdPlaceholder', { - defaultMessage: 'Job ID', - })} - value={jobId} - onChange={(e) => setFormState({ jobId: e.target.value })} - aria-label={i18n.translate( - 'xpack.ml.dataframe.analytics.create.jobIdInputAriaLabel', - { - defaultMessage: 'Choose a unique analytics job ID.', - } - )} - isInvalid={(!jobIdEmpty && !jobIdValid) || jobIdExists} - data-test-subj="mlAnalyticsCreateJobFlyoutJobIdInput" - /> - - - - - {!isJobCreated && ( - - a.label.localeCompare(b.label) - )} - selectedOptions={ - indexPatternsMap[sourceIndex] !== undefined ? [{ label: sourceIndex }] : [] - } - onChange={onSourceIndexChange} - isClearable={false} - data-test-subj="mlAnalyticsCreateJobFlyoutSourceIndexSelect" - /> - )} - {isJobCreated && ( - - )} - - - - {i18n.translate( - 'xpack.ml.dataframe.analytics.create.destinationIndexInvalidError', - { - defaultMessage: 'Invalid destination index name.', - } - )} -
- - {i18n.translate( - 'xpack.ml.dataframe.stepDetailsForm.destinationIndexInvalidErrorLink', - { - defaultMessage: 'Learn more about index name limitations.', - } - )} - -
, - ] - } - > - setFormState({ destinationIndex: e.target.value })} - aria-label={i18n.translate( - 'xpack.ml.dataframe.analytics.create.destinationIndexInputAriaLabel', - { - defaultMessage: 'Choose a unique destination index name.', - } - )} - isInvalid={!destinationIndexNameEmpty && !destinationIndexNameValid} - data-test-subj="mlAnalyticsCreateJobFlyoutDestinationIndexInput" - /> - - {(jobType === ANALYSIS_CONFIG_TYPE.REGRESSION || - jobType === ANALYSIS_CONFIG_TYPE.CLASSIFICATION) && ( - - - {i18n.translate( - 'xpack.ml.dataframe.analytics.create.dependentVariableMaxDistictValuesError', - { - defaultMessage: 'Invalid. {message}', - values: { message: maxDistinctValuesError }, - } - )} - , - ] - : []), - ]} - > - - - - {i18n.translate( - 'xpack.ml.dataframe.analytics.create.dependentVariableOptionsFetchError', - { - defaultMessage: - 'There was a problem fetching fields. Please refresh the page and try again.', - } - )} - , - ] - : []), - ]} - > - - setFormState({ - dependentVariable: selectedOptions[0].label || '', - }) - } - isClearable={false} - isInvalid={dependentVariable === ''} - data-test-subj="mlAnalyticsCreateJobFlyoutDependentVariableSelect" - /> - - - setFormState({ trainingPercent: +e.target.value })} - data-test-subj="mlAnalyticsCreateJobFlyoutTrainingPercentSlider" - /> - - {/* num_top_feature_importance_values */} - - {i18n.translate( - 'xpack.ml.dataframe.analytics.create.numTopFeatureImportanceValuesErrorText', - { - defaultMessage: - 'Invalid maximum number of feature importance values.', - } - )} - , - ] - : []), - ]} - > - setFormState({ numTopFeatureImportanceValues: +e.target.value })} - step={1} - value={numTopFeatureImportanceValues} - /> - - - )} - - - - - ({ - label: field, - }))} - onCreateOption={onCreateOption} - onChange={(selectedOptions) => - setFormState({ excludes: selectedOptions.map((option) => option.label) }) - } - isClearable={true} - data-test-subj="mlAnalyticsCreateJobFlyoutExcludesSelect" - /> - - - setFormState({ modelMemoryLimit: e.target.value })} - isInvalid={modelMemoryLimitValidationResult !== null} - data-test-subj="mlAnalyticsCreateJobFlyoutModelMemoryInput" - /> - - - setFormState({ createIndexPattern: !createIndexPattern })} - data-test-subj="mlAnalyticsCreateJobFlyoutCreateIndexPatternSwitch" - /> - - - )} -
- ); -}; diff --git a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/create_analytics_form/job_description.tsx b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/create_analytics_form/job_description.tsx deleted file mode 100644 index 46301a6f832e7..0000000000000 --- a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/create_analytics_form/job_description.tsx +++ /dev/null @@ -1,37 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import React, { FC } from 'react'; -import { EuiFormRow, EuiTextArea } from '@elastic/eui'; -import { i18n } from '@kbn/i18n'; - -const helpText = i18n.translate('xpack.ml.dataframe.analytics.create.jobDescription.helpText', { - defaultMessage: 'Optional descriptive text', -}); - -interface Props { - description: string; - setFormState: React.Dispatch>; -} - -export const JobDescriptionInput: FC = ({ description, setFormState }) => ( - - { - const value = e.target.value; - setFormState({ description: value }); - }} - data-test-subj="mlDFAnalyticsJobCreationJobDescription" - /> - -); diff --git a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/create_analytics_form/job_type.tsx b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/create_analytics_form/job_type.tsx deleted file mode 100644 index 6daa72dd805b1..0000000000000 --- a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/create_analytics_form/job_type.tsx +++ /dev/null @@ -1,80 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import React, { Fragment, FC } from 'react'; -import { i18n } from '@kbn/i18n'; - -import { EuiFormRow, EuiSelect } from '@elastic/eui'; -import { ANALYSIS_CONFIG_TYPE } from '../../../../common'; - -import { AnalyticsJobType } from '../../hooks/use_create_analytics_form/state'; - -interface Props { - type: AnalyticsJobType; - setFormState: React.Dispatch>; -} - -export const JobType: FC = ({ type, setFormState }) => { - const outlierHelpText = i18n.translate( - 'xpack.ml.dataframe.analytics.create.outlierDetectionHelpText', - { - defaultMessage: - 'Outlier detection jobs require a source index that is mapped as a table-like data structure and analyze only numeric and boolean fields. Use the advanced editor to add custom options to the configuration.', - } - ); - - const regressionHelpText = i18n.translate( - 'xpack.ml.dataframe.analytics.create.outlierRegressionHelpText', - { - defaultMessage: - 'Regression jobs analyze only numeric fields. Use the advanced editor to apply custom options, such as the prediction field name.', - } - ); - - const classificationHelpText = i18n.translate( - 'xpack.ml.dataframe.analytics.create.classificationHelpText', - { - defaultMessage: - 'Classification jobs require a source index that is mapped as a table-like data structure and support fields that are numeric, boolean, text, keyword, or ip. Use the advanced editor to apply custom options, such as the prediction field name.', - } - ); - - const helpText = { - [ANALYSIS_CONFIG_TYPE.REGRESSION]: regressionHelpText, - [ANALYSIS_CONFIG_TYPE.OUTLIER_DETECTION]: outlierHelpText, - [ANALYSIS_CONFIG_TYPE.CLASSIFICATION]: classificationHelpText, - }; - - return ( - - - ({ - value: jobType, - text: jobType.replace(/_/g, ' '), - }))} - value={type} - hasNoInitialSelection={true} - onChange={(e) => { - const value = e.target.value as AnalyticsJobType; - setFormState({ - previousJobType: type, - jobType: value, - excludes: [], - requiredFieldsError: undefined, - }); - }} - data-test-subj="mlAnalyticsCreateJobFlyoutJobTypeSelect" - /> - - - ); -}; diff --git a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/hooks/use_create_analytics_form/actions.ts b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/hooks/use_create_analytics_form/actions.ts index c42e03b584a56..a9eedbb2bc5e3 100644 --- a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/hooks/use_create_analytics_form/actions.ts +++ b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/hooks/use_create_analytics_form/actions.ts @@ -57,11 +57,6 @@ export type Action = } | { type: ACTION.SET_IS_JOB_CREATED; isJobCreated: State['isJobCreated'] } | { type: ACTION.SET_IS_JOB_STARTED; isJobStarted: State['isJobStarted'] } - | { - type: ACTION.SET_IS_MODAL_BUTTON_DISABLED; - isModalButtonDisabled: State['isModalButtonDisabled']; - } - | { type: ACTION.SET_IS_MODAL_VISIBLE; isModalVisible: State['isModalVisible'] } | { type: ACTION.SET_JOB_CONFIG; payload: State['jobConfig'] } | { type: ACTION.SET_JOB_IDS; jobIds: State['jobIds'] } | { type: ACTION.SET_ESTIMATED_MODEL_MEMORY_LIMIT; value: State['estimatedModelMemoryLimit'] } @@ -71,12 +66,10 @@ export type Action = export interface ActionDispatchers { closeModal: () => void; createAnalyticsJob: () => void; - openModal: () => Promise; initiateWizard: () => Promise; resetAdvancedEditorMessages: () => void; setAdvancedEditorRawString: (payload: State['advancedEditorRawString']) => void; setFormState: (payload: Partial) => void; - setIsModalVisible: (payload: State['isModalVisible']) => void; setJobConfig: (payload: State['jobConfig']) => void; startAnalyticsJob: () => void; switchToAdvancedEditor: () => void; diff --git a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/hooks/use_create_analytics_form/reducer.test.ts b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/hooks/use_create_analytics_form/reducer.test.ts index fc604c9f5eb0b..e6769a7b64e2b 100644 --- a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/hooks/use_create_analytics_form/reducer.test.ts +++ b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/hooks/use_create_analytics_form/reducer.test.ts @@ -79,21 +79,6 @@ describe('useCreateAnalyticsForm', () => { expect(resettedState).toEqual(initialState); }); - test('reducer(): open/close the modal', () => { - const initialState = getInitialState(); - expect(initialState.isModalVisible).toBe(false); - - const openModalState = reducer(initialState, { - type: ACTION.OPEN_MODAL, - }); - expect(openModalState.isModalVisible).toBe(true); - - const closedModalState = reducer(openModalState, { - type: ACTION.CLOSE_MODAL, - }); - expect(closedModalState.isModalVisible).toBe(false); - }); - test('reducer(): add/reset request messages', () => { const initialState = getInitialState(); expect(initialState.requestMessages).toHaveLength(0); diff --git a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/hooks/use_create_analytics_form/reducer.ts b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/hooks/use_create_analytics_form/reducer.ts index a79a8fcf61ed4..1353a35d8ecc6 100644 --- a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/hooks/use_create_analytics_form/reducer.ts +++ b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/hooks/use_create_analytics_form/reducer.ts @@ -442,12 +442,6 @@ export function reducer(state: State, action: Action): State { case ACTION.RESET_REQUEST_MESSAGES: return { ...state, requestMessages: [] }; - case ACTION.CLOSE_MODAL: - return { ...state, isModalVisible: false }; - - case ACTION.OPEN_MODAL: - return { ...state, isModalVisible: true }; - case ACTION.RESET_ADVANCED_EDITOR_MESSAGES: return { ...state, advancedEditorMessages: [] }; @@ -536,12 +530,6 @@ export function reducer(state: State, action: Action): State { case ACTION.SET_IS_JOB_STARTED: return { ...state, isJobStarted: action.isJobStarted }; - case ACTION.SET_IS_MODAL_BUTTON_DISABLED: - return { ...state, isModalButtonDisabled: action.isModalButtonDisabled }; - - case ACTION.SET_IS_MODAL_VISIBLE: - return { ...state, isModalVisible: action.isModalVisible }; - case ACTION.SET_JOB_CONFIG: return validateAdvancedEditor({ ...state, jobConfig: action.payload }); diff --git a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/hooks/use_create_analytics_form/state.test.ts b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/hooks/use_create_analytics_form/state.test.ts index 547a55da7438b..b9a9caadcebd0 100644 --- a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/hooks/use_create_analytics_form/state.test.ts +++ b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/hooks/use_create_analytics_form/state.test.ts @@ -4,7 +4,43 @@ * you may not use this file except in compliance with the Elastic License. */ -import { getInitialState, getJobConfigFromFormState } from './state'; +import { + getCloneFormStateFromJobConfig, + getInitialState, + getJobConfigFromFormState, +} from './state'; + +const regJobConfig = { + id: 'reg-test-01', + description: 'Reg test job description', + source: { + index: ['reg-test-index'], + query: { + match_all: {}, + }, + }, + dest: { + index: 'reg-test-01-index', + results_field: 'ml', + }, + analysis: { + regression: { + dependent_variable: 'price', + num_top_feature_importance_values: 2, + prediction_field_name: 'airbnb_test', + training_percent: 5, + randomize_seed: 4998776294664380000, + }, + }, + analyzed_fields: { + includes: [], + excludes: [], + }, + model_memory_limit: '22mb', + create_time: 1590514291395, + version: '8.0.0', + allow_lazy_start: false, +}; describe('useCreateAnalyticsForm', () => { test('state: getJobConfigFromFormState()', () => { @@ -28,4 +64,20 @@ describe('useCreateAnalyticsForm', () => { 'the-source-index-2', ]); }); + + test('state: getCloneFormStateFromJobConfig()', () => { + const clonedState = getCloneFormStateFromJobConfig(regJobConfig); + + expect(clonedState?.sourceIndex).toBe('reg-test-index'); + expect(clonedState?.excludes).toStrictEqual([]); + expect(clonedState?.dependentVariable).toBe('price'); + expect(clonedState?.numTopFeatureImportanceValues).toBe(2); + expect(clonedState?.predictionFieldName).toBe('airbnb_test'); + expect(clonedState?.trainingPercent).toBe(5); + expect(clonedState?.randomizeSeed).toBe(4998776294664380000); + expect(clonedState?.modelMemoryLimit).toBe('22mb'); + // destination index and job id should be undefined + expect(clonedState?.destinationIndex).toBe(undefined); + expect(clonedState?.jobId).toBe(undefined); + }); }); diff --git a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/hooks/use_create_analytics_form/state.ts b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/hooks/use_create_analytics_form/state.ts index 387ce89ee4120..8a07704e39910 100644 --- a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/hooks/use_create_analytics_form/state.ts +++ b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/hooks/use_create_analytics_form/state.ts @@ -92,7 +92,6 @@ export interface State { outlierFraction: undefined | number; predictionFieldName: undefined | string; previousJobType: null | AnalyticsJobType; - previousSourceIndex: EsIndexName | undefined; requiredFieldsError: string | undefined; randomizeSeed: undefined | number; sourceIndex: EsIndexName; @@ -110,8 +109,6 @@ export interface State { isAdvancedEditorValidJson: boolean; isJobCreated: boolean; isJobStarted: boolean; - isModalButtonDisabled: boolean; - isModalVisible: boolean; isValid: boolean; jobConfig: DeepPartial; jobIds: DataFrameAnalyticsId[]; @@ -167,7 +164,6 @@ export const getInitialState = (): State => ({ outlierFraction: undefined, predictionFieldName: undefined, previousJobType: null, - previousSourceIndex: undefined, requiredFieldsError: undefined, randomizeSeed: undefined, sourceIndex: '', @@ -189,8 +185,6 @@ export const getInitialState = (): State => ({ isAdvancedEditorValidJson: true, isJobCreated: false, isJobStarted: false, - isModalVisible: false, - isModalButtonDisabled: false, isValid: false, jobIds: [], requestMessages: [], @@ -328,6 +322,14 @@ export const getJobConfigFromFormState = ( return jobConfig; }; +function toCamelCase(property: string): string { + const camelCased = property.replace(/_([a-z])/g, function (g) { + return g[1].toUpperCase(); + }); + + return camelCased; +} + /** * Extracts form state for a job clone from the analytics job configuration. * For cloning we keep job id and destination index empty. @@ -353,13 +355,12 @@ export function getCloneFormStateFromJobConfig( ) { const analysisConfig = analyticsJobConfig.analysis[jobType]; - resultState.dependentVariable = analysisConfig.dependent_variable; - resultState.numTopFeatureImportanceValues = analysisConfig.num_top_feature_importance_values; - resultState.trainingPercent = analysisConfig.training_percent; - - if (isClassificationAnalysis(analyticsJobConfig.analysis)) { - // @ts-ignore - resultState.numTopClasses = analysisConfig.num_top_classes; + for (const key in analysisConfig) { + if (analysisConfig.hasOwnProperty(key)) { + const camelCased = toCamelCase(key); + // @ts-ignore + resultState[camelCased] = analysisConfig[key]; + } } } diff --git a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/hooks/use_create_analytics_form/use_create_analytics_form.test.tsx b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/hooks/use_create_analytics_form/use_create_analytics_form.test.tsx index 182e50a5d74d1..ac1c710e1d106 100644 --- a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/hooks/use_create_analytics_form/use_create_analytics_form.test.tsx +++ b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/hooks/use_create_analytics_form/use_create_analytics_form.test.tsx @@ -46,39 +46,13 @@ describe('getErrorMessage()', () => { describe('useCreateAnalyticsForm()', () => { test('initialization', () => { const { getLastHookValue } = getMountedHook(); - const { state, actions } = getLastHookValue(); + const { actions } = getLastHookValue(); - expect(state.isModalVisible).toBe(false); - expect(typeof actions.closeModal).toBe('function'); expect(typeof actions.createAnalyticsJob).toBe('function'); - expect(typeof actions.openModal).toBe('function'); expect(typeof actions.startAnalyticsJob).toBe('function'); expect(typeof actions.setFormState).toBe('function'); }); - test('open/close modal', () => { - const { act, getLastHookValue } = getMountedHook(); - const { state, actions } = getLastHookValue(); - - expect(state.isModalVisible).toBe(false); - - act(() => { - // this should be actions.openModal(), but that doesn't work yet because act() doesn't support async yet. - // we need to wait for an update to React 16.9 - actions.setIsModalVisible(true); - }); - const { state: stateModalOpen } = getLastHookValue(); - expect(stateModalOpen.isModalVisible).toBe(true); - - act(() => { - // this should be actions.closeModal(), but that doesn't work yet because act() doesn't support async yet. - // we need to wait for an update to React 16.9 - actions.setIsModalVisible(false); - }); - const { state: stateModalClosed } = getLastHookValue(); - expect(stateModalClosed.isModalVisible).toBe(false); - }); - // TODO // add tests for createAnalyticsJob() and startAnalyticsJob() // once React 16.9 with support for async act() is available. diff --git a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/hooks/use_create_analytics_form/use_create_analytics_form.ts b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/hooks/use_create_analytics_form/use_create_analytics_form.ts index c4cbe149f88bc..2de9a1dcadd4b 100644 --- a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/hooks/use_create_analytics_form/use_create_analytics_form.ts +++ b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/hooks/use_create_analytics_form/use_create_analytics_form.ts @@ -87,12 +87,6 @@ export const useCreateAnalyticsForm = (): CreateAnalyticsFormProps => { dispatch({ type: ACTION.SET_IS_JOB_STARTED, isJobStarted }); }; - const setIsModalButtonDisabled = (isModalButtonDisabled: boolean) => - dispatch({ type: ACTION.SET_IS_MODAL_BUTTON_DISABLED, isModalButtonDisabled }); - - const setIsModalVisible = (isModalVisible: boolean) => - dispatch({ type: ACTION.SET_IS_MODAL_VISIBLE, isModalVisible }); - const setJobIds = (jobIds: DataFrameAnalyticsId[]) => dispatch({ type: ACTION.SET_JOB_IDS, jobIds }); @@ -102,7 +96,6 @@ export const useCreateAnalyticsForm = (): CreateAnalyticsFormProps => { const createAnalyticsJob = async () => { resetRequestMessages(); - setIsModalButtonDisabled(true); const analyticsJobConfig = (isAdvancedEditorEnabled ? jobConfig @@ -123,7 +116,6 @@ export const useCreateAnalyticsForm = (): CreateAnalyticsFormProps => { } ), }); - setIsModalButtonDisabled(false); setIsJobCreated(true); if (createIndexPattern) { createKibanaIndexPattern(); @@ -139,7 +131,6 @@ export const useCreateAnalyticsForm = (): CreateAnalyticsFormProps => { } ), }); - setIsModalButtonDisabled(false); } }; @@ -267,13 +258,6 @@ export const useCreateAnalyticsForm = (): CreateAnalyticsFormProps => { } }; - const openModal = async () => { - await mlContext.indexPatterns.clearCache(); - resetForm(); - await prepareFormValidation(); - dispatch({ type: ACTION.OPEN_MODAL }); - }; - const initiateWizard = async () => { await mlContext.indexPatterns.clearCache(); await prepareFormValidation(); @@ -327,8 +311,6 @@ export const useCreateAnalyticsForm = (): CreateAnalyticsFormProps => { const setJobClone = async (cloneJob: DeepReadonly) => { resetForm(); - await prepareFormValidation(); - const config = extractCloningConfig(cloneJob); if (isAdvancedConfig(config)) { setJobConfig(config); @@ -339,18 +321,15 @@ export const useCreateAnalyticsForm = (): CreateAnalyticsFormProps => { } dispatch({ type: ACTION.SET_JOB_CLONE, cloneJob }); - dispatch({ type: ACTION.OPEN_MODAL }); }; const actions: ActionDispatchers = { closeModal, createAnalyticsJob, - openModal, initiateWizard, resetAdvancedEditorMessages, setAdvancedEditorRawString, setFormState, - setIsModalVisible, setJobConfig, startAnalyticsJob, switchToAdvancedEditor, diff --git a/x-pack/plugins/ml/public/application/routing/routes/data_frame_analytics/analytics_job_creation.tsx b/x-pack/plugins/ml/public/application/routing/routes/data_frame_analytics/analytics_job_creation.tsx index 68af9a2a49cab..ebc7bd95fb0c3 100644 --- a/x-pack/plugins/ml/public/application/routing/routes/data_frame_analytics/analytics_job_creation.tsx +++ b/x-pack/plugins/ml/public/application/routing/routes/data_frame_analytics/analytics_job_creation.tsx @@ -30,12 +30,15 @@ export const analyticsJobsCreationRoute: MlRoute = { }; const PageWrapper: FC = ({ location, deps }) => { - const { index, savedSearchId }: Record = parse(location.search, { sort: false }); + const { index, jobId, savedSearchId }: Record = parse(location.search, { + sort: false, + }); + const { context } = useResolver(index, savedSearchId, deps.config, basicResolvers(deps)); return ( - + ); }; diff --git a/x-pack/plugins/translations/translations/ja-JP.json b/x-pack/plugins/translations/translations/ja-JP.json index 7c0ba89f5b341..47a8d75c4ae06 100644 --- a/x-pack/plugins/translations/translations/ja-JP.json +++ b/x-pack/plugins/translations/translations/ja-JP.json @@ -9662,7 +9662,6 @@ "xpack.ml.dataframe.analytics.classificationExploration.showActions": "アクションを表示", "xpack.ml.dataframe.analytics.classificationExploration.showAllColumns": "すべての列を表示", "xpack.ml.dataframe.analytics.classificationExploration.tableJobIdTitle": "分類ジョブID {jobId}のデスティネーションインデックス", - "xpack.ml.dataframe.analytics.clone.flyoutHeaderTitle": "{job_id} からのジョブのクローンを作成", "xpack.ml.dataframe.analytics.create.advancedEditor.codeEditorAriaLabel": "高度な分析ジョブエディター", "xpack.ml.dataframe.analytics.create.advancedEditor.configRequestBody": "構成リクエスト本文", "xpack.ml.dataframe.analytics.create.advancedEditor.jobIdExistsError": "このIDの分析ジョブが既に存在します。", @@ -9695,22 +9694,12 @@ "xpack.ml.dataframe.analytics.create.destinationIndexLabel": "デスティネーションインデックス", "xpack.ml.dataframe.analytics.create.duplicateIndexPatternErrorMessage": "Kibanaインデックスパターンの作成中にエラーが発生しました。", "xpack.ml.dataframe.analytics.create.duplicateIndexPatternErrorMessageError": "インデックスパターン{indexPatternName}はすでに作成されています。", - "xpack.ml.dataframe.analytics.create.enableAdvancedEditorHelpText": "高度なエディターからこのフォームには戻れません。", - "xpack.ml.dataframe.analytics.create.enableAdvancedEditorSwitch": "詳細エディターを有効にする", "xpack.ml.dataframe.analytics.create.errorCreatingDataFrameAnalyticsJob": "データフレーム分析ジョブの作成中にエラーが発生しました。", "xpack.ml.dataframe.analytics.create.errorGettingDataFrameAnalyticsList": "既存のデータフレーム分析ジョブIDの取得中にエラーが発生しました。", "xpack.ml.dataframe.analytics.create.errorGettingDataFrameIndexNames": "既存のインデックス名の取得中にエラーが発生しました。", "xpack.ml.dataframe.analytics.create.errorGettingIndexPatternTitles": "既存のインデックスパターンのタイトルの取得中にエラーが発生しました。", "xpack.ml.dataframe.analytics.create.errorStartingDataFrameAnalyticsJob": "データフレーム分析ジョブの開始中にエラーが発生しました。", - "xpack.ml.dataframe.analytics.create.excludedFieldsHelpText": "分析から除外するフィールドを選択してください。他のすべてのサポートされるフィールドが含まれます。", "xpack.ml.dataframe.analytics.create.excludedFieldsLabel": "除外されたフィールド", - "xpack.ml.dataframe.analytics.create.excludesInputAriaLabel": "任意。除外するフィールドを入力または選択してください。", - "xpack.ml.dataframe.analytics.create.excludesOptionsNoSupportedFields": "このインデックスパターンのサポートされている分析フィールドが見つかりませんでした。", - "xpack.ml.dataframe.analytics.create.flyoutCancelButton": "キャンセル", - "xpack.ml.dataframe.analytics.create.flyoutCloseButton": "閉じる", - "xpack.ml.dataframe.analytics.create.flyoutCreateButton": "作成", - "xpack.ml.dataframe.analytics.create.flyoutHeaderTitle": "分析ジョブの作成", - "xpack.ml.dataframe.analytics.create.flyoutStartButton": "開始", "xpack.ml.dataframe.analytics.create.indexPatternAlreadyExistsError": "このタイトルのインデックスパターンが既に存在します。", "xpack.ml.dataframe.analytics.create.indexPatternExistsError": "このタイトルのインデックスパターンが既に存在します。", "xpack.ml.dataframe.analytics.create.jobDescription.helpText": "オプションの説明テキストです", @@ -9731,12 +9720,6 @@ "xpack.ml.dataframe.analytics.create.numTopFeatureImportanceValuesLabel": "機能重要度値", "xpack.ml.dataframe.analytics.create.outlierDetectionHelpText": "外れ値検出ジョブは、表に示すようなデータ構造でマッピングされたソースインデックスを必要とし、数字とブール値フィールドのみを分析します。カスタムオプションを構成に追加するには、詳細エディターを使用します。", "xpack.ml.dataframe.analytics.create.outlierRegressionHelpText": "リグレッションジョブは数値フィールドのみを分析します。予測フィールド名などのカスタムオプションを適用するには、詳細エディターを使用します。", - "xpack.ml.dataframe.analytics.create.sourceIndexFieldCheckError": "数値フィールドの確認中に問題が発生しました。ページを更新して再起動してください。", - "xpack.ml.dataframe.analytics.create.sourceIndexHelpText": "このインデックスパターンには数字タイプのフィールドが含まれていません。分析ジョブで外れ値が検出されない可能性があります。", - "xpack.ml.dataframe.analytics.create.sourceIndexInputAriaLabel": "ソースインデックスパターンまたは検索。", - "xpack.ml.dataframe.analytics.create.sourceIndexInvalidError": "無効なソースインデックス名。スペースや{characterList}を含めることはできません", - "xpack.ml.dataframe.analytics.create.sourceIndexLabel": "ソースインデックス", - "xpack.ml.dataframe.analytics.create.sourceIndexPlaceholder": "ソースインデックスパターンを選択してください。", "xpack.ml.dataframe.analytics.create.startDataFrameAnalyticsSuccessMessage": "データフレーム分析 {jobId} の開始リクエストが受け付けられました。", "xpack.ml.dataframe.analytics.create.trainingPercentLabel": "トレーニングパーセンテージ", "xpack.ml.dataframe.analytics.errorCallout.evaluateErrorTitle": "データの読み込み中にエラーが発生しました。", diff --git a/x-pack/plugins/translations/translations/zh-CN.json b/x-pack/plugins/translations/translations/zh-CN.json index 2470d3463cb2a..2985ea78bea03 100644 --- a/x-pack/plugins/translations/translations/zh-CN.json +++ b/x-pack/plugins/translations/translations/zh-CN.json @@ -9666,7 +9666,6 @@ "xpack.ml.dataframe.analytics.classificationExploration.showActions": "显示操作", "xpack.ml.dataframe.analytics.classificationExploration.showAllColumns": "显示所有列", "xpack.ml.dataframe.analytics.classificationExploration.tableJobIdTitle": "分类作业 ID {jobId} 的目标索引", - "xpack.ml.dataframe.analytics.clone.flyoutHeaderTitle": "从 {job_id} 克隆作业", "xpack.ml.dataframe.analytics.create.advancedEditor.codeEditorAriaLabel": "高级分析作业编辑器", "xpack.ml.dataframe.analytics.create.advancedEditor.configRequestBody": "配置请求正文", "xpack.ml.dataframe.analytics.create.advancedEditor.jobIdExistsError": "已存在具有此 ID 的分析作业。", @@ -9699,22 +9698,12 @@ "xpack.ml.dataframe.analytics.create.destinationIndexLabel": "目标 IP", "xpack.ml.dataframe.analytics.create.duplicateIndexPatternErrorMessage": "创建 Kibana 索引模式时发生错误:", "xpack.ml.dataframe.analytics.create.duplicateIndexPatternErrorMessageError": "索引模式 {indexPatternName} 已存在。", - "xpack.ml.dataframe.analytics.create.enableAdvancedEditorHelpText": "您不能从高级编辑器切回到此表单。", - "xpack.ml.dataframe.analytics.create.enableAdvancedEditorSwitch": "启用高级编辑器", "xpack.ml.dataframe.analytics.create.errorCreatingDataFrameAnalyticsJob": "创建数据帧分析作业时发生错误:", "xpack.ml.dataframe.analytics.create.errorGettingDataFrameAnalyticsList": "获取现有数据帧分析作业 ID 时发生错误:", "xpack.ml.dataframe.analytics.create.errorGettingDataFrameIndexNames": "获取现有索引名称时发生错误:", "xpack.ml.dataframe.analytics.create.errorGettingIndexPatternTitles": "获取现有索引模式标题时发生错误:", "xpack.ml.dataframe.analytics.create.errorStartingDataFrameAnalyticsJob": "启动数据帧分析作业时发生错误:", - "xpack.ml.dataframe.analytics.create.excludedFieldsHelpText": "选择要从分析中排除的字段。包括所有其他支持的字段。", "xpack.ml.dataframe.analytics.create.excludedFieldsLabel": "排除的字段", - "xpack.ml.dataframe.analytics.create.excludesInputAriaLabel": "可选。输入或选择要排除的字段。", - "xpack.ml.dataframe.analytics.create.excludesOptionsNoSupportedFields": "没有为此索引模式找到任何支持的分析字段。", - "xpack.ml.dataframe.analytics.create.flyoutCancelButton": "取消", - "xpack.ml.dataframe.analytics.create.flyoutCloseButton": "关闭", - "xpack.ml.dataframe.analytics.create.flyoutCreateButton": "创建", - "xpack.ml.dataframe.analytics.create.flyoutHeaderTitle": "创建分析作业", - "xpack.ml.dataframe.analytics.create.flyoutStartButton": "开始", "xpack.ml.dataframe.analytics.create.indexPatternAlreadyExistsError": "具有此名称的索引模式已存在。", "xpack.ml.dataframe.analytics.create.indexPatternExistsError": "具有此名称的索引模式已存在。", "xpack.ml.dataframe.analytics.create.jobDescription.helpText": "可选的描述文本", @@ -9735,12 +9724,6 @@ "xpack.ml.dataframe.analytics.create.numTopFeatureImportanceValuesLabel": "功能重要性值", "xpack.ml.dataframe.analytics.create.outlierDetectionHelpText": "离群值检测作业需要映射为表状数据结构的源索引,并仅分析数值和布尔值字段。使用高级编辑器将定制选项添加到配置。", "xpack.ml.dataframe.analytics.create.outlierRegressionHelpText": "回归作业仅分析数值字段。使用高级编辑器来应用定制选项,如预测字段名称。", - "xpack.ml.dataframe.analytics.create.sourceIndexFieldCheckError": "检查数值字段时出现问题。请刷新页面并重试。", - "xpack.ml.dataframe.analytics.create.sourceIndexHelpText": "此索引模式不包含任何数值类型字段。分析作业可能无法提供任何离群值。", - "xpack.ml.dataframe.analytics.create.sourceIndexInputAriaLabel": "源索引模式或搜索。", - "xpack.ml.dataframe.analytics.create.sourceIndexInvalidError": "源索引名称无效,其不能包含空格或以下字符:{characterList}", - "xpack.ml.dataframe.analytics.create.sourceIndexLabel": "源索引", - "xpack.ml.dataframe.analytics.create.sourceIndexPlaceholder": "选择源索引模式。", "xpack.ml.dataframe.analytics.create.startDataFrameAnalyticsSuccessMessage": "数据帧分析 {jobId} 启动请求已确认。", "xpack.ml.dataframe.analytics.create.trainingPercentLabel": "训练百分比", "xpack.ml.dataframe.analytics.errorCallout.evaluateErrorTitle": "加载数据时出错。",