From cb5fdc43b64d4704779981988f16c6e4066a7afd Mon Sep 17 00:00:00 2001 From: Melissa Alvarez Date: Tue, 5 May 2020 13:53:19 -0400 Subject: [PATCH] ensure at least one field besides depVar included in analysis --- .../create_analytics_form.tsx | 46 ++++++++++++++++--- .../create_analytics_form/job_type.tsx | 1 + .../use_create_analytics_form/reducer.ts | 4 ++ .../hooks/use_create_analytics_form/state.ts | 2 + 4 files changed, 46 insertions(+), 7 deletions(-) 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 index 199100d8b5ab008..9ab98e3c3a672ab 100644 --- 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 @@ -48,6 +48,8 @@ import { } from '../../../../common/analytics'; import { shouldAddAsDepVarOption, OMIT_FIELDS } from './form_options_validation'; +const requiredFieldsErrorText = 'At least one field must be included in the analysis.'; + export const CreateAnalyticsForm: FC = ({ actions, state }) => { const { services: { docLinks }, @@ -96,6 +98,7 @@ export const CreateAnalyticsForm: FC = ({ actions, sta numTopFeatureImportanceValuesValid, previousJobType, previousSourceIndex, + requiredFieldsError, sourceIndex, sourceIndexNameEmpty, sourceIndexNameValid, @@ -158,6 +161,8 @@ export const CreateAnalyticsForm: FC = ({ actions, sta }; const debouncedGetExplainData = debounce(async () => { + const jobTypeOrIndexChanged = + previousSourceIndex !== sourceIndex || previousJobType !== jobType; const shouldUpdateModelMemoryLimit = !firstUpdate.current || !modelMemoryLimit; const shouldUpdateEstimatedMml = !firstUpdate.current || !modelMemoryLimit || estimatedModelMemoryLimit === ''; @@ -167,7 +172,7 @@ export const CreateAnalyticsForm: FC = ({ actions, sta } // 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 (previousSourceIndex !== sourceIndex || previousJobType !== jobType) { + if (jobTypeOrIndexChanged) { setFormState({ loadingFieldOptions: true, }); @@ -186,8 +191,21 @@ export const CreateAnalyticsForm: FC = ({ actions, sta setEstimatedModelMemoryLimit(expectedMemoryWithoutDisk); } + const fieldSelection: FieldSelectionItem[] = 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 (previousSourceIndex !== sourceIndex || previousJobType !== jobType) { + if (jobTypeOrIndexChanged) { const analyzedFieldsOptions: EuiComboBoxOptionOption[] = []; if (resp.field_selection) { @@ -204,21 +222,24 @@ export const CreateAnalyticsForm: FC = ({ actions, sta 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.message !== undefined && - e.message.includes('status_exception') && - e.message.includes('must have at most') + e.body && + e.body.message !== undefined && + e.body.message.includes('status_exception') && + e.body.message.includes('must have at most') ) { - errorMessage = e.message; + errorMessage = e.body.message; } const fallbackModelMemoryLimit = jobType !== undefined @@ -321,6 +342,7 @@ export const CreateAnalyticsForm: FC = ({ actions, sta excludesOptions: [], previousSourceIndex: sourceIndex, sourceIndex: selectedOptions[0].label || '', + requiredFieldsError: undefined, }); }; @@ -565,7 +587,9 @@ export const CreateAnalyticsForm: FC = ({ actions, sta = ({ actions, sta , ] : []), + ...(requiredFieldsError !== undefined + ? [ + i18n.translate('xpack.ml.dataframe.analytics.create.requiredFieldsError', { + defaultMessage: 'Invalid. {message}', + values: { message: requiredFieldsError }, + }), + ] + : []), ]} > 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 index 0269ae2915d573f..210f2c2dbedf199 100644 --- 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 @@ -69,6 +69,7 @@ export const JobType: FC = ({ type, 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/reducer.ts b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/hooks/use_create_analytics_form/reducer.ts index d55eb14a20e290e..1cab42d8ee12d17 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 @@ -124,6 +124,7 @@ export const validateAdvancedEditor = (state: State): State => { createIndexPattern, excludes, maxDistinctValuesError, + requiredFieldsError, } = state.form; const { jobConfig } = state; @@ -330,6 +331,7 @@ export const validateAdvancedEditor = (state: State): State => { state.isValid = maxDistinctValuesError === undefined && + requiredFieldsError === undefined && excludesValid && trainingPercentValid && state.form.modelMemoryLimitUnitValid && @@ -397,6 +399,7 @@ const validateForm = (state: State): State => { maxDistinctValuesError, modelMemoryLimit, numTopFeatureImportanceValuesValid, + requiredFieldsError, } = state.form; const { estimatedModelMemoryLimit } = state; @@ -412,6 +415,7 @@ const validateForm = (state: State): State => { state.isValid = maxDistinctValuesError === undefined && + requiredFieldsError === undefined && !jobTypeEmpty && !mmlValidationResult && !jobIdEmpty && 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 70840a442f6f6ef..8ca985a537b6e76 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 @@ -76,6 +76,7 @@ export interface State { numTopFeatureImportanceValuesValid: boolean; previousJobType: null | AnalyticsJobType; previousSourceIndex: EsIndexName | undefined; + requiredFieldsError: string | undefined; sourceIndex: EsIndexName; sourceIndexNameEmpty: boolean; sourceIndexNameValid: boolean; @@ -133,6 +134,7 @@ export const getInitialState = (): State => ({ numTopFeatureImportanceValuesValid: true, previousJobType: null, previousSourceIndex: undefined, + requiredFieldsError: undefined, sourceIndex: '', sourceIndexNameEmpty: true, sourceIndexNameValid: false,