From 4acfc04f85ba8b71d98267907156c678df63f792 Mon Sep 17 00:00:00 2001 From: James Gowdy Date: Wed, 14 Aug 2019 17:23:00 +0100 Subject: [PATCH] [ML] Adding post create job options (#43205) * [ML] Adding post create job options * adding toasts * fixing toast string * tweaking continue job function * updating ids * removing ts-ignore --- .../create_watch_flyout.js | 11 +- .../jobs_list_view/jobs_list_view.js | 1 - .../common/job_creator/job_creator.ts | 5 +- .../job_creator/multi_metric_job_creator.ts | 1 + .../job_creator/population_job_creator.ts | 1 + .../job_creator/single_metric_job_creator.ts | 1 + .../common/job_runner/job_runner.ts | 29 ++++- .../components/post_save_options/index.ts | 7 ++ .../post_save_options/post_save_options.tsx | 104 ++++++++++++++++++ .../pages/components/summary_step/summary.tsx | 22 +++- .../ml/public/services/job_service.d.ts | 7 +- 11 files changed, 175 insertions(+), 14 deletions(-) create mode 100644 x-pack/legacy/plugins/ml/public/jobs/new_job_new/pages/components/summary_step/components/post_save_options/index.ts create mode 100644 x-pack/legacy/plugins/ml/public/jobs/new_job_new/pages/components/summary_step/components/post_save_options/post_save_options.tsx diff --git a/x-pack/legacy/plugins/ml/public/jobs/jobs_list/components/create_watch_flyout/create_watch_flyout.js b/x-pack/legacy/plugins/ml/public/jobs/jobs_list/components/create_watch_flyout/create_watch_flyout.js index ab84ee7dbd795..a5519314c7c68 100644 --- a/x-pack/legacy/plugins/ml/public/jobs/jobs_list/components/create_watch_flyout/create_watch_flyout.js +++ b/x-pack/legacy/plugins/ml/public/jobs/jobs_list/components/create_watch_flyout/create_watch_flyout.js @@ -80,8 +80,12 @@ class CreateWatchFlyoutUI extends Component { } } - closeFlyout = () => { - this.setState({ isFlyoutVisible: false }); + closeFlyout = (watchCreated = false) => { + this.setState({ isFlyoutVisible: false }, ()=>{ + if (typeof this.props.flyoutHidden === 'function') { + this.props.flyoutHidden(watchCreated); + } + }); } showFlyout = (jobId) => { @@ -107,7 +111,7 @@ class CreateWatchFlyoutUI extends Component { mlCreateWatchService.createNewWatch(this.state.jobId) .then((resp) => { toastNotifications.addSuccess(getSuccessToast(resp.id, resp.url, intl)); - this.closeFlyout(); + this.closeFlyout(true); }) .catch((error) => { toastNotifications.addDanger(intl.formatMessage({ @@ -194,6 +198,7 @@ class CreateWatchFlyoutUI extends Component { CreateWatchFlyoutUI.propTypes = { setShowFunction: PropTypes.func.isRequired, unsetShowFunction: PropTypes.func.isRequired, + flyoutHidden: PropTypes.func, }; export const CreateWatchFlyout = injectI18n(CreateWatchFlyoutUI); diff --git a/x-pack/legacy/plugins/ml/public/jobs/jobs_list/components/jobs_list_view/jobs_list_view.js b/x-pack/legacy/plugins/ml/public/jobs/jobs_list/components/jobs_list_view/jobs_list_view.js index 337bb5a12f61c..985275acf7d51 100644 --- a/x-pack/legacy/plugins/ml/public/jobs/jobs_list/components/jobs_list_view/jobs_list_view.js +++ b/x-pack/legacy/plugins/ml/public/jobs/jobs_list/components/jobs_list_view/jobs_list_view.js @@ -430,7 +430,6 @@ export class JobsListView extends Component { ); diff --git a/x-pack/legacy/plugins/ml/public/jobs/new_job_new/common/job_creator/job_creator.ts b/x-pack/legacy/plugins/ml/public/jobs/new_job_new/common/job_creator/job_creator.ts index baeec2dd6576b..7416157e60c3e 100644 --- a/x-pack/legacy/plugins/ml/public/jobs/new_job_new/common/job_creator/job_creator.ts +++ b/x-pack/legacy/plugins/ml/public/jobs/new_job_new/common/job_creator/job_creator.ts @@ -247,11 +247,12 @@ export class JobCreator { return this._subscribers; } - public async createAndStartJob() { + public async createAndStartJob(): Promise { try { await this.createJob(); await this.createDatafeed(); - await this.startDatafeed(); + const jobRunner = await this.startDatafeed(); + return jobRunner; } catch (error) { throw error; } diff --git a/x-pack/legacy/plugins/ml/public/jobs/new_job_new/common/job_creator/multi_metric_job_creator.ts b/x-pack/legacy/plugins/ml/public/jobs/new_job_new/common/job_creator/multi_metric_job_creator.ts index c44cadca33adb..df28af6b83c6a 100644 --- a/x-pack/legacy/plugins/ml/public/jobs/new_job_new/common/job_creator/multi_metric_job_creator.ts +++ b/x-pack/legacy/plugins/ml/public/jobs/new_job_new/common/job_creator/multi_metric_job_creator.ts @@ -141,6 +141,7 @@ export class MultiMetricJobCreator extends JobCreator { public cloneFromExistingJob(job: Job, datafeed: Datafeed) { this._overrideConfigs(job, datafeed); this.jobId = ''; + this.createdBy = CREATED_BY_LABEL.MULTI_METRIC; const detectors = getRichDetectors(job.analysis_config.detectors); this.removeAllDetectors(); diff --git a/x-pack/legacy/plugins/ml/public/jobs/new_job_new/common/job_creator/population_job_creator.ts b/x-pack/legacy/plugins/ml/public/jobs/new_job_new/common/job_creator/population_job_creator.ts index d567e108e02e8..1f6d3392cfa0c 100644 --- a/x-pack/legacy/plugins/ml/public/jobs/new_job_new/common/job_creator/population_job_creator.ts +++ b/x-pack/legacy/plugins/ml/public/jobs/new_job_new/common/job_creator/population_job_creator.ts @@ -130,6 +130,7 @@ export class PopulationJobCreator extends JobCreator { public cloneFromExistingJob(job: Job, datafeed: Datafeed) { this._overrideConfigs(job, datafeed); this.jobId = ''; + this.createdBy = CREATED_BY_LABEL.POPULATION; const detectors = getRichDetectors(job.analysis_config.detectors); this.removeAllDetectors(); diff --git a/x-pack/legacy/plugins/ml/public/jobs/new_job_new/common/job_creator/single_metric_job_creator.ts b/x-pack/legacy/plugins/ml/public/jobs/new_job_new/common/job_creator/single_metric_job_creator.ts index a2ba0801e3324..c38c52681fd31 100644 --- a/x-pack/legacy/plugins/ml/public/jobs/new_job_new/common/job_creator/single_metric_job_creator.ts +++ b/x-pack/legacy/plugins/ml/public/jobs/new_job_new/common/job_creator/single_metric_job_creator.ts @@ -181,6 +181,7 @@ export class SingleMetricJobCreator extends JobCreator { public cloneFromExistingJob(job: Job, datafeed: Datafeed) { this._overrideConfigs(job, datafeed); this.jobId = ''; + this.createdBy = CREATED_BY_LABEL.SINGLE_METRIC; const detectors = getRichDetectors(job.analysis_config.detectors); this.removeAllDetectors(); diff --git a/x-pack/legacy/plugins/ml/public/jobs/new_job_new/common/job_runner/job_runner.ts b/x-pack/legacy/plugins/ml/public/jobs/new_job_new/common/job_runner/job_runner.ts index 3f8bdfa371d01..830cfb713ac2d 100644 --- a/x-pack/legacy/plugins/ml/public/jobs/new_job_new/common/job_runner/job_runner.ts +++ b/x-pack/legacy/plugins/ml/public/jobs/new_job_new/common/job_runner/job_runner.ts @@ -66,10 +66,20 @@ export class JobRunner { // start the datafeed and then start polling for progress // the complete percentage is added to an observable // so all pre-subscribed listeners can follow along. - public async startDatafeed(): Promise { + private async _startDatafeed( + start: number | undefined, + end: number | undefined, + pollProgress: boolean + ): Promise { try { await this.openJob(); - await mlJobService.startDatafeed(this._datafeedId, this._jobId, this._start, this._end); + const { started } = await mlJobService.startDatafeed( + this._datafeedId, + this._jobId, + start, + end + ); + this._datafeedState = DATAFEED_STATE.STARTED; this._percentageComplete = 0; @@ -87,12 +97,25 @@ export class JobRunner { }; // wait for the first check to run and then return success. // all subsequent checks will update the observable - await check(); + if (pollProgress === true) { + await check(); + } + return started; } catch (error) { throw error; } } + public async startDatafeed() { + return await this._startDatafeed(this._start, this._end, true); + } + + public async startDatafeedInRealTime(continueJob: boolean) { + // if continuing a job, set the start to be the end date + const start = continueJob ? this._end : this._start; + return await this._startDatafeed(start, undefined, false); + } + public async getProgress(): Promise<{ progress: Progress; isRunning: boolean }> { return await ml.jobs.getLookBackProgress(this._jobId, this._start, this._end); } diff --git a/x-pack/legacy/plugins/ml/public/jobs/new_job_new/pages/components/summary_step/components/post_save_options/index.ts b/x-pack/legacy/plugins/ml/public/jobs/new_job_new/pages/components/summary_step/components/post_save_options/index.ts new file mode 100644 index 0000000000000..dacf9184843c4 --- /dev/null +++ b/x-pack/legacy/plugins/ml/public/jobs/new_job_new/pages/components/summary_step/components/post_save_options/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 { PostSaveOptions } from './post_save_options'; diff --git a/x-pack/legacy/plugins/ml/public/jobs/new_job_new/pages/components/summary_step/components/post_save_options/post_save_options.tsx b/x-pack/legacy/plugins/ml/public/jobs/new_job_new/pages/components/summary_step/components/post_save_options/post_save_options.tsx new file mode 100644 index 0000000000000..e1251ad74c4f0 --- /dev/null +++ b/x-pack/legacy/plugins/ml/public/jobs/new_job_new/pages/components/summary_step/components/post_save_options/post_save_options.tsx @@ -0,0 +1,104 @@ +/* + * 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, Fragment, useContext, useState } from 'react'; +import { toastNotifications } from 'ui/notify'; +import { EuiButton } from '@elastic/eui'; +import { i18n } from '@kbn/i18n'; +import { FormattedMessage } from '@kbn/i18n/react'; +import { JobRunner } from '../../../../../common/job_runner'; + +// @ts-ignore +import { CreateWatchFlyout } from '../../../../../../jobs_list/components/create_watch_flyout'; +import { JobCreatorContext } from '../../../../components/job_creator_context'; +import { DATAFEED_STATE } from '../../../../../../../../common/constants/states'; + +interface Props { + jobRunner: JobRunner | null; +} + +type ShowFlyout = (jobId: string) => void; + +export const PostSaveOptions: FC = ({ jobRunner }) => { + const { jobCreator } = useContext(JobCreatorContext); + const [datafeedState, setDatafeedState] = useState(DATAFEED_STATE.STOPPED); + const [watchFlyoutVisible, setWatchFlyoutVisible] = useState(false); + const [watchCreated, setWatchCreated] = useState(false); + + function setShowCreateWatchFlyoutFunction(showFlyout: ShowFlyout) { + showFlyout(jobCreator.jobId); + } + + function flyoutHidden(jobCreated: boolean) { + setWatchFlyoutVisible(false); + setWatchCreated(jobCreated); + } + + function unsetShowCreateWatchFlyoutFunction() { + setWatchFlyoutVisible(false); + } + + async function startJobInRealTime() { + setDatafeedState(DATAFEED_STATE.STARTING); + if (jobRunner !== null) { + try { + const started = await jobRunner.startDatafeedInRealTime(true); + setDatafeedState(started === true ? DATAFEED_STATE.STARTED : DATAFEED_STATE.STOPPED); + toastNotifications.addSuccess({ + title: i18n.translate('xpack.ml.newJob.wizard.startJobInRealTimeSuccess', { + defaultMessage: `Job {jobId} started`, + values: { jobId: jobCreator.jobId }, + }), + }); + } catch (error) { + setDatafeedState(DATAFEED_STATE.STOPPED); + toastNotifications.addDanger({ + title: i18n.translate('xpack.ml.newJob.wizard.startJobInRealTimeError', { + defaultMessage: `Error starting job`, + }), + text: error.message, + }); + } + } + } + + return ( + +   + + + +   + setWatchFlyoutVisible(true)} + data-test-subj="mlButtonUseFullData" + > + + + {datafeedState === DATAFEED_STATE.STARTED && watchFlyoutVisible && ( + + )} + + ); +}; diff --git a/x-pack/legacy/plugins/ml/public/jobs/new_job_new/pages/components/summary_step/summary.tsx b/x-pack/legacy/plugins/ml/public/jobs/new_job_new/pages/components/summary_step/summary.tsx index 6812935794baf..e89892f8883d2 100644 --- a/x-pack/legacy/plugins/ml/public/jobs/new_job_new/pages/components/summary_step/summary.tsx +++ b/x-pack/legacy/plugins/ml/public/jobs/new_job_new/pages/components/summary_step/summary.tsx @@ -11,12 +11,14 @@ import { toastNotifications } from 'ui/notify'; import { WizardNav } from '../wizard_nav'; import { WIZARD_STEPS, StepProps } from '../step_types'; import { JobCreatorContext } from '../job_creator_context'; +import { JobRunner } from '../../../common/job_runner'; import { mlJobService } from '../../../../../services/job_service'; import { JsonFlyout } from './json_flyout'; import { isSingleMetricJobCreator } from '../../../common/job_creator'; import { JobDetails } from './job_details'; import { DetectorChart } from './detector_chart'; import { JobProgress } from './components/job_progress'; +import { PostSaveOptions } from './components/post_save_options'; export const SummaryStep: FC = ({ setCurrentStep, isCurrentStep }) => { const { jobCreator, jobValidator, jobValidatorUpdated, resultsLoader } = useContext( @@ -24,7 +26,9 @@ export const SummaryStep: FC = ({ setCurrentStep, isCurrentStep }) => ); const [progress, setProgress] = useState(resultsLoader.progress); const [showJsonFlyout, setShowJsonFlyout] = useState(false); + const [creatingJob, setCreatingJob] = useState(false); const [isValid, setIsValid] = useState(jobValidator.validationSummary.basic); + const [jobRunner, setJobRunner] = useState(null); useEffect(() => { jobCreator.subscribeToProgress(setProgress); @@ -32,8 +36,10 @@ export const SummaryStep: FC = ({ setCurrentStep, isCurrentStep }) => async function start() { setShowJsonFlyout(false); + setCreatingJob(true); try { - await jobCreator.createAndStartJob(); + const jr = await jobCreator.createAndStartJob(); + setJobRunner(jr); } catch (error) { // catch and display all job creation errors toastNotifications.addDanger({ @@ -42,6 +48,7 @@ export const SummaryStep: FC = ({ setCurrentStep, isCurrentStep }) => }), text: error.message, }); + setCreatingJob(false); } } @@ -79,13 +86,16 @@ export const SummaryStep: FC = ({ setCurrentStep, isCurrentStep }) => 0} - disabled={isValid === false} + isDisabled={creatingJob === true || isValid === false} data-test-subj="mlJobWizardButtonCreateJob" > Create job   + + )} + {creatingJob === false && ( + = ({ setCurrentStep, isCurrentStep }) => {showJsonFlyout && ( setShowJsonFlyout(false)} jobCreator={jobCreator} /> )} -   )} {progress > 0 && ( @@ -105,6 +114,11 @@ export const SummaryStep: FC = ({ setCurrentStep, isCurrentStep }) => View results + {progress === 100 && ( + + + + )} )} diff --git a/x-pack/legacy/plugins/ml/public/services/job_service.d.ts b/x-pack/legacy/plugins/ml/public/services/job_service.d.ts index d206f3748b36f..d32e55a58f3a4 100644 --- a/x-pack/legacy/plugins/ml/public/services/job_service.d.ts +++ b/x-pack/legacy/plugins/ml/public/services/job_service.d.ts @@ -20,7 +20,12 @@ declare interface JobService { cloneJob(job: any): any; openJob(jobId: string): Promise; saveNewDatafeed(datafeedConfig: any, jobId: string): Promise; - startDatafeed(datafeedId: string, jobId: string, start: number, end: number): Promise; + startDatafeed( + datafeedId: string, + jobId: string, + start: number | undefined, + end: number | undefined + ): Promise; createResultsUrl(jobId: string[], start: number, end: number, location: string): string; getJobAndGroupIds(): ExistingJobsAndGroups; }