Skip to content

Commit

Permalink
[ML] Add checkbox to enable model plot in Advanced job wizard (#25468) (
Browse files Browse the repository at this point in the history
#25831)

* Move cardinality success check to utils

* enableModelPlot checkbox base added

* Run cardinality check on add/update fields

* Handle changes made via json

* only run cardinality check if model plot enabled

* Handle model plot enabled via EditJSON tab

* show message on cardinality check error

* multi-metric + pop: show message on cardinality check error

* add test for callout component

* Fix flexitem overflow in IE11
  • Loading branch information
alvarezmelissa87 authored Nov 17, 2018
1 parent ff4a102 commit afc72eb
Show file tree
Hide file tree
Showing 12 changed files with 305 additions and 33 deletions.
4 changes: 4 additions & 0 deletions x-pack/plugins/ml/public/jobs/new_job/advanced/_advanced.scss
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
.ml-new-job {
display: block;
}
// Required to prevent overflow of flex item in IE11
.ml-new-job-callout {
width: 100%;
}

// SASSTODO: Proper calcs. This looks too brittle to touch quickly
.detector {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ module.directive('mlJobDetectorsList', function ($modal) {
fields: '=mlFields',
catFieldNameSelected: '=mlCatFieldNameSelected',
editMode: '=mlEditMode',
onUpdate: '=mlOnDetectorsUpdate'
},
template,
controller: function ($scope) {
Expand All @@ -42,11 +43,14 @@ module.directive('mlJobDetectorsList', function ($modal) {
} else {
$scope.detectors.push(dtr);
}

$scope.onUpdate();
}
};

$scope.removeDetector = function (index) {
$scope.detectors.splice(index, 1);
$scope.onUpdate();
};

$scope.editDetector = function (index) {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
/*
* 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 from 'react';
import { mount } from 'enzyme';
import { EnableModelPlotCallout } from './enable_model_plot_callout_view.js';

const message = 'Test message';

describe('EnableModelPlotCallout', () => {

test('Callout is rendered correctly with message', () => {
const wrapper = mount(<EnableModelPlotCallout message={message} />);
const calloutText = wrapper.find('EuiText');

expect(calloutText.text()).toBe(message);
});

});
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
/*
* 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 'ngreact';

import { uiModules } from 'ui/modules';
const module = uiModules.get('apps/ml', ['react']);

import { EnableModelPlotCallout } from './enable_model_plot_callout_view.js';

module.directive('mlEnableModelPlotCallout', function (reactDirective) {
return reactDirective(
EnableModelPlotCallout,
undefined,
{ restrict: 'E' }
);
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
/*
* 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 PropTypes from 'prop-types';
import React, { Fragment } from 'react';

import {
EuiCallOut,
EuiFlexGroup,
EuiFlexItem,
} from '@elastic/eui';


export const EnableModelPlotCallout = ({ message }) => (
<Fragment>
<EuiFlexGroup direction="column">
<EuiFlexItem grow={false}>
<EuiCallOut
title={'Proceed with caution!'}
color="warning"
iconType="help"
>
<p>
{message}
</p>
</EuiCallOut>
</EuiFlexItem>
</EuiFlexGroup>
</Fragment>
);

EnableModelPlotCallout.propTypes = {
message: PropTypes.string.isRequired,
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
/*
* 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 './enable_model_plot_callout_directive.js';
1 change: 1 addition & 0 deletions x-pack/plugins/ml/public/jobs/new_job/advanced/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,3 +12,4 @@ import './save_status_modal';
import './field_select_directive';
import 'plugins/ml/components/job_group_select';
import 'plugins/ml/jobs/components/job_timepicker_modal';
import './enable_model_plot_callout';
28 changes: 28 additions & 0 deletions x-pack/plugins/ml/public/jobs/new_job/advanced/new_job.html
Original file line number Diff line number Diff line change
Expand Up @@ -238,6 +238,7 @@ <h3 class="euiTitle euiTitle--large">{{ui.pageTitle}}</h3>
ml-fields="fields"
ml-cat-field-name-selected="(job.analysis_config.categorization_field_name?true:false)"
ml-edit-mode="'NEW'"
ml-on-detectors-update="onDetectorsUpdate"
></div>
<div ng-hide="ui.validation.tabs[1].checks.detectors.valid" class="validation-error">
{{ ( ui.validation.tabs[1].checks.detectors.message || "At least one detector should be configured" ) }}
Expand Down Expand Up @@ -275,6 +276,33 @@ <h3 class="euiTitle euiTitle--large">{{ui.pageTitle}}</h3>
<div ng-hide="ui.validation.tabs[1].checks.influencers.valid" class="validation-error">
{{ ( ui.validation.tabs[1].checks.influencers.message || "At least one influencer should be selected" ) }}
</div>

<hr class="euiHorizontalRule euiHorizontalRule--full euiHorizontalRule--marginMedium">

<div class="form-group">
<label class='kuiCheckBoxLabel kuiVerticalRhythm'>
<input
type="checkbox"
aria-labelledby="ml_aria_label_new_job_enable_model_plot"
aria-describedby="ml_aria_description_new_job_enable_model_plot"
class='kuiCheckBox'
ng-change="setModelPlotEnabled()"
ng-model="ui.enableModelPlot" />
<span class='kuiCheckBoxLabel__text'>
<span id="ml_aria_label_new_job_enable_model_plot">
{{ ui.cardinalityValidator.status === ui.cardinalityValidator.STATUS.RUNNING ? 'Validating cardinality...' : 'Enable model plot' }}
</span>
<i ml-info-icon="new_job_enable_model_plot" />
</span>
</label>
<div class='ml-new-job-callout kuiVerticalRhythm'>
<ml-enable-model-plot-callout
message='ui.cardinalityValidator.message'
ng-show="ui.cardinalityValidator.status === ui.cardinalityValidator.STATUS.WARNING ||
ui.cardinalityValidator.status === ui.cardinalityValidator.STATUS.FAILED">
</ml-enable-model-plot-callout>
</div>
</div>
</div>
</ml-job-tab-1>

Expand Down
126 changes: 124 additions & 2 deletions x-pack/plugins/ml/public/jobs/new_job/advanced/new_job_controller.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,12 @@ import { checkFullLicense } from 'plugins/ml/license/check_license';
import { checkCreateJobsPrivilege } from 'plugins/ml/privilege/check_privilege';
import template from './new_job.html';
import saveStatusTemplate from 'plugins/ml/jobs/new_job/advanced/save_status_modal/save_status_modal.html';
import { createSearchItems, createJobForSaving } from 'plugins/ml/jobs/new_job/utils/new_job_utils';
import {
createSearchItems,
createJobForSaving,
checkCardinalitySuccess,
getMinimalValidJob,
} from 'plugins/ml/jobs/new_job/utils/new_job_utils';
import { loadIndexPatterns, loadCurrentIndexPattern, loadCurrentSavedSearch, timeBasedIndexCheck } from 'plugins/ml/util/index_utils';
import { ML_JOB_FIELD_TYPES, ES_FIELD_TYPES } from 'plugins/ml/../common/constants/field_types';
import { ALLOWED_DATA_UNITS } from 'plugins/ml/../common/constants/validation';
Expand Down Expand Up @@ -114,6 +119,8 @@ module.controller('MlNewJob',
const mlConfirm = mlConfirmModalService;
msgs.clear();
const jobDefaults = newJobDefaults();
// For keeping a copy of the detectors for comparison
const currentConfigs = { detectors: [], model_plot_config: { enabled: false } };

$scope.job = {};
$scope.mode = MODE.NEW;
Expand Down Expand Up @@ -156,6 +163,15 @@ module.controller('MlNewJob',
$scope.ui.validation.tabs[tab].valid = valid;
}
},
cardinalityValidator: {
status: 0, message: '', STATUS: {
FAILED: -1,
NOT_RUNNING: 0,
RUNNING: 1,
FINISHED: 2,
WARNING: 3,
}
},
jsonText: '',
changeTab: changeTab,
influencers: [],
Expand All @@ -181,6 +197,7 @@ module.controller('MlNewJob',
types: {},
isDatafeed: true,
useDedicatedIndex: false,
enableModelPlot: false,
modelMemoryLimit: '',
modelMemoryLimitDefault: jobDefaults.anomaly_detectors.model_memory_limit,

Expand Down Expand Up @@ -282,9 +299,37 @@ module.controller('MlNewJob',
});
}

function checkForConfigUpdates() {
const { STATUS } = $scope.ui.cardinalityValidator;
// Check if enable model plot was set/has changed and update if it has.
const jobModelPlotValue = $scope.job.model_plot_config ? $scope.job.model_plot_config : { enabled: false };
const modelPlotSettingsEqual = _.isEqual(currentConfigs.model_plot_config, jobModelPlotValue);

if (!modelPlotSettingsEqual) {
// Update currentConfigs.
currentConfigs.model_plot_config.enabled = jobModelPlotValue.enabled;
// Update ui portion so checkbox is checked
$scope.ui.enableModelPlot = jobModelPlotValue.enabled;
}

if ($scope.ui.enableModelPlot === true) {
const unchanged = _.isEqual(currentConfigs.detectors, $scope.job.analysis_config.detectors);
// if detectors changed OR model plot was just toggled on run cardinality
if (!unchanged || !modelPlotSettingsEqual) {
runValidateCardinality();
}
} else {
$scope.ui.cardinalityValidator.status = STATUS.FINISHED;
$scope.ui.cardinalityValidator.message = '';
}
}

function changeTab(tab) {
$scope.ui.currentTab = tab.index;
if (tab.index === 4) {
// Selecting Analysis Configuration tab
if (tab.index === 1) {
checkForConfigUpdates();
} else if (tab.index === 4) {
createJSONText();
} else if (tab.index === 5) {
if ($scope.ui.dataLocation === 'ES') {
Expand Down Expand Up @@ -651,6 +696,83 @@ module.controller('MlNewJob',
}
};

function runValidateCardinality() {
const { STATUS } = $scope.ui.cardinalityValidator;
$scope.ui.cardinalityValidator.status = $scope.ui.cardinalityValidator.STATUS.RUNNING;

const tempJob = mlJobService.cloneJob($scope.job);
_.merge(tempJob, getMinimalValidJob());

ml.validateCardinality(tempJob)
.then((response) => {
const validationResult = checkCardinalitySuccess(response);

if (validationResult.success === true) {
$scope.ui.cardinalityValidator.status = STATUS.FINISHED;
$scope.ui.cardinalityValidator.message = '';
} else {
$scope.ui.cardinalityValidator.message = `Creating model plots is resource intensive and not recommended
where the cardinality of the selected fields is greater than 100. Estimated cardinality
for this job is ${validationResult.highCardinality}.
If you enable model plot with this configuration
we recommend you select a dedicated results index on the Job Details tab.`;

$scope.ui.cardinalityValidator.status = STATUS.WARNING;
}
})
.catch((error) => {
console.log('Cardinality check error:', error);
$scope.ui.cardinalityValidator.message = `An error occurred validating the configuration
for running the job with model plot enabled.
Creating model plots can be resource intensive and not recommended where the cardinality of the selected fields is high.
You may want to select a dedicated results index on the Job Details tab.`;

$scope.ui.cardinalityValidator.status = STATUS.FAILED;
});
}

$scope.onDetectorsUpdate = function () {
const { STATUS } = $scope.ui.cardinalityValidator;

if ($scope.ui.enableModelPlot === true) {
// Update currentConfigs since config changed
currentConfigs.detectors = _.cloneDeep($scope.job.analysis_config.detectors);

if ($scope.job.analysis_config.detectors.length === 0) {
$scope.ui.cardinalityValidator.status = STATUS.FINISHED;
$scope.ui.cardinalityValidator.message = '';
} else {
runValidateCardinality();
}
}
};

$scope.setModelPlotEnabled = function () {
const { STATUS } = $scope.ui.cardinalityValidator;

if ($scope.ui.enableModelPlot === true) {
// Start keeping track of the config in case of changes from Edit JSON tab requiring another cardinality check
currentConfigs.detectors = _.cloneDeep($scope.job.analysis_config.detectors);

$scope.job.model_plot_config = {
enabled: true
};

currentConfigs.model_plot_config.enabled = true;
// return early if there's nothing to run a check on yet.
if ($scope.job.analysis_config.detectors.length === 0) {
return;
}

runValidateCardinality();
} else {
currentConfigs.model_plot_config.enabled = false;
$scope.ui.cardinalityValidator.status = STATUS.FINISHED;
$scope.ui.cardinalityValidator.message = '';
delete $scope.job.model_plot_config;
}
};

// function called by field-select components to set
// properties in the analysis_config
$scope.setAnalysisConfigProperty = function (value, field) {
Expand Down
Loading

0 comments on commit afc72eb

Please sign in to comment.