diff --git a/x-pack/plugins/ml/public/components/json_tooltip/tooltips.json b/x-pack/plugins/ml/public/components/json_tooltip/tooltips.json index 84a9f2cc27912d..b6cbc55699822c 100644 --- a/x-pack/plugins/ml/public/components/json_tooltip/tooltips.json +++ b/x-pack/plugins/ml/public/components/json_tooltip/tooltips.json @@ -107,6 +107,9 @@ "new_job_dedicated_index": { "text": "Select to store results in a separate index for this job." }, + "new_job_enable_model_plot": { + "text": "Select to enable model plot. Stores model information along with results. Can add considerable overhead to the performance of the system." + }, "new_job_model_memory_limit": { "text": "An approximate limit for the amount of memory used by the analytical models." }, diff --git a/x-pack/plugins/ml/public/jobs/new_job/simple/components/enable_model_plot_checkbox/enable_model_plot_checkbox.test.js b/x-pack/plugins/ml/public/jobs/new_job/simple/components/enable_model_plot_checkbox/enable_model_plot_checkbox.test.js new file mode 100644 index 00000000000000..5e73541a99bece --- /dev/null +++ b/x-pack/plugins/ml/public/jobs/new_job/simple/components/enable_model_plot_checkbox/enable_model_plot_checkbox.test.js @@ -0,0 +1,40 @@ +/* + * 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 { EnableModelPlotCheckbox } from './enable_model_plot_checkbox_view.js'; + +const defaultProps = { + checkboxText: 'Enable model plot', + onCheckboxChange: () => {}, + warningStatus: false, + warningContent: 'Test warning content', +}; + +describe('EnableModelPlotCheckbox', () => { + + test('checkbox default is rendered correctly', () => { + const wrapper = mount(); + const checkbox = wrapper.find({ type: 'checkbox' }); + const label = wrapper.find('label'); + + expect(checkbox.props().checked).toBe(false); + expect(label.text()).toBe('Enable model plot'); + }); + + test('onCheckboxChange function prop is called when checkbox is toggled', () => { + const mockOnChange = jest.fn(); + defaultProps.onCheckboxChange = mockOnChange; + + const wrapper = mount(); + const checkbox = wrapper.find({ type: 'checkbox' }); + + checkbox.simulate('change', { target: { checked: true } }); + expect(mockOnChange).toBeCalled(); + }); + +}); diff --git a/x-pack/plugins/ml/public/jobs/new_job/simple/components/enable_model_plot_checkbox/enable_model_plot_checkbox_directive.js b/x-pack/plugins/ml/public/jobs/new_job/simple/components/enable_model_plot_checkbox/enable_model_plot_checkbox_directive.js new file mode 100644 index 00000000000000..e153c695994bde --- /dev/null +++ b/x-pack/plugins/ml/public/jobs/new_job/simple/components/enable_model_plot_checkbox/enable_model_plot_checkbox_directive.js @@ -0,0 +1,154 @@ +/* + * 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 ReactDOM from 'react-dom'; + +import { EnableModelPlotCheckbox } from './enable_model_plot_checkbox_view.js'; +import { ml } from '../../../../../services/ml_api_service'; + +import { uiModules } from 'ui/modules'; +const module = uiModules.get('apps/ml'); + +module.directive('mlEnableModelPlotCheckbox', function () { + return { + restrict: 'AE', + replace: false, + scope: { + formConfig: '=', + ui: '=ui', + getJobFromConfig: '=' + }, + link: function ($scope, $element) { + const STATUS = { + FAILED: -1, + NOT_RUNNING: 0, + RUNNING: 1, + FINISHED: 2, + WARNING: 3, + }; + + function errorHandler(error) { + console.log('Cardinality could not be validated', error); + $scope.ui.cardinalityValidator.status = STATUS.FAILED; + $scope.ui.cardinalityValidator.message = 'Cardinality could not be validated'; + } + + // Only model plot cardinality relevant + // format:[{id:"cardinality_model_plot_high",modelPlotCardinality:11405}, {id:"cardinality_partition_field",fieldName:"clientip"}] + function checkCardinalitySuccess(data) { + const response = { + success: true, + }; + // There were no fields to run cardinality on. + if (Array.isArray(data) && data.length === 0) { + return response; + } + + for (let i = 0; i < data.length; i++) { + if (data[i].id === 'success_cardinality') { + break; + } + + if (data[i].id === 'cardinality_model_plot_high') { + response.success = false; + response.highCardinality = data[i].modelPlotCardinality; + break; + } + } + + return response; + } + + function validateCardinality() { + $scope.ui.cardinalityValidator.status = STATUS.RUNNING; + $scope.ui.cardinalityValidator.message = ''; + + // create temporary job since cardinality validation expects that format + const tempJob = $scope.getJobFromConfig($scope.formConfig); + + ml.validateCardinality(tempJob) + .then((response) => { + const validationResult = checkCardinalitySuccess(response); + + if (validationResult.success === true) { + $scope.formConfig.enableModelPlot = true; + $scope.ui.cardinalityValidator.status = STATUS.FINISHED; + } 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 use a dedicated results index.`; + + $scope.ui.cardinalityValidator.status = STATUS.WARNING; + // Go ahead and check the dedicated index box for them + $scope.formConfig.useDedicatedIndex = true; + // show the advanced section so the warning message is visible since validation failed + $scope.ui.showAdvanced = true; + } + }) + .catch(errorHandler); + } + + // Re-validate cardinality for updated fields/splitField + // when enable model plot is checked and form valid + function revalidateCardinalityOnFieldChange() { + if ($scope.formConfig.enableModelPlot === true && $scope.ui.formValid === true) { + validateCardinality(); + } + } + + $scope.handleCheckboxChange = (isChecked) => { + if (isChecked) { + $scope.formConfig.enableModelPlot = true; + validateCardinality(); + } else { + $scope.formConfig.enableModelPlot = false; + $scope.ui.cardinalityValidator.status = STATUS.FINISHED; + $scope.ui.cardinalityValidator.message = ''; + updateCheckbox(); + } + }; + + // Update checkbox on these changes + $scope.$watch('ui.formValid', updateCheckbox, true); + $scope.$watch('ui.cardinalityValidator.status', updateCheckbox, true); + // MultiMetric: Fire off cardinality validatation when fields and/or split by field is updated + $scope.$watch('formConfig.fields', revalidateCardinalityOnFieldChange, true); + $scope.$watch('formConfig.splitField', revalidateCardinalityOnFieldChange, true); + // Population: Fire off cardinality validatation when overField is updated + $scope.$watch('formConfig.overField', revalidateCardinalityOnFieldChange, true); + + function updateCheckbox() { + // disable if (check is running && checkbox checked) or (form is invalid && checkbox unchecked) + const checkboxDisabled = ( + ($scope.ui.cardinalityValidator.status === STATUS.RUNNING && + $scope.formConfig.enableModelPlot === true) || + ($scope.ui.formValid !== true && + $scope.formConfig.enableModelPlot === false) + ); + const validatorRunning = ($scope.ui.cardinalityValidator.status === STATUS.RUNNING); + const warningStatus = ($scope.ui.cardinalityValidator.status === STATUS.WARNING && $scope.ui.formValid === true); + const checkboxText = (validatorRunning) ? 'Validating cardinality...' : 'Enable model plot'; + + const props = { + checkboxDisabled, + checkboxText, + onCheckboxChange: $scope.handleCheckboxChange, + warningContent: $scope.ui.cardinalityValidator.message, + warningStatus, + }; + + ReactDOM.render( + React.createElement(EnableModelPlotCheckbox, props), + $element[0] + ); + } + + updateCheckbox(); + } + }; +}); diff --git a/x-pack/plugins/ml/public/jobs/new_job/simple/components/enable_model_plot_checkbox/enable_model_plot_checkbox_view.js b/x-pack/plugins/ml/public/jobs/new_job/simple/components/enable_model_plot_checkbox/enable_model_plot_checkbox_view.js new file mode 100644 index 00000000000000..a01e63dc7625f6 --- /dev/null +++ b/x-pack/plugins/ml/public/jobs/new_job/simple/components/enable_model_plot_checkbox/enable_model_plot_checkbox_view.js @@ -0,0 +1,92 @@ +/* + * 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, Component } from 'react'; + +import { + EuiCallOut, + EuiCheckbox, + EuiFlexGroup, + EuiFlexItem, +} from '@elastic/eui'; + +import { JsonTooltip } from '../../../../../components/json_tooltip/json_tooltip'; + + +export class EnableModelPlotCheckbox extends Component { + constructor(props) { + super(props); + + this.state = { + checked: false, + }; + } + + warningTitle = 'Proceed with caution!'; + + onChange = (e) => { + this.setState({ + checked: e.target.checked, + }); + this.props.onCheckboxChange(e.target.checked); + }; + + renderWarningCallout = () => ( + + + + +

+ {this.props.warningContent} +

+
+
+
+
+ ); + + render() { + return ( + + + + + + + + + + + { this.props.warningStatus && this.renderWarningCallout() } + + ); + } +} + +EnableModelPlotCheckbox.propTypes = { + checkboxDisabled: PropTypes.bool, + checkboxText: PropTypes.string.isRequired, + onCheckboxChange: PropTypes.func.isRequired, + warningStatus: PropTypes.bool.isRequired, + warningContent: PropTypes.string.isRequired, +}; + +EnableModelPlotCheckbox.defaultProps = { + checkboxDisabled: false, +}; diff --git a/x-pack/plugins/ml/public/jobs/new_job/simple/components/enable_model_plot_checkbox/index.js b/x-pack/plugins/ml/public/jobs/new_job/simple/components/enable_model_plot_checkbox/index.js new file mode 100644 index 00000000000000..574f51888697e0 --- /dev/null +++ b/x-pack/plugins/ml/public/jobs/new_job/simple/components/enable_model_plot_checkbox/index.js @@ -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_checkbox_directive.js'; diff --git a/x-pack/plugins/ml/public/jobs/new_job/simple/components/general_job_details/general_job_details.html b/x-pack/plugins/ml/public/jobs/new_job/simple/components/general_job_details/general_job_details.html index 1a055c2063b0bc..3dc1521dbd7bf3 100644 --- a/x-pack/plugins/ml/public/jobs/new_job/simple/components/general_job_details/general_job_details.html +++ b/x-pack/plugins/ml/public/jobs/new_job/simple/components/general_job_details/general_job_details.html @@ -47,6 +47,13 @@
+
+ + +