Skip to content

Commit

Permalink
[ML] DF Analytics: Creation wizard part 2 (#68462) (#68707)
Browse files Browse the repository at this point in the history
* 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
  • Loading branch information
alvarezmelissa87 authored Jun 9, 2020
1 parent 8eb064c commit 9a695e5
Show file tree
Hide file tree
Showing 38 changed files with 236 additions and 1,493 deletions.
Original file line number Diff line number Diff line change
@@ -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';
Original file line number Diff line number Diff line change
Expand Up @@ -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(() => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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';

Expand Down Expand Up @@ -56,7 +56,7 @@ export const ConfigurationStepForm: FC<CreateAnalyticsStepProps> = ({
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<boolean>(true);
const {
Expand All @@ -75,9 +75,12 @@ export const ConfigurationStepForm: FC<CreateAnalyticsStepProps> = ({
modelMemoryLimit,
previousJobType,
requiredFieldsError,
sourceIndex,
trainingPercent,
} = form;

const toastNotifications = getToastNotifications();

const setJobConfigQuery = ({ query, queryString }: { query: any; queryString: string }) => {
setFormState({ jobConfigQuery: query, jobConfigQueryString: queryString });
};
Expand All @@ -90,7 +93,7 @@ export const ConfigurationStepForm: FC<CreateAnalyticsStepProps> = ({
const indexPreviewProps = {
...indexData,
dataTestSubj: 'mlAnalyticsCreationDataGrid',
toastNotifications: getToastNotifications(),
toastNotifications,
};

const isJobTypeWithDepVar =
Expand Down Expand Up @@ -209,7 +212,8 @@ export const ConfigurationStepForm: FC<CreateAnalyticsStepProps> = ({
});
}
} catch (e) {
let errorMessage;
let maxDistinctValuesErrorMessage;

if (
jobType === ANALYSIS_CONFIG_TYPE.CLASSIFICATION &&
e.body &&
Expand All @@ -218,7 +222,23 @@ export const ConfigurationStepForm: FC<CreateAnalyticsStepProps> = ({
(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
Expand All @@ -227,17 +247,13 @@ export const ConfigurationStepForm: FC<CreateAnalyticsStepProps> = ({
setEstimatedModelMemoryLimit(fallbackModelMemoryLimit);
setFormState({
fieldOptionsFetchFail: true,
maxDistinctValuesError: errorMessage,
maxDistinctValuesError: maxDistinctValuesErrorMessage,
loadingFieldOptions: false,
...(shouldUpdateModelMemoryLimit ? { modelMemoryLimit: fallbackModelMemoryLimit } : {}),
});
}
}, 300);

useEffect(() => {
initiateWizard();
}, []);

useEffect(() => {
setFormState({ sourceIndex: currentIndexPattern.title });
}, []);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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']);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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';

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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<CreateAnalyticsFormProps> = (props) => {
const { actions, state } = props;
Expand Down Expand Up @@ -125,7 +125,6 @@ export const CreateAnalyticsAdvancedEditor: FC<CreateAnalyticsFormProps> = (prop
onChange={onChange}
setOptions={{
fontSize: '12px',
maxLines: 20,
}}
theme="textmate"
aria-label={i18n.translate(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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';

Expand All @@ -26,14 +26,7 @@ interface Props extends CreateAnalyticsFormProps {

export const CreateStep: FC<Props> = ({ 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<boolean>(true);

Expand Down Expand Up @@ -75,7 +68,7 @@ export const CreateStep: FC<Props> = ({ actions, state, step }) => {
<EuiFlexItem grow={false}>
<EuiButton
className="mlAnalyticsCreateWizard__footerButton"
disabled={!isValid || !isAdvancedEditorValidJson || isModalButtonDisabled}
disabled={!isValid || !isAdvancedEditorValidJson}
onClick={handleCreation}
fill
data-test-subj="mlAnalyticsCreateJobWizardCreateButton"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,4 +4,4 @@
* you may not use this file except in compliance with the Elastic License.
*/

export { CreateAnalyticsForm } from './create_analytics_form';
export { Messages } from './messages';
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import React, { Fragment, FC } from 'react';

import { EuiCallOut, EuiCodeBlock, EuiSpacer } from '@elastic/eui';

import { FormMessage } from '../../hooks/use_create_analytics_form/state'; // State
import { FormMessage } from '../../../analytics_management/hooks/use_create_analytics_form/state';

interface Props {
messages: FormMessage[];
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,10 @@ import { i18n } from '@kbn/i18n';
import { FormattedMessage } from '@kbn/i18n/react';
import { useMlContext } from '../../../contexts/ml';
import { newJobCapsService } from '../../../services/new_job_capabilities_service';
import { ml } from '../../../services/ml_api_service';
import { DataFrameAnalyticsId } from '../../common/analytics';
import { useCreateAnalyticsForm } from '../analytics_management/hooks/use_create_analytics_form';
import { CreateAnalyticsAdvancedEditor } from '../analytics_management/components/create_analytics_advanced_editor';
import { CreateAnalyticsAdvancedEditor } from './components/create_analytics_advanced_editor';
import { AdvancedStep, ConfigurationStep, CreateStep, DetailsStep } from './components';

export enum ANALYTICS_STEPS {
Expand All @@ -34,7 +36,11 @@ export enum ANALYTICS_STEPS {
CREATE,
}

export const Page: FC = () => {
interface Props {
jobId?: DataFrameAnalyticsId;
}

export const Page: FC<Props> = ({ jobId }) => {
const [currentStep, setCurrentStep] = useState<ANALYTICS_STEPS>(ANALYTICS_STEPS.CONFIGURATION);
const [activatedSteps, setActivatedSteps] = useState<boolean[]>([true, false, false, false]);

Expand All @@ -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', {
Expand Down Expand Up @@ -127,10 +146,19 @@ export const Page: FC = () => {
<EuiFlexItem grow={false}>
<EuiTitle size="m">
<h1>
<FormattedMessage
id="xpack.ml.dataframe.analytics.creationPageTitle"
defaultMessage="Create analytics job"
/>
{jobId === undefined && (
<FormattedMessage
id="xpack.ml.dataframe.analytics.creationPageTitle"
defaultMessage="Create analytics job"
/>
)}
{jobId !== undefined && (
<FormattedMessage
id="xpack.ml.dataframe.analytics.clone.creationPageTitle"
defaultMessage="Clone analytics job from {jobId}"
values={{ jobId }}
/>
)}
</h1>
</EuiTitle>
</EuiFlexItem>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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'],
Expand All @@ -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: {
Expand All @@ -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', () => {
Expand Down Expand Up @@ -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",
Expand Down
Loading

0 comments on commit 9a695e5

Please sign in to comment.