Skip to content

Commit

Permalink
[ML] Allow job cloning via wizards (elastic#20227)
Browse files Browse the repository at this point in the history
* [ML] [WIP] Allow job cloning via wizards

* updating clone link

* removing-created-by-data-when-editing-job

* removing commented out code

* changes based on review
  • Loading branch information
jgowdyelastic authored Jul 4, 2018
1 parent a1e44ef commit 9d57acc
Show file tree
Hide file tree
Showing 14 changed files with 317 additions and 59 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ export function FullTimeRangeSelectorServiceProvider(Notifier) {

function setFullTimeRange(indexPattern, query) {
// load the earliest and latest time stamps for the index
ml.getTimeFieldRange({
return ml.getTimeFieldRange({
index: indexPattern.title,
timeFieldName: indexPattern.timeFieldName,
query
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -148,7 +148,7 @@ module.controller('MlJobsList',

$scope.cloneJob = function (job) {
mlJobService.currentJob = job;
$location.path('jobs/new_job/advanced');
$location.path('jobs/new_job');
};

$scope.closeJob = function (job) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
*/


import { difference } from 'lodash';
import { newJobLimits } from 'plugins/ml/jobs/new_job/utils/new_job_defaults';
import { mlJobService } from 'plugins/ml/services/job_service';

Expand All @@ -17,11 +18,17 @@ export function saveJob(job, newJobData, finish) {
...extractGroups(job, newJobData),
...extractMML(job, newJobData),
...extractDetectorDescriptions(job, newJobData),
...extractCustomSettings(job, newJobData),
};
const datafeedData = {
...extractDatafeed(job, newJobData),
};

if (jobData.custom_settings !== undefined) {
// remove the created_by setting if too much of the job has changed
jobData.custom_settings = processCustomSettings(jobData, datafeedData);
}

const saveDatafeedWrapper = () => {
saveDatafeed(datafeedData, job, finish)
.then(() => {
Expand Down Expand Up @@ -77,7 +84,8 @@ function extractDescription(job, newJobData) {
function extractGroups(job, newJobData) {
const groups = newJobData.groups;
if (newJobData.groups !== undefined) {
return { groups };
const diffCount = difference(job.groups, groups).length + difference(groups, job.groups).length;
return (diffCount === 0) ? {} : { groups };
}
return {};
}
Expand Down Expand Up @@ -118,6 +126,14 @@ function extractDetectorDescriptions(job, newJobData) {
return (detectors.length) ? { detectors } : {};
}

function extractCustomSettings(job, newJobData) {
const settingsData = {};
if (job.custom_settings && newJobData) {
settingsData.custom_settings = job.custom_settings;
}
return settingsData;
}

function extractDatafeed(job, newDatafeedData) {
const datafeedData = {};
if (job.datafeed_config !== undefined) {
Expand Down Expand Up @@ -145,3 +161,18 @@ function extractDatafeed(job, newDatafeedData) {

return datafeedData;
}

function processCustomSettings(jobData, datafeedData) {
let customSettings = {};
if (jobData.custom_settings !== undefined) {
customSettings = { ...jobData.custom_settings };

if (jobData.custom_settings.created_by !== undefined) {
if (jobData.detectors !== undefined || Object.keys(datafeedData).length) {
delete customSettings.created_by;
}
}
}

return customSettings;
}
Original file line number Diff line number Diff line change
Expand Up @@ -110,10 +110,10 @@ function showResults(resp, action) {
}

export function cloneJob(jobId) {
mlJobService.refreshJob(jobId)
.then(() => {
mlJobService.currentJob = mlJobService.getJob(jobId);
window.location.href = `#/jobs/new_job/advanced`;
loadFullJob(jobId)
.then((job) => {
mlJobService.currentJob = job;
window.location.href = `#/jobs/new_job`;
})
.catch(() => {
toastNotifications.addDanger(`Could not clone ${jobId}. Job could not be found`);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,3 +7,9 @@


export const EVENT_RATE_COUNT_FIELD = '__ml_event_rate_count__';

export const WIZARD_TYPE = {
SINGLE_METRIC: 'single-metric-wizard',
MULTI_METRIC: 'multi-metric-wizard',
POPULATION: 'population-wizard',
};
Original file line number Diff line number Diff line change
Expand Up @@ -29,10 +29,6 @@ export function populateAppStateSettings(appState, scope) {

function finish(success) {
if (success) {
if (scope.bucketSpanEstimatorExportedFunctions.guessBucketSpan &&
typeof scope.bucketSpanEstimatorExportedFunctions.guessBucketSpan === 'function') {
scope.bucketSpanEstimatorExportedFunctions.guessBucketSpan();
}
$('#job-id-input').focus();
}
}
Expand All @@ -42,7 +38,7 @@ export function populateAppStateSettings(appState, scope) {
// single metric page
function populateSingleMetricSettings(jobSettings, scope) {
return new Promise((resolve, reject) => {
jobSettings.fields.forEach(f => {
jobSettings.fields.forEach((f) => {

if (f.agg !== undefined) {
// find the aggregation object in the aggTypeOptions list which has the same name
Expand All @@ -66,7 +62,8 @@ function populateSingleMetricSettings(jobSettings, scope) {

if ((scope.formConfig.agg.type !== undefined && scope.formConfig.field !== undefined) ||
(scope.formConfig.agg.type.name === 'count' && scope.formConfig.field === undefined)) {
scope.loadVis();

populateCommonSettings(jobSettings, scope);
resolve();
} else {
reject();
Expand All @@ -77,19 +74,18 @@ function populateSingleMetricSettings(jobSettings, scope) {
// multi metric page
function populateMultiMetricSettings(jobSettings, scope) {
return new Promise((resolve, reject) => {
jobSettings.fields.forEach(f => {
jobSettings.fields.forEach((f) => {

if (f.fieldName !== undefined) {
const field = scope.ui.fields.find(o => (o.id === f.fieldName));
if (field !== undefined) {
scope.formConfig.fields[field.id] = field;
}
const fieldName = (f.fieldName !== undefined) ? f.fieldName : '__ml_event_rate_count__';
const field = scope.ui.fields.find(o => (o.id === fieldName));
if (field !== undefined) {
scope.formConfig.fields[field.id] = field;
}

if (f.agg !== undefined) {
const agg = scope.ui.aggTypeOptions.find(o => (o.name === f.agg));
if (agg !== undefined) {
scope.formConfig.fields[field.id].agg.type = agg;
}
if (f.agg !== undefined) {
const agg = scope.ui.aggTypeOptions.find(o => (o.name === f.agg));
if (agg !== undefined) {
scope.formConfig.fields[field.id].agg.type = agg;
}
}
});
Expand All @@ -103,7 +99,7 @@ function populateMultiMetricSettings(jobSettings, scope) {
}

if (scope.formConfig.fields !== undefined && (Object.keys(scope.formConfig.fields).length > 0)) {
scope.loadVis();
populateCommonSettings(jobSettings, scope);
resolve();
} else {
reject();
Expand All @@ -121,21 +117,19 @@ function populatePopulationSettings(jobSettings, scope) {
scope.overChange();
}

jobSettings.fields.forEach(f => {

if (f.fieldName !== undefined) {
const tempField = scope.ui.fields.find(o => (o.id === f.fieldName));
const field = { ...tempField };
if (field !== undefined) {
jobSettings.fields.forEach((f) => {
const fieldName = (f.fieldName !== undefined) ? f.fieldName : '__ml_event_rate_count__';
const tempField = scope.ui.fields.find(o => (o.id === fieldName));
const field = { ...tempField };
if (field !== undefined) {

if (f.agg !== undefined) {
const agg = scope.ui.aggTypeOptions.find(o => (o.name === f.agg));
if (agg !== undefined) {
field.agg = { type: agg };
}
if (f.agg !== undefined) {
const agg = scope.ui.aggTypeOptions.find(o => (o.name === f.agg));
if (agg !== undefined) {
field.agg = { type: agg };
}
scope.formConfig.fields.push(field);
}
scope.formConfig.fields.push(field);
}
});

Expand Down Expand Up @@ -171,11 +165,44 @@ function populatePopulationSettings(jobSettings, scope) {

}
if (scope.formConfig.fields !== undefined && (Object.keys(scope.formConfig.fields).length > 0)) {
populateCommonSettings(jobSettings, scope);
resolve();
} else {
reject();
}
});
}

function populateCommonSettings(jobSettings, scope) {
if (typeof jobSettings.modelMemoryLimit === 'string') {
scope.formConfig.modelMemoryLimit = jobSettings.modelMemoryLimit;
}

if (typeof jobSettings.bucketSpan === 'string') {
scope.formConfig.bucketSpan = jobSettings.bucketSpan;
} else {
runEstimateBucketSpan(scope);
}

if (typeof jobSettings.description === 'string') {
scope.formConfig.description = jobSettings.description;
}

if (Array.isArray(jobSettings.groups)) {
scope.formConfig.jobGroups = jobSettings.groups;
}

if (Array.isArray(jobSettings.influencers)) {
scope.formConfig.influencerFields = jobSettings.influencers.map((i) => scope.ui.fields.find(f => f.id === i));
}
}

function runEstimateBucketSpan(scope) {
setTimeout(() => {
if (scope.bucketSpanEstimatorExportedFunctions.guessBucketSpan &&
typeof scope.bucketSpanEstimatorExportedFunctions.guessBucketSpan === 'function') {
scope.bucketSpanEstimatorExportedFunctions.guessBucketSpan();
}
}, 0);
}

Original file line number Diff line number Diff line change
@@ -0,0 +1,134 @@
/*
* 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 { mlJobService } from 'plugins/ml/services/job_service';
import { populateAppStateSettings } from 'plugins/ml/jobs/new_job/simple/components/utils/app_state_settings';
import { WIZARD_TYPE } from 'plugins/ml/jobs/new_job/simple/components/constants/general';


export function preLoadJob($scope, appState) {
const job = mlJobService.currentJob;
mlJobService.currentJob = undefined;
if (job !== undefined) {
const mlJobSettings = jobSettingsFromJob(job, $scope.ui.aggTypeOptions);
populateAppStateSettings({ mlJobSettings }, $scope);
$scope.setFullTimeRange()
.then(() => $scope.loadVis())
.catch(() => $scope.loadVis());
} else {
// populate the fields with any settings from the URL
populateAppStateSettings(appState, $scope);
}
}


export function jobSettingsFromJob(job, aggTypeOptions) {
if (job.custom_settings === undefined) {
return {};
}

function getKibanaAggName(mlAggName) {
const agg = aggTypeOptions.find(a => a.mlName === mlAggName);
return (agg) ? agg.name : undefined;
}

const jobSettings = {};

const dtrs = job.analysis_config.detectors;

if (job.custom_settings.created_by === WIZARD_TYPE.SINGLE_METRIC) {
// single metric
const d = dtrs[0];
const field = { agg: getKibanaAggName(d.function, aggTypeOptions) };
if (d.field_name) {
field.fieldName = d.field_name;
}

jobSettings.fields = [field];
} else if (job.custom_settings.created_by === WIZARD_TYPE.MULTI_METRIC) {
// multi metric
let splitField = '';

jobSettings.fields = dtrs.map((d) => {
if (d.partition_field_name) {
splitField = d.partition_field_name;
}

const field = { agg: getKibanaAggName(d.function, aggTypeOptions) };
if (d.field_name) {
field.fieldName = d.field_name;
}
return field;
});

if (splitField !== '') {
jobSettings.split = splitField;
}

} else if (job.custom_settings.created_by === WIZARD_TYPE.POPULATION) {
let overField = '';
const splitFields = {};

jobSettings.fields = dtrs.map((d) => {
// population
if (d.over_field_name) {
overField = d.over_field_name;
}

const field = { agg: getKibanaAggName(d.function, aggTypeOptions) };
if (d.field_name) {
field.fieldName = d.field_name;
}

if (d.by_field_name) {
field.split = d.by_field_name;
}

return field;
});

if (overField !== '') {
jobSettings.population = overField;
}

const numberOfSplits = Object.keys(splitFields).length;

if (numberOfSplits > 0) {
if (numberOfSplits > 1) {
// multiple splits, population or advanced job
for (const f in splitFields) {
if (splitFields.hasOwnProperty(f)) {
const i = splitFields[f];
jobSettings.fields[i] = f;
}
}
} else {
jobSettings.split = Object.keys(splitFields)[0];
}
}
}


jobSettings.bucketSpan = job.analysis_config.bucket_span;
if (job.analysis_limits && job.analysis_limits.model_memory_limit) {
jobSettings.modelMemoryLimit = job.analysis_limits.model_memory_limit;
}

if (job.description !== '') {
jobSettings.description = job.description;
}

if (job.groups !== undefined && job.groups.length) {
jobSettings.groups = job.groups;
}

if (job.analysis_config && job.analysis_config.influencers) {
jobSettings.influencers = job.analysis_config.influencers;
}

return jobSettings;
}
Loading

0 comments on commit 9d57acc

Please sign in to comment.