Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[7.x] [ML] DF Analytics: Creation wizard part 2 (#68462) #68707

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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