[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
This commit is contained in:
James Gowdy 2019-08-14 17:23:00 +01:00 committed by GitHub
parent 6da05e0b8d
commit 4acfc04f85
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
11 changed files with 175 additions and 14 deletions

View file

@ -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);

View file

@ -430,7 +430,6 @@ export class JobsListView extends Component {
<CreateWatchFlyout
setShowFunction={this.setShowCreateWatchFlyoutFunction}
unsetShowFunction={this.unsetShowCreateWatchFlyoutFunction}
compile={this.props.compile}
/>
</div>
);

View file

@ -247,11 +247,12 @@ export class JobCreator {
return this._subscribers;
}
public async createAndStartJob() {
public async createAndStartJob(): Promise<JobRunner> {
try {
await this.createJob();
await this.createDatafeed();
await this.startDatafeed();
const jobRunner = await this.startDatafeed();
return jobRunner;
} catch (error) {
throw error;
}

View file

@ -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();

View file

@ -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();

View file

@ -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();

View file

@ -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<void> {
private async _startDatafeed(
start: number | undefined,
end: number | undefined,
pollProgress: boolean
): Promise<boolean> {
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);
}

View file

@ -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';

View file

@ -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<Props> = ({ 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 (
<Fragment>
&emsp;
<EuiButton
isDisabled={
datafeedState === DATAFEED_STATE.STARTING || datafeedState === DATAFEED_STATE.STARTED
}
onClick={startJobInRealTime}
data-test-subj="mlButtonUseFullData3"
>
<FormattedMessage
id="xpack.ml.newJob.wizard.startJobInRealTime"
defaultMessage="Start job running in real time"
/>
</EuiButton>
&emsp;
<EuiButton
isDisabled={
datafeedState === DATAFEED_STATE.STOPPED ||
datafeedState === DATAFEED_STATE.STARTING ||
watchCreated === true
}
onClick={() => setWatchFlyoutVisible(true)}
data-test-subj="mlButtonUseFullData"
>
<FormattedMessage id="xpack.ml.newJob.wizard.createWatch" defaultMessage="Create watch" />
</EuiButton>
{datafeedState === DATAFEED_STATE.STARTED && watchFlyoutVisible && (
<CreateWatchFlyout
setShowFunction={setShowCreateWatchFlyoutFunction}
unsetShowFunction={unsetShowCreateWatchFlyoutFunction}
flyoutHidden={flyoutHidden}
/>
)}
</Fragment>
);
};

View file

@ -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<StepProps> = ({ setCurrentStep, isCurrentStep }) => {
const { jobCreator, jobValidator, jobValidatorUpdated, resultsLoader } = useContext(
@ -24,7 +26,9 @@ export const SummaryStep: FC<StepProps> = ({ 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<JobRunner | null>(null);
useEffect(() => {
jobCreator.subscribeToProgress(setProgress);
@ -32,8 +36,10 @@ export const SummaryStep: FC<StepProps> = ({ 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<StepProps> = ({ setCurrentStep, isCurrentStep }) =>
}),
text: error.message,
});
setCreatingJob(false);
}
}
@ -79,13 +86,16 @@ export const SummaryStep: FC<StepProps> = ({ setCurrentStep, isCurrentStep }) =>
<Fragment>
<EuiButton
onClick={start}
isDisabled={progress > 0}
disabled={isValid === false}
isDisabled={creatingJob === true || isValid === false}
data-test-subj="mlJobWizardButtonCreateJob"
>
Create job
</EuiButton>
&emsp;
</Fragment>
)}
{creatingJob === false && (
<Fragment>
<EuiButtonEmpty
size="s"
onClick={toggleJsonFlyout}
@ -97,7 +107,6 @@ export const SummaryStep: FC<StepProps> = ({ setCurrentStep, isCurrentStep }) =>
{showJsonFlyout && (
<JsonFlyout closeFlyout={() => setShowJsonFlyout(false)} jobCreator={jobCreator} />
)}
&emsp;
</Fragment>
)}
{progress > 0 && (
@ -105,6 +114,11 @@ export const SummaryStep: FC<StepProps> = ({ setCurrentStep, isCurrentStep }) =>
<EuiButton onClick={viewResults} data-test-subj="mlJobWizardButtonViewResults">
View results
</EuiButton>
{progress === 100 && (
<Fragment>
<PostSaveOptions jobRunner={jobRunner} />
</Fragment>
)}
</Fragment>
)}
</Fragment>

View file

@ -20,7 +20,12 @@ declare interface JobService {
cloneJob(job: any): any;
openJob(jobId: string): Promise<any>;
saveNewDatafeed(datafeedConfig: any, jobId: string): Promise<any>;
startDatafeed(datafeedId: string, jobId: string, start: number, end: number): Promise<any>;
startDatafeed(
datafeedId: string,
jobId: string,
start: number | undefined,
end: number | undefined
): Promise<any>;
createResultsUrl(jobId: string[], start: number, end: number, location: string): string;
getJobAndGroupIds(): ExistingJobsAndGroups;
}