diff --git a/x-pack/legacy/plugins/ml/public/data_frame_analytics/pages/analytics_management/components/create_analytics_button/create_analytics_button.tsx b/x-pack/legacy/plugins/ml/public/data_frame_analytics/pages/analytics_management/components/create_analytics_button/create_analytics_button.tsx index ab968533c62fc..4a6f025669acf 100644 --- a/x-pack/legacy/plugins/ml/public/data_frame_analytics/pages/analytics_management/components/create_analytics_button/create_analytics_button.tsx +++ b/x-pack/legacy/plugins/ml/public/data_frame_analytics/pages/analytics_management/components/create_analytics_button/create_analytics_button.tsx @@ -4,37 +4,36 @@ * you may not use this file except in compliance with the Elastic License. */ -import React, { FC } from 'react'; +import React, { Fragment, FC } from 'react'; import { EuiButton, EuiToolTip } from '@elastic/eui'; -import { FormattedMessage } from '@kbn/i18n/react'; +import { i18n } from '@kbn/i18n'; -import { - checkPermission, - createPermissionFailureMessage, -} from '../../../../../privilege/check_privilege'; +import { createPermissionFailureMessage } from '../../../../../privilege/check_privilege'; -import { moveToAnalyticsWizard } from '../../../../common'; +import { useCreateAnalyticsForm } from '../../hooks/use_create_analytics_form'; + +import { CreateAnalyticsForm } from '../create_analytics_form'; +import { CreateAnalyticsModal } from '../create_analytics_modal'; export const CreateAnalyticsButton: FC = () => { - const disabled = - !checkPermission('canCreateDataFrameAnalytics') || - !checkPermission('canStartStopDataFrameAnalytics'); + const { state, actions } = useCreateAnalyticsForm(); + const { disabled, isModalVisible } = state; + const { openModal } = actions; const button = ( - + {i18n.translate('xpack.ml.dataframe.analyticsList.createDataFrameAnalyticsButton', { + defaultMessage: 'Create data frame analytics job', + })} ); @@ -49,5 +48,14 @@ export const CreateAnalyticsButton: FC = () => { ); } - return button; + return ( + + {button} + {isModalVisible && ( + + + + )} + + ); }; diff --git a/x-pack/legacy/plugins/ml/public/data_frame_analytics/pages/analytics_management/components/create_analytics_form/create_analytics_form.tsx b/x-pack/legacy/plugins/ml/public/data_frame_analytics/pages/analytics_management/components/create_analytics_form/create_analytics_form.tsx new file mode 100644 index 0000000000000..873a762276b07 --- /dev/null +++ b/x-pack/legacy/plugins/ml/public/data_frame_analytics/pages/analytics_management/components/create_analytics_form/create_analytics_form.tsx @@ -0,0 +1,262 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import React, { Fragment, FC } from 'react'; + +import { + EuiCallOut, + EuiComboBox, + EuiForm, + EuiFieldText, + EuiFormRow, + EuiLink, + EuiSpacer, + EuiSwitch, +} from '@elastic/eui'; + +import { i18n } from '@kbn/i18n'; + +import { metadata } from 'ui/metadata'; + +import { CreateAnalyticsFormProps } from '../../hooks/use_create_analytics_form'; + +export const CreateAnalyticsForm: FC = ({ actions, formState }) => { + const { setFormState } = actions; + const { + createIndexPattern, + destinationIndex, + destinationIndexNameEmpty, + destinationIndexNameExists, + destinationIndexNameValid, + destinationIndexPatternTitleExists, + indexPatternsWithNumericFields, + indexPatternTitles, + isJobCreated, + jobId, + jobIdEmpty, + jobIdExists, + jobIdValid, + requestMessages, + sourceIndex, + sourceIndexNameEmpty, + sourceIndexNameExists, + sourceIndexNameValid, + } = formState; + + return ( + + {requestMessages.map((requestMessage, i) => ( + + + {requestMessage.error !== undefined ?

{requestMessage.error}

: null} +
+ +
+ ))} + {!isJobCreated && ( + + + setFormState({ jobId: e.target.value })} + aria-label={i18n.translate( + 'xpack.ml.dataframe.analytics.create.jobIdInputAriaLabel', + { + defaultMessage: 'Choose a unique analytics job id.', + } + )} + isInvalid={(!jobIdEmpty && !jobIdValid) || jobIdExists} + /> + + + + {i18n.translate('xpack.ml.dataframe.analytics.create.sourceIndexInvalidError', { + defaultMessage: 'Invalid source index name.', + })} +
+ + {i18n.translate( + 'xpack.ml.dataframe.stepDetailsForm.sourceIndexInvalidErrorLink', + { + defaultMessage: 'Learn more about index name limitations.', + } + )} + +
, + ]) || + (!sourceIndexNameEmpty && + !sourceIndexNameExists && [ + + {i18n.translate( + 'xpack.ml.dataframe.analytics.create.sourceIndexDoesNotExistError', + { + defaultMessage: 'An index with this name does not exist.', + } + )} + , + ]) + } + > + + {!isJobCreated && ( + ({ label: d }))} + selectedOptions={[{ label: sourceIndex }]} + onChange={selectedOptions => + setFormState({ sourceIndex: selectedOptions[0].label || '' }) + } + isClearable={false} + /> + )} + {isJobCreated && ( + + )} + + + + + {i18n.translate( + 'xpack.ml.dataframe.analytics.create.destinationIndexInvalidError', + { + defaultMessage: 'Invalid destination index name.', + } + )} +
+ + {i18n.translate( + 'xpack.ml.dataframe.stepDetailsForm.destinationIndexInvalidErrorLink', + { + defaultMessage: 'Learn more about index name limitations.', + } + )} + + , + ] + } + > + setFormState({ destinationIndex: e.target.value })} + aria-label={i18n.translate( + 'xpack.ml.dataframe.analytics.create.destinationIndexInputAriaLabel', + { + defaultMessage: 'Choose a unique destination index name.', + } + )} + isInvalid={!destinationIndexNameEmpty && !destinationIndexNameValid} + /> +
+ + + setFormState({ createIndexPattern: !createIndexPattern })} + /> + + + )} +
+ ); +}; diff --git a/x-pack/legacy/plugins/ml/public/data_frame_analytics/pages/analytics_management/components/create_analytics_form/index.ts b/x-pack/legacy/plugins/ml/public/data_frame_analytics/pages/analytics_management/components/create_analytics_form/index.ts new file mode 100644 index 0000000000000..20b96a3668e4b --- /dev/null +++ b/x-pack/legacy/plugins/ml/public/data_frame_analytics/pages/analytics_management/components/create_analytics_form/index.ts @@ -0,0 +1,7 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +export { CreateAnalyticsForm } from './create_analytics_form'; diff --git a/x-pack/legacy/plugins/ml/public/data_frame_analytics/pages/analytics_management/components/create_analytics_modal/create_analytics_modal.tsx b/x-pack/legacy/plugins/ml/public/data_frame_analytics/pages/analytics_management/components/create_analytics_modal/create_analytics_modal.tsx new file mode 100644 index 0000000000000..0c905b6797652 --- /dev/null +++ b/x-pack/legacy/plugins/ml/public/data_frame_analytics/pages/analytics_management/components/create_analytics_modal/create_analytics_modal.tsx @@ -0,0 +1,83 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import React, { FC } from 'react'; + +import { + EuiButton, + EuiButtonEmpty, + EuiModal, + EuiModalBody, + EuiModalFooter, + EuiModalHeader, + EuiModalHeaderTitle, + EuiOverlayMask, +} from '@elastic/eui'; + +import { i18n } from '@kbn/i18n'; + +import { CreateAnalyticsFormProps } from '../../hooks/use_create_analytics_form'; + +export const CreateAnalyticsModal: FC = ({ + actions, + children, + formState, +}) => { + const { closeModal, createAnalyticsJob, startAnalyticsJob } = actions; + const { isJobCreated, isJobStarted, isModalButtonDisabled, isValid } = formState; + + return ( + + + + + {i18n.translate('xpack.ml.dataframe.analytics.create.modalHeaderTitle', { + defaultMessage: 'Create data frame analytics job', + })} + + + + {children} + + + {(!isJobCreated || !isJobStarted) && ( + + {i18n.translate('xpack.ml.dataframe.analytics.create.modalCancelButton', { + defaultMessage: 'Cancel', + })} + + )} + + {!isJobCreated && !isJobStarted && ( + + {i18n.translate('xpack.ml.dataframe.analytics.create.modalCreateButton', { + defaultMessage: 'Create', + })} + + )} + {isJobCreated && !isJobStarted && ( + + {i18n.translate('xpack.ml.dataframe.analytics.create.modalStartButton', { + defaultMessage: 'Start', + })} + + )} + {isJobCreated && isJobStarted && ( + + {i18n.translate('xpack.ml.dataframe.analytics.create.modalCloseButton', { + defaultMessage: 'Close', + })} + + )} + + + + ); +}; diff --git a/x-pack/legacy/plugins/ml/public/data_frame_analytics/pages/analytics_management/components/create_analytics_modal/index.ts b/x-pack/legacy/plugins/ml/public/data_frame_analytics/pages/analytics_management/components/create_analytics_modal/index.ts new file mode 100644 index 0000000000000..3713dd161143a --- /dev/null +++ b/x-pack/legacy/plugins/ml/public/data_frame_analytics/pages/analytics_management/components/create_analytics_modal/index.ts @@ -0,0 +1,7 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +export { CreateAnalyticsModal } from './create_analytics_modal'; diff --git a/x-pack/legacy/plugins/ml/public/data_frame_analytics/pages/analytics_management/directive.tsx b/x-pack/legacy/plugins/ml/public/data_frame_analytics/pages/analytics_management/directive.tsx index a4c3d44fd813c..adf98e12b3232 100644 --- a/x-pack/legacy/plugins/ml/public/data_frame_analytics/pages/analytics_management/directive.tsx +++ b/x-pack/legacy/plugins/ml/public/data_frame_analytics/pages/analytics_management/directive.tsx @@ -11,17 +11,51 @@ import ReactDOM from 'react-dom'; import { uiModules } from 'ui/modules'; const module = uiModules.get('apps/ml', ['react']); +import { IndexPattern, IndexPatterns } from 'ui/index_patterns'; import { I18nContext } from 'ui/i18n'; +import { IPrivate } from 'ui/private'; + +import { InjectorService } from '../../../../common/types/angular'; +// @ts-ignore +import { SearchItemsProvider } from '../../../jobs/new_job/utils/new_job_utils'; +// Simple drop-in type until new_job_utils offers types. +type CreateSearchItems = () => { + indexPattern: IndexPattern; + savedSearch: any; + combinedQuery: any; +}; + +import { KibanaConfigTypeFix, KibanaContext } from '../../../contexts/kibana'; + import { Page } from './page'; -module.directive('mlDataFrameAnalyticsManagement', () => { +module.directive('mlDataFrameAnalyticsManagement', ($injector: InjectorService) => { return { scope: {}, restrict: 'E', link: (scope: ng.IScope, element: ng.IAugmentedJQuery) => { + const indexPatterns = $injector.get('indexPatterns'); + const kbnBaseUrl = $injector.get('kbnBaseUrl'); + const kibanaConfig = $injector.get('config'); + const Private = $injector.get('Private'); + + const createSearchItems: CreateSearchItems = Private(SearchItemsProvider); + const { indexPattern, savedSearch, combinedQuery } = createSearchItems(); + + const kibanaContext = { + combinedQuery, + currentIndexPattern: indexPattern, + currentSavedSearch: savedSearch, + indexPatterns, + kbnBaseUrl, + kibanaConfig, + }; + ReactDOM.render( - + + + , element[0] ); diff --git a/x-pack/legacy/plugins/ml/public/data_frame_analytics/pages/analytics_management/hooks/use_create_analytics_form.ts b/x-pack/legacy/plugins/ml/public/data_frame_analytics/pages/analytics_management/hooks/use_create_analytics_form.ts new file mode 100644 index 0000000000000..e443187e5e8fd --- /dev/null +++ b/x-pack/legacy/plugins/ml/public/data_frame_analytics/pages/analytics_management/hooks/use_create_analytics_form.ts @@ -0,0 +1,457 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { useReducer } from 'react'; + +import { i18n } from '@kbn/i18n'; + +import { checkPermission } from '../../../../privilege/check_privilege'; +import { ml } from '../../../../services/ml_api_service'; +import { useKibanaContext } from '../../../../contexts/kibana'; + +import { isValidIndexName } from '../../../../../common/util/es_utils'; + +import { + isAnalyticsIdValid, + useRefreshAnalyticsList, + DataFrameAnalyticsId, + DataFrameAnalyticsOutlierConfig, +} from '../../../common'; + +export type EsIndexName = string; +export type IndexPatternTitle = string; + +interface RequestMessage { + error?: string; + message: string; +} + +interface State { + createIndexPattern: boolean; + destinationIndex: EsIndexName; + destinationIndexNameExists: boolean; + destinationIndexNameEmpty: boolean; + destinationIndexNameValid: boolean; + destinationIndexPatternTitleExists: boolean; + disabled: boolean; + indexNames: EsIndexName[]; + indexPatternTitles: IndexPatternTitle[]; + indexPatternsWithNumericFields: IndexPatternTitle[]; + isJobCreated: boolean; + isJobStarted: boolean; + isModalButtonDisabled: boolean; + isModalVisible: boolean; + isValid: boolean; + jobId: DataFrameAnalyticsId; + jobIds: DataFrameAnalyticsId[]; + jobIdExists: boolean; + jobIdEmpty: boolean; + jobIdValid: boolean; + requestMessages: RequestMessage[]; + sourceIndex: EsIndexName; + sourceIndexNameExists: boolean; + sourceIndexNameEmpty: boolean; + sourceIndexNameValid: boolean; +} + +export const getInitialState = (): State => ({ + createIndexPattern: false, + destinationIndex: '', + destinationIndexNameExists: false, + destinationIndexNameEmpty: true, + destinationIndexNameValid: false, + destinationIndexPatternTitleExists: false, + disabled: + !checkPermission('canCreateDataFrameAnalytics') || + !checkPermission('canStartStopDataFrameAnalytics'), + indexNames: [], + indexPatternTitles: [], + indexPatternsWithNumericFields: [], + isJobCreated: false, + isJobStarted: false, + isModalVisible: false, + isModalButtonDisabled: false, + isValid: false, + jobId: '', + jobIds: [], + jobIdExists: false, + jobIdEmpty: true, + jobIdValid: false, + requestMessages: [], + sourceIndex: '', + sourceIndexNameExists: false, + sourceIndexNameEmpty: true, + sourceIndexNameValid: false, +}); + +const validate = (state: State): State => { + state.isValid = + !state.jobIdEmpty && + state.jobIdValid && + !state.jobIdExists && + !state.sourceIndexNameEmpty && + state.sourceIndexNameValid && + !state.destinationIndexNameEmpty && + state.destinationIndexNameValid && + (!state.destinationIndexPatternTitleExists || !state.createIndexPattern); + + return state; +}; + +enum ACTION { + ADD_REQUEST_MESSAGE = 'add_request_message', + RESET_REQUEST_MESSAGES = 'reset_request_messages', + CLOSE_MODAL = 'close_modal', + OPEN_MODAL = 'open_modal', + RESET_FORM = 'reset_form', + SET_FORM_STATE = 'set_form_state', +} + +type Action = + | { type: ACTION.ADD_REQUEST_MESSAGE; requestMessage: RequestMessage } + | { type: ACTION.RESET_REQUEST_MESSAGES } + | { type: ACTION.CLOSE_MODAL } + | { type: ACTION.OPEN_MODAL } + | { type: ACTION.RESET_FORM } + | { type: ACTION.SET_FORM_STATE; payload: Partial }; + +export interface Actions { + closeModal: () => void; + createAnalyticsJob: () => void; + openModal: () => void; + startAnalyticsJob: () => void; + setFormState: (payload: Partial) => void; +} + +export interface CreateAnalyticsFormProps { + actions: Actions; + formState: State; +} + +export function reducer(state: State, action: Action): State { + switch (action.type) { + case ACTION.ADD_REQUEST_MESSAGE: + state.requestMessages.push(action.requestMessage); + return state; + + case ACTION.RESET_REQUEST_MESSAGES: + return { ...state, requestMessages: [] }; + + case ACTION.CLOSE_MODAL: + return { ...state, isModalVisible: false }; + + case ACTION.OPEN_MODAL: + return { ...state, isModalVisible: true }; + + case ACTION.RESET_FORM: + return getInitialState(); + + case ACTION.SET_FORM_STATE: + const newState = { ...state, ...action.payload }; + + // update state attributes which are derived from other state attributes. + if (action.payload.destinationIndex !== undefined) { + newState.destinationIndexNameExists = newState.indexNames.some( + name => newState.destinationIndex === name + ); + newState.destinationIndexNameEmpty = newState.destinationIndex === ''; + newState.destinationIndexNameValid = isValidIndexName(newState.destinationIndex); + newState.destinationIndexPatternTitleExists = newState.indexPatternTitles.some( + name => newState.destinationIndex === name + ); + } + + if (action.payload.indexNames !== undefined) { + newState.destinationIndexNameExists = newState.indexNames.some( + name => newState.destinationIndex === name + ); + newState.sourceIndexNameExists = newState.indexNames.some( + name => newState.sourceIndex === name + ); + } + + if (action.payload.indexPatternTitles !== undefined) { + newState.destinationIndexPatternTitleExists = newState.indexPatternTitles.some( + name => newState.destinationIndex === name + ); + } + + if (action.payload.jobId !== undefined) { + newState.jobIdExists = newState.jobIds.some(id => newState.jobId === id); + newState.jobIdEmpty = newState.jobId === ''; + newState.jobIdValid = isAnalyticsIdValid(newState.jobId); + } + + if (action.payload.jobIds !== undefined) { + newState.jobIdExists = newState.jobIds.some(id => newState.jobId === id); + } + + if (action.payload.sourceIndex !== undefined) { + newState.sourceIndexNameExists = newState.indexNames.some( + name => newState.sourceIndex === name + ); + newState.sourceIndexNameEmpty = newState.sourceIndex === ''; + newState.sourceIndexNameValid = isValidIndexName(newState.sourceIndex); + } + + return validate(newState); + } + + return state; +} + +// List of system fields we want to ignore for the numeric field check. +const OMIT_FIELDS: string[] = ['_source', '_type', '_index', '_id', '_version', '_score']; + +function getErrorMessage(error: any) { + if (typeof error === 'object' && typeof error.message === 'string') { + return error.message; + } + + return JSON.stringify(error); +} + +export const useCreateAnalyticsForm = () => { + const kibanaContext = useKibanaContext(); + const [state, dispatch] = useReducer(reducer, getInitialState()); + const { refresh } = useRefreshAnalyticsList(); + + const { createIndexPattern, destinationIndex, jobId, sourceIndex } = state; + + const addRequestMessage = (requestMessage: RequestMessage) => + dispatch({ type: ACTION.ADD_REQUEST_MESSAGE, requestMessage }); + const closeModal = () => dispatch({ type: ACTION.CLOSE_MODAL }); + const resetRequestMessages = () => dispatch({ type: ACTION.RESET_REQUEST_MESSAGES }); + const resetForm = () => dispatch({ type: ACTION.RESET_FORM }); + + const createAnalyticsJob = async () => { + resetRequestMessages(); + setFormState({ isModalButtonDisabled: true }); + + const analyticsJobConfig = { + source: { + index: sourceIndex, + }, + dest: { + index: destinationIndex, + }, + analysis: { + outlier_detection: {}, + }, + }; + + try { + await ml.dataFrameAnalytics.createDataFrameAnalytics(jobId, analyticsJobConfig); + addRequestMessage({ + message: i18n.translate( + 'xpack.ml.dataframe.stepCreateForm.createDataFrameAnalyticsSuccessMessage', + { + defaultMessage: 'Analytics job {jobId} created.', + values: { jobId }, + } + ), + }); + setFormState({ isJobCreated: true, isModalButtonDisabled: false }); + if (createIndexPattern) { + createKibanaIndexPattern(); + } + refresh(); + } catch (e) { + addRequestMessage({ + error: getErrorMessage(e), + message: i18n.translate( + 'xpack.ml.dataframe.analytics.create.errorCreatingDataFrameAnalyticsJob', + { + defaultMessage: 'An error occurred creating the data frame analytics job:', + } + ), + }); + setFormState({ + isModalButtonDisabled: false, + }); + } + }; + + const createKibanaIndexPattern = async () => { + const indexPatternName = destinationIndex; + + try { + const newIndexPattern = await kibanaContext.indexPatterns.get(); + + Object.assign(newIndexPattern, { + id: '', + title: indexPatternName, + }); + + const id = await newIndexPattern.create(); + + // id returns false if there's a duplicate index pattern. + if (id === false) { + addRequestMessage({ + error: i18n.translate( + 'xpack.ml.dataframe.analytics.create.duplicateIndexPatternErrorMessageError', + { + defaultMessage: 'The index pattern {indexPatternName} already exists.', + values: { indexPatternName }, + } + ), + message: i18n.translate( + 'xpack.ml.dataframe.analytics.create.duplicateIndexPatternErrorMessage', + { + defaultMessage: 'An error occurred creating the Kibana index pattern:', + } + ), + }); + return; + } + + // check if there's a default index pattern, if not, + // set the newly created one as the default index pattern. + if (!kibanaContext.kibanaConfig.get('defaultIndex')) { + await kibanaContext.kibanaConfig.set('defaultIndex', id); + } + + addRequestMessage({ + message: i18n.translate( + 'xpack.ml.dataframe.analytics.create.createIndexPatternSuccessMessage', + { + defaultMessage: 'Kibana index pattern {indexPatternName} created.', + values: { indexPatternName }, + } + ), + }); + } catch (e) { + addRequestMessage({ + error: getErrorMessage(e), + message: i18n.translate( + 'xpack.ml.dataframe.analytics.create.createIndexPatternErrorMessage', + { + defaultMessage: 'An error occurred creating the Kibana index pattern:', + } + ), + }); + } + }; + + const openModal = async () => { + resetForm(); + + // re-fetch existing analytics job IDs and indices for form validation + try { + setFormState({ + jobIds: (await ml.dataFrameAnalytics.getDataFrameAnalytics()).data_frame_analytics.map( + (job: DataFrameAnalyticsOutlierConfig) => job.id + ), + }); + } catch (e) { + addRequestMessage({ + error: getErrorMessage(e), + message: i18n.translate( + 'xpack.ml.dataframe.analytics.create.errorGettingDataFrameAnalyticsList', + { + defaultMessage: 'An error occurred getting the existing data frame analytics job Ids:', + } + ), + }); + } + + try { + setFormState({ indexNames: (await ml.getIndices()).map(index => index.name) }); + } catch (e) { + addRequestMessage({ + error: getErrorMessage(e), + message: i18n.translate( + 'xpack.ml.dataframe.analytics.create.errorGettingDataFrameIndexNames', + { + defaultMessage: 'An error occurred getting the existing index names:', + } + ), + }); + } + + try { + // Set the index pattern titles which the user can choose as the source. + setFormState({ indexPatternTitles: await kibanaContext.indexPatterns.getTitles(true) }); + // Find out which index patterns contain numeric fields. + // This will be used to provide a hint in the form that an analytics jobs is not + // able to identify outliers if there are no numeric fields present. + const ids = await kibanaContext.indexPatterns.getIds(true); + const newIndexPatternsWithNumericFields: IndexPatternTitle[] = []; + ids.forEach(async id => { + const indexPattern = await kibanaContext.indexPatterns.get(id); + if ( + indexPattern.fields + .filter(f => !OMIT_FIELDS.includes(f.name)) + .map(f => f.type) + .includes('number') + ) { + newIndexPatternsWithNumericFields.push(indexPattern.title); + } + }); + setFormState({ + indexPatternsWithNumericFields: newIndexPatternsWithNumericFields, + }); + } catch (e) { + addRequestMessage({ + error: getErrorMessage(e), + message: i18n.translate( + 'xpack.ml.dataframe.analytics.create.errorGettingIndexPatternTitles', + { + defaultMessage: 'An error occurred getting the existing index pattern titles:', + } + ), + }); + } + + dispatch({ type: ACTION.OPEN_MODAL }); + }; + + const startAnalyticsJob = async () => { + setFormState({ isModalButtonDisabled: true }); + try { + const response = await ml.dataFrameAnalytics.startDataFrameAnalytics(jobId); + if (response.acknowledged !== true) { + throw new Error(response); + } + addRequestMessage({ + message: i18n.translate( + 'xpack.ml.dataframe.analytics.create.startDataFrameAnalyticsSuccessMessage', + { + defaultMessage: 'Analytics job {jobId} started.', + values: { jobId }, + } + ), + }); + setFormState({ isJobStarted: true, isModalButtonDisabled: false }); + refresh(); + } catch (e) { + addRequestMessage({ + error: getErrorMessage(e), + message: i18n.translate( + 'xpack.ml.dataframe.analytics.create.errorCreatingDataFrameAnalyticsJob', + { + defaultMessage: 'An error occurred creating the data frame analytics job:', + } + ), + }); + setFormState({ isModalButtonDisabled: false }); + } + }; + + const setFormState = (payload: Partial) => { + dispatch({ type: ACTION.SET_FORM_STATE, payload }); + }; + + const actions: Actions = { + closeModal, + createAnalyticsJob, + openModal, + startAnalyticsJob, + setFormState, + }; + + return { state, actions }; +}; diff --git a/x-pack/legacy/plugins/ml/public/data_frame_analytics/pages/analytics_management/route.ts b/x-pack/legacy/plugins/ml/public/data_frame_analytics/pages/analytics_management/route.ts index c85ec7388ada7..f7771796c7e21 100644 --- a/x-pack/legacy/plugins/ml/public/data_frame_analytics/pages/analytics_management/route.ts +++ b/x-pack/legacy/plugins/ml/public/data_frame_analytics/pages/analytics_management/route.ts @@ -11,6 +11,8 @@ import { checkFullLicense } from '../../../license/check_license'; // @ts-ignore import { checkGetJobsPrivilege } from '../../../privilege/check_privilege'; // @ts-ignore +import { loadCurrentIndexPattern, loadCurrentSavedSearch } from '../../../util/index_utils'; +// @ts-ignore import { loadIndexPatterns } from '../../../util/index_utils'; // @ts-ignore import { getDataFrameAnalyticsBreadcrumbs } from '../../breadcrumbs'; @@ -23,6 +25,8 @@ uiRoutes.when('/data_frame_analytics/?', { resolve: { CheckLicense: checkFullLicense, privileges: checkGetJobsPrivilege, + indexPattern: loadCurrentIndexPattern, indexPatterns: loadIndexPatterns, + savedSearch: loadCurrentSavedSearch, }, });