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,
},
});