mirror of
https://github.com/elastic/kibana.git
synced 2025-04-24 01:38:56 -04:00
[ML] Adding option to create AD jobs without starting the datafeed (#77484)
* [ML] Adding option to create AD jobs without starting the datafeed * changing translation id * i just need some space * adding missed spelling change * disabling switch when running * improving logic * further test improvements Co-authored-by: Elastic Machine <elasticmachine@users.noreply.github.com>
This commit is contained in:
parent
06e8c54751
commit
ae55391035
12 changed files with 273 additions and 18 deletions
|
@ -268,8 +268,13 @@ export function resetJob(jobCreator: JobCreatorType, navigateToPath: NavigateToP
|
|||
navigateToPath('/jobs/new_job');
|
||||
}
|
||||
|
||||
export function advancedStartDatafeed(jobCreator: JobCreatorType, navigateToPath: NavigateToPath) {
|
||||
stashCombinedJob(jobCreator, false, false);
|
||||
export function advancedStartDatafeed(
|
||||
jobCreator: JobCreatorType | null,
|
||||
navigateToPath: NavigateToPath
|
||||
) {
|
||||
if (jobCreator !== null) {
|
||||
stashCombinedJob(jobCreator, false, false);
|
||||
}
|
||||
navigateToPath('/jobs');
|
||||
}
|
||||
|
||||
|
|
|
@ -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 { StartDatafeedSwitch } from './start_datafeed_switch';
|
|
@ -0,0 +1,44 @@
|
|||
/*
|
||||
* 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 { i18n } from '@kbn/i18n';
|
||||
import { EuiSwitch, EuiFormRow, EuiSpacer } from '@elastic/eui';
|
||||
interface Props {
|
||||
startDatafeed: boolean;
|
||||
setStartDatafeed(start: boolean): void;
|
||||
disabled?: boolean;
|
||||
}
|
||||
|
||||
export const StartDatafeedSwitch: FC<Props> = ({
|
||||
startDatafeed,
|
||||
setStartDatafeed,
|
||||
disabled = false,
|
||||
}) => {
|
||||
return (
|
||||
<>
|
||||
<EuiSpacer />
|
||||
<EuiFormRow
|
||||
helpText={i18n.translate(
|
||||
'xpack.ml.newJob.wizard.summaryStep.startDatafeedCheckboxHelpText',
|
||||
{
|
||||
defaultMessage: 'If unselected, job can be started later from the jobs list.',
|
||||
}
|
||||
)}
|
||||
>
|
||||
<EuiSwitch
|
||||
data-test-subj="mlJobWizardStartDatafeedCheckbox"
|
||||
label={i18n.translate('xpack.ml.newJob.wizard.summaryStep.startDatafeedCheckbox', {
|
||||
defaultMessage: 'Start immediately',
|
||||
})}
|
||||
checked={startDatafeed}
|
||||
onChange={(e) => setStartDatafeed(e.target.checked)}
|
||||
disabled={disabled}
|
||||
/>
|
||||
</EuiFormRow>
|
||||
</>
|
||||
);
|
||||
};
|
|
@ -28,6 +28,7 @@ import { DatafeedDetails } from './components/datafeed_details';
|
|||
import { DetectorChart } from './components/detector_chart';
|
||||
import { JobProgress } from './components/job_progress';
|
||||
import { PostSaveOptions } from './components/post_save_options';
|
||||
import { StartDatafeedSwitch } from './components/start_datafeed_switch';
|
||||
import { toastNotificationServiceProvider } from '../../../../../services/toast_notification_service';
|
||||
import {
|
||||
convertToAdvancedJob,
|
||||
|
@ -50,6 +51,7 @@ export const SummaryStep: FC<StepProps> = ({ setCurrentStep, isCurrentStep }) =>
|
|||
const [creatingJob, setCreatingJob] = useState(false);
|
||||
const [isValid, setIsValid] = useState(jobValidator.validationSummary.basic);
|
||||
const [jobRunner, setJobRunner] = useState<JobRunner | null>(null);
|
||||
const [startDatafeed, setStartDatafeed] = useState(true);
|
||||
|
||||
const isAdvanced = isAdvancedJobCreator(jobCreator);
|
||||
const jsonEditorMode = isAdvanced ? EDITOR_MODE.EDITABLE : EDITOR_MODE.READONLY;
|
||||
|
@ -59,15 +61,17 @@ export const SummaryStep: FC<StepProps> = ({ setCurrentStep, isCurrentStep }) =>
|
|||
}, []);
|
||||
|
||||
async function start() {
|
||||
setCreatingJob(true);
|
||||
if (isAdvanced) {
|
||||
await startAdvanced();
|
||||
await createAdvancedJob();
|
||||
} else if (startDatafeed === true) {
|
||||
await createAndStartJob();
|
||||
} else {
|
||||
await startInline();
|
||||
await createAdvancedJob(false);
|
||||
}
|
||||
}
|
||||
|
||||
async function startInline() {
|
||||
setCreatingJob(true);
|
||||
async function createAndStartJob() {
|
||||
try {
|
||||
const jr = await jobCreator.createAndStartJob();
|
||||
setJobRunner(jr);
|
||||
|
@ -76,12 +80,11 @@ export const SummaryStep: FC<StepProps> = ({ setCurrentStep, isCurrentStep }) =>
|
|||
}
|
||||
}
|
||||
|
||||
async function startAdvanced() {
|
||||
setCreatingJob(true);
|
||||
async function createAdvancedJob(showStartModal: boolean = true) {
|
||||
try {
|
||||
await jobCreator.createJob();
|
||||
await jobCreator.createDatafeed();
|
||||
advancedStartDatafeed(jobCreator, navigateToPath);
|
||||
advancedStartDatafeed(showStartModal ? jobCreator : null, navigateToPath);
|
||||
} catch (error) {
|
||||
handleJobCreationError(error);
|
||||
}
|
||||
|
@ -131,6 +134,14 @@ export const SummaryStep: FC<StepProps> = ({ setCurrentStep, isCurrentStep }) =>
|
|||
<EuiSpacer size="m" />
|
||||
<JobDetails />
|
||||
|
||||
{isAdvanced === false && (
|
||||
<StartDatafeedSwitch
|
||||
startDatafeed={startDatafeed}
|
||||
setStartDatafeed={setStartDatafeed}
|
||||
disabled={creatingJob}
|
||||
/>
|
||||
)}
|
||||
|
||||
{isAdvanced && (
|
||||
<Fragment>
|
||||
<EuiHorizontalRule />
|
||||
|
|
|
@ -108,7 +108,7 @@ export default function ({ getService }: FtrProviderContext) {
|
|||
await ml.testExecution.logTestStep('job creation displays the time range step');
|
||||
await ml.jobWizardCommon.assertTimeRangeSectionExists();
|
||||
|
||||
await ml.testExecution.logTestStep('job creation sets the timerange');
|
||||
await ml.testExecution.logTestStep('job creation sets the time range');
|
||||
await ml.jobWizardCommon.clickUseFullDataButton(
|
||||
'Apr 5, 2019 @ 11:25:35.770',
|
||||
'Nov 21, 2019 @ 06:01:13.914'
|
||||
|
@ -230,7 +230,7 @@ export default function ({ getService }: FtrProviderContext) {
|
|||
await ml.testExecution.logTestStep('job cloning displays the time range step');
|
||||
await ml.jobWizardCommon.assertTimeRangeSectionExists();
|
||||
|
||||
await ml.testExecution.logTestStep('job cloning sets the timerange');
|
||||
await ml.testExecution.logTestStep('job cloning sets the time range');
|
||||
await ml.jobWizardCommon.clickUseFullDataButton(
|
||||
'Apr 5, 2019 @ 11:25:35.770',
|
||||
'Nov 21, 2019 @ 06:01:13.914'
|
||||
|
|
|
@ -10,6 +10,7 @@ export default function ({ loadTestFile }: FtrProviderContext) {
|
|||
this.tags(['skipFirefox']);
|
||||
|
||||
loadTestFile(require.resolve('./single_metric_job'));
|
||||
loadTestFile(require.resolve('./single_metric_job_without_datafeed_start'));
|
||||
loadTestFile(require.resolve('./multi_metric_job'));
|
||||
loadTestFile(require.resolve('./population_job'));
|
||||
loadTestFile(require.resolve('./saved_search_job'));
|
||||
|
|
|
@ -104,7 +104,7 @@ export default function ({ getService }: FtrProviderContext) {
|
|||
await ml.testExecution.logTestStep('job creation displays the time range step');
|
||||
await ml.jobWizardCommon.assertTimeRangeSectionExists();
|
||||
|
||||
await ml.testExecution.logTestStep('job creation sets the timerange');
|
||||
await ml.testExecution.logTestStep('job creation sets the time range');
|
||||
await ml.jobWizardCommon.clickUseFullDataButton(
|
||||
'Feb 7, 2016 @ 00:00:00.000',
|
||||
'Feb 11, 2016 @ 23:59:54.000'
|
||||
|
@ -235,7 +235,7 @@ export default function ({ getService }: FtrProviderContext) {
|
|||
await ml.testExecution.logTestStep('job cloning displays the time range step');
|
||||
await ml.jobWizardCommon.assertTimeRangeSectionExists();
|
||||
|
||||
await ml.testExecution.logTestStep('job cloning sets the timerange');
|
||||
await ml.testExecution.logTestStep('job cloning sets the time range');
|
||||
await ml.jobWizardCommon.clickUseFullDataButton(
|
||||
'Feb 7, 2016 @ 00:00:00.000',
|
||||
'Feb 11, 2016 @ 23:59:54.000'
|
||||
|
|
|
@ -118,7 +118,7 @@ export default function ({ getService }: FtrProviderContext) {
|
|||
await ml.testExecution.logTestStep('job creation displays the time range step');
|
||||
await ml.jobWizardCommon.assertTimeRangeSectionExists();
|
||||
|
||||
await ml.testExecution.logTestStep('job creation sets the timerange');
|
||||
await ml.testExecution.logTestStep('job creation sets the time range');
|
||||
await ml.jobWizardCommon.clickUseFullDataButton(
|
||||
'Jun 12, 2019 @ 00:04:19.000',
|
||||
'Jul 12, 2019 @ 23:45:36.000'
|
||||
|
@ -261,7 +261,7 @@ export default function ({ getService }: FtrProviderContext) {
|
|||
await ml.testExecution.logTestStep('job cloning displays the time range step');
|
||||
await ml.jobWizardCommon.assertTimeRangeSectionExists();
|
||||
|
||||
await ml.testExecution.logTestStep('job cloning sets the timerange');
|
||||
await ml.testExecution.logTestStep('job cloning sets the time range');
|
||||
await ml.jobWizardCommon.clickUseFullDataButton(
|
||||
'Jun 12, 2019 @ 00:04:19.000',
|
||||
'Jul 12, 2019 @ 23:45:36.000'
|
||||
|
|
|
@ -306,7 +306,7 @@ export default function ({ getService }: FtrProviderContext) {
|
|||
await ml.testExecution.logTestStep('job creation displays the time range step');
|
||||
await ml.jobWizardCommon.assertTimeRangeSectionExists();
|
||||
|
||||
await ml.testExecution.logTestStep('job creation sets the timerange');
|
||||
await ml.testExecution.logTestStep('job creation sets the time range');
|
||||
await ml.jobWizardCommon.clickUseFullDataButton(
|
||||
'Feb 7, 2016 @ 00:00:00.000',
|
||||
'Feb 11, 2016 @ 23:59:54.000'
|
||||
|
|
|
@ -103,7 +103,7 @@ export default function ({ getService }: FtrProviderContext) {
|
|||
await ml.testExecution.logTestStep('job creation displays the time range step');
|
||||
await ml.jobWizardCommon.assertTimeRangeSectionExists();
|
||||
|
||||
await ml.testExecution.logTestStep('job creation sets the timerange');
|
||||
await ml.testExecution.logTestStep('job creation sets the time range');
|
||||
await ml.jobWizardCommon.clickUseFullDataButton(
|
||||
'Feb 7, 2016 @ 00:00:00.000',
|
||||
'Feb 11, 2016 @ 23:59:54.000'
|
||||
|
@ -212,7 +212,7 @@ export default function ({ getService }: FtrProviderContext) {
|
|||
await ml.testExecution.logTestStep('job cloning displays the time range step');
|
||||
await ml.jobWizardCommon.assertTimeRangeSectionExists();
|
||||
|
||||
await ml.testExecution.logTestStep('job cloning sets the timerange');
|
||||
await ml.testExecution.logTestStep('job cloning sets the time range');
|
||||
await ml.jobWizardCommon.clickUseFullDataButton(
|
||||
'Feb 7, 2016 @ 00:00:00.000',
|
||||
'Feb 11, 2016 @ 23:59:54.000'
|
||||
|
|
|
@ -0,0 +1,151 @@
|
|||
/*
|
||||
* 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 { FtrProviderContext } from '../../../ftr_provider_context';
|
||||
|
||||
export default function ({ getService }: FtrProviderContext) {
|
||||
const esArchiver = getService('esArchiver');
|
||||
const ml = getService('ml');
|
||||
|
||||
const jobId = `fq_single_1_${Date.now()}`;
|
||||
const aggAndFieldIdentifier = 'Mean(responsetime)';
|
||||
const bucketSpan = '30m';
|
||||
|
||||
function getExpectedRow(expectedJobId: string) {
|
||||
return {
|
||||
id: expectedJobId,
|
||||
description: '',
|
||||
jobGroups: [],
|
||||
recordCount: '0',
|
||||
memoryStatus: 'ok',
|
||||
jobState: 'closed',
|
||||
datafeedState: 'stopped',
|
||||
latestTimestamp: '',
|
||||
};
|
||||
}
|
||||
|
||||
function getExpectedCounts(expectedJobId: string) {
|
||||
return {
|
||||
job_id: expectedJobId,
|
||||
processed_record_count: '0',
|
||||
processed_field_count: '0',
|
||||
input_bytes: '0.0 B',
|
||||
input_field_count: '0',
|
||||
invalid_date_count: '0',
|
||||
missing_field_count: '0',
|
||||
out_of_order_timestamp_count: '0',
|
||||
empty_bucket_count: '0',
|
||||
sparse_bucket_count: '0',
|
||||
bucket_count: '0',
|
||||
};
|
||||
}
|
||||
|
||||
function getExpectedModelSizeStats(expectedJobId: string) {
|
||||
return {
|
||||
job_id: expectedJobId,
|
||||
result_type: 'model_size_stats',
|
||||
total_by_field_count: '0',
|
||||
total_over_field_count: '0',
|
||||
total_partition_field_count: '0',
|
||||
bucket_allocation_failures_count: '0',
|
||||
memory_status: 'ok',
|
||||
};
|
||||
}
|
||||
|
||||
describe('single metric without datafeed start', function () {
|
||||
this.tags(['mlqa']);
|
||||
before(async () => {
|
||||
await esArchiver.loadIfNeeded('ml/farequote');
|
||||
await ml.testResources.createIndexPatternIfNeeded('ft_farequote', '@timestamp');
|
||||
await ml.testResources.setKibanaTimeZoneToUTC();
|
||||
|
||||
await ml.securityUI.loginAsMlPowerUser();
|
||||
});
|
||||
|
||||
after(async () => {
|
||||
await ml.api.cleanMlIndices();
|
||||
});
|
||||
|
||||
it('job creation loads the single metric wizard for the source data', async () => {
|
||||
await ml.testExecution.logTestStep('job creation loads the job management page');
|
||||
await ml.navigation.navigateToMl();
|
||||
await ml.navigation.navigateToJobManagement();
|
||||
|
||||
await ml.testExecution.logTestStep('job creation loads the new job source selection page');
|
||||
await ml.jobManagement.navigateToNewJobSourceSelection();
|
||||
|
||||
await ml.testExecution.logTestStep('job creation loads the job type selection page');
|
||||
await ml.jobSourceSelection.selectSourceForAnomalyDetectionJob('ft_farequote');
|
||||
|
||||
await ml.testExecution.logTestStep('job creation loads the single metric job wizard page');
|
||||
await ml.jobTypeSelection.selectSingleMetricJob();
|
||||
});
|
||||
|
||||
it('job creation navigates through the single metric wizard and sets all needed fields', async () => {
|
||||
await ml.testExecution.logTestStep('job creation displays the time range step');
|
||||
await ml.jobWizardCommon.assertTimeRangeSectionExists();
|
||||
|
||||
await ml.testExecution.logTestStep('job creation sets the time range');
|
||||
await ml.jobWizardCommon.clickUseFullDataButton(
|
||||
'Feb 7, 2016 @ 00:00:00.000',
|
||||
'Feb 11, 2016 @ 23:59:54.000'
|
||||
);
|
||||
|
||||
await ml.testExecution.logTestStep('job creation displays the event rate chart');
|
||||
await ml.jobWizardCommon.assertEventRateChartExists();
|
||||
await ml.jobWizardCommon.assertEventRateChartHasData();
|
||||
|
||||
await ml.testExecution.logTestStep('job creation displays the pick fields step');
|
||||
await ml.jobWizardCommon.advanceToPickFieldsSection();
|
||||
|
||||
await ml.testExecution.logTestStep('job creation selects field and aggregation');
|
||||
await ml.jobWizardCommon.assertAggAndFieldInputExists();
|
||||
await ml.jobWizardCommon.selectAggAndField(aggAndFieldIdentifier, true);
|
||||
await ml.jobWizardCommon.assertAnomalyChartExists('LINE');
|
||||
|
||||
await ml.testExecution.logTestStep('job creation inputs the bucket span');
|
||||
await ml.jobWizardCommon.assertBucketSpanInputExists();
|
||||
await ml.jobWizardCommon.setBucketSpan(bucketSpan);
|
||||
|
||||
await ml.testExecution.logTestStep('job creation displays the job details step');
|
||||
await ml.jobWizardCommon.advanceToJobDetailsSection();
|
||||
|
||||
await ml.testExecution.logTestStep('job creation inputs the job id');
|
||||
await ml.jobWizardCommon.assertJobIdInputExists();
|
||||
await ml.jobWizardCommon.setJobId(jobId);
|
||||
|
||||
await ml.testExecution.logTestStep('job creation displays the validation step');
|
||||
await ml.jobWizardCommon.advanceToValidationSection();
|
||||
|
||||
await ml.testExecution.logTestStep('job creation displays the summary step');
|
||||
await ml.jobWizardCommon.advanceToSummarySection();
|
||||
});
|
||||
|
||||
it('job creation runs the job and displays it correctly in the job list', async () => {
|
||||
await ml.testExecution.logTestStep('job creation creates the job and finishes processing');
|
||||
|
||||
await ml.jobWizardCommon.assertStartDatafeedSwitchExists();
|
||||
await ml.jobWizardCommon.toggleStartDatafeedSwitch(false);
|
||||
|
||||
await ml.jobWizardCommon.assertCreateJobButtonExists();
|
||||
await ml.jobWizardCommon.createJobWithoutDatafeedStart();
|
||||
|
||||
await ml.jobTable.waitForJobsToLoad();
|
||||
await ml.jobTable.filterWithSearchString(jobId, 1);
|
||||
|
||||
await ml.testExecution.logTestStep(
|
||||
'job creation displays details for the created job in the job list'
|
||||
);
|
||||
await ml.jobTable.assertJobRowFields(jobId, getExpectedRow(jobId));
|
||||
|
||||
await ml.jobTable.assertJobRowDetailsCounts(
|
||||
jobId,
|
||||
getExpectedCounts(jobId),
|
||||
getExpectedModelSizeStats(jobId)
|
||||
);
|
||||
});
|
||||
});
|
||||
}
|
|
@ -335,6 +335,37 @@ export function MachineLearningJobWizardCommonProvider(
|
|||
}
|
||||
},
|
||||
|
||||
async assertStartDatafeedSwitchExists() {
|
||||
const subj = 'mlJobWizardStartDatafeedCheckbox';
|
||||
await testSubjects.existOrFail(subj, { allowHidden: true });
|
||||
},
|
||||
|
||||
async getStartDatafeedSwitchCheckedState(): Promise<boolean> {
|
||||
const subj = 'mlJobWizardStartDatafeedCheckbox';
|
||||
const isSelected = await testSubjects.getAttribute(subj, 'aria-checked');
|
||||
return isSelected === 'true';
|
||||
},
|
||||
|
||||
async assertStartDatafeedSwitchCheckedState(expectedValue: boolean) {
|
||||
const actualCheckedState = await this.getStartDatafeedSwitchCheckedState();
|
||||
expect(actualCheckedState).to.eql(
|
||||
expectedValue,
|
||||
`Expected start datafeed switch to be '${expectedValue ? 'enabled' : 'disabled'}' (got '${
|
||||
actualCheckedState ? 'enabled' : 'disabled'
|
||||
}')`
|
||||
);
|
||||
},
|
||||
|
||||
async toggleStartDatafeedSwitch(toggle: boolean) {
|
||||
const subj = 'mlJobWizardStartDatafeedCheckbox';
|
||||
if ((await this.getStartDatafeedSwitchCheckedState()) !== toggle) {
|
||||
await retry.tryForTime(5 * 1000, async () => {
|
||||
await testSubjects.clickWhenNotDisabled(subj);
|
||||
await this.assertStartDatafeedSwitchCheckedState(toggle);
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
async assertModelMemoryLimitInputExists(
|
||||
sectionOptions: SectionOptions = { withAdvancedSection: true }
|
||||
) {
|
||||
|
@ -510,5 +541,10 @@ export function MachineLearningJobWizardCommonProvider(
|
|||
await testSubjects.clickWhenNotDisabled('mlJobWizardButtonCreateJob');
|
||||
await testSubjects.existOrFail('mlJobWizardButtonRunInRealTime', { timeout: 2 * 60 * 1000 });
|
||||
},
|
||||
|
||||
async createJobWithoutDatafeedStart() {
|
||||
await testSubjects.clickWhenNotDisabled('mlJobWizardButtonCreateJob');
|
||||
await testSubjects.existOrFail('mlPageJobManagement');
|
||||
},
|
||||
};
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue