[ML] Data Frame Analytics creation: improve existing job check (#89627)

* use jobsExist endpoint instead of preloaded job list

* remove unused translation

* memoize jobCheck so cancel call works correctly
This commit is contained in:
Melissa Alvarez 2021-01-29 14:48:55 -05:00 committed by GitHub
parent 4f6de5a407
commit f53bc9825b
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
7 changed files with 85 additions and 41 deletions

View file

@ -4,8 +4,8 @@
* you may not use this file except in compliance with the Elastic License.
*/
import React, { FC, Fragment, useEffect, useRef } from 'react';
import React, { FC, Fragment, useEffect, useMemo, useRef } from 'react';
import { debounce } from 'lodash';
import {
EuiCallOut,
EuiCodeEditor,
@ -22,6 +22,9 @@ import { XJsonMode } from '../../../../../../../shared_imports';
const xJsonMode = new XJsonMode();
import { useNotifications } from '../../../../../contexts/kibana';
import { ml } from '../../../../../services/ml_api_service';
import { extractErrorMessage } from '../../../../../../../common/util/errors';
import { CreateAnalyticsFormProps } from '../../../analytics_management/hooks/use_create_analytics_form';
import { CreateStep } from '../create_step';
import { ANALYTICS_STEPS } from '../../page';
@ -42,11 +45,33 @@ export const CreateAnalyticsAdvancedEditor: FC<CreateAnalyticsFormProps> = (prop
} = state.form;
const forceInput = useRef<HTMLInputElement | null>(null);
const { toasts } = useNotifications();
const onChange = (str: string) => {
setAdvancedEditorRawString(str);
};
const debouncedJobIdCheck = useMemo(
() =>
debounce(async () => {
try {
const { results } = await ml.dataFrameAnalytics.jobsExists([jobId], true);
setFormState({ jobIdExists: results[jobId] });
} catch (e) {
toasts.addDanger(
i18n.translate(
'xpack.ml.dataframe.analytics.create.advancedEditor.errorCheckingJobIdExists',
{
defaultMessage: 'The following error occurred checking if job id exists: {error}',
values: { error: extractErrorMessage(e) },
}
)
);
}
}, 400),
[jobId]
);
// Temp effect to close the context menu popover on Clone button click
useEffect(() => {
if (forceInput.current === null) {
@ -57,6 +82,18 @@ export const CreateAnalyticsAdvancedEditor: FC<CreateAnalyticsFormProps> = (prop
forceInput.current.dispatchEvent(evt);
}, []);
useEffect(() => {
if (jobIdValid === true) {
debouncedJobIdCheck();
} else if (typeof jobId === 'string' && jobId.trim() === '' && jobIdExists === true) {
setFormState({ jobIdExists: false });
}
return () => {
debouncedJobIdCheck.cancel();
};
}, [jobId]);
return (
<EuiForm className="mlDataFrameAnalyticsCreateForm">
<EuiFormRow

View file

@ -4,7 +4,7 @@
* you may not use this file except in compliance with the Elastic License.
*/
import React, { FC, Fragment, useRef, useEffect, useState } from 'react';
import React, { FC, Fragment, useRef, useEffect, useMemo, useState } from 'react';
import { debounce } from 'lodash';
import {
EuiFieldText,
@ -94,6 +94,36 @@ export const DetailsStepForm: FC<CreateAnalyticsStepProps> = ({
}
}, 400);
const debouncedJobIdCheck = useMemo(
() =>
debounce(async () => {
try {
const { results } = await ml.dataFrameAnalytics.jobsExists([jobId], true);
setFormState({ jobIdExists: results[jobId] });
} catch (e) {
notifications.toasts.addDanger(
i18n.translate('xpack.ml.dataframe.analytics.create.errorCheckingJobIdExists', {
defaultMessage: 'The following error occurred checking if job id exists: {error}',
values: { error: extractErrorMessage(e) },
})
);
}
}, 400),
[jobId]
);
useEffect(() => {
if (jobIdValid === true) {
debouncedJobIdCheck();
} else if (typeof jobId === 'string' && jobId.trim() === '' && jobIdExists === true) {
setFormState({ jobIdExists: false });
}
return () => {
debouncedJobIdCheck.cancel();
};
}, [jobId]);
useEffect(() => {
if (destinationIndexNameValid === true) {
debouncedIndexCheck();

View file

@ -499,7 +499,6 @@ export function reducer(state: State, action: Action): State {
}
if (action.payload.jobId !== undefined) {
newFormState.jobIdExists = state.jobIds.some((id) => newFormState.jobId === id);
newFormState.jobIdEmpty = newFormState.jobId === '';
newFormState.jobIdValid = isJobIdValid(newFormState.jobId);
newFormState.jobIdInvalidMaxLength = !!maxLengthValidator(JOB_ID_MAX_LENGTH)(
@ -542,12 +541,6 @@ export function reducer(state: State, action: Action): State {
case ACTION.SET_JOB_CONFIG:
return validateAdvancedEditor({ ...state, jobConfig: action.payload });
case ACTION.SET_JOB_IDS: {
const newState = { ...state, jobIds: action.jobIds };
newState.form.jobIdExists = newState.jobIds.some((id) => newState.form.jobId === id);
return newState;
}
case ACTION.SWITCH_TO_ADVANCED_EDITOR:
const jobConfig = getJobConfigFromFormState(state.form);
const shouldDisableSwitchToForm = isAdvancedConfig(jobConfig);
@ -562,7 +555,7 @@ export function reducer(state: State, action: Action): State {
});
case ACTION.SWITCH_TO_FORM:
const { jobConfig: config, jobIds } = state;
const { jobConfig: config } = state;
const { jobId } = state.form;
// @ts-ignore
const formState = getFormStateFromJobConfig(config, false);
@ -571,7 +564,6 @@ export function reducer(state: State, action: Action): State {
formState.jobId = jobId;
}
formState.jobIdExists = jobIds.some((id) => formState.jobId === id);
formState.jobIdEmpty = jobId === '';
formState.jobIdValid = isJobIdValid(jobId);
formState.jobIdInvalidMaxLength = !!maxLengthValidator(JOB_ID_MAX_LENGTH)(jobId);

View file

@ -14,11 +14,7 @@ import { ml } from '../../../../../services/ml_api_service';
import { useMlContext } from '../../../../../contexts/ml';
import { DuplicateIndexPatternError } from '../../../../../../../../../../src/plugins/data/public';
import {
useRefreshAnalyticsList,
DataFrameAnalyticsId,
DataFrameAnalyticsConfig,
} from '../../../../common';
import { useRefreshAnalyticsList, DataFrameAnalyticsConfig } from '../../../../common';
import { extractCloningConfig, isAdvancedConfig } from '../../components/action_clone';
import { ActionDispatchers, ACTION } from './actions';
@ -80,9 +76,6 @@ export const useCreateAnalyticsForm = (): CreateAnalyticsFormProps => {
dispatch({ type: ACTION.SET_IS_JOB_STARTED, isJobStarted });
};
const setJobIds = (jobIds: DataFrameAnalyticsId[]) =>
dispatch({ type: ACTION.SET_JOB_IDS, jobIds });
const resetRequestMessages = () => dispatch({ type: ACTION.RESET_REQUEST_MESSAGES });
const resetForm = () => dispatch({ type: ACTION.RESET_FORM });
@ -180,25 +173,6 @@ export const useCreateAnalyticsForm = (): CreateAnalyticsFormProps => {
};
const prepareFormValidation = async () => {
// re-fetch existing analytics job IDs and indices for form validation
try {
setJobIds(
(await ml.dataFrameAnalytics.getDataFrameAnalytics()).data_frame_analytics.map(
(job: DataFrameAnalyticsConfig) => job.id
)
);
} catch (e) {
addRequestMessage({
error: extractErrorMessage(e),
message: i18n.translate(
'xpack.ml.dataframe.analytics.create.errorGettingDataFrameAnalyticsList',
{
defaultMessage: 'An error occurred getting the existing data frame analytics job IDs:',
}
),
});
}
try {
// Set the existing index pattern titles.
const indexPatternsMap: SourceIndexMap = {};

View file

@ -45,6 +45,11 @@ interface DeleteDataFrameAnalyticsWithIndexResponse {
destIndexDeleted: DeleteDataFrameAnalyticsWithIndexStatus;
destIndexPatternDeleted: DeleteDataFrameAnalyticsWithIndexStatus;
}
interface JobsExistsResponse {
results: {
[jobId: string]: boolean;
};
}
export const dataFrameAnalytics = {
getDataFrameAnalytics(analyticsId?: string) {
@ -98,6 +103,14 @@ export const dataFrameAnalytics = {
query: { treatAsRoot, type },
});
},
jobsExists(analyticsIds: string[], allSpaces: boolean = false) {
const body = JSON.stringify({ analyticsIds, allSpaces });
return http<JobsExistsResponse>({
path: `${basePath()}/data_frame/analytics/jobs_exist`,
method: 'POST',
body,
});
},
evaluateDataFrameAnalytics(evaluateConfig: any) {
const body = JSON.stringify(evaluateConfig);
return http<any>({

View file

@ -12595,7 +12595,6 @@
"xpack.ml.dataframe.analytics.create.duplicateIndexPatternErrorMessageError": "インデックスパターン{indexPatternName}はすでに作成されています。",
"xpack.ml.dataframe.analytics.create.errorCheckingIndexExists": "既存のインデックス名の取得中に次のエラーが発生しました:{error}",
"xpack.ml.dataframe.analytics.create.errorCreatingDataFrameAnalyticsJob": "データフレーム分析ジョブの作成中にエラーが発生しました。",
"xpack.ml.dataframe.analytics.create.errorGettingDataFrameAnalyticsList": "既存のデータフレーム分析ジョブIDの取得中にエラーが発生しました。",
"xpack.ml.dataframe.analytics.create.errorGettingIndexPatternTitles": "既存のインデックスパターンのタイトルの取得中にエラーが発生しました。",
"xpack.ml.dataframe.analytics.create.errorStartingDataFrameAnalyticsJob": "データフレーム分析ジョブの開始中にエラーが発生しました。",
"xpack.ml.dataframe.analytics.create.etaInputAriaLabel": "縮小が重みに適用されました",

View file

@ -12624,7 +12624,6 @@
"xpack.ml.dataframe.analytics.create.duplicateIndexPatternErrorMessageError": "索引模式 {indexPatternName} 已存在。",
"xpack.ml.dataframe.analytics.create.errorCheckingIndexExists": "获取现有索引名称时发生以下错误:{error}",
"xpack.ml.dataframe.analytics.create.errorCreatingDataFrameAnalyticsJob": "创建数据帧分析作业时发生错误:",
"xpack.ml.dataframe.analytics.create.errorGettingDataFrameAnalyticsList": "获取现有数据帧分析作业 ID 时发生错误:",
"xpack.ml.dataframe.analytics.create.errorGettingIndexPatternTitles": "获取现有索引模式标题时发生错误:",
"xpack.ml.dataframe.analytics.create.errorStartingDataFrameAnalyticsJob": "启动数据帧分析作业时发生错误:",
"xpack.ml.dataframe.analytics.create.etaInputAriaLabel": "缩小量已应用于权重",