"use strict"; var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault"); Object.defineProperty(exports, "__esModule", { value: true }); exports.AnnotationFlyoutUI = exports.AnnotationFlyout = void 0; var _extends2 = _interopRequireDefault(require("@babel/runtime/helpers/extends")); var _defineProperty2 = _interopRequireDefault(require("@babel/runtime/helpers/defineProperty")); var _react = _interopRequireWildcard(require("react")); var _useObservable = _interopRequireDefault(require("react-use/lib/useObservable")); var _lodash = require("lodash"); var _eui = require("@elastic/eui"); var _i18n = require("@kbn/i18n"); var _i18nReact = require("@kbn/i18n-react"); var _mlAnomalyUtils = require("@kbn/ml-anomaly-utils"); var _annotations = require("../../../../../common/constants/annotations"); var _annotations_service = require("../../../services/annotations_service"); var _annotation_description_list = require("../annotation_description_list"); var _delete_annotation_modal = require("../delete_annotation_modal"); var _ml_api_service = require("../../../services/ml_api_service"); var _dependency_cache = require("../../../util/dependency_cache"); var _annotations2 = require("../../../../../common/types/annotations"); var _ml_annotation_updates_context = require("../../../contexts/ml/ml_annotation_updates_context"); function _getRequireWildcardCache(nodeInterop) { if (typeof WeakMap !== "function") return null; var cacheBabelInterop = new WeakMap(); var cacheNodeInterop = new WeakMap(); return (_getRequireWildcardCache = function (nodeInterop) { return nodeInterop ? cacheNodeInterop : cacheBabelInterop; })(nodeInterop); } function _interopRequireWildcard(obj, nodeInterop) { if (!nodeInterop && obj && obj.__esModule) { return obj; } if (obj === null || typeof obj !== "object" && typeof obj !== "function") { return { default: obj }; } var cache = _getRequireWildcardCache(nodeInterop); if (cache && cache.has(obj)) { return cache.get(obj); } var newObj = {}; var hasPropertyDescriptor = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var key in obj) { if (key !== "default" && Object.prototype.hasOwnProperty.call(obj, key)) { var desc = hasPropertyDescriptor ? Object.getOwnPropertyDescriptor(obj, key) : null; if (desc && (desc.get || desc.set)) { Object.defineProperty(newObj, key, desc); } else { newObj[key] = obj[key]; } } } newObj.default = obj; if (cache) { cache.set(obj, newObj); } return newObj; } /* * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one * or more contributor license agreements. Licensed under the Elastic License * 2.0; you may not use this file except in compliance with the Elastic License * 2.0. */ class AnnotationFlyoutUI extends _react.Component { constructor(...args) { super(...args); (0, _defineProperty2.default)(this, "deletionInProgress", false); (0, _defineProperty2.default)(this, "state", { isDeleteModalVisible: false, applyAnnotationToSeries: true, annotationState: null }); (0, _defineProperty2.default)(this, "annotationSub", null); (0, _defineProperty2.default)(this, "annotationTextChangeHandler", e => { if (this.state.annotationState === null) { return; } const { annotationUpdatesService } = this.props; annotationUpdatesService.setValue({ ...this.state.annotationState, annotation: e.target.value }); }); (0, _defineProperty2.default)(this, "cancelEditingHandler", () => { const { annotationUpdatesService } = this.props; annotationUpdatesService.setValue(null); }); (0, _defineProperty2.default)(this, "deleteConfirmHandler", () => { this.setState({ isDeleteModalVisible: true }); }); (0, _defineProperty2.default)(this, "deleteHandler", async () => { if (this.deletionInProgress) return; const { annotationState } = this.state; const toastNotifications = (0, _dependency_cache.getToastNotifications)(); if (annotationState === null || annotationState._id === undefined) { return; } this.deletionInProgress = true; try { await _ml_api_service.ml.annotations.deleteAnnotation(annotationState._id); toastNotifications.addSuccess(_i18n.i18n.translate('xpack.ml.timeSeriesExplorer.timeSeriesChart.deletedAnnotationNotificationMessage', { defaultMessage: 'Deleted annotation for job with ID {jobId}.', values: { jobId: annotationState.job_id } })); } catch (err) { toastNotifications.addDanger(_i18n.i18n.translate('xpack.ml.timeSeriesExplorer.timeSeriesChart.errorWithDeletingAnnotationNotificationErrorMessage', { defaultMessage: 'An error occurred deleting the annotation for job with ID {jobId}: {error}', values: { jobId: annotationState.job_id, error: JSON.stringify(err) } })); } this.closeDeleteModal(); this.deletionInProgress = false; const { annotationUpdatesService } = this.props; annotationUpdatesService.setValue(null); (0, _annotations_service.annotationsRefreshed)(); }); (0, _defineProperty2.default)(this, "closeDeleteModal", () => { this.setState({ isDeleteModalVisible: false }); }); (0, _defineProperty2.default)(this, "validateAnnotationText", () => { // Validates the entered text, returning an array of error messages // for display in the form. An empty array is returned if the text is valid. const { annotationState } = this.state; const errors = []; if (annotationState === null) { return errors; } if (annotationState.annotation.trim().length === 0) { errors.push(_i18n.i18n.translate('xpack.ml.timeSeriesExplorer.annotationFlyout.noAnnotationTextError', { defaultMessage: 'Enter annotation text' })); } const textLength = annotationState.annotation.length; if (textLength > _annotations.ANNOTATION_MAX_LENGTH_CHARS) { const charsOver = textLength - _annotations.ANNOTATION_MAX_LENGTH_CHARS; errors.push(_i18n.i18n.translate('xpack.ml.timeSeriesExplorer.annotationFlyout.maxLengthError', { defaultMessage: '{charsOver, number} {charsOver, plural, one {character} other {characters}} above maximum length of {maxChars}', values: { maxChars: _annotations.ANNOTATION_MAX_LENGTH_CHARS, charsOver } })); } return errors; }); (0, _defineProperty2.default)(this, "saveOrUpdateAnnotation", () => { var _chartDetails$entityD, _annotation$event; const { annotationState: originalAnnotation } = this.state; const { chartDetails, detectorIndex, annotationUpdatesService } = this.props; if (originalAnnotation === null) { return; } const annotation = (0, _lodash.cloneDeep)(originalAnnotation); if (this.state.applyAnnotationToSeries && chartDetails !== null && chartDetails !== void 0 && (_chartDetails$entityD = chartDetails.entityData) !== null && _chartDetails$entityD !== void 0 && _chartDetails$entityD.entities) { chartDetails.entityData.entities.forEach(entity => { const { fieldName, fieldValue } = entity; const fieldType = entity.fieldType; annotation[(0, _annotations2.getAnnotationFieldName)(fieldType)] = fieldName; annotation[(0, _annotations2.getAnnotationFieldValue)(fieldType)] = fieldValue; }); annotation.detector_index = detectorIndex; } // if unchecked, remove all the partitions before indexing if (!this.state.applyAnnotationToSeries) { delete annotation.detector_index; _mlAnomalyUtils.ML_PARTITION_FIELDS.forEach(fieldType => { delete annotation[(0, _annotations2.getAnnotationFieldName)(fieldType)]; delete annotation[(0, _annotations2.getAnnotationFieldValue)(fieldType)]; }); } // Mark the annotation created by `user` if and only if annotation is being created, not updated annotation.event = (_annotation$event = annotation.event) !== null && _annotation$event !== void 0 ? _annotation$event : _annotations.ANNOTATION_EVENT_USER; annotationUpdatesService.setValue(null); _ml_api_service.ml.annotations.indexAnnotation(annotation).then(() => { (0, _annotations_service.annotationsRefreshed)(); const toastNotifications = (0, _dependency_cache.getToastNotifications)(); if (typeof annotation._id === 'undefined') { toastNotifications.addSuccess(_i18n.i18n.translate('xpack.ml.timeSeriesExplorer.timeSeriesChart.addedAnnotationNotificationMessage', { defaultMessage: 'Added an annotation for job with ID {jobId}.', values: { jobId: annotation.job_id } })); } else { toastNotifications.addSuccess(_i18n.i18n.translate('xpack.ml.timeSeriesExplorer.timeSeriesChart.updatedAnnotationNotificationMessage', { defaultMessage: 'Updated annotation for job with ID {jobId}.', values: { jobId: annotation.job_id } })); } }).catch(resp => { const toastNotifications = (0, _dependency_cache.getToastNotifications)(); if (typeof annotation._id === 'undefined') { toastNotifications.addDanger(_i18n.i18n.translate('xpack.ml.timeSeriesExplorer.timeSeriesChart.errorWithCreatingAnnotationNotificationErrorMessage', { defaultMessage: 'An error occurred creating the annotation for job with ID {jobId}: {error}', values: { jobId: annotation.job_id, error: JSON.stringify(resp) } })); } else { toastNotifications.addDanger(_i18n.i18n.translate('xpack.ml.timeSeriesExplorer.timeSeriesChart.errorWithUpdatingAnnotationNotificationErrorMessage', { defaultMessage: 'An error occurred updating the annotation for job with ID {jobId}: {error}', values: { jobId: annotation.job_id, error: JSON.stringify(resp) } })); } }); }); } componentDidMount() { const { annotationUpdatesService } = this.props; this.annotationSub = annotationUpdatesService.update$().subscribe(v => { this.setState({ annotationState: v }); }); } componentWillUnmount() { this.annotationSub.unsubscribe(); } render() { const { detectors, detectorIndex } = this.props; const { annotationState, isDeleteModalVisible } = this.state; if (!annotationState) return null; const isExistingAnnotation = typeof annotationState._id !== 'undefined'; // Check the length of the text is within the max length limit, // and warn if the length is approaching the limit. const validationErrors = this.validateAnnotationText(); const isInvalid = validationErrors.length > 0; const lengthRatioToShowWarning = 0.95; let helpText = null; if (isInvalid === false && annotationState.annotation.length > _annotations.ANNOTATION_MAX_LENGTH_CHARS * lengthRatioToShowWarning) { helpText = _i18n.i18n.translate('xpack.ml.timeSeriesExplorer.annotationFlyout.approachingMaxLengthWarning', { defaultMessage: '{charsRemaining, number} {charsRemaining, plural, one {character} other {characters}} remaining', values: { charsRemaining: _annotations.ANNOTATION_MAX_LENGTH_CHARS - annotationState.annotation.length } }); } const detector = detectors ? detectors.find(d => d.index === detectorIndex) : undefined; const detectorDescription = detector && 'detector_description' in detector ? detector.detector_description : ''; return /*#__PURE__*/_react.default.createElement(_react.default.Fragment, null, /*#__PURE__*/_react.default.createElement(_eui.EuiFlyoutBody, null, /*#__PURE__*/_react.default.createElement(_annotation_description_list.AnnotationDescriptionList, { annotation: annotationState, detectorDescription: detectorDescription }), /*#__PURE__*/_react.default.createElement(_eui.EuiSpacer, { size: "m" }), /*#__PURE__*/_react.default.createElement(_eui.EuiFormRow, { label: /*#__PURE__*/_react.default.createElement(_i18nReact.FormattedMessage, { id: "xpack.ml.timeSeriesExplorer.annotationFlyout.annotationTextLabel", defaultMessage: "Annotation text" }), fullWidth: true, helpText: helpText, isInvalid: isInvalid, error: validationErrors }, /*#__PURE__*/_react.default.createElement(_eui.EuiTextArea, { fullWidth: true, isInvalid: isInvalid, onChange: this.annotationTextChangeHandler, placeholder: "...", value: annotationState.annotation, "data-test-subj": 'mlAnnotationsFlyoutTextInput' })), /*#__PURE__*/_react.default.createElement(_eui.EuiFormRow, null, /*#__PURE__*/_react.default.createElement(_eui.EuiCheckbox, { id: 'xpack.ml.annotationFlyout.applyToPartition', label: /*#__PURE__*/_react.default.createElement(_i18nReact.FormattedMessage, { id: "xpack.ml.annotationFlyout.applyToPartitionTextLabel", defaultMessage: "Apply annotation to this series" }), checked: this.state.applyAnnotationToSeries, onChange: () => this.setState({ applyAnnotationToSeries: !this.state.applyAnnotationToSeries }), "data-test-subj": 'mlAnnotationsFlyoutApplyToSeriesButton' }))), /*#__PURE__*/_react.default.createElement(_eui.EuiFlyoutFooter, null, /*#__PURE__*/_react.default.createElement(_eui.EuiFlexGroup, null, /*#__PURE__*/_react.default.createElement(_eui.EuiFlexItem, { grow: false }, /*#__PURE__*/_react.default.createElement(_eui.EuiButtonEmpty, { onClick: this.cancelEditingHandler, flush: "left", "data-test-subj": 'mlAnnotationsFlyoutCancelButton' }, /*#__PURE__*/_react.default.createElement(_i18nReact.FormattedMessage, { id: "xpack.ml.timeSeriesExplorer.annotationFlyout.cancelButtonLabel", defaultMessage: "Cancel" }))), /*#__PURE__*/_react.default.createElement(_eui.EuiFlexItem, { grow: false, style: { marginLeft: 'auto' } }, isExistingAnnotation && /*#__PURE__*/_react.default.createElement(_eui.EuiButtonEmpty, { color: "danger", onClick: this.deleteConfirmHandler, "data-test-subj": 'mlAnnotationsFlyoutDeleteButton' }, /*#__PURE__*/_react.default.createElement(_i18nReact.FormattedMessage, { id: "xpack.ml.timeSeriesExplorer.annotationFlyout.deleteButtonLabel", defaultMessage: "Delete" }))), /*#__PURE__*/_react.default.createElement(_eui.EuiFlexItem, { grow: false }, /*#__PURE__*/_react.default.createElement(_eui.EuiButton, { fill: true, isDisabled: isInvalid === true, onClick: this.saveOrUpdateAnnotation, "data-test-subj": 'annotationFlyoutUpdateOrCreateButton' }, isExistingAnnotation ? /*#__PURE__*/_react.default.createElement(_i18nReact.FormattedMessage, { id: "xpack.ml.timeSeriesExplorer.annotationFlyout.updateButtonLabel", defaultMessage: "Update" }) : /*#__PURE__*/_react.default.createElement(_i18nReact.FormattedMessage, { id: "xpack.ml.timeSeriesExplorer.annotationFlyout.createButtonLabel", defaultMessage: "Create" }))))), /*#__PURE__*/_react.default.createElement(_delete_annotation_modal.DeleteAnnotationModal, { cancelAction: this.closeDeleteModal, deleteAction: this.deleteHandler, isVisible: isDeleteModalVisible })); } } exports.AnnotationFlyoutUI = AnnotationFlyoutUI; const AnnotationFlyout = props => { const annotationUpdatesService = (0, _react.useContext)(_ml_annotation_updates_context.MlAnnotationUpdatesContext); const annotationProp = (0, _useObservable.default)(annotationUpdatesService.isAnnotationInitialized$()); const cancelEditingHandler = (0, _react.useCallback)(() => { annotationUpdatesService.setValue(null); // eslint-disable-next-line react-hooks/exhaustive-deps }, []); if (annotationProp === undefined || annotationProp === null) { return null; } const isExistingAnnotation = typeof annotationProp._id !== 'undefined'; return /*#__PURE__*/_react.default.createElement(_eui.EuiFlyout, { onClose: cancelEditingHandler, size: "m", "aria-labelledby": "Add annotation", "data-test-subj": 'mlAnnotationFlyout', className: 'mlAnnotationFlyout' }, /*#__PURE__*/_react.default.createElement(_eui.EuiFlyoutHeader, { hasBorder: true }, /*#__PURE__*/_react.default.createElement(_eui.EuiTitle, { size: "s", "data-test-subj": 'mlAnnotationFlyoutTitle' }, /*#__PURE__*/_react.default.createElement("h2", { id: "mlAnnotationFlyoutTitle" }, isExistingAnnotation ? /*#__PURE__*/_react.default.createElement(_i18nReact.FormattedMessage, { id: "xpack.ml.timeSeriesExplorer.annotationFlyout.editAnnotationTitle", defaultMessage: "Edit annotation" }) : /*#__PURE__*/_react.default.createElement(_i18nReact.FormattedMessage, { id: "xpack.ml.timeSeriesExplorer.annotationFlyout.addAnnotationTitle", defaultMessage: "Add annotation" })))), /*#__PURE__*/_react.default.createElement(AnnotationFlyoutUI, (0, _extends2.default)({}, props, { annotationUpdatesService: annotationUpdatesService }))); }; exports.AnnotationFlyout = AnnotationFlyout;