diff --git a/public/components/Flyout/Flyout.js b/public/components/Flyout/Flyout.js index ebfec019..2655108b 100644 --- a/public/components/Flyout/Flyout.js +++ b/public/components/Flyout/Flyout.js @@ -45,7 +45,11 @@ const Flyout = ({ flyout, onClose }) => { } = flyoutData; const flyoutHeader = header && {header}; - const flyoutBody = body && {body}; + const flyoutBody = body && ( + + {body} + + ); const flyoutFooter = footer && {footer}; return ( diff --git a/public/components/Flyout/__snapshots__/Flyout.test.js.snap b/public/components/Flyout/__snapshots__/Flyout.test.js.snap index 9c9f5c7f..d48e1f11 100644 --- a/public/components/Flyout/__snapshots__/Flyout.test.js.snap +++ b/public/components/Flyout/__snapshots__/Flyout.test.js.snap @@ -31,7 +31,9 @@ exports[`Flyout renders 1`] = ` - + { + return ( +
+

+ {' '} + Field: {featureAttributes.fieldName || ''} +

+

+ {' '} + Aggregation method: {featureAttributes.aggregationType || ''} +

+
+ ); + }, + }, + { + field: 'state', + name: 'State', + }, + ]; + return ( + + + +

Features

+
+
+ + + { + return { + textOnly: true, + }; + }} + /> + + +
+ ); + } +} + +export class FilterDisplay extends Component { + constructor(props) { + super(props); + } + render() { + let filter = this.props; + if (filter === 'all fields are included') { + return ( + +

-

+
+ ); + } else { + return {filter}; + } + } +} +const FixedWidthRow = (props) => ; + +async function createAndStartDetector(context) { + const configs = context.adConfigs; + const httpClient = context.httpClient; + try { + const response = await httpClient.post('../api/alerting/detectors', { + configs, + }); + const { + data: { + ok, + response: { _id }, + }, + } = response; + const detectorId = _id; + if (ok) { + try { + const response = await httpClient.post(`../api/alerting/detectors/${detectorId}/_start`); + const { + data: { + ok, + response: { _id }, + }, + } = response; + if (ok) { + context.setFlyout(null); + context.renderStartedDetectorFlyout(configs, _id, context.queriesForOverview); + } + } catch (err) { + if (typeof err === 'string') throw err; + throw 'There was a problem starting detector'; + } + } + } catch (err) { + if (typeof err === 'string') throw err; + throw 'There was a problem creating detector'; + } +} + +function warningCallOut(message, color, iconType) { + return ( + + + + + + + {' '} + {message} + + + + ); +} + +function triggerCorrectCallOut(context) { + if (context.startedDetector) { + return detectorCreatedCallOut(context); + } + let calloutType = 'notValid'; + if ( + Object.keys(context.failures).length == 0 && + Object.keys(context.suggestedChanges).length == 0 + ) { + calloutType = 'valid'; + } + for (let [key, value] of Object.entries(context.suggestedChanges)) { + if (key === 'detection_interval') { + let intervalMinutes = Math.ceil(value[0] / 60000) + 1; + if (isNaN(intervalMinutes) || intervalMinutes > MAX_INTERVAl_LENGTH_MINUTES) { + calloutType = 'maxInterval'; + context.adConfigs.detection_interval = { + period: { interval: MAX_INTERVAl_LENGTH_MINUTES, unit: 'MINUTES' }, + }; + } else { + context.adConfigs.detection_interval = { + period: { interval: intervalMinutes, unit: 'MINUTES' }, + }; + if ( + Object.keys(context.failures).length == 0 && + Object.keys(context.suggestedChanges).length == 1 + ) { + calloutType = 'valid'; + } + } + } + if ( + key === 'filter_query' && + Object.keys(context.failures).length == 0 && + Object.keys(context.suggestedChanges).length == 1 + ) { + calloutType = 'filterQueryTooSparse'; + } + } + return informationCallOut(calloutType); +} + +function detectorCreatedCallOut(context) { + const detectorID = context.detectorID; + return ( + + + + + + + + + Anomaly detector has been created from the monitor and can be accessed{' '} + { + + {'here'} + + } + + + + + + ); +} + +function informationCallOut(callOut) { + if (callOut === 'filterQueryTooSparse') { + return warningCallOut(DETECTOR_CREATION_CALLOUTS.FILTER_QUERY_NO_RESULTS, 'warning', 'help'); + } + if (callOut === 'maxInterval') { + return warningCallOut(DETECTOR_CREATION_CALLOUTS.MAX_INTERVAL, 'warning', 'help'); + } + if (callOut === 'notValid') { + return warningCallOut(DETECTOR_CREATION_CALLOUTS.NOT_VALID, 'warning', 'help'); + } else if (callOut === 'valid') { + return warningCallOut(DETECTOR_CREATION_CALLOUTS.VALID, 'primary', 'check'); + } + return null; +} + +async function validateDetector(newValue, context) { + const search = { + searchType: 'graph', + timeField: newValue.time_field, + where: { + fieldName: newValue.where.fieldName === undefined ? [] : newValue.where.fieldName, + fieldRangeEnd: newValue.where.fieldRangeEnd === undefined ? 0 : newValue.where.fieldRangeEnd, + fieldRangeStart: + newValue.where.fieldRangeStart === undefined ? 0 : newValue.where.fieldRangeStart, + fieldValue: newValue.where.fieldValue === undefined ? '' : newValue.where.fieldValue, + operator: newValue.where.operator === undefined ? 'is' : newValue.where.operator, + }, + }; + let filterQuery = formikToWhereClause(search); + let adFilterQuery = ''; + if (filterQuery) { + adFilterQuery = { bool: { filter: [filterQuery] } }; + } else { + adFilterQuery = { match_all: { boost: 1.0 } }; + } + context.adConfigs.filter_query = adFilterQuery; + context.adConfigs.name = newValue.name; + context.adConfigs.description = newValue.description; + context.adConfigs.time_field = newValue.time_field; + context.adConfigs.inidices = newValue.indices; + context.adConfigs.window_delay = { period: { interval: newValue.window_delay, unit: 'MINUTES' } }; + context.adConfigs.detection_interval = { + period: { interval: newValue.detection_interval, unit: 'MINUTES' }, + }; + context.queriesForOverview.where = search.where; + context.queriesForOverview.filter_query = adFilterQuery; + const configs = context.adConfigs; + const httpClient = context.httpClient; + try { + const response = await httpClient.post('../api/alerting/detectors/_validate', { + configs, + }); + let resp = _.get(response, 'data.response'); + const { + data: { + ok, + response: { _id }, + }, + } = response; + if (ok) { + context.setFlyout(null); + context.renderFlyout(configs, resp, context.queriesForOverview); + } + } catch (err) { + if (typeof err === 'string') throw err; + throw 'There was a problem validating the configurations'; + } +} + +const ConfigCell = (props) => { + return ( + + +

{props.description}

+
+
+ ); +}; + +const validationErrorCallOut = (failures, suggestedChanges, field) => { + let message; + for (let [key, value] of Object.entries(failures)) { + if (key === 'duplicates' && field === 'name') { + message = 'Detector name is a duplicate'; + } else if (key === 'missing' && value[0] === field) { + message = 'This field is required'; + } else if ((key === 'regex' && field === 'name') || (key === 'format' && field === 'name')) { + message = 'Valid characters are a-z, A-Z, 0-9, -(hyphen) and _(underscore)'; + } + } + for (let [key, value] of Object.entries(suggestedChanges)) { + if (key === 'window_delay' && field === 'window_delay') { + message = 'Window delay should be at least ' + value[0] + ' minutes'; + } else if (key === 'filter_query' && field === 'filter_query') { + message = value[0]; + } else if (key === 'detectionIntervalMax' && field === 'detection_interval') { + message = value; + } + } + if (message) { + return ( + + ); + } + return null; +}; + +const createDetector = (context) => { + const [isFilterPopoverOpen, setFilterPopoverOpen] = useState(false); + const [dataTypes, setDataTypes] = useState({}); + + useEffect(() => { + onQueryMappings(); + }, []); + + const handleFieldChange = (option, field, form) => { + form.setFieldValue(field.name, option); + // User can remove where condition + if (option.length === 0) { + form.setFieldError('where', undefined); + } + }; + + async function onQueryMappings() { + const index = context.adConfigs.indices; + try { + const mappings = await queryMappings(index); + const data = getPathsPerDataType(mappings); + setDataTypes(data); + } catch (err) { + console.error('There was an error getting mappings for query', err); + } + } + const handleChangeWrapper = (e, field) => { + field.onChange(e); + }; + + const helpTextInterval = (context) => { + if ( + Object.keys(context.failures).length != 0 && + toString(context.adConfigs.detection_interval) == '1' + ) { + return "Detector interval hasn't been validated yet, please fix other failures first"; + } else if ( + Object.keys(context.failures).length == 0 && + toString(context.adConfigs.detection_interval) == '1' && + context.suggestedChanges.hasOwnProperty('filter_query') + ) { + return ( + "Detector interval recommendation hasn't been made since query filter returns no hits, you can " + + 'either choose to continue creation without validation or try to fix filter_query' + ); + } else { + return ''; + } + }; + + const renderBetweenAnd = (valuess) => { + const values = valuess; + return ( + + + validateRange(value, values.where), + }} + inputProps={{ onChange: handleChangeWrapper, isInvalid }} + /> + + + TO + + + validateRange(value, values.where), + }} + inputProps={{ onChange: handleChangeWrapper, isInvalid }} + /> + + + ); + }; + + const renderValueField = (fieldType, fieldOperator) => { + if (fieldType == DATA_TYPES.NUMBER) { + return isRangeOperator(fieldOperator) ? ( + renderBetweenAnd() + ) : ( + + ); + } else if (fieldType == DATA_TYPES.BOOLEAN) { + return ( + + ); + } else { + return ( + + ); + } + }; + + async function queryMappings(index) { + if (!index.length) { + return {}; + } + try { + const response = await context.httpClient.post('../api/alerting/_mappings', { index }); + if (response.data.ok) { + return response.data.resp; + } + return {}; + } catch (err) { + throw err; + } + } + + const handleOperatorChange = (e, field) => { + field.onChange(e); + }; + + const indexFields = getIndexFields(dataTypes, ['number', 'text', 'keyword', 'boolean']); + const onAddFilterButton = () => + setFilterPopoverOpen((isFilterPopoverOpen) => !isFilterPopoverOpen); + const closePopover = () => setFilterPopoverOpen(false); + return { + flyoutProps: { + 'aria-labelledby': 'createDetectorFlyout', + maxWidth: 900, + size: 'l', + }, + headerProps: { hasBorder: true }, + header: ( + +

+ Create Detector +

+
+ ), + body: ( + + + + + {!context.startedDetector ? ( + validateDetector(value, context)} + validateOnChange={false} + render={({ handleSubmit, values }) => ( + + + + + + {validationErrorCallOut( + context.failures, + context.suggestedChanges, + 'name' + )} + + + + + + + + + + + + Minutes], + }} + /> + {validationErrorCallOut( + context.failures, + context.suggestedChanges, + 'window_delay' + )} + + + + + Minutes, + }} + /> + + + + Data Filter + + onAddFilterButton()} + /> + } + isOpen={isFilterPopoverOpen} + closePopover={closePopover} + panelPaddingSize="none" + ownFocus + withTitle + anchorPosition="downLeft" + > +
+ + + + + + + + {!isNullOperator(_.get(values, 'where.operator', 'is')) && ( + + {renderValueField( + _.get(values, 'where.fieldName[0].type', 'number'), + _.get(values, 'where.operator', 'is') + )} + + )} + +
+
+
+ {validationErrorCallOut( + context.failures, + context.suggestedChanges, + 'filter_query' + )} +
+
+
+ + + + Validate + + + +
+ )} + /> + ) : ( + + + + + + + + + + + + + + + + + + + + + )} + +
+ + + +
+
+ ), + footerProps: {}, + footer: ( + + {context.startedDetector ? null : ( + + + context.setFlyout(null)}>Cancel + + + createAndStartDetector(context)} + fill + > + Create Detector + + + + )} + + ), + }; +}; +export default createDetector; diff --git a/public/components/Flyout/flyouts/detectorFailure.js b/public/components/Flyout/flyouts/detectorFailure.js new file mode 100644 index 00000000..e1f4429c --- /dev/null +++ b/public/components/Flyout/flyouts/detectorFailure.js @@ -0,0 +1,53 @@ +/* + * Copyright 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * or in the "license" file accompanying this file. This file is distributed + * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ + +import React, { Fragment } from 'react'; +import { + EuiText, + EuiTitle, + EuiFlexGroup, + EuiFlexItem, + EuiButtonEmpty, + EuiCallOut, +} from '@elastic/eui'; +import { URL } from '../../../../utils/constants'; + +const detectorFailure = (context) => ({ + flyoutProps: { + 'aria-labelledby': 'createDetectorFlyout', + maxWidth: 500, + size: 'm', + }, + headerProps: { hasBorder: true }, + header: ( + +

+ Anomaly Detector Can't Be Created +

+
+ ), + body: ( + + +

+ Unfortunately we aren’t able to automatically create an Anomaly detector from this monitor + due to the follow reason: {context.message} +

+
+
+ ), +}); + +export default detectorFailure; diff --git a/public/components/Flyout/flyouts/index.js b/public/components/Flyout/flyouts/index.js index ccb30627..4c848ce6 100644 --- a/public/components/Flyout/flyouts/index.js +++ b/public/components/Flyout/flyouts/index.js @@ -16,11 +16,15 @@ import message from './message'; import messageFrequency from './messageFrequency'; import triggerCondition from './triggerCondition'; +import createDetector from './createDetector'; +import detectorFailure from './detectorFailure'; const Flyouts = { messageFrequency, message, triggerCondition, + createDetector, + detectorFailure, }; export default Flyouts; diff --git a/public/pages/CreateMonitor/containers/DefineMonitor/DefineMonitor.js b/public/pages/CreateMonitor/containers/DefineMonitor/DefineMonitor.js index e07dd2e8..69cc39ec 100644 --- a/public/pages/CreateMonitor/containers/DefineMonitor/DefineMonitor.js +++ b/public/pages/CreateMonitor/containers/DefineMonitor/DefineMonitor.js @@ -128,7 +128,7 @@ class DefineMonitor extends Component { try { const pluginsResponse = await httpClient.get('../api/alerting/_plugins'); if (pluginsResponse.data.ok) { - this.setState({ plugins: pluginsResponse.data.resp.map(plugin => plugin.component) }); + this.setState({ plugins: pluginsResponse.data.resp.map((plugin) => plugin.component) }); } else { console.error('There was a problem getting plugins list'); } @@ -167,7 +167,6 @@ class DefineMonitor extends Component { async onRunQuery() { const { httpClient, values } = this.props; const formikSnapshot = _.cloneDeep(values); - // If we are running a visual graph query, then we need to run two separate queries // 1. The actual query that will be saved on the monitor, to get accurate query performance stats // 2. The UI generated query that gets [BUCKET_COUNT] times the aggregated buckets to show past history of query @@ -178,7 +177,7 @@ class DefineMonitor extends Component { } try { - const promises = searchRequests.map(searchRequest => { + const promises = searchRequests.map((searchRequest) => { // Fill in monitor name in case it's empty (in create workflow) // Set triggers to empty array so they are not executed (if in edit workflow) // Set input search to query/graph query and then use execute API to fill in period_start/period_end diff --git a/public/pages/CreateMonitor/containers/MonitorIndex/MonitorIndex.js b/public/pages/CreateMonitor/containers/MonitorIndex/MonitorIndex.js index dbbafa2d..89774c27 100644 --- a/public/pages/CreateMonitor/containers/MonitorIndex/MonitorIndex.js +++ b/public/pages/CreateMonitor/containers/MonitorIndex/MonitorIndex.js @@ -49,7 +49,6 @@ const propTypes = { class MonitorIndex extends React.Component { constructor(props) { super(props); - this.lastQuery = null; this.state = { isLoading: false, @@ -63,7 +62,6 @@ class MonitorIndex extends React.Component { partialMatchedAliases: [], exactMatchedAliases: [], }; - this.onCreateOption = this.onCreateOption.bind(this); this.onSearchChange = this.onSearchChange.bind(this); this.handleQueryIndices = this.handleQueryIndices.bind(this); @@ -258,5 +256,4 @@ class MonitorIndex extends React.Component { } MonitorIndex.propTypes = propTypes; - export default MonitorIndex; diff --git a/public/pages/MonitorDetails/containers/MonitorDetails.js b/public/pages/MonitorDetails/containers/MonitorDetails.js index 22c20ad4..4fc46555 100644 --- a/public/pages/MonitorDetails/containers/MonitorDetails.js +++ b/public/pages/MonitorDetails/containers/MonitorDetails.js @@ -1,5 +1,5 @@ /* - * Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * Copyright 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"). * You may not use this file except in compliance with the License. @@ -27,6 +27,7 @@ import { EuiText, EuiTitle, EuiIcon, + EuiToolTip, } from '@elastic/eui'; import CreateMonitor from '../../CreateMonitor'; @@ -42,6 +43,8 @@ import { MONITOR_INPUT_DETECTOR_ID, } from '../../../utils/constants'; import { migrateTriggerMetadata } from './utils/helpers'; +import { formikToWhereClause } from '../../CreateMonitor/containers/CreateMonitor/utils/formikToMonitor'; +import { displayText } from '../../CreateMonitor/components/MonitorExpressions/expressions/utils/whereHelpers'; export default class MonitorDetails extends Component { constructor(props) { @@ -55,6 +58,7 @@ export default class MonitorDetails extends Component { activeCount: 0, loading: true, updating: false, + creatingDetector: false, error: null, triggerToEdit: null, }; @@ -77,11 +81,11 @@ export default class MonitorDetails extends Component { this.props.setFlyout(null); } - getDetector = id => { + getDetector = (id) => { const { httpClient } = this.props; httpClient .get(`../api/alerting/detectors/${id}`) - .then(resp => { + .then((resp) => { const { ok, detector, version: detectorVersion, seqNo, primaryTerm } = resp.data; if (ok) { this.setState({ @@ -92,16 +96,16 @@ export default class MonitorDetails extends Component { console.log('can not get detector', id); } }) - .catch(err => { + .catch((err) => { console.log('error while getting detector', err); }); }; - getMonitor = id => { + getMonitor = (id) => { const { httpClient } = this.props; httpClient .get(`../api/alerting/monitors/${id}`) - .then(resp => { + .then((resp) => { const { ok, resp: monitor, @@ -131,12 +135,12 @@ export default class MonitorDetails extends Component { this.props.history.push('/monitors'); } }) - .catch(err => { + .catch((err) => { console.log('err', err); }); }; - updateMonitor = update => { + updateMonitor = (update) => { const { match: { params: { monitorId }, @@ -150,12 +154,12 @@ export default class MonitorDetails extends Component { `../api/alerting/monitors/${monitorId}?ifSeqNo=${ifSeqNo}&ifPrimaryTerm=${ifPrimaryTerm}`, { ...monitor, ...update } ) - .then(resp => { + .then((resp) => { const { version: monitorVersion } = resp.data; this.setState({ monitorVersion, updating: false }); return resp; }) - .catch(err => { + .catch((err) => { console.log('err', err); this.setState({ updating: false }); return err; @@ -174,7 +178,7 @@ export default class MonitorDetails extends Component { this.setState({ triggerToEdit: null }); }; - onEditTrigger = trigger => { + onEditTrigger = (trigger) => { this.setState({ triggerToEdit: trigger }); this.props.history.push({ ...this.props.location, @@ -182,6 +186,181 @@ export default class MonitorDetails extends Component { }); }; + convertToADConfigs = async (monitor) => { + const uiMetadata = _.get(monitor, 'ui_metadata'); + let adName = monitor.name + '-Detector'; + let adTimeField = uiMetadata.search.timeField; + let adIndices = monitor.inputs[0].search.indices; + let adDetectorInterval = { period: { interval: 1, unit: 'MINUTES' } }; + const { + frequency, + period: { interval, unit }, + daily, + weekly, + monthly: { day }, + cronExpression, + timezone, + } = _.get(uiMetadata, 'schedule', {}); + const search = _.get(uiMetadata, 'search'); + if (frequency === 'interval') { + adDetectorInterval = { period: { unit: unit, interval: interval } }; + } + let windowDelay = await this.getLatestTimeStamp(adTimeField, adIndices); + let adWindowDelay = { period: { interval: 10, unit: 'MINUTES' } }; + if (windowDelay) { + adWindowDelay = { period: { interval: windowDelay, unit: 'MINUTES' } }; + } + let filterQuery = formikToWhereClause(search); + let adFilterQuery = ''; + if (filterQuery) { + adFilterQuery = { bool: { filter: [filterQuery] } }; + } else { + adFilterQuery = { match_all: { boost: 1.0 } }; + } + let { aggregationType, fieldName } = search; + let adFeatures; + if (!aggregationType || !fieldName) { + adFeatures = {}; + } else { + if (aggregationType == 'count') { + aggregationType = 'value_count'; + } + adFeatures = { + feature_name: 'feature-1', + feature_enabled: true, + aggregation_query: { aggregation_name: { [aggregationType]: { field: fieldName } } }, + }; + } + let adConfigs = { + name: adName, + description: '', + time_field: adTimeField, + indices: adIndices, + feature_attributes: [adFeatures], + filter_query: adFilterQuery, + detection_interval: adDetectorInterval, + window_delay: adWindowDelay, + }; + let queriesForOverview = { + filter_query: displayText(_.get(search, 'where')), + feature_attributes: { + feature_name: 'feature-1', + aggregationType: aggregationType, + fieldName: fieldName, + }, + where: _.get(search, 'where'), + }; + let validationResponse = await this.validateADConfigs(adConfigs); + this.decideWhichFlyout(validationResponse, adConfigs, queriesForOverview); + }; + + decideWhichFlyout = (validationResponse, adConfigs, queriesForOverview) => { + const setFlyout = this.props.setFlyout; + if (validationResponse.failures.others) { + let message = ''; + for (let [key, value] of Object.entries(validationResponse.failures)) { + if (key === 'others') { + message = value; + } + } + this.props.setFlyout({ type: 'detectorFailure', payload: { setFlyout, message } }); + } else { + this.renderFlyout(adConfigs, validationResponse, queriesForOverview); + } + }; + + renderStartedDetectorFlyout = (configs, detectorID, queries) => { + const { httpClient } = this.props; + const setFlyout = this.props.setFlyout; + let startedDetector = true; + const adConfigs = configs; + const queriesForOverview = queries; + this.props.setFlyout({ + type: 'createDetector', + payload: { + adConfigs, + queriesForOverview, + httpClient, + setFlyout, + startedDetector, + detectorID, + }, + }); + }; + + renderFlyout = (adConfigs, validationResponse, queriesForOverview) => { + const { httpClient } = this.props; + const setFlyout = this.props.setFlyout; + const renderStartedDetectorFlyout = this.renderStartedDetectorFlyout; + const renderFlyout = this.renderFlyout; + const failures = Object.assign(validationResponse.failures); + const suggestedChanges = Object.assign(validationResponse.suggestedChanges); + const startedDetector = false; + this.props.setFlyout({ + type: 'createDetector', + payload: { + adConfigs, + queriesForOverview, + httpClient, + setFlyout, + renderStartedDetectorFlyout, + failures, + suggestedChanges, + renderFlyout, + startedDetector, + }, + }); + }; + + validateADConfigs = async (configs) => { + const { httpClient } = this.props; + try { + const response = await httpClient.post('../api/alerting/detectors/_validate', { + configs, + }); + let resp = _.get(response, 'data.response'); + return resp; + } catch (err) { + if (typeof err === 'string') throw err; + throw 'There was a problem validating the configurations'; + } + }; + + getLatestTimeStamp = async (adTimeField, adIndices) => { + const { httpClient } = this.props; + const searchQuery = { + size: 1, + sort: [ + { + timestamp: { + order: 'desc', + }, + }, + ], + aggregations: { + max_timefield: { + max: { + field: adTimeField, + }, + }, + }, + }; + try { + const options = { + index: adIndices, + query: searchQuery, + }; + const response = await httpClient.post('../api/alerting/_search', options); + let maxStamp = _.get(response, 'data.resp.aggregations.max_timefield.value'); + let delayMS = Date.now() - maxStamp; + let delayMinutes = Math.ceil(delayMS / 60000) + 1; + return delayMinutes; + } catch (err) { + if (typeof err === 'string') throw err; + throw 'There was a problem getting the last historical data point'; + } + }; + renderNoTriggersCallOut = () => { const { monitor } = this.state; if (!monitor.triggers.length) { @@ -269,7 +448,6 @@ export default class MonitorDetails extends Component { /> ); } - return (
{this.renderNoTriggersCallOut()} @@ -287,7 +465,6 @@ export default class MonitorDetails extends Component { {monitor.name} - {detector ? ( @@ -299,7 +476,28 @@ export default class MonitorDetails extends Component { ) : null} - + + + this.convertToADConfigs(this.state.monitor)} + disabled={ + monitor.ui_metadata.search.searchType !== 'graph' || + monitor.ui_metadata.search.fieldName === '' || + monitor.ui_metadata.schedule.timezone + } + > + Create Detector + + + { diff --git a/public/pages/MonitorDetails/containers/utils/helpers.js b/public/pages/MonitorDetails/containers/utils/helpers.js index 5ba9e5d2..51cac0a3 100644 --- a/public/pages/MonitorDetails/containers/utils/helpers.js +++ b/public/pages/MonitorDetails/containers/utils/helpers.js @@ -12,9 +12,10 @@ * express or implied. See the License for the specific language governing * permissions and limitations under the License. */ - +export const NAME_REGEX = RegExp('^[a-zA-Z0-9._-]+$'); +export const MAX_INTERVAl_LENGTH_MINUTES = 10080; import { get, isEmpty } from 'lodash'; -export const migrateTriggerMetadata = monitor => { +export const migrateTriggerMetadata = (monitor) => { const uiMetadata = get(monitor, 'ui_metadata', {}); if (isEmpty(uiMetadata)) return monitor; // Already migrated no need to perform any action diff --git a/public/utils/constants.js b/public/utils/constants.js index 8604c4d0..2991c6bb 100644 --- a/public/utils/constants.js +++ b/public/utils/constants.js @@ -1,5 +1,5 @@ /* - * Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * Copyright 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"). * You may not use this file except in compliance with the License. @@ -21,6 +21,29 @@ export const ALERT_STATE = Object.freeze({ DELETED: 'DELETED', }); +export const DETECTOR_CREATION_CALLOUTS = { + FILTER_QUERY_NO_RESULTS: + 'No Data is found with the current filter query used for the past ' + + NUM_INTERVALS_FILTER_QUERY_CHECKED_AGAINST + + ' intervals, you' + + ' can try to manually change detector interval and still continue with validation' + + ' however the data is most likely to be too sparse', + MAX_INTERVAL: + 'No optimal detector interval was found with the current data source, (checked up to' + + MAX_MINUTE_INTERVAL_LENGTH + + ') you can still proceed with this detector creation however it will' + + ' likely fail since the data is too sparse', + NOT_VALID: + 'Please fix and validate any needed field in order to successfuly create an anomaly' + + ' detector. Anomaly detection creation requires configurations that lead to enough data', + VALID: + 'Anomaly detector configurations have been validated, click create detector to confirm creation', +}; + +export const MAX_MINUTE_INTERVAL_LENGTH = 10800; + +export const NUM_INTERVALS_FILTER_QUERY_CHECKED_AGAINST = '384'; + export const DEFAULT_EMPTY_DATA = '-'; export const APP_PATH = { diff --git a/public/utils/validate.js b/public/utils/validate.js index 66f00953..cdeae16b 100644 --- a/public/utils/validate.js +++ b/public/utils/validate.js @@ -24,13 +24,13 @@ export const isInvalid = (name, form) => export const hasError = (name, form) => _.get(form.errors, name); -export const validateActionName = trigger => value => { +export const validateActionName = (trigger) => (value) => { if (!value) return 'Required'; - const matches = trigger.actions.filter(action => action.name === value); + const matches = trigger.actions.filter((action) => action.name === value); if (matches.length > 1) return 'Action name is already used'; }; -export const isInvalidActionThrottle = action => { +export const isInvalidActionThrottle = (action) => { if (_.get(action, 'throttle_enabled')) { var value = _.get(action, 'throttle.value'); if (!value || value < 1 || value > MAX_THROTTLE_VALUE) { @@ -40,17 +40,30 @@ export const isInvalidActionThrottle = action => { return false; }; -export const validateActionThrottle = action => value => { +export const validateActionThrottle = (action) => (value) => { if (isInvalidActionThrottle(action)) { return WRONG_THROTTLE_WARNING; } }; -export const required = value => { +export const required = (value) => { if (!value) return 'Required'; }; -export const validateMonitorName = (httpClient, monitorToEdit) => async value => { +export const validateDetectorName = (value) => { + console.log('in validate: ' + value); + try { + if (!value) { + throw 'required'; + } else if (!NAME_REGEX.test(value)) { + throw 'Valid characters are a-z, A-Z, 0-9, -(hyphen) and _(underscore)'; + } + } catch (err) { + if (typeof err === 'string') throw err; + } +}; + +export const validateMonitorName = (httpClient, monitorToEdit) => async (value) => { try { if (!value) throw 'Required'; const options = { @@ -70,20 +83,20 @@ export const validateMonitorName = (httpClient, monitorToEdit) => async value => } }; -export const validateTimezone = value => { +export const validateTimezone = (value) => { if (!Array.isArray(value)) return 'Required'; if (!value.length) return 'Required'; }; -export const validatePositiveInteger = value => { +export const validatePositiveInteger = (value) => { if (!Number.isInteger(value) || value < 1) return 'Must be a positive integer'; }; -export const validateUnit = value => { +export const validateUnit = (value) => { if (!['MINUTES', 'HOURS', 'DAYS'].includes(value)) return 'Must be one of minutes, hours, days'; }; -export const validateMonthlyDay = value => { +export const validateMonthlyDay = (value) => { if (!Number.isInteger(value) || value < 1 || value > 31) return 'Must be a positive integer between 1-31'; }; @@ -96,7 +109,7 @@ export const validateDetector = (detectorId, selectedDetector) => { return 'Must choose detector which has features'; }; -export const validateIndex = options => { +export const validateIndex = (options) => { if (!Array.isArray(options)) return 'Must specify an index'; if (!options.length) return 'Must specify an index'; @@ -116,7 +129,7 @@ export function isIndexPatternQueryValid(pattern, illegalCharacters) { return false; } - return !illegalCharacters.some(char => pattern.includes(char)); + return !illegalCharacters.some((char) => pattern.includes(char)); } export function validateExtractionQuery(value) { diff --git a/server/clusters/alerting/adPlugin.js b/server/clusters/alerting/adPlugin.js index 586e5e22..9c058966 100644 --- a/server/clusters/alerting/adPlugin.js +++ b/server/clusters/alerting/adPlugin.js @@ -34,6 +34,21 @@ export default function alertingADPlugin(Client, config, components) { method: 'GET', }); + alertingAD.createDetector = ca({ + url: { + fmt: `${AD_BASE_API}`, + }, + needBody: true, + method: 'POST', + }); + + alertingAD.validateDetector = ca({ + url: { + fmt: `${AD_BASE_API}/_validate`, + }, + method: 'POST', + }); + alertingAD.searchDetectors = ca({ url: { fmt: `${AD_BASE_API}/_search`, @@ -41,6 +56,19 @@ export default function alertingADPlugin(Client, config, components) { needBody: true, method: 'POST', }); + + alertingAD.startDetector = ca({ + url: { + fmt: `${AD_BASE_API}/<%=detectorId%>/_start`, + req: { + detectorId: { + type: 'string', + required: true, + }, + }, + }, + method: 'POST', + }); alertingAD.previewDetector = ca({ url: { fmt: `${AD_BASE_API}/<%=detectorId%>/_preview`, diff --git a/server/routes/anomalyDetector.js b/server/routes/anomalyDetector.js index a7713570..72fa0a72 100644 --- a/server/routes/anomalyDetector.js +++ b/server/routes/anomalyDetector.js @@ -13,7 +13,7 @@ * permissions and limitations under the License. */ -export default function(server, services) { +export default function (server, services) { const { anomalyDetectorService } = services; server.route({ @@ -22,12 +22,30 @@ export default function(server, services) { handler: anomalyDetectorService.getDetector, }); + server.route({ + path: '/api/alerting/detectors', + method: 'POST', + handler: anomalyDetectorService.createDetector, + }); + + server.route({ + path: '/api/alerting/detectors/{detectorId}/_start', + method: 'POST', + handler: anomalyDetectorService.startDetector, + }); + server.route({ path: '/api/alerting/detectors/_search', method: 'POST', handler: anomalyDetectorService.getDetectors, }); + server.route({ + path: '/api/alerting/detectors/_validate', + method: 'POST', + handler: anomalyDetectorService.validateDetector, + }); + server.route({ path: '/api/alerting/detectors/{detectorId}/results', method: 'GET', diff --git a/server/services/AnomalyDetectorService.js b/server/services/AnomalyDetectorService.js index bd3e4785..7c4e97e4 100644 --- a/server/services/AnomalyDetectorService.js +++ b/server/services/AnomalyDetectorService.js @@ -42,6 +42,36 @@ export default class DestinationsService { } }; + validateDetector = async (req, h) => { + const params = { body: JSON.stringify(req.payload.configs) }; + const { callWithRequest } = this.esDriver.getCluster(CLUSTER.AD_ALERTING); + try { + const resp = await callWithRequest(req, 'alertingAD.validateDetector', params); + return { + ok: true, + response: resp, + }; + } catch (err) { + console.error('Alerting - AnomalyDetectorService - validateDetector:', err); + return { ok: false, resp: err.message }; + } + }; + + createDetector = async (req, h) => { + const { callWithRequest } = this.esDriver.getCluster(CLUSTER.AD_ALERTING); + const requestBody = { body: JSON.stringify(req.payload.configs) }; + try { + const resp = await callWithRequest(req, 'alertingAD.createDetector', requestBody); + return { + ok: true, + response: resp, + }; + } catch (err) { + console.error('Alerting - AnomalyDetectorService - createDetector:', err); + return { ok: false, resp: err.message }; + } + }; + getDetectors = async (req, h) => { const searchRequest = { query: { match_all: {} }, @@ -54,7 +84,7 @@ export default class DestinationsService { }); const totalDetectors = resp.hits.total.value; - const detectors = resp.hits.hits.map(hit => { + const detectors = resp.hits.hits.map((hit) => { const { _source: detector, _id: id, @@ -71,6 +101,21 @@ export default class DestinationsService { } }; + startDetector = async (req, h) => { + const { callWithRequest } = this.esDriver.getCluster(CLUSTER.AD_ALERTING); + const { detectorId } = req.params; + try { + const response = await callWithRequest(req, 'alertingAD.startDetector', { detectorId }); + return { + ok: true, + response: response, + }; + } catch (err) { + console.error('Alerting - AnomalyDetectorService - startDetector', err); + return { ok: false, response: err.message }; + } + }; + getDetectorResults = async (req, h) => { try { const { startTime = 0, endTime = 20, preview = 'false' } = req.query; @@ -126,7 +171,7 @@ export default class DestinationsService { const anomaliesResponse = await callWithRequest(req, 'alertingAD.searchResults', { body: requestBody, }); - const transformedKeys = get(anomaliesResponse, 'hits.hits', []).map(result => + const transformedKeys = get(anomaliesResponse, 'hits.hits', []).map((result) => mapKeysDeep(result._source, toCamel) ); return { diff --git a/yarn.lock b/yarn.lock index da2630fc..84ff791f 100644 --- a/yarn.lock +++ b/yarn.lock @@ -17,18 +17,18 @@ "@babel/highlight" "^7.10.4" "@babel/core@^7.10.2": - version "7.11.4" - resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.11.4.tgz#4301dfdfafa01eeb97f1896c5501a3f0655d4229" - integrity sha512-5deljj5HlqRXN+5oJTY7Zs37iH3z3b++KjiKtIsJy1NrjOOVSEaJHEetLBhyu0aQOSNNZ/0IuEAan9GzRuDXHg== + version "7.11.5" + resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.11.5.tgz#6ad96e2f71899ea3f9b651f0a911e85205d1ff6d" + integrity sha512-fsEANVOcZHzrsV6dMVWqpSeXClq3lNbYrfFGme6DE25FQWe7pyeYpXyx9guqUnpy466JLzZ8z4uwSr2iv60V5Q== dependencies: "@babel/code-frame" "^7.10.4" - "@babel/generator" "^7.11.4" + "@babel/generator" "^7.11.5" "@babel/helper-module-transforms" "^7.11.0" "@babel/helpers" "^7.10.4" - "@babel/parser" "^7.11.4" + "@babel/parser" "^7.11.5" "@babel/template" "^7.10.4" - "@babel/traverse" "^7.11.0" - "@babel/types" "^7.11.0" + "@babel/traverse" "^7.11.5" + "@babel/types" "^7.11.5" convert-source-map "^1.7.0" debug "^4.1.0" gensync "^1.0.0-beta.1" @@ -36,7 +36,7 @@ lodash "^4.17.19" resolve "^1.3.2" semver "^5.4.1" - source-map "^0.5.0" + source-map "^0.6.1" "@babel/generator@^7.10.5": version "7.10.5" @@ -47,14 +47,14 @@ jsesc "^2.5.1" source-map "^0.5.0" -"@babel/generator@^7.11.0", "@babel/generator@^7.11.4": - version "7.11.4" - resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.11.4.tgz#1ec7eec00defba5d6f83e50e3ee72ae2fee482be" - integrity sha512-Rn26vueFx0eOoz7iifCN2UHT6rGtnkSGWSoDRIy8jZN3B91PzeSULbswfLoOWuTuAcNwpG/mxy+uCTDnZ9Mp1g== +"@babel/generator@^7.11.5": + version "7.11.5" + resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.11.5.tgz#a5582773425a468e4ba269d9a1f701fbca6a7a82" + integrity sha512-9UqHWJ4IwRTy4l0o8gq2ef8ws8UPzvtMkVKjTLAiRmza9p9V6Z+OfuNd9fB1j5Q67F+dVJtPC2sZXI8NM9br4g== dependencies: - "@babel/types" "^7.11.0" + "@babel/types" "^7.11.5" jsesc "^2.5.1" - source-map "^0.5.0" + source-map "^0.6.1" "@babel/helper-function-name@^7.10.4": version "7.10.4" @@ -175,10 +175,10 @@ resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.10.5.tgz#e7c6bf5a7deff957cec9f04b551e2762909d826b" integrity sha512-wfryxy4bE1UivvQKSQDU4/X6dr+i8bctjUjj8Zyt3DQy7NtPizJXT8M52nqpNKL+nq2PW8lxk4ZqLj0fD4B4hQ== -"@babel/parser@^7.11.0", "@babel/parser@^7.11.4": - version "7.11.4" - resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.11.4.tgz#6fa1a118b8b0d80d0267b719213dc947e88cc0ca" - integrity sha512-MggwidiH+E9j5Sh8pbrX5sJvMcsqS5o+7iB42M9/k0CD63MjYbdP4nhSh7uB5wnv2/RVzTZFTxzF/kIa5mrCqA== +"@babel/parser@^7.11.5": + version "7.11.5" + resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.11.5.tgz#c7ff6303df71080ec7a4f5b8c003c58f1cf51037" + integrity sha512-X9rD8qqm695vgmeaQ4fvz/o3+Wk4ZzQvSHkDBgpYKxpD4qTAUm88ZKtHkVqIOsYFFbIQ6wQYhC6q7pjqVK0E0Q== "@babel/runtime@^7.1.2": version "7.9.2" @@ -187,13 +187,6 @@ dependencies: regenerator-runtime "^0.13.4" -"@babel/runtime@^7.5.1", "@babel/runtime@^7.5.5": - version "7.11.2" - resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.11.2.tgz#f549c13c754cc40b87644b9fa9f09a6a95fe0736" - integrity sha512-TeWkU52so0mPtDcaCTxNBI/IHiz0pZgr8VEFqXFtZWpYD08ZB6FaSwVAS8MKRQAP3bYKiVjwysOJgMFY28o6Tw== - dependencies: - regenerator-runtime "^0.13.4" - "@babel/template@^7.10.4": version "7.10.4" resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.10.4.tgz#3251996c4200ebc71d1a8fc405fba940f36ba278" @@ -218,17 +211,17 @@ globals "^11.1.0" lodash "^4.17.19" -"@babel/traverse@^7.11.0": - version "7.11.0" - resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.11.0.tgz#9b996ce1b98f53f7c3e4175115605d56ed07dd24" - integrity sha512-ZB2V+LskoWKNpMq6E5UUCrjtDUh5IOTAyIl0dTjIEoXum/iKWkoIEKIRDnUucO6f+2FzNkE0oD4RLKoPIufDtg== +"@babel/traverse@^7.11.5": + version "7.11.5" + resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.11.5.tgz#be777b93b518eb6d76ee2e1ea1d143daa11e61c3" + integrity sha512-EjiPXt+r7LiCZXEfRpSJd+jUMnBd4/9OUv7Nx3+0u9+eimMwJmG0Q98lw4/289JCoxSE8OolDMNZaaF/JZ69WQ== dependencies: "@babel/code-frame" "^7.10.4" - "@babel/generator" "^7.11.0" + "@babel/generator" "^7.11.5" "@babel/helper-function-name" "^7.10.4" "@babel/helper-split-export-declaration" "^7.11.0" - "@babel/parser" "^7.11.0" - "@babel/types" "^7.11.0" + "@babel/parser" "^7.11.5" + "@babel/types" "^7.11.5" debug "^4.1.0" globals "^11.1.0" lodash "^4.17.19" @@ -251,6 +244,15 @@ lodash "^4.17.19" to-fast-properties "^2.0.0" +"@babel/types@^7.11.5": + version "7.11.5" + resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.11.5.tgz#d9de577d01252d77c6800cee039ee64faf75662d" + integrity sha512-bvM7Qz6eKnJVFIn+1LPtjlBFPVN5jNDc1XmN15vWe7Q3DPBufWWsLiIvUu7xW87uTG6QoggpIDnUgLQvPheU+Q== + dependencies: + "@babel/helper-validator-identifier" "^7.10.4" + lodash "^4.17.19" + to-fast-properties "^2.0.0" + "@elastic/eslint-config-kibana@link:../../packages/eslint-config-kibana": version "0.0.0" uid "" @@ -259,15 +261,6 @@ version "0.0.0" uid "" -"@jest/types@^24.9.0": - version "24.9.0" - resolved "https://registry.yarnpkg.com/@jest/types/-/types-24.9.0.tgz#63cb26cb7500d069e5a389441a7c6ab5e909fc59" - integrity sha512-XKK7ze1apu5JWQ5eZjHITP66AX+QsLlbaJRBGYr8pNzwcAE2JVkwnf0yqjHTsDRcjR0mujy/NmZMXw5kl+kGBw== - dependencies: - "@types/istanbul-lib-coverage" "^2.0.0" - "@types/istanbul-reports" "^1.1.1" - "@types/yargs" "^13.0.0" - "@kbn/expect@link:../../packages/kbn-expect": version "0.0.0" uid "" @@ -317,50 +310,6 @@ dependencies: any-observable "^0.3.0" -"@sheerun/mutationobserver-shim@^0.3.2": - version "0.3.3" - resolved "https://registry.yarnpkg.com/@sheerun/mutationobserver-shim/-/mutationobserver-shim-0.3.3.tgz#5405ee8e444ed212db44e79351f0c70a582aae25" - integrity sha512-DetpxZw1fzPD5xUBrIAoplLChO2VB8DlL5Gg+I1IR9b2wPqYIca2WSUxL5g1vLeR4MsQq1NeWriXAVffV+U1Fw== - -"@testing-library/dom@^5.6.1": - version "5.6.1" - resolved "https://registry.yarnpkg.com/@testing-library/dom/-/dom-5.6.1.tgz#705a1cb4a039b877c1e69e916824038e837ab637" - integrity sha512-Y1T2bjtvQMewffn1CJ28kpgnuvPYKsBcZMagEH0ppfEMZPDc8AkkEnTk4smrGZKw0cblNB3lhM2FMnpfLExlHg== - dependencies: - "@babel/runtime" "^7.5.5" - "@sheerun/mutationobserver-shim" "^0.3.2" - aria-query "3.0.0" - pretty-format "^24.8.0" - wait-for-expect "^1.2.0" - -"@testing-library/jest-dom@^4.0.0": - version "4.2.4" - resolved "https://registry.yarnpkg.com/@testing-library/jest-dom/-/jest-dom-4.2.4.tgz#00dfa0cbdd837d9a3c2a7f3f0a248ea6e7b89742" - integrity sha512-j31Bn0rQo12fhCWOUWy9fl7wtqkp7In/YP2p5ZFyRuiiB9Qs3g+hS4gAmDWONbAHcRmVooNJ5eOHQDCOmUFXHg== - dependencies: - "@babel/runtime" "^7.5.1" - chalk "^2.4.1" - css "^2.2.3" - css.escape "^1.5.1" - jest-diff "^24.0.0" - jest-matcher-utils "^24.0.0" - lodash "^4.17.11" - pretty-format "^24.0.0" - redent "^3.0.0" - -"@testing-library/react@^8.0.5": - version "8.0.9" - resolved "https://registry.yarnpkg.com/@testing-library/react/-/react-8.0.9.tgz#1ecd96bc3471b06dd2f9763b6e53a7ace28a54a2" - integrity sha512-I7zd+MW5wk8rQA5VopZgBfxGKUd91jgZ6Vzj2gMqFf2iGGtKwvI5SVTrIJcSFaOXK88T2EUsbsIKugDtoqOcZQ== - dependencies: - "@babel/runtime" "^7.5.5" - "@testing-library/dom" "^5.6.1" - -"@testing-library/user-event@^4.1.0": - version "4.2.4" - resolved "https://registry.yarnpkg.com/@testing-library/user-event/-/user-event-4.2.4.tgz#9029bdf35d7f0557954452916f22ba0e9f11eff9" - integrity sha512-Hi74+nCCjTM+HFRmN1CASq3Y1DC8XZk/oF0Ov/sIRXS5izvXCXtjxJcpPNzyi7YL5jGjWuldzsQTiPPtYbudwg== - "@types/color-name@^1.1.1": version "1.1.1" resolved "https://registry.yarnpkg.com/@types/color-name/-/color-name-1.1.1.tgz#1c1261bbeaa10a8055bbc5d8ab84b7b2afc846a0" @@ -380,26 +329,6 @@ "@types/minimatch" "*" "@types/node" "*" -"@types/istanbul-lib-coverage@*", "@types/istanbul-lib-coverage@^2.0.0": - version "2.0.3" - resolved "https://registry.yarnpkg.com/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.3.tgz#4ba8ddb720221f432e443bd5f9117fd22cfd4762" - integrity sha512-sz7iLqvVUg1gIedBOvlkxPlc8/uVzyS5OwGz1cKjXzkl3FpL3al0crU8YGU1WoHkxn0Wxbw5tyi6hvzJKNzFsw== - -"@types/istanbul-lib-report@*": - version "3.0.0" - resolved "https://registry.yarnpkg.com/@types/istanbul-lib-report/-/istanbul-lib-report-3.0.0.tgz#c14c24f18ea8190c118ee7562b7ff99a36552686" - integrity sha512-plGgXAPfVKFoYfa9NpYDAkseG+g6Jr294RqeqcqDixSbU34MZVJRi/P+7Y8GDpzkEwLaGZZOpKIEmeVZNtKsrg== - dependencies: - "@types/istanbul-lib-coverage" "*" - -"@types/istanbul-reports@^1.1.1": - version "1.1.2" - resolved "https://registry.yarnpkg.com/@types/istanbul-reports/-/istanbul-reports-1.1.2.tgz#e875cc689e47bce549ec81f3df5e6f6f11cfaeb2" - integrity sha512-P/W9yOX/3oPZSpaYOCQzGqgCQRXn0FFO/V8bWrCQs+wLmvVVxk6CRBXALEvNs9OHIatlnlFokfhuDo2ug01ciw== - dependencies: - "@types/istanbul-lib-coverage" "*" - "@types/istanbul-lib-report" "*" - "@types/minimatch@*": version "3.0.3" resolved "https://registry.yarnpkg.com/@types/minimatch/-/minimatch-3.0.3.tgz#3dca0e3f33b200fc7d1139c0cd96c1268cadfd9d" @@ -415,18 +344,6 @@ resolved "https://registry.yarnpkg.com/@types/normalize-package-data/-/normalize-package-data-2.4.0.tgz#e486d0d97396d79beedd0a6e33f4534ff6b4973e" integrity sha512-f5j5b/Gf71L+dbqxIpQ4Z2WlmI/mPJ0fOkGGmFgtb6sAu97EPczzbS3/tJKxmcYDj55OX6ssqwDAWOHIYDRDGA== -"@types/yargs-parser@*": - version "15.0.0" - resolved "https://registry.yarnpkg.com/@types/yargs-parser/-/yargs-parser-15.0.0.tgz#cb3f9f741869e20cce330ffbeb9271590483882d" - integrity sha512-FA/BWv8t8ZWJ+gEOnLLd8ygxH/2UFbAvgEonyfN6yWGLKc7zVjbpl2Y4CTjid9h2RfgPP6SEt6uHwEOply00yw== - -"@types/yargs@^13.0.0": - version "13.0.10" - resolved "https://registry.yarnpkg.com/@types/yargs/-/yargs-13.0.10.tgz#e77bf3fc73c781d48c2eb541f87c453e321e5f4b" - integrity sha512-MU10TSgzNABgdzKvQVW1nuuT+sgBMWeXNc3XOs5YXV5SDAK+PPja2eUuBNB9iqElu03xyEDqlnGw0jgl4nbqGQ== - dependencies: - "@types/yargs-parser" "*" - "@webassemblyjs/ast@1.9.0": version "1.9.0" resolved "https://registry.yarnpkg.com/@webassemblyjs/ast/-/ast-1.9.0.tgz#bd850604b4042459a5a41cd7d338cbed695ed964" @@ -652,7 +569,7 @@ ansi-regex@^3.0.0: resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-3.0.0.tgz#ed0317c322064f79466c02966bddb605ab37d998" integrity sha1-7QMXwyIGT3lGbAKWa922Bas32Zg= -ansi-regex@^4.0.0, ansi-regex@^4.1.0: +ansi-regex@^4.1.0: version "4.1.0" resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-4.1.0.tgz#8b9f8f08cf1acb843756a839ca8c7e3168c51997" integrity sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg== @@ -740,14 +657,6 @@ argv-split@^2.0.1: resolved "https://registry.yarnpkg.com/argv-split/-/argv-split-2.0.1.tgz#be264117790dbd5ccd63ec3f449a1804814ac4c5" integrity sha1-viZBF3kNvVzNY+w/RJoYBIFKxMU= -aria-query@3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/aria-query/-/aria-query-3.0.0.tgz#65b3fcc1ca1155a8c9ae64d6eee297f15d5133cc" - integrity sha1-ZbP8wcoRVajJrmTW7uKX8V1RM8w= - dependencies: - ast-types-flow "0.0.7" - commander "^2.11.0" - arr-diff@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/arr-diff/-/arr-diff-4.0.0.tgz#d6461074febfec71e7e15235761a329a5dc7c520" @@ -839,11 +748,6 @@ assign-symbols@^1.0.0: resolved "https://registry.yarnpkg.com/assign-symbols/-/assign-symbols-1.0.0.tgz#59667f41fadd4f20ccbc2bb96b8d4f7f78ec0367" integrity sha1-WWZ/QfrdTyDMvCu5a41Pf3jsA2c= -ast-types-flow@0.0.7: - version "0.0.7" - resolved "https://registry.yarnpkg.com/ast-types-flow/-/ast-types-flow-0.0.7.tgz#f70b735c6bca1a5c9c22d982c3e39e7feba3bdad" - integrity sha1-9wtzXGvKGlycItmCw+Oef+ujva0= - async-each@^1.0.1: version "1.0.3" resolved "https://registry.yarnpkg.com/async-each/-/async-each-1.0.3.tgz#b727dbf87d7651602f06f4d4ac387f47d91b0cbf" @@ -1178,7 +1082,7 @@ chalk@^1.0.0, chalk@^1.1.1, chalk@^1.1.3: strip-ansi "^3.0.0" supports-color "^2.0.0" -chalk@^2.0.0, chalk@^2.0.1, chalk@^2.4.1, chalk@^2.4.2: +chalk@^2.0.0, chalk@^2.4.1, chalk@^2.4.2: version "2.4.2" resolved "https://registry.yarnpkg.com/chalk/-/chalk-2.4.2.tgz#cd42541677a54333cf541a49108c1432b44c9424" integrity sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ== @@ -1374,7 +1278,7 @@ combined-stream@^1.0.6, combined-stream@~1.0.6: dependencies: delayed-stream "~1.0.0" -commander@^2.11.0, commander@^2.20.0, commander@^2.9.0: +commander@^2.20.0, commander@^2.9.0: version "2.20.3" resolved "https://registry.yarnpkg.com/commander/-/commander-2.20.3.tgz#fd485e84c03eb4881c20722ba48035e8531aeb33" integrity sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ== @@ -1547,21 +1451,6 @@ crypto-browserify@^3.11.0: randombytes "^2.0.0" randomfill "^1.0.3" -css.escape@^1.5.1: - version "1.5.1" - resolved "https://registry.yarnpkg.com/css.escape/-/css.escape-1.5.1.tgz#42e27d4fa04ae32f931a4b4d4191fa9cddee97cb" - integrity sha1-QuJ9T6BK4y+TGktNQZH6nN3ul8s= - -css@^2.2.3: - version "2.2.4" - resolved "https://registry.yarnpkg.com/css/-/css-2.2.4.tgz#c646755c73971f2bba6a601e2cf2fd71b1298929" - integrity sha512-oUnjmWpy0niI3x/mPL8dVEI1l7MnG3+HHyRPHf+YFSbK+svOhXpmSOcDURUh2aOCgl2grzrOPt1nHLuCVFULLw== - dependencies: - inherits "^2.0.3" - source-map "^0.6.1" - source-map-resolve "^0.5.2" - urix "^0.1.0" - currently-unhandled@^0.4.1: version "0.4.1" resolved "https://registry.yarnpkg.com/currently-unhandled/-/currently-unhandled-0.4.1.tgz#988df33feab191ef799a61369dd76c17adf957ea" @@ -1795,11 +1684,6 @@ des.js@^1.0.0: inherits "^2.0.1" minimalistic-assert "^1.0.0" -diff-sequences@^24.9.0: - version "24.9.0" - resolved "https://registry.yarnpkg.com/diff-sequences/-/diff-sequences-24.9.0.tgz#5715d6244e2aa65f48bba0bc972db0b0b11e95b5" - integrity sha512-Dj6Wk3tWyTE+Fo1rW8v0Xhwk80um6yFYKbuAxc9c3EZxIHFDYwbi34Uk42u1CdnIiVorvt4RmlSDjIPyzGC2ew== - diffie-hellman@^5.0.0: version "5.0.3" resolved "https://registry.yarnpkg.com/diffie-hellman/-/diffie-hellman-5.0.3.tgz#40e8ee98f55a2149607146921c63e1ae5f3d2875" @@ -3220,31 +3104,6 @@ isstream@~0.1.2: resolved "https://registry.yarnpkg.com/isstream/-/isstream-0.1.2.tgz#47e63f7af55afa6f92e1500e690eb8b8529c099a" integrity sha1-R+Y/evVa+m+S4VAOaQ64uFKcCZo= -jest-diff@^24.0.0, jest-diff@^24.9.0: - version "24.9.0" - resolved "https://registry.yarnpkg.com/jest-diff/-/jest-diff-24.9.0.tgz#931b7d0d5778a1baf7452cb816e325e3724055da" - integrity sha512-qMfrTs8AdJE2iqrTp0hzh7kTd2PQWrsFyj9tORoKmu32xjPjeE4NyjVRDz8ybYwqS2ik8N4hsIpiVTyFeo2lBQ== - dependencies: - chalk "^2.0.1" - diff-sequences "^24.9.0" - jest-get-type "^24.9.0" - pretty-format "^24.9.0" - -jest-get-type@^24.9.0: - version "24.9.0" - resolved "https://registry.yarnpkg.com/jest-get-type/-/jest-get-type-24.9.0.tgz#1684a0c8a50f2e4901b6644ae861f579eed2ef0e" - integrity sha512-lUseMzAley4LhIcpSP9Jf+fTrQ4a1yHQwLNeeVa2cEmbCGeoZAtYPOIv8JaxLD/sUpKxetKGP+gsHl8f8TSj8Q== - -jest-matcher-utils@^24.0.0: - version "24.9.0" - resolved "https://registry.yarnpkg.com/jest-matcher-utils/-/jest-matcher-utils-24.9.0.tgz#f5b3661d5e628dffe6dd65251dfdae0e87c3a073" - integrity sha512-OZz2IXsu6eaiMAwe67c1T+5tUAtQyQx27/EMEkbFAGiw52tB9em+uGbzpcgYVpA8wl0hlxKPZxrly4CXU/GjHA== - dependencies: - chalk "^2.0.1" - jest-diff "^24.9.0" - jest-get-type "^24.9.0" - pretty-format "^24.9.0" - js-base64@^2.1.8: version "2.6.3" resolved "https://registry.yarnpkg.com/js-base64/-/js-base64-2.6.3.tgz#7afdb9b57aa7717e15d370b66e8f36a9cb835dc3" @@ -3484,11 +3343,6 @@ lodash@^4.0.0, lodash@^4.17.15, lodash@^4.17.19, lodash@^4.17.4, lodash@^4.3.0, resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.19.tgz#e48ddedbe30b3321783c5b4301fbd353bc1e4a4b" integrity sha512-JNvd8XER9GQX0v2qJgsaN/mzFCNA5BRe/j8JN9d+tWyGLSodKQHKFicdwNYzWwI3wjRnaKPsGj1XkBjx/F96DQ== -lodash@^4.17.11: - version "4.17.20" - resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.20.tgz#b44a9b6297bcb698f1c51a3545a2b3b368d59c52" - integrity sha512-PlhdFcillOINfeV7Ni6oF1TAEayyZBoZ8bcshTHqOYJYlrqzRK5hagpagky5o4HfCzzd1TRkXPMFq6cKk9rGmA== - log-symbols@^1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/log-symbols/-/log-symbols-1.0.2.tgz#376ff7b58ea3086a0f09facc74617eca501e1a18" @@ -3687,11 +3541,6 @@ min-document@^2.19.0: dependencies: dom-walk "^0.1.0" -min-indent@^1.0.0: - version "1.0.1" - resolved "https://registry.yarnpkg.com/min-indent/-/min-indent-1.0.1.tgz#a63f681673b30571fbe8bc25686ae746eefa9869" - integrity sha512-I9jwMn07Sy/IwOj3zVkVik2JTvgpaykDZEigL6Rx6N9LbMywwUSMtxET+7lVoDLLd3O3IXwJwvuuns8UB/HeAg== - minimalistic-assert@^1.0.0, minimalistic-assert@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/minimalistic-assert/-/minimalistic-assert-1.0.1.tgz#2e194de044626d4a10e7f7fbc00ce73e83e4d5c7" @@ -4365,16 +4214,6 @@ posix-character-classes@^0.1.0: resolved "https://registry.yarnpkg.com/posix-character-classes/-/posix-character-classes-0.1.1.tgz#01eac0fe3b5af71a2a6c02feabb8c1fef7e00eab" integrity sha1-AerA/jta9xoqbAL+q7jB/vfgDqs= -pretty-format@^24.0.0, pretty-format@^24.8.0, pretty-format@^24.9.0: - version "24.9.0" - resolved "https://registry.yarnpkg.com/pretty-format/-/pretty-format-24.9.0.tgz#12fac31b37019a4eea3c11aa9a959eb7628aa7c9" - integrity sha512-00ZMZUiHaJrNfk33guavqgvfJS30sLYf0f8+Srklv0AMPodGGHcoHgksZ3OThYnIvOd+8yMCn0YiEOogjlgsnA== - dependencies: - "@jest/types" "^24.9.0" - ansi-regex "^4.0.0" - ansi-styles "^3.2.0" - react-is "^16.8.4" - process-nextick-args@^2.0.0, process-nextick-args@~2.0.0: version "2.0.1" resolved "https://registry.yarnpkg.com/process-nextick-args/-/process-nextick-args-2.0.1.tgz#7820d9b16120cc55ca9ae7792680ae7dba6d7fe2" @@ -4524,7 +4363,7 @@ react-fast-compare@^1.0.0: resolved "https://registry.yarnpkg.com/react-fast-compare/-/react-fast-compare-1.0.0.tgz#813a039155e49b43ceffe99528fe5e9d97a6c938" integrity sha512-dcQpdWr62flXQJuM8/bVEY5/10ad2SYBUafp8H4q4WHR3fTA/MMlp8mpzX12I0CCoEJc1P6QdiMg7U+7lFS6Rw== -react-is@^16.8.1, react-is@^16.8.4: +react-is@^16.8.1: version "16.13.1" resolved "https://registry.yarnpkg.com/react-is/-/react-is-16.13.1.tgz#789729a4dc36de2999dc156dd6c1d9c18cea56a4" integrity sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ== @@ -4660,14 +4499,6 @@ redent@^1.0.0: indent-string "^2.1.0" strip-indent "^1.0.1" -redent@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/redent/-/redent-3.0.0.tgz#e557b7998316bb53c9f1f56fa626352c6963059f" - integrity sha512-6tDA8g98We0zd0GvVeMT9arEOnTw9qM03L9cJXaCjrip1OO764RDBLBfrB4cwzNGDj5OA5ioymC9GkizgWJDUg== - dependencies: - indent-string "^4.0.0" - strip-indent "^3.0.0" - regenerator-runtime@^0.13.4: version "0.13.5" resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.13.5.tgz#d878a1d094b4306d10b9096484b33ebd55e26697" @@ -5077,7 +4908,7 @@ source-list-map@^2.0.0: resolved "https://registry.yarnpkg.com/source-list-map/-/source-list-map-2.0.1.tgz#3993bd873bfc48479cca9ea3a547835c7c154b34" integrity sha512-qnQ7gVMxGNxsiL4lEuJwe/To8UnK7fAnmbGEEH8RpLouuKbeEm0lhbQVFIrNSuB+G7tVrAlVsZgETT5nljf+Iw== -source-map-resolve@^0.5.0, source-map-resolve@^0.5.2: +source-map-resolve@^0.5.0: version "0.5.3" resolved "https://registry.yarnpkg.com/source-map-resolve/-/source-map-resolve-0.5.3.tgz#190866bece7553e1f8f267a2ee82c606b5509a1a" integrity sha512-Htz+RnsXWk5+P2slx5Jh3Q66vhQj1Cllm0zvnaY98+NFx+Dv2CF/f5O/t8x+KaNdrdIAsruNzoh/KpialbqAnw== @@ -5387,13 +5218,6 @@ strip-indent@^1.0.1: dependencies: get-stdin "^4.0.1" -strip-indent@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/strip-indent/-/strip-indent-3.0.0.tgz#c32e1cee940b6b3432c771bc2c54bcce73cd3001" - integrity sha512-laJTa3Jb+VQpaC6DseHhF7dXVqHTfJPCRDaEbid/drOhgitgYku/letMUqOXFoWV0zIIUbjpdH2t+tYj4bQMRQ== - dependencies: - min-indent "^1.0.0" - supports-color@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-2.0.0.tgz#535d045ce6b6363fa40117084629995e9df324c7" @@ -5820,11 +5644,6 @@ vm-browserify@^1.0.1: resolved "https://registry.yarnpkg.com/vm-browserify/-/vm-browserify-1.1.2.tgz#78641c488b8e6ca91a75f511e7a3b32a86e5dda0" integrity sha512-2ham8XPWTONajOR0ohOKOHXkm3+gaBmGut3SRuu75xLd/RRaY6vqgh8NBYYk7+RW3u5AtzPQZG8F10LHkl0lAQ== -wait-for-expect@^1.2.0: - version "1.3.0" - resolved "https://registry.yarnpkg.com/wait-for-expect/-/wait-for-expect-1.3.0.tgz#65241ce355425f907f5d127bdb5e72c412ff830c" - integrity sha512-8fJU7jiA96HfGPt+P/UilelSAZfhMBJ52YhKzlmZQvKEZU2EcD1GQ0yqGB6liLdHjYtYAoGVigYwdxr5rktvzA== - warning@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/warning/-/warning-3.0.0.tgz#32e5377cb572de4ab04753bdf8821c01ed605b7c"