[ML] Fix job wizard when converting to different wizard type (#143347) (#143636)

* [ML] Fix job wizard when converting to different wizard type

* using url constants

* fix validation issue and the MML check

* fix dependencies in react hooks

Co-authored-by: Dima Arnautov <dmitrii.arnautov@elastic.co>
(cherry picked from commit b5cb3f6a22)

Co-authored-by: James Gowdy <jgowdy@elastic.co>
This commit is contained in:
Kibana Machine 2022-10-19 06:30:52 -06:00 committed by GitHub
parent d4118a4ae0
commit 8a9da5065d
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
12 changed files with 68 additions and 48 deletions

View file

@ -37,12 +37,16 @@ export const ML_PAGES = {
* Open index data visualizer viewer page
*/
DATA_VISUALIZER_INDEX_VIEWER: 'jobs/new_job/datavisualizer',
ANOMALY_DETECTION_CREATE_JOB: `jobs/new_job`,
ANOMALY_DETECTION_CREATE_JOB_RECOGNIZER: `jobs/new_job/recognize`,
ANOMALY_DETECTION_CREATE_JOB_ADVANCED: `jobs/new_job/advanced`,
ANOMALY_DETECTION_CREATE_JOB_SELECT_TYPE: `jobs/new_job/step/job_type`,
ANOMALY_DETECTION_CREATE_JOB_SELECT_INDEX: `jobs/new_job/step/index_or_search`,
ANOMALY_DETECTION_CREATE_JOB_FROM_LENS: `jobs/new_job/from_lens`,
ANOMALY_DETECTION_CREATE_JOB: 'jobs/new_job',
ANOMALY_DETECTION_CREATE_JOB_RECOGNIZER: 'jobs/new_job/recognize',
ANOMALY_DETECTION_CREATE_JOB_SINGLE_METRIC: 'jobs/new_job/single_metric',
ANOMALY_DETECTION_CREATE_JOB_MULTI_METRIC: 'jobs/new_job/multi_metric',
ANOMALY_DETECTION_CREATE_JOB_CONVERT_TO_MULTI_METRIC: 'jobs/new_job/convert_to_multi_metric',
ANOMALY_DETECTION_CREATE_JOB_ADVANCED: 'jobs/new_job/advanced',
ANOMALY_DETECTION_CREATE_JOB_CONVERT_TO_ADVANCED: 'jobs/new_job/convert_to_advanced',
ANOMALY_DETECTION_CREATE_JOB_SELECT_TYPE: 'jobs/new_job/step/job_type',
ANOMALY_DETECTION_CREATE_JOB_SELECT_INDEX: 'jobs/new_job/step/index_or_search',
ANOMALY_DETECTION_CREATE_JOB_FROM_LENS: 'jobs/new_job/from_lens',
SETTINGS: 'settings',
CALENDARS_MANAGE: 'settings/calendars_list',
CALENDARS_NEW: 'settings/calendars_list/new_calendar',

View file

@ -46,8 +46,7 @@ export const TimeRangePicker: FC<Props> = ({ setTimeRange, timeRange }) => {
end: endMoment.valueOf(),
});
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [startMoment, endMoment]);
}, [startMoment, endMoment, setTimeRange]);
// update our local start and end moment objects if
// the parent start and end updates.

View file

@ -20,6 +20,7 @@ import {
DOC_COUNT,
_DOC_COUNT,
} from '../../../../../../../common/constants/field_types';
import { ML_PAGES } from '../../../../../../../common/constants/locator';
import {
EVENT_RATE_FIELD_ID,
Field,
@ -261,25 +262,25 @@ export function convertToMultiMetricJob(
jobCreator.createdBy = CREATED_BY_LABEL.MULTI_METRIC;
jobCreator.modelPlot = false;
stashJobForCloning(jobCreator, true, true);
navigateToPath(`jobs/new_job/${JOB_TYPE.MULTI_METRIC}`, true);
navigateToPath(ML_PAGES.ANOMALY_DETECTION_CREATE_JOB_CONVERT_TO_MULTI_METRIC, true);
}
export function convertToAdvancedJob(jobCreator: JobCreatorType, navigateToPath: NavigateToPath) {
jobCreator.createdBy = null;
stashJobForCloning(jobCreator, true, true);
navigateToPath(`jobs/new_job/${JOB_TYPE.ADVANCED}`, true);
navigateToPath(ML_PAGES.ANOMALY_DETECTION_CREATE_JOB_CONVERT_TO_ADVANCED, true);
}
export function resetAdvancedJob(jobCreator: JobCreatorType, navigateToPath: NavigateToPath) {
jobCreator.createdBy = null;
stashJobForCloning(jobCreator, true, false);
navigateToPath('/jobs/new_job');
navigateToPath(ML_PAGES.ANOMALY_DETECTION_CREATE_JOB);
}
export function resetJob(jobCreator: JobCreatorType, navigateToPath: NavigateToPath) {
jobCreator.jobId = '';
stashJobForCloning(jobCreator, true, true);
navigateToPath('/jobs/new_job');
navigateToPath(ML_PAGES.ANOMALY_DETECTION_CREATE_JOB);
}
export function advancedStartDatafeed(

View file

@ -92,8 +92,7 @@ export const useModelMemoryEstimator = (
// Initialize model memory estimator only once
const modelMemoryEstimator = useMemo<ModelMemoryEstimator>(
() => modelMemoryEstimatorProvider(jobCreator, jobValidator),
// eslint-disable-next-line react-hooks/exhaustive-deps
[]
[jobCreator, jobValidator]
);
// Listen for estimation results and errors
@ -131,7 +130,7 @@ export const useModelMemoryEstimator = (
subscription.unsubscribe();
};
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
}, [modelMemoryEstimator]);
// Update model memory estimation payload on the job creator updates
useEffect(() => {
@ -145,5 +144,5 @@ export const useModelMemoryEstimator = (
latestMs: jobCreator.end,
});
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [jobCreatorUpdated]);
}, [jobCreator, jobCreatorUpdated]);
};

View file

@ -59,8 +59,7 @@ export const MultiMetricDetectorsSummary: FC = () => {
return () => {
subscription.unsubscribe();
};
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
}, [chartLoader, resultsLoader, jobCreator]);
useEffect(() => {
if (allDataReady()) {

View file

@ -68,7 +68,7 @@ export const SingleMetricDetectors: FC<Props> = ({ setIsValid }) => {
setIsValid(aggFieldPair !== null);
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [aggFieldPair]);
}, [jobCreator, aggFieldPair]);
useEffect(() => {
if (jobCreator.start !== start || jobCreator.end !== end) {
@ -82,7 +82,7 @@ export const SingleMetricDetectors: FC<Props> = ({ setIsValid }) => {
loadChart();
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [jobCreatorUpdated]);
}, [jobCreator, jobCreatorUpdated]);
async function loadChart() {
if (aggFieldPair !== null) {

View file

@ -49,7 +49,7 @@ export const SingleMetricDetectorsSummary: FC = () => {
subscription.unsubscribe();
};
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
}, [resultsLoader]);
async function loadChart() {
if (jobCreator.aggFieldPair !== null) {

View file

@ -33,8 +33,7 @@ export const PickFieldsStep: FC<StepProps> = ({ setCurrentStep, isCurrentStep })
useEffect(() => {
setNextActive(selectionValid && jobValidator.isPickFieldsStepValid);
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [jobValidatorUpdated, selectionValid]);
}, [jobValidator, jobValidatorUpdated, selectionValid]);
return (
<Fragment>

View file

@ -70,15 +70,21 @@ export const TimeRangeStep: FC<StepProps> = ({ setCurrentStep, isCurrentStep })
jobCreatorUpdate();
loadChart();
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [JSON.stringify(timeRange)]);
}, [
jobCreator,
chartInterval,
timeRange.start,
timeRange.end,
jobCreatorUpdate,
services.data.query.timefilter,
]);
useEffect(() => {
setTimeRange({
start: jobCreator.start,
end: jobCreator.end,
});
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [jobCreatorUpdated]);
}, [jobCreator, jobCreatorUpdated]);
function fullTimeRangeCallback(range: GetTimeFieldRangeResponse) {
if (range.start !== null && range.end !== null) {

View file

@ -12,6 +12,7 @@ import {
} from '@elastic/eui';
import { i18n } from '@kbn/i18n';
import { FormattedMessage } from '@kbn/i18n-react';
import { useTimeBuckets } from '../../../../components/custom_hooks/use_time_buckets';
import { Wizard } from './wizard';
import { WIZARD_STEPS } from '../components/step_types';
import { getJobCreatorTitle } from '../../common/job_creator/util/general';
@ -31,7 +32,6 @@ import { ResultsLoader } from '../../common/results_loader';
import { JobValidator } from '../../common/job_validator';
import { useMlContext } from '../../../../contexts/ml';
import { getTimeFilterRange } from '../../../../components/full_time_range_selector';
import { getTimeBucketsFromCache } from '../../../../util/time_buckets';
import { ExistingJobsAndGroups, mlJobService } from '../../../../services/job_service';
import { newJobCapsService } from '../../../../services/new_job_capabilities/new_job_capabilities_service';
import { EVENT_RATE_FIELD_ID } from '../../../../../../common/types/fields';
@ -50,6 +50,9 @@ export interface PageProps {
export const Page: FC<PageProps> = ({ existingJobsAndGroups, jobType }) => {
const mlContext = useMlContext();
const chartInterval = useTimeBuckets();
const jobCreator = useMemo(
() =>
jobCreatorFactory(jobType)(
@ -61,6 +64,8 @@ export const Page: FC<PageProps> = ({ existingJobsAndGroups, jobType }) => {
[jobType]
);
const jobValidator = useMemo(() => new JobValidator(jobCreator), [jobCreator]);
const { displayErrorToast } = useToastNotificationService();
const { from, to } = getTimeFilterRange();
@ -182,23 +187,18 @@ export const Page: FC<PageProps> = ({ existingJobsAndGroups, jobType }) => {
}
}
const chartInterval = getTimeBucketsFromCache();
chartInterval.setBarTarget(BAR_TARGET);
chartInterval.setMaxBars(MAX_BARS);
chartInterval.setInterval('auto');
const chartLoader = useMemo(
() => new ChartLoader(mlContext.currentDataView, jobCreator.query),
// eslint-disable-next-line react-hooks/exhaustive-deps
[]
[mlContext.currentDataView, jobCreator.query]
);
const jobValidator = useMemo(() => new JobValidator(jobCreator), [jobCreator]);
const resultsLoader = useMemo(
() => new ResultsLoader(jobCreator, chartInterval, chartLoader),
// eslint-disable-next-line react-hooks/exhaustive-deps
[]
[jobCreator, chartInterval, chartLoader]
);
useEffect(() => {

View file

@ -5,9 +5,8 @@
* 2.0.
*/
import React, { FC, useReducer, useState, useEffect } from 'react';
import React, { FC, useState, useEffect, useCallback } from 'react';
import { useModelMemoryEstimator } from '../../common/job_creator/util/model_memory_estimator';
import { WIZARD_STEPS } from '../components/step_types';
import { TimeBuckets } from '../../../../util/time_buckets';
@ -43,15 +42,15 @@ export const Wizard: FC<Props> = ({
existingJobsAndGroups,
firstWizardStep = WIZARD_STEPS.TIME_RANGE,
}) => {
const [jobCreatorUpdated, setJobCreatorUpdate] = useReducer<(s: number, action: any) => number>(
(s) => s + 1,
0
);
const jobCreatorUpdate = () => setJobCreatorUpdate(jobCreatorUpdated);
const [jobCreatorUpdated, setJobCreatorUpdate] = useState(0);
const jobCreatorUpdate = useCallback(() => {
setJobCreatorUpdate((prev) => prev + 1);
}, []);
const [jobValidatorUpdated, setJobValidatorUpdate] = useReducer<
(s: number, action: any) => number
>((s) => s + 1, 0);
const [jobValidatorUpdated, setJobValidatorUpdate] = useState(0);
const jobValidatorUpdate = useCallback(() => {
setJobValidatorUpdate((prev) => prev + 1);
}, []);
const jobCreatorContext: JobCreatorContextValue = {
jobCreatorUpdated,
@ -82,18 +81,18 @@ export const Wizard: FC<Props> = ({
useEffect(() => {
const subscription = jobValidator.validationResult$.subscribe(() => {
setJobValidatorUpdate(jobValidatorUpdated);
jobValidatorUpdate();
});
return () => {
return subscription.unsubscribe();
};
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
}, [jobValidator]);
useEffect(() => {
jobValidator.validate(() => {
setJobValidatorUpdate(jobValidatorUpdated);
jobValidatorUpdate();
});
// if the job config has changed, reset the highestStep

View file

@ -8,7 +8,7 @@
import { parse } from 'query-string';
import React, { FC } from 'react';
import { i18n } from '@kbn/i18n';
import { Redirect } from 'react-router-dom';
import { NavigateToPath } from '../../../contexts/kibana';
import { basicResolvers } from '../../resolvers';
@ -114,6 +114,13 @@ export const multiMetricRouteFactory = (
breadcrumbs: getMultiMetricBreadcrumbs(navigateToPath, basePath),
});
// redirect route to reset the job wizard when converting to multi metric job
export const multiMetricRouteFactoryRedirect = (): MlRoute => ({
path: '/jobs/new_job/convert_to_multi_metric',
render: (props) => <Redirect to={`/jobs/new_job/multi_metric${props.location.search}`} />,
breadcrumbs: [],
});
export const populationRouteFactory = (
navigateToPath: NavigateToPath,
basePath: string
@ -132,6 +139,13 @@ export const advancedRouteFactory = (
breadcrumbs: getAdvancedBreadcrumbs(navigateToPath, basePath),
});
// redirect route to reset the job wizard when converting to advanced job
export const advancedRouteFactoryRedirect = (): MlRoute => ({
path: '/jobs/new_job/convert_to_advanced',
render: (props) => <Redirect to={`/jobs/new_job/advanced${props.location.search}`} />,
breadcrumbs: [],
});
export const categorizationRouteFactory = (
navigateToPath: NavigateToPath,
basePath: string