From ec5598b5956b90c64f5996df4c9c1e7f7f68a53d Mon Sep 17 00:00:00 2001 From: AWSHurneyt Date: Thu, 9 Dec 2021 15:15:26 -0800 Subject: [PATCH 1/7] Implemented unit and integ tests for the alerts dashboard flyout. Refactored AlertsDashboardFlyoutComponent::getBucketLevelGraphConditions to return a string with line breaks instead of an array of HTML elements. Signed-off-by: AWSHurneyt Signed-off-by: AWSHurneyt --- ...le_alerts_flyout_bucket_level_monitor.json | 134 +++ ...ple_alerts_flyout_query_level_monitor.json | 101 ++ .../alerts_dashboard_flyout_spec.js | 287 +++++ .../Flyout/flyouts/alertsDashboard.js | 229 +--- .../AlertsDashboardFlyoutComponent.js | 586 +++++++++++ .../DashboardControls/DashboardControls.js | 7 +- .../DashboardControls.test.js.snap | 1 + .../pages/Dashboard/containers/Dashboard.js | 172 ++- .../__snapshots__/Dashboard.test.js.snap | 6 + public/pages/Dashboard/utils/helpers.js | 83 +- public/pages/Dashboard/utils/helpers.test.js | 994 ++++++++++++++++++ public/pages/Dashboard/utils/tableUtils.js | 34 +- .../Monitors/containers/Monitors/Monitors.js | 23 +- 13 files changed, 2305 insertions(+), 352 deletions(-) create mode 100644 cypress/fixtures/sample_alerts_flyout_bucket_level_monitor.json create mode 100644 cypress/fixtures/sample_alerts_flyout_query_level_monitor.json create mode 100644 cypress/integration/alerts_dashboard_flyout_spec.js create mode 100644 public/components/Flyout/flyouts/components/AlertsDashboardFlyoutComponent.js create mode 100644 public/pages/Dashboard/utils/helpers.test.js diff --git a/cypress/fixtures/sample_alerts_flyout_bucket_level_monitor.json b/cypress/fixtures/sample_alerts_flyout_bucket_level_monitor.json new file mode 100644 index 000000000..3647ed055 --- /dev/null +++ b/cypress/fixtures/sample_alerts_flyout_bucket_level_monitor.json @@ -0,0 +1,134 @@ +{ + "name": "sample_alerts_flyout_bucket_level_monitor", + "type": "monitor", + "monitor_type": "bucket_level_monitor", + "enabled": true, + "schedule": { + "period": { + "unit": "MINUTES", + "interval": 1 + } + }, + "inputs": [ + { + "search": { + "indices": ["opensearch_dashboards_sample_data_ecommerce"], + "query": { + "size": 0, + "aggregations": { + "composite_agg": { + "composite": { + "sources": [ + { + "customer_gender": { + "terms": { + "field": "customer_gender" + } + } + }, + { + "user": { + "terms": { + "field": "user" + } + } + } + ] + }, + "aggs": { + "avg_products_price": { + "avg": { + "field": "products.price" + } + } + } + } + }, + "query": { + "bool": { + "filter": [ + { + "range": { + "order_date": { + "gte": "{{period_end}}||-10d", + "lte": "{{period_end}}", + "format": "epoch_millis" + } + } + } + ] + } + } + } + } + } + ], + "triggers": [ + { + "bucket_level_trigger": { + "id": "JHpsfH0BYHgJ26-yS5n7", + "name": "sample_alerts_flyout_bucket_level_trigger", + "severity": "4", + "condition": { + "buckets_path": { + "_count": "_count", + "avg_products_price": "avg_products_price" + }, + "parent_bucket_path": "composite_agg", + "script": { + "source": "params._count < 10000 || params.avg_products_price == 10", + "lang": "painless" + }, + "gap_policy": "skip" + }, + "actions": [] + } + } + ], + "ui_metadata": { + "schedule": { + "timezone": null, + "frequency": "interval", + "period": { + "unit": "MINUTES", + "interval": 1 + }, + "daily": 0, + "weekly": { + "tue": false, + "wed": false, + "thur": false, + "sat": false, + "fri": false, + "mon": false, + "sun": false + }, + "monthly": { + "type": "day", + "day": 1 + }, + "cronExpression": "0 */1 * * *" + }, + "search": { + "searchType": "graph", + "timeField": "order_date", + "aggregations": [ + { + "aggregationType": "avg", + "fieldName": "products.price" + } + ], + "groupBy": ["customer_gender", "user"], + "bucketValue": 10, + "bucketUnitOfTime": "d", + "where": { + "fieldName": [], + "fieldRangeEnd": 0, + "fieldRangeStart": 0, + "fieldValue": "", + "operator": "is" + } + }, + "monitor_type": "bucket_level_monitor" + } +} diff --git a/cypress/fixtures/sample_alerts_flyout_query_level_monitor.json b/cypress/fixtures/sample_alerts_flyout_query_level_monitor.json new file mode 100644 index 000000000..ce9753d5e --- /dev/null +++ b/cypress/fixtures/sample_alerts_flyout_query_level_monitor.json @@ -0,0 +1,101 @@ +{ + "name": "sample_alerts_flyout_query_level_monitor", + "type": "monitor", + "monitor_type": "query_level_monitor", + "enabled": true, + "schedule": { + "period": { + "unit": "MINUTES", + "interval": 1 + } + }, + "inputs": [ + { + "search": { + "indices": ["opensearch_dashboards_sample_data_ecommerce"], + "query": { + "size": 0, + "aggregations": { + "terms_agg": { + "terms": { + "field": "user" + } + } + }, + "query": { + "bool": { + "filter": [ + { + "range": { + "order_date": { + "gte": "{{period_end}}||-10d", + "lte": "{{period_end}}", + "format": "epoch_millis" + } + } + } + ] + } + } + } + } + } + ], + "triggers": [ + { + "query_level_trigger": { + "id": "YHpufH0BYHgJ26-yhJm-", + "name": "sample_alerts_flyout_query_level_trigger", + "severity": "2", + "condition": { + "script": { + "source": "ctx.results[0].hits.total.value < 10000", + "lang": "painless" + } + }, + "actions": [] + } + } + ], + "ui_metadata": { + "schedule": { + "timezone": null, + "frequency": "interval", + "period": { + "unit": "MINUTES", + "interval": 1 + }, + "daily": 0, + "weekly": { + "tue": false, + "wed": false, + "thur": false, + "sat": false, + "fri": false, + "mon": false, + "sun": false + }, + "monthly": { + "type": "day", + "day": 1 + }, + "cronExpression": "0 */1 * * *" + }, + "search": { + "searchType": "graph", + "timeField": "order_date", + "aggregations": [], + "groupBy": ["user"], + "bucketValue": 10, + "bucketUnitOfTime": "d", + "where": { + "fieldName": [], + "fieldRangeEnd": 0, + "fieldRangeStart": 0, + "fieldValue": "", + "operator": "is" + } + }, + "monitor_type": "query_level_monitor" + } +} diff --git a/cypress/integration/alerts_dashboard_flyout_spec.js b/cypress/integration/alerts_dashboard_flyout_spec.js new file mode 100644 index 000000000..87feb174b --- /dev/null +++ b/cypress/integration/alerts_dashboard_flyout_spec.js @@ -0,0 +1,287 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * + * The OpenSearch Contributors require contributions made to + * this file be licensed under the Apache-2.0 license or a + * compatible open source license. + * + * Modifications Copyright OpenSearch Contributors. See + * GitHub history for details. + */ + +/* + * Copyright 2021 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 from 'react'; +import { INDEX, PLUGIN_NAME } from '../support/constants'; +import sampleAlertsFlyoutBucketMonitor from '../fixtures/sample_alerts_flyout_bucket_level_monitor.json'; +import sampleAlertsFlyoutQueryMonitor from '../fixtures/sample_alerts_flyout_query_level_monitor.json'; + +const BUCKET_MONITOR = 'sample_alerts_flyout_bucket_level_monitor'; +const BUCKET_TRIGGER = 'sample_alerts_flyout_bucket_level_trigger'; +const QUERY_MONITOR = 'sample_alerts_flyout_query_level_monitor'; +const QUERY_TRIGGER = 'sample_alerts_flyout_query_level_trigger'; + +describe('Alerts by trigger flyout', () => { + before(() => { + // Delete any existing monitors + cy.deleteAllMonitors(); + + // Load sample data + cy.loadSampleEcommerceData(); + + // Create the test monitors + cy.createMonitor(sampleAlertsFlyoutBucketMonitor); + cy.createMonitor(sampleAlertsFlyoutQueryMonitor); + cy.wait(10000); + + // Visit Alerting OpenSearch Dashboards + cy.visit(`${Cypress.env('opensearch_dashboards')}/app/${PLUGIN_NAME}#/monitors`); + + // Confirm test monitors were created successfully + cy.contains(BUCKET_MONITOR); + cy.contains(QUERY_MONITOR); + + // Wait 1.5 minute for the test monitors to trigger alerts, then go to the 'Alerts by trigger' dashboard page to view alerts + cy.wait(90000); + cy.visit(`${Cypress.env('opensearch_dashboards')}/app/${PLUGIN_NAME}#/dashboard`); + }); + + beforeEach(() => { + // Reloading the page to close any flyouts that were not closed by other tests that had failures. + cy.reload(); + cy.wait(10000); + }); + + it('Bucket-level monitor flyout test', () => { + // Click the link for the flyout. + cy.get(`[data-test-subj="euiLink_${BUCKET_TRIGGER}"]`).click(); + + // Wait for the flyout to load the trigger-specific alerts. + cy.wait(10000); + + // Perform the test checks within the flyout component. + cy.get(`[data-test-subj="alertsDashboardFlyout_${BUCKET_TRIGGER}"]`).within(() => { + // Confirm flyout header contains expected text. + cy.get(`[data-test-subj="alertsDashboardFlyout_header_${BUCKET_TRIGGER}"]`).contains( + `Alerts by ${BUCKET_TRIGGER}` + ); + + // Confirm 'Trigger name' sections renders as expected. + cy.get(`[data-test-subj="alertsDashboardFlyout_triggerName_${BUCKET_TRIGGER}"]`).contains( + 'Trigger name' + ); + cy.get(`[data-test-subj="alertsDashboardFlyout_triggerName_${BUCKET_TRIGGER}"]`).contains( + BUCKET_TRIGGER + ); + + // Confirm 'Severity' sections renders as expected. + cy.get(`[data-test-subj="alertsDashboardFlyout_severity_${BUCKET_TRIGGER}"]`).contains( + 'Severity' + ); + cy.get(`[data-test-subj="alertsDashboardFlyout_severity_${BUCKET_TRIGGER}"]`).contains( + '4 (Low)' + ); + + // Confirm 'Monitor' sections renders as expected. + cy.get(`[data-test-subj="alertsDashboardFlyout_monitor_${BUCKET_TRIGGER}"]`).contains( + 'Monitor' + ); + cy.get(`[data-test-subj="alertsDashboardFlyout_monitor_${BUCKET_TRIGGER}"]`).contains( + BUCKET_MONITOR + ); + + // Confirm 'Conditions' sections renders as expected. + cy.get(`[data-test-subj="alertsDashboardFlyout_conditions_${BUCKET_TRIGGER}"]`).contains( + 'Conditions' + ); + + // Confirm the 'Conditions' sections renders with all of the expected conditions. + ['params._count < 10000', 'OR', 'params.avg_products_price == 10'].forEach((entry) => + cy + .get(`[data-test-subj="alertsDashboardFlyout_conditions_${BUCKET_TRIGGER}"]`) + .contains(entry) + ); + + // Confirm 'Time range for the last' sections renders as expected. + cy.get(`[data-test-subj="alertsDashboardFlyout_timeRange_${BUCKET_TRIGGER}"]`).contains( + 'Time range for the last' + ); + cy.get(`[data-test-subj="alertsDashboardFlyout_timeRange_${BUCKET_TRIGGER}"]`).contains( + '10 day(s)' + ); + + // Confirm 'Filters' sections renders as expected. + cy.get(`[data-test-subj="alertsDashboardFlyout_filters_${BUCKET_TRIGGER}"]`).contains( + 'Filters' + ); + cy.get(`[data-test-subj="alertsDashboardFlyout_filters_${BUCKET_TRIGGER}"]`).contains( + 'All fields are included' + ); + + // Confirm 'Group by' sections renders as expected. + cy.get(`[data-test-subj="alertsDashboardFlyout_groupBy_${BUCKET_TRIGGER}"]`).contains( + 'Group by' + ); + cy.get(`[data-test-subj="alertsDashboardFlyout_groupBy_${BUCKET_TRIGGER}"]`).contains( + 'customer_gender, user' + ); + + // Set the 'severity' filter to only display ACTIVE alerts. + cy.get('[data-test-subj="dashboardAlertStateFilter"]').select('Active'); + cy.wait(10000); + + // This monitor configuration consistently returns 46 alerts when testing locally. + // Confirm the flyout dashboard contains more than 1 ACTIVE alert. + cy.get('tbody > tr').should(($tr) => expect($tr).to.have.length.greaterThan(1)); + + // Select the first and last alerts in the table. + cy.get('input[data-test-subj^="checkboxSelectRow-"]').first().click(); + cy.get('input[data-test-subj^="checkboxSelectRow-"]').last().click(); + + // Press the flyout 'Acknowledge button, and wait for the AcknowledgeAlerts API call to complete. + cy.get('[data-test-subj="flyoutAcknowledgeAlertsButton"]').click(); + }); + + // Confirm acknowledge alerts toast displays expected text. + cy.contains('Successfully acknowledged 2 alerts.'); + + // Confirm alerts were acknowledged as expected. + cy.get(`[data-test-subj="alertsDashboardFlyout_${BUCKET_TRIGGER}"]`).within(() => { + // Wait for GetAlerts API call to complete. + cy.wait(10000); + + // Set the 'severity' filter to only display ACKNOWLEDGED alerts. + cy.get('[data-test-subj="dashboardAlertStateFilter"]').select('Acknowledged'); + cy.wait(10000); + + // Confirm the table displays 2 acknowledged alerts. + cy.get('tbody > tr').should(($tr) => expect($tr).to.have.length(2)); + }); + + // Confirm close button hides the flyout. + cy.get(`[data-test-subj="alertsDashboardFlyout_closeButton_${BUCKET_TRIGGER}"]`).click(); + cy.contains(`[data-test-subj="alertsDashboardFlyout_${BUCKET_TRIGGER}"]`).should('not.exist'); + }); + + it('Query-level monitor flyout test', () => { + // Click the link for the flyout. + cy.get(`[data-test-subj="euiLink_${QUERY_TRIGGER}"]`).click(); + + // Wait for the flyout to load the trigger-specific alerts. + cy.wait(10000); + + // Perform the test checks within the flyout component. + cy.get(`[data-test-subj="alertsDashboardFlyout_${QUERY_TRIGGER}"]`).within(() => { + // Confirm flyout header contains expected text. + cy.get(`[data-test-subj="alertsDashboardFlyout_header_${QUERY_TRIGGER}"]`).contains( + `Alerts by ${QUERY_TRIGGER}` + ); + + // Confirm 'Trigger name' sections renders as expected. + cy.get(`[data-test-subj="alertsDashboardFlyout_triggerName_${QUERY_TRIGGER}"]`).contains( + 'Trigger name' + ); + cy.get(`[data-test-subj="alertsDashboardFlyout_triggerName_${QUERY_TRIGGER}"]`).contains( + QUERY_TRIGGER + ); + + // Confirm 'Severity' sections renders as expected. + cy.get(`[data-test-subj="alertsDashboardFlyout_severity_${QUERY_TRIGGER}"]`).contains( + 'Severity' + ); + cy.get(`[data-test-subj="alertsDashboardFlyout_severity_${QUERY_TRIGGER}"]`).contains( + '2 (High)' + ); + + // Confirm 'Monitor' sections renders as expected. + cy.get(`[data-test-subj="alertsDashboardFlyout_monitor_${QUERY_TRIGGER}"]`).contains( + 'Monitor' + ); + cy.get(`[data-test-subj="alertsDashboardFlyout_monitor_${QUERY_TRIGGER}"]`).contains( + QUERY_MONITOR + ); + + // Confirm 'Conditions' sections renders as expected. + cy.get(`[data-test-subj="alertsDashboardFlyout_conditions_${QUERY_TRIGGER}"]`).contains( + 'Condition' + ); + cy.get(`[data-test-subj="alertsDashboardFlyout_conditions_${QUERY_TRIGGER}"]`).contains( + `ctx.results[0].hits.total.value < 10000` + ); + + // Confirm 'Time range for the last' sections renders as expected. + cy.get(`[data-test-subj="alertsDashboardFlyout_timeRange_${QUERY_TRIGGER}"]`).contains( + 'Time range for the last' + ); + cy.get(`[data-test-subj="alertsDashboardFlyout_timeRange_${QUERY_TRIGGER}"]`).contains( + '10 day(s)' + ); + + // Confirm 'Filters' sections renders as expected. + cy.get(`[data-test-subj="alertsDashboardFlyout_filters_${QUERY_TRIGGER}"]`).contains( + 'Filters' + ); + cy.get(`[data-test-subj="alertsDashboardFlyout_filters_${QUERY_TRIGGER}"]`).contains('-'); + + // Confirm 'Group by' sections renders as expected. + cy.get(`[data-test-subj="alertsDashboardFlyout_groupBy_${QUERY_TRIGGER}"]`).contains( + 'Group by' + ); + cy.get(`[data-test-subj="alertsDashboardFlyout_groupBy_${QUERY_TRIGGER}"]`).contains('user'); + + // Set the 'severity' filter to only display ACTIVE alerts. + cy.get('[data-test-subj="dashboardAlertStateFilter"]').select('Active'); + cy.wait(10000); + + // Confirm the flyout dashboard contains 1 alert. + cy.get('tbody > tr').should(($tr) => expect($tr).to.have.length(1)); + + // Select the alert. + cy.get('input[data-test-subj^="checkboxSelectRow-"]').first().click(); + + // Press the flyout 'Acknowledge button, and wait for the AcknowledgeAlerts API call to complete. + cy.get('[data-test-subj="flyoutAcknowledgeAlertsButton"]').click(); + }); + + // Confirm acknowledge alerts toast displays expected text. + cy.contains('Successfully acknowledged 1 alert.'); + + // Confirm alerts were acknowledged as expected. + cy.get(`[data-test-subj="alertsDashboardFlyout_${QUERY_TRIGGER}"]`).within(() => { + // Wait for GetAlerts API call to complete. + cy.wait(10000); + + // Set the 'severity' filter to only display ACKNOWLEDGED alerts. + cy.get('[data-test-subj="dashboardAlertStateFilter"]').select('Acknowledged'); + cy.wait(10000); + + // Confirm the table displays 1 acknowledged alert. + cy.get('tbody > tr').should(($tr) => expect($tr).to.have.length(1)); + }); + + // Confirm close button hides the flyout. + cy.get(`[data-test-subj="alertsDashboardFlyout_closeButton_${QUERY_TRIGGER}"]`).click(); + cy.contains(`[data-test-subj="alertsDashboardFlyout_${QUERY_TRIGGER}"]`).should('not.exist'); + }); + + after(() => { + // Delete all monitors + cy.deleteAllMonitors(); + + // Delete sample data + cy.deleteIndexByName(`${INDEX.SAMPLE_DATA_ECOMMERCE}`); + }); +}); diff --git a/public/components/Flyout/flyouts/alertsDashboard.js b/public/components/Flyout/flyouts/alertsDashboard.js index 17bf4df95..eeabf967f 100644 --- a/public/components/Flyout/flyouts/alertsDashboard.js +++ b/public/components/Flyout/flyouts/alertsDashboard.js @@ -25,121 +25,21 @@ */ import React from 'react'; -import _ from 'lodash'; -import { - EuiButtonEmpty, - EuiFlexGroup, - EuiFlexItem, - EuiHorizontalRule, - EuiLink, - EuiSpacer, - EuiText, -} from '@elastic/eui'; -import { getTime } from '../../../pages/MonitorDetails/components/MonitorOverview/utils/getOverviewStats'; -import { PLUGIN_NAME } from '../../../../utils/constants'; -import { - MONITOR_GROUP_BY, - MONITOR_INPUT_DETECTOR_ID, - MONITOR_TYPE, - SEARCH_TYPE, -} from '../../../utils/constants'; -import { TRIGGER_TYPE } from '../../../pages/CreateTrigger/containers/CreateTrigger/utils/constants'; -import { SEVERITY_OPTIONS } from '../../../pages/CreateTrigger/containers/DefineTrigger/DefineTrigger'; -import Dashboard from '../../../pages/Dashboard/containers/Dashboard'; -import { UNITS_OF_TIME } from '../../../pages/CreateMonitor/components/MonitorExpressions/expressions/utils/constants'; -import { DEFAULT_WHERE_EXPRESSION_TEXT } from '../../../pages/CreateMonitor/components/MonitorExpressions/expressions/utils/whereHelpers'; - -export const DEFAULT_NUM_FLYOUT_ROWS = 10; - -const getBucketLevelGraphConditions = (trigger) => { - let conditions = _.get(trigger, 'condition.script.source', '-'); - conditions = _.replace(conditions, ' && ', '&AND&'); - conditions = _.replace(conditions, ' || ', '&OR&'); - conditions = conditions.split(/&/); - return conditions.map((condition, index) => { - return ( -

- {condition} -

- ); - }); -}; - -const getSeverityText = (severity) => { - return _.get(_.find(SEVERITY_OPTIONS, { value: severity }), 'text'); -}; - -const getBucketLevelGraphFilter = (trigger) => { - const compositeAggFilter = _.get(trigger, 'condition.composite_agg_filter'); - if (_.isEmpty(compositeAggFilter)) return DEFAULT_WHERE_EXPRESSION_TEXT; - const keyword = _.keys(compositeAggFilter)[0]; - const operator = _.keys(compositeAggFilter[keyword])[0]; - const value = _.get(compositeAggFilter, `${keyword}.${operator}`); - return `${keyword} ${_.upperCase(operator)}S ${value}`; -}; +import { EuiButtonEmpty, EuiText } from '@elastic/eui'; +import AlertsDashboardFlyoutComponent from './components/AlertsDashboardFlyoutComponent'; const alertsDashboard = (payload) => { - const { - alerts, - history, - httpClient, - last_notification_time, - loadingMonitors, - location, - monitors, - monitor_id, - monitor_name, - notifications, - setFlyout, - start_time, - triggerID, - trigger_name, - } = payload; - const monitor = _.get(_.find(monitors, { _id: monitor_id }), '_source'); - const monitorType = _.get(monitor, 'monitor_type', MONITOR_TYPE.QUERY_LEVEL); - const searchType = _.get(monitor, 'ui_metadata.search.searchType', SEARCH_TYPE.GRAPH); - const detectorId = _.get(monitor, MONITOR_INPUT_DETECTOR_ID); - - const triggerType = - monitorType === MONITOR_TYPE.QUERY_LEVEL ? TRIGGER_TYPE.QUERY_LEVEL : TRIGGER_TYPE.BUCKET_LEVEL; - - let trigger = _.get(monitor, 'triggers', []).find((trigger) => { - return trigger[triggerType].id === triggerID; - }); - trigger = _.get(trigger, triggerType); - - const severity = _.get(trigger, 'severity'); - const groupBy = _.get(monitor, MONITOR_GROUP_BY); - - const condition = - monitorType === MONITOR_TYPE.BUCKET_LEVEL && searchType === SEARCH_TYPE.GRAPH - ? getBucketLevelGraphConditions(trigger) - : _.get(trigger, 'condition.script.source', '-'); - - const filters = - monitorType === MONITOR_TYPE.BUCKET_LEVEL && searchType === SEARCH_TYPE.GRAPH - ? getBucketLevelGraphFilter(trigger) - : '-'; - - const bucketValue = _.get(monitor, 'ui_metadata.search.bucketValue'); - let bucketUnitOfTime = _.get(monitor, 'ui_metadata.search.bucketUnitOfTime'); - UNITS_OF_TIME.map((entry) => { - if (entry.value === bucketUnitOfTime) bucketUnitOfTime = entry.text; - }); - const timeRangeForLast = - bucketValue !== undefined && !_.isEmpty(bucketUnitOfTime) - ? `${bucketValue} ${bucketUnitOfTime}` - : '-'; - + const { closeFlyout, trigger_name } = payload; return { flyoutProps: { 'aria-labelledby': 'alertsDashboardFlyout', size: 'm', hideCloseButton: true, + 'data-test-subj': `alertsDashboardFlyout_${trigger_name}`, }, headerProps: { hasBorder: true }, header: ( - +

{`Alerts by ${trigger_name}`}

), @@ -147,127 +47,14 @@ const alertsDashboard = (payload) => { footer: ( setFlyout(null)} + onClick={() => closeFlyout()} style={{ paddingLeft: '0px', marginLeft: '0px' }} + data-test-subj={`alertsDashboardFlyout_closeButton_${trigger_name}`} > Close ), - body: ( -
- - - - Trigger name -

{trigger_name}

-
-
- - - Severity -

{getSeverityText(severity) || severity || '-'}

-
-
-
- - - - - - - Trigger start time -

{getTime(start_time)}

-
-
- - - Trigger last updated -

{getTime(last_notification_time)}

-
-
-
- - - - - - - Monitor -

- {monitor_name} -

-
-
-
- - - - - - - {_.isArray(condition) ? 'Conditions' : 'Condition'} - {loadingMonitors ? ( - 'Loading conditions...' - ) : _.isArray(condition) ? ( - condition - ) : ( -

{condition}

- )} -
-
- - - Time range for the last -

{timeRangeForLast}

-
-
-
- - - - - - - Filters -

{loadingMonitors ? 'Loading filters...' : filters}

-
-
- - - Group by -

- {loadingMonitors - ? 'Loading groups...' - : !_.isEmpty(groupBy) - ? _.join(_.orderBy(groupBy), ', ') - : '-'} -

-
-
-
- - - - - - - - - -
- ), + body: , }; }; diff --git a/public/components/Flyout/flyouts/components/AlertsDashboardFlyoutComponent.js b/public/components/Flyout/flyouts/components/AlertsDashboardFlyoutComponent.js new file mode 100644 index 000000000..77ef4eb82 --- /dev/null +++ b/public/components/Flyout/flyouts/components/AlertsDashboardFlyoutComponent.js @@ -0,0 +1,586 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * + * The OpenSearch Contributors require contributions made to + * this file be licensed under the Apache-2.0 license or a + * compatible open source license. + * + * Modifications Copyright OpenSearch Contributors. See + * GitHub history for details. + */ + +/* + * Copyright 2021 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, { Component } from 'react'; +import _ from 'lodash'; +import { + EuiBasicTable, + EuiButton, + EuiFlexGroup, + EuiFlexItem, + EuiHorizontalRule, + EuiIcon, + EuiLink, + EuiSpacer, + EuiText, +} from '@elastic/eui'; +import { getTime } from '../../../../pages/MonitorDetails/components/MonitorOverview/utils/getOverviewStats'; +import { PLUGIN_NAME } from '../../../../../utils/constants'; +import { + ALERT_STATE, + MONITOR_GROUP_BY, + MONITOR_INPUT_DETECTOR_ID, + MONITOR_TYPE, + OPENSEARCH_DASHBOARDS_AD_PLUGIN, + SEARCH_TYPE, +} from '../../../../utils/constants'; +import { TRIGGER_TYPE } from '../../../../pages/CreateTrigger/containers/CreateTrigger/utils/constants'; +import { SEVERITY_OPTIONS } from '../../../../pages/CreateTrigger/containers/DefineTrigger/DefineTrigger'; +import { UNITS_OF_TIME } from '../../../../pages/CreateMonitor/components/MonitorExpressions/expressions/utils/constants'; +import { DEFAULT_WHERE_EXPRESSION_TEXT } from '../../../../pages/CreateMonitor/components/MonitorExpressions/expressions/utils/whereHelpers'; +import { backendErrorNotification } from '../../../../utils/helpers'; +import { + displayAcknowledgedAlertsToast, + filterActiveAlerts, + getQueryObjectFromState, + getURLQueryParams, + insertGroupByColumn, + removeColumns, +} from '../../../../pages/Dashboard/utils/helpers'; +import DashboardControls from '../../../../pages/Dashboard/components/DashboardControls'; +import ContentPanel from '../../../ContentPanel'; +import { queryColumns } from '../../../../pages/Dashboard/utils/tableUtils'; +import { DEFAULT_PAGE_SIZE_OPTIONS } from '../../../../pages/Monitors/containers/Monitors/utils/constants'; +import queryString from 'query-string'; +import { MAX_ALERT_COUNT } from '../../../../pages/Dashboard/utils/constants'; + +export const DEFAULT_NUM_FLYOUT_ROWS = 10; + +export default class AlertsDashboardFlyoutComponent extends Component { + constructor(props) { + super(props); + const { location, monitor_id } = this.props; + + const { + alertState, + from, + search, + severityLevel, + size, + sortDirection, + sortField, + } = getURLQueryParams(location); + + this.state = { + alerts: [], + alertState: alertState, + loading: true, + monitors: [], + monitorIds: [monitor_id], + page: Math.floor(from / size), + search: search, + selectable: true, + selectedItems: [], + severityLevel: severityLevel, + size: DEFAULT_NUM_FLYOUT_ROWS, + sortDirection: sortDirection, + sortField: sortField, + totalAlerts: 0, + }; + } + + componentDidMount() { + const { + alertState, + page, + search, + severityLevel, + size, + sortDirection, + sortField, + monitorIds, + } = this.state; + this.getAlerts( + page * size, + size, + search, + sortField, + sortDirection, + severityLevel, + alertState, + monitorIds + ); + } + + componentDidUpdate(prevProps, prevState) { + const prevQuery = getQueryObjectFromState(prevState); + const currQuery = getQueryObjectFromState(this.state); + if (!_.isEqual(prevQuery, currQuery)) { + const { + page, + size, + search, + sortField, + sortDirection, + severityLevel, + alertState, + monitorIds, + } = this.state; + this.getAlerts( + page * size, + size, + search, + sortField, + sortDirection, + severityLevel, + alertState, + monitorIds + ); + } + } + + getBucketLevelGraphConditions = (trigger) => { + let conditions = _.get(trigger, 'condition.script.source', '-'); + conditions = _.replace(conditions, ' && ', '&AND&'); + conditions = _.replace(conditions, ' || ', '&OR&'); + conditions = conditions.split(/&/); + return conditions.join('\n'); + }; + + getSeverityText = (severity) => { + return _.get(_.find(SEVERITY_OPTIONS, { value: severity }), 'text'); + }; + + getBucketLevelGraphFilter = (trigger) => { + const compositeAggFilter = _.get(trigger, 'condition.composite_agg_filter'); + if (_.isEmpty(compositeAggFilter)) return DEFAULT_WHERE_EXPRESSION_TEXT; + const keyword = _.keys(compositeAggFilter)[0]; + const operator = _.keys(compositeAggFilter[keyword])[0]; + const value = _.get(compositeAggFilter, `${keyword}.${operator}`); + return `${keyword} ${_.upperCase(operator)}S ${value}`; + }; + + getAlerts = async () => { + this.setState({ ...this.state, loading: true }); + const { + from, + search, + sortField, + sortDirection, + severityLevel, + alertState, + monitorIds, + } = this.state; + + const { httpClient, history, notifications, triggerID } = this.props; + + const params = { + from, + size: MAX_ALERT_COUNT, + search, + sortField, + sortDirection, + severityLevel, + alertState, + monitorIds, + }; + + const queryParamsString = queryString.stringify(params); + history.replace({ ...this.props.location, search: queryParamsString }); + + httpClient.get('../api/alerting/alerts', { query: params }).then((resp) => { + if (resp.ok) { + const { alerts } = resp; + const filteredAlerts = _.filter(alerts, { trigger_id: triggerID }); + this.setState({ + ...this.state, + alerts: filteredAlerts, + totalAlerts: filteredAlerts.length, + }); + } else { + console.log('error getting alerts:', resp); + backendErrorNotification(notifications, 'get', 'alerts', resp.err); + } + }); + + this.setState({ ...this.state, loading: false }); + }; + + acknowledgeAlerts = async () => { + const { selectedItems } = this.state; + const { httpClient, notifications } = this.props; + + if (!selectedItems.length) return; + + const selectedAlerts = filterActiveAlerts(selectedItems); + + const monitorAlerts = selectedAlerts.reduce((monitorAlerts, alert) => { + const { id, monitor_id: monitorId } = alert; + if (monitorAlerts[monitorId]) monitorAlerts[monitorId].push(id); + else monitorAlerts[monitorId] = [id]; + return monitorAlerts; + }, {}); + + const promises = Object.entries(monitorAlerts).map(([monitorId, alerts]) => + httpClient + .post(`../api/alerting/monitors/${monitorId}/_acknowledge/alerts`, { + body: JSON.stringify({ alerts }), + }) + .then((resp) => { + if (!resp.ok) { + backendErrorNotification(notifications, 'acknowledge', 'alert', resp.resp); + } else { + const successfulCount = _.get(resp, 'resp.success', []).length; + displayAcknowledgedAlertsToast(notifications, successfulCount); + } + }) + .catch((error) => error) + ); + + const values = await Promise.all(promises); + console.log('values:', values); + // // TODO: Show which values failed, succeeded, etc. + const { + page, + size, + search, + sortField, + sortDirection, + severityLevel, + alertState, + monitorIds, + } = this.state; + await this.getAlerts( + page * size, + size, + search, + sortField, + sortDirection, + severityLevel, + alertState, + monitorIds + ); + this.setState({ ...this.state, selectedItems: [] }); + this.props.refreshDashboard(); + }; + + onAlertStateChange = (e) => { + this.setState({ page: 0, alertState: e.target.value }); + }; + + onPageClick = (page) => { + this.setState({ page }); + }; + + onSearchChange = (e) => { + this.setState({ page: 0, search: e.target.value }); + }; + + onSelectionChange = (selectedItems) => { + this.setState({ selectedItems }); + }; + + onTableChange = ({ page: tablePage = {}, sort = {} }) => { + const { index: page, size } = tablePage; + + const { field: sortField, direction: sortDirection } = sort; + this.setState({ + page, + size, + sortField, + sortDirection, + }); + + const { alerts } = this.props; + this.setState({ alerts }); + }; + + render() { + const { + last_notification_time, + loadingMonitors, + monitors, + monitor_id, + monitor_name, + start_time, + triggerID, + trigger_name, + } = this.props; + const monitor = _.get(_.find(monitors, { _id: monitor_id }), '_source'); + const monitorType = _.get(monitor, 'monitor_type', MONITOR_TYPE.QUERY_LEVEL); + const searchType = _.get(monitor, 'ui_metadata.search.searchType', SEARCH_TYPE.GRAPH); + const detectorId = _.get(monitor, MONITOR_INPUT_DETECTOR_ID); + + const triggerType = + monitorType === MONITOR_TYPE.QUERY_LEVEL + ? TRIGGER_TYPE.QUERY_LEVEL + : TRIGGER_TYPE.BUCKET_LEVEL; + + let trigger = _.get(monitor, 'triggers', []).find((trigger) => { + return trigger[triggerType].id === triggerID; + }); + trigger = _.get(trigger, triggerType); + + const severity = _.get(trigger, 'severity'); + const groupBy = _.get(monitor, MONITOR_GROUP_BY); + + const condition = + monitorType === MONITOR_TYPE.BUCKET_LEVEL && searchType === SEARCH_TYPE.GRAPH + ? this.getBucketLevelGraphConditions(trigger) + : _.get(trigger, 'condition.script.source', '-'); + + const filters = + monitorType === MONITOR_TYPE.BUCKET_LEVEL && searchType === SEARCH_TYPE.GRAPH + ? this.getBucketLevelGraphFilter(trigger) + : '-'; + + const bucketValue = _.get(monitor, 'ui_metadata.search.bucketValue'); + let bucketUnitOfTime = _.get(monitor, 'ui_metadata.search.bucketUnitOfTime'); + UNITS_OF_TIME.map((entry) => { + if (entry.value === bucketUnitOfTime) bucketUnitOfTime = entry.text; + }); + const timeRangeForLast = + bucketValue !== undefined && !_.isEmpty(bucketUnitOfTime) + ? `${bucketValue} ${bucketUnitOfTime}` + : '-'; + + const actions = () => { + const { selectedItems } = this.state; + const actions = [ + + Acknowledge + , + ]; + if (!_.isEmpty(detectorId)) { + actions.unshift( + + View detector + + ); + } + return actions; + }; + + const getItemId = (item) => { + switch (monitorType) { + case MONITOR_TYPE.QUERY_LEVEL: + return `${item.id}-${item.version}`; + case MONITOR_TYPE.BUCKET_LEVEL: + return item.id; + } + }; + + const { + alerts, + alertState, + loading, + page, + search, + selectable, + severityLevel, + size, + sortDirection, + sortField, + totalAlerts, + } = this.state; + + const columnType = () => { + let columns = []; + switch (monitorType) { + case MONITOR_TYPE.QUERY_LEVEL: + columns = queryColumns; + break; + case MONITOR_TYPE.BUCKET_LEVEL: + columns = insertGroupByColumn(groupBy); + break; + } + return removeColumns(['severity', 'trigger_name'], columns); + }; + + const pagination = { + pageIndex: page, + pageSize: size, + totalItemCount: totalAlerts, + pageSizeOptions: DEFAULT_PAGE_SIZE_OPTIONS, + }; + + const selection = { + onSelectionChange: this.onSelectionChange, + selectable: (item) => item.state === ALERT_STATE.ACTIVE, + selectableMessage: (selectable) => + selectable ? undefined : 'Only active alerts can be acknowledged.', + }; + + const sorting = { + sort: { + direction: sortDirection, + field: sortField, + }, + }; + + const trimmedAlerts = alerts.slice(page * size, page * size + size); + + return ( +
+ + + + Trigger name +

{trigger_name}

+
+
+ + + Severity +

{this.getSeverityText(severity) || severity || '-'}

+
+
+
+ + + + + + + Trigger start time +

{getTime(start_time)}

+
+
+ + + Trigger last updated +

{getTime(last_notification_time)}

+
+
+
+ + + + + + + Monitor +

+ {monitor_name} +

+
+
+
+ + + + + + + + {monitorType === MONITOR_TYPE.BUCKET_LEVEL ? 'Conditions' : 'Condition'} + +

+ {loadingMonitors || loading ? 'Loading conditions...' : condition} +

+
+
+ + + Time range for the last +

{timeRangeForLast}

+
+
+
+ + + + + + + Filters +

{loadingMonitors || loading ? 'Loading filters...' : filters}

+
+
+ + + Group by +

+ {loadingMonitors || loading + ? 'Loading groups...' + : !_.isEmpty(groupBy) + ? _.join(_.orderBy(groupBy), ', ') + : '-'} +

+
+
+
+ + + + + + + + + + + + + + + +
+ ); + } +} diff --git a/public/pages/Dashboard/components/DashboardControls/DashboardControls.js b/public/pages/Dashboard/components/DashboardControls/DashboardControls.js index 645f6e636..3b728921a 100644 --- a/public/pages/Dashboard/components/DashboardControls/DashboardControls.js +++ b/public/pages/Dashboard/components/DashboardControls/DashboardControls.js @@ -75,7 +75,12 @@ const DashboardControls = ({ )} - + diff --git a/public/pages/Dashboard/components/DashboardControls/__snapshots__/DashboardControls.test.js.snap b/public/pages/Dashboard/components/DashboardControls/__snapshots__/DashboardControls.test.js.snap index 03257100b..32801493a 100644 --- a/public/pages/Dashboard/components/DashboardControls/__snapshots__/DashboardControls.test.js.snap +++ b/public/pages/Dashboard/components/DashboardControls/__snapshots__/DashboardControls.test.js.snap @@ -103,6 +103,7 @@ exports[`DashboardControls renders 1`] = ` > { const triggerID = alert.trigger_id; @@ -39,7 +40,6 @@ export function groupAlertsByTrigger(alerts) { : addFirstAlert(alert); alertsByTriggers.set(triggerID, newAlertList); }); - return Array.from(alertsByTriggers, ([triggerID, alerts]) => ({ ...alerts, triggerID })); } @@ -69,6 +69,7 @@ export function addFirstAlert(firstAlert) { monitor_id, }; } + export function addAlert(alertList, newAlert) { const state = newAlert.state; alertList[state]++; @@ -87,7 +88,7 @@ export const renderEmptyValue = (value) => { return value === undefined ? DEFAULT_EMPTY_DATA : value; }; -export function insertGroupByColumn(groupBy) { +export function insertGroupByColumn(groupBy = []) { let result = _.cloneDeep(bucketColumns); groupBy.map((fieldName) => result.splice(0, 0, { @@ -101,14 +102,70 @@ export function insertGroupByColumn(groupBy) { return result; } -export function removeColumns(columnFieldNames = [], allColumns) { - return allColumns.filter((column) => { - return !_.includes(columnFieldNames, column.field); - }); +export function removeColumns(columnFieldNames = [], allColumns = []) { + return allColumns.filter((column) => !_.includes(columnFieldNames, column.field)); +} + +export function getInitialSize( + perAlertView = false, + defaultSize = DEFAULT_GET_ALERTS_QUERY_PARAMS.size +) { + return perAlertView && defaultSize >= 0 ? defaultSize : MAX_ALERT_COUNT; +} + +export function displayAcknowledgedAlertsToast(notifications, successfulCount = 0) { + const successfulText = `Successfully acknowledged ${successfulCount} ${ + successfulCount === 1 ? 'alert' : 'alerts' + }.`; + if (successfulCount > 0) notifications.toasts.addSuccess(successfulText); +} + +export function filterActiveAlerts(alerts = []) { + return _.filter(alerts, { state: ALERT_STATE.ACTIVE }); } -export function getInitialSize(isAlertsFlyout, perAlertView, defaultSize) { - if (!perAlertView) return MAX_ALERT_COUNT; - if (isAlertsFlyout) return DEFAULT_NUM_FLYOUT_ROWS; - return defaultSize; +export function getQueryObjectFromState({ + page, + size, + search, + sortField, + sortDirection, + severityLevel, + alertState, + monitorIds, + flyoutIsOpen, +}) { + return { + page, + size, + search, + sortField, + sortDirection, + severityLevel, + alertState, + monitorIds, + flyoutIsOpen, + }; +} + +export function getURLQueryParams(location) { + const { + from = DEFAULT_GET_ALERTS_QUERY_PARAMS.from, + size = DEFAULT_GET_ALERTS_QUERY_PARAMS.size, + search = DEFAULT_GET_ALERTS_QUERY_PARAMS.search, + sortField = DEFAULT_GET_ALERTS_QUERY_PARAMS.sortField, + sortDirection = DEFAULT_GET_ALERTS_QUERY_PARAMS.sortDirection, + severityLevel = DEFAULT_GET_ALERTS_QUERY_PARAMS.severityLevel, + alertState = DEFAULT_GET_ALERTS_QUERY_PARAMS.alertState, + } = queryString.parse(location.search); + + return { + from: isNaN(parseInt(from, 10)) ? DEFAULT_GET_ALERTS_QUERY_PARAMS.from : parseInt(from, 10), + size: isNaN(parseInt(size, 10)) ? DEFAULT_GET_ALERTS_QUERY_PARAMS.size : parseInt(size, 10), + search, + sortField, + sortDirection, + severityLevel, + alertState, + }; } diff --git a/public/pages/Dashboard/utils/helpers.test.js b/public/pages/Dashboard/utils/helpers.test.js new file mode 100644 index 000000000..a4618243a --- /dev/null +++ b/public/pages/Dashboard/utils/helpers.test.js @@ -0,0 +1,994 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * + * The OpenSearch Contributors require contributions made to + * this file be licensed under the Apache-2.0 license or a + * compatible open source license. + * + * Modifications Copyright OpenSearch Contributors. See + * GitHub history for details. + */ + +/* + * Copyright 2021 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 _ from 'lodash'; +import { + addAlert, + addFirstAlert, + displayAcknowledgedAlertsToast, + filterActiveAlerts, + getInitialSize, + getQueryObjectFromState, + getURLQueryParams, + groupAlertsByTrigger, + insertGroupByColumn, + removeColumns, + renderEmptyValue, +} from './helpers'; +import { ALERT_STATE, DEFAULT_EMPTY_DATA } from '../../../utils/constants'; +import { bucketColumns } from './tableUtils'; +import { DEFAULT_GET_ALERTS_QUERY_PARAMS, MAX_ALERT_COUNT } from './constants'; +import coreMock from '../../../../test/mocks/CoreMock'; + +describe('Dashboard/utils/helpers', () => { + describe('groupAlertsByTrigger', () => { + test('with empty alerts list', () => { + const alerts = []; + const expectedOutput = []; + expect(groupAlertsByTrigger(alerts)).toEqual(expectedOutput); + }); + + test('with undefined alerts list', () => { + const alerts = undefined; + const expectedOutput = []; + expect(groupAlertsByTrigger(alerts)).toEqual(expectedOutput); + }); + + test('with valid alerts list', () => { + const alerts = [ + { + id: 'bucket-monitor-trigger1-alert1', + monitor_name: 'bucket-monitor', + trigger_id: 'bucket-monitor-trigger1-id', + trigger_name: 'bucket-monitor-trigger1', + state: 'ACTIVE', + start_time: 1638360000000, + end_time: null, + agg_alert_content: { + parent_bucket_path: 'composite_agg', + bucket_keys: ['groupBy1KeywordValue', 'groupBy2KeywordValue'], + bucket: { + doc_count: 10, + field1: { + value: 100, + }, + field2: { + value: 200, + }, + key: { + groupBy1: 'groupBy1Keyword', + groupBy2: 'groupBy2Keyword', + }, + }, + }, + }, + { + id: 'bucket-monitor-trigger1-alert2', + monitor_name: 'bucket-monitor', + trigger_id: 'bucket-monitor-trigger1-id', + trigger_name: 'bucket-monitor-trigger1', + state: 'ACTIVE', + start_time: 1638360000000, + end_time: null, + agg_alert_content: { + parent_bucket_path: 'composite_agg', + bucket_keys: ['groupBy1KeywordValue', 'groupBy2KeywordValue'], + bucket: { + doc_count: 10, + field1: { + value: 100, + }, + field2: { + value: 200, + }, + key: { + groupBy1: 'groupBy1Keyword', + groupBy2: 'groupBy2Keyword', + }, + }, + }, + }, + { + id: 'bucket-monitor-trigger2-alert1', + monitor_name: 'bucket-monitor', + trigger_id: 'bucket-monitor-trigger2-id', + trigger_name: 'bucket-monitor-trigger2', + state: 'ACTIVE', + start_time: 1638361800000, + end_time: null, + agg_alert_content: { + parent_bucket_path: 'composite_agg', + bucket_keys: ['groupBy1KeywordValue', 'groupBy2KeywordValue'], + bucket: { + doc_count: 10, + field1: { + value: 100, + }, + field2: { + value: 200, + }, + key: { + groupBy1: 'groupBy1Keyword', + groupBy2: 'groupBy2Keyword', + }, + }, + }, + }, + { + id: 'query-monitor-trigger1-alert1', + monitor_name: 'query-monitor', + trigger_id: 'query-monitor-trigger1-id', + trigger_name: 'query-monitor-trigger1', + state: 'ACTIVE', + start_time: 1638360000000, + end_time: null, + }, + { + id: 'query-monitor-trigger2-alert1', + monitor_name: 'query-monitor', + trigger_id: 'query-monitor-trigger2-id', + trigger_name: 'query-monitor-trigger2', + state: 'ACTIVE', + start_time: 1638361800000, + end_time: null, + }, + ]; + const expectedOutput = [ + { + ACTIVE: 2, + ACKNOWLEDGED: 0, + ERROR: 0, + total: 2, + alerts: [ + { + id: 'bucket-monitor-trigger1-alert1', + monitor_name: 'bucket-monitor', + trigger_id: 'bucket-monitor-trigger1-id', + trigger_name: 'bucket-monitor-trigger1', + state: 'ACTIVE', + start_time: 1638360000000, + end_time: null, + agg_alert_content: { + parent_bucket_path: 'composite_agg', + bucket_keys: ['groupBy1KeywordValue', 'groupBy2KeywordValue'], + bucket: { + doc_count: 10, + field1: { + value: 100, + }, + field2: { + value: 200, + }, + key: { + groupBy1: 'groupBy1Keyword', + groupBy2: 'groupBy2Keyword', + }, + }, + }, + }, + { + id: 'bucket-monitor-trigger1-alert2', + monitor_name: 'bucket-monitor', + trigger_id: 'bucket-monitor-trigger1-id', + trigger_name: 'bucket-monitor-trigger1', + state: 'ACTIVE', + start_time: 1638360000000, + end_time: null, + agg_alert_content: { + parent_bucket_path: 'composite_agg', + bucket_keys: ['groupBy1KeywordValue', 'groupBy2KeywordValue'], + bucket: { + doc_count: 10, + field1: { + value: 100, + }, + field2: { + value: 200, + }, + key: { + groupBy1: 'groupBy1Keyword', + groupBy2: 'groupBy2Keyword', + }, + }, + }, + }, + ], + trigger_name: 'bucket-monitor-trigger1', + start_time: 1638360000000, + monitor_name: 'bucket-monitor', + triggerID: 'bucket-monitor-trigger1-id', + }, + { + ACTIVE: 1, + ACKNOWLEDGED: 0, + ERROR: 0, + total: 1, + alerts: [ + { + id: 'bucket-monitor-trigger2-alert1', + monitor_name: 'bucket-monitor', + trigger_id: 'bucket-monitor-trigger2-id', + trigger_name: 'bucket-monitor-trigger2', + state: 'ACTIVE', + start_time: 1638361800000, + end_time: null, + agg_alert_content: { + parent_bucket_path: 'composite_agg', + bucket_keys: ['groupBy1KeywordValue', 'groupBy2KeywordValue'], + bucket: { + doc_count: 10, + field1: { + value: 100, + }, + field2: { + value: 200, + }, + key: { + groupBy1: 'groupBy1Keyword', + groupBy2: 'groupBy2Keyword', + }, + }, + }, + }, + ], + trigger_name: 'bucket-monitor-trigger2', + start_time: 1638361800000, + monitor_name: 'bucket-monitor', + triggerID: 'bucket-monitor-trigger2-id', + }, + { + ACTIVE: 1, + ACKNOWLEDGED: 0, + ERROR: 0, + total: 1, + alerts: [ + { + id: 'query-monitor-trigger1-alert1', + monitor_name: 'query-monitor', + trigger_id: 'query-monitor-trigger1-id', + trigger_name: 'query-monitor-trigger1', + state: 'ACTIVE', + start_time: 1638360000000, + end_time: null, + }, + ], + trigger_name: 'query-monitor-trigger1', + start_time: 1638360000000, + monitor_name: 'query-monitor', + triggerID: 'query-monitor-trigger1-id', + }, + { + ACTIVE: 1, + ACKNOWLEDGED: 0, + ERROR: 0, + total: 1, + alerts: [ + { + id: 'query-monitor-trigger2-alert1', + monitor_name: 'query-monitor', + trigger_id: 'query-monitor-trigger2-id', + trigger_name: 'query-monitor-trigger2', + state: 'ACTIVE', + start_time: 1638361800000, + end_time: null, + }, + ], + trigger_name: 'query-monitor-trigger2', + start_time: 1638361800000, + monitor_name: 'query-monitor', + triggerID: 'query-monitor-trigger2-id', + }, + ]; + expect(groupAlertsByTrigger(alerts)).toEqual(expectedOutput); + }); + }); + + describe('addFirstAlert', () => { + test('with bucket-level alert', () => { + const alert = { + id: 'bucket-monitor-trigger1-alert1', + last_notification_time: null, + monitor_id: 'bucket-monitor-id', + monitor_name: 'bucket-monitor', + severity: 5, + trigger_id: 'bucket-monitor-trigger1-id', + trigger_name: 'bucket-monitor-trigger1', + version: 1, + state: 'ACTIVE', + start_time: 1638360000000, + end_time: null, + agg_alert_content: { + parent_bucket_path: 'composite_agg', + bucket_keys: ['groupBy1KeywordValue', 'groupBy2KeywordValue'], + bucket: { + doc_count: 10, + field1: { + value: 100, + }, + field2: { + value: 200, + }, + key: { + groupBy1: 'groupBy1Keyword', + groupBy2: 'groupBy2Keyword', + }, + }, + }, + }; + const expectedOutput = { + ACTIVE: 1, + ACKNOWLEDGED: 0, + ERROR: 0, + total: 1, + alerts: [ + { + id: 'bucket-monitor-trigger1-alert1', + last_notification_time: null, + monitor_id: 'bucket-monitor-id', + monitor_name: 'bucket-monitor', + severity: 5, + trigger_id: 'bucket-monitor-trigger1-id', + trigger_name: 'bucket-monitor-trigger1', + version: 1, + state: 'ACTIVE', + start_time: 1638360000000, + end_time: null, + agg_alert_content: { + parent_bucket_path: 'composite_agg', + bucket_keys: ['groupBy1KeywordValue', 'groupBy2KeywordValue'], + bucket: { + doc_count: 10, + field1: { + value: 100, + }, + field2: { + value: 200, + }, + key: { + groupBy1: 'groupBy1Keyword', + groupBy2: 'groupBy2Keyword', + }, + }, + }, + }, + ], + version: 1, + trigger_name: 'bucket-monitor-trigger1', + severity: 5, + start_time: 1638360000000, + last_notification_time: null, + monitor_name: 'bucket-monitor', + monitor_id: 'bucket-monitor-id', + }; + expect(addFirstAlert(alert)).toEqual(expectedOutput); + }); + test('with query-level alert', () => { + const alert = { + id: 'query-monitor-trigger1-alert1', + last_notification_time: null, + monitor_id: 'query-monitor-id', + monitor_name: 'query-monitor', + severity: 5, + trigger_id: 'query-monitor-trigger1-id', + trigger_name: 'query-monitor-trigger1', + version: 1, + state: 'ACTIVE', + start_time: 1638360000000, + end_time: null, + }; + const expectedOutput = { + ACTIVE: 1, + ACKNOWLEDGED: 0, + ERROR: 0, + total: 1, + alerts: [ + { + id: 'query-monitor-trigger1-alert1', + last_notification_time: null, + monitor_id: 'query-monitor-id', + monitor_name: 'query-monitor', + severity: 5, + trigger_id: 'query-monitor-trigger1-id', + trigger_name: 'query-monitor-trigger1', + version: 1, + state: 'ACTIVE', + start_time: 1638360000000, + end_time: null, + }, + ], + version: 1, + trigger_name: 'query-monitor-trigger1', + severity: 5, + start_time: 1638360000000, + last_notification_time: null, + monitor_name: 'query-monitor', + monitor_id: 'query-monitor-id', + }; + expect(addFirstAlert(alert)).toEqual(expectedOutput); + }); + }); + + describe('addAlert', () => { + test('with valid alerts list', () => { + const alert = { + id: 'bucket-monitor-trigger1-alert2', + monitor_name: 'bucket-monitor', + trigger_id: 'bucket-monitor-trigger1-id', + trigger_name: 'bucket-monitor-trigger1', + state: 'ACTIVE', + start_time: 1638360000000, + end_time: null, + agg_alert_content: { + parent_bucket_path: 'composite_agg', + bucket_keys: ['groupBy1KeywordValue', 'groupBy2KeywordValue'], + bucket: { + doc_count: 10, + field1: { + value: 100, + }, + field2: { + value: 200, + }, + key: { + groupBy1: 'groupBy1Keyword', + groupBy2: 'groupBy2Keyword', + }, + }, + }, + }; + const alertsList = { + ACTIVE: 1, + ACKNOWLEDGED: 0, + ERROR: 0, + total: 1, + alerts: [ + { + id: 'bucket-monitor-trigger1-alert1', + monitor_name: 'bucket-monitor', + trigger_id: 'bucket-monitor-trigger1-id', + trigger_name: 'bucket-monitor-trigger1', + state: 'ACTIVE', + start_time: 1638360000000, + end_time: null, + agg_alert_content: { + parent_bucket_path: 'composite_agg', + bucket_keys: ['groupBy1KeywordValue', 'groupBy2KeywordValue'], + bucket: { + doc_count: 10, + field1: { + value: 100, + }, + field2: { + value: 200, + }, + key: { + groupBy1: 'groupBy1Keyword', + groupBy2: 'groupBy2Keyword', + }, + }, + }, + }, + ], + trigger_name: 'bucket-monitor-trigger1', + start_time: 1638360000000, + monitor_name: 'bucket-monitor', + triggerID: 'bucket-monitor-trigger1-id', + }; + const expectedOutput = { + ACTIVE: 2, + ACKNOWLEDGED: 0, + ERROR: 0, + total: 2, + alerts: [ + { + id: 'bucket-monitor-trigger1-alert1', + monitor_name: 'bucket-monitor', + trigger_id: 'bucket-monitor-trigger1-id', + trigger_name: 'bucket-monitor-trigger1', + state: 'ACTIVE', + start_time: 1638360000000, + end_time: null, + agg_alert_content: { + parent_bucket_path: 'composite_agg', + bucket_keys: ['groupBy1KeywordValue', 'groupBy2KeywordValue'], + bucket: { + doc_count: 10, + field1: { + value: 100, + }, + field2: { + value: 200, + }, + key: { + groupBy1: 'groupBy1Keyword', + groupBy2: 'groupBy2Keyword', + }, + }, + }, + }, + { + id: 'bucket-monitor-trigger1-alert2', + monitor_name: 'bucket-monitor', + trigger_id: 'bucket-monitor-trigger1-id', + trigger_name: 'bucket-monitor-trigger1', + state: 'ACTIVE', + start_time: 1638360000000, + end_time: null, + agg_alert_content: { + parent_bucket_path: 'composite_agg', + bucket_keys: ['groupBy1KeywordValue', 'groupBy2KeywordValue'], + bucket: { + doc_count: 10, + field1: { + value: 100, + }, + field2: { + value: 200, + }, + key: { + groupBy1: 'groupBy1Keyword', + groupBy2: 'groupBy2Keyword', + }, + }, + }, + }, + ], + trigger_name: 'bucket-monitor-trigger1', + start_time: 1638360000000, + monitor_name: 'bucket-monitor', + triggerID: 'bucket-monitor-trigger1-id', + }; + const test = [ + { + ACTIVE: 2, + ACKNOWLEDGED: 0, + ERROR: 0, + total: 2, + alerts: [ + { + id: 'bucket-monitor-trigger1-alert1', + monitor_name: 'bucket-monitor', + trigger_id: 'bucket-monitor-trigger1-id', + trigger_name: 'bucket-monitor-trigger1', + state: 'ACTIVE', + start_time: 1638360000000, + end_time: null, + agg_alert_content: { + parent_bucket_path: 'composite_agg', + bucket_keys: ['groupBy1KeywordValue', 'groupBy2KeywordValue'], + bucket: { + doc_count: 10, + field1: { + value: 100, + }, + field2: { + value: 200, + }, + key: { + groupBy1: 'groupBy1Keyword', + groupBy2: 'groupBy2Keyword', + }, + }, + }, + }, + { + id: 'bucket-monitor-trigger1-alert2', + monitor_name: 'bucket-monitor', + trigger_id: 'bucket-monitor-trigger1-id', + trigger_name: 'bucket-monitor-trigger1', + state: 'ACTIVE', + start_time: 1638360000000, + end_time: null, + agg_alert_content: { + parent_bucket_path: 'composite_agg', + bucket_keys: ['groupBy1KeywordValue', 'groupBy2KeywordValue'], + bucket: { + doc_count: 10, + field1: { + value: 100, + }, + field2: { + value: 200, + }, + key: { + groupBy1: 'groupBy1Keyword', + groupBy2: 'groupBy2Keyword', + }, + }, + }, + }, + ], + trigger_name: 'bucket-monitor-trigger1', + start_time: 1638360000000, + monitor_name: 'bucket-monitor', + triggerID: 'bucket-monitor-trigger1-id', + }, + { + ACTIVE: 1, + ACKNOWLEDGED: 0, + ERROR: 0, + total: 1, + alerts: [ + { + id: 'bucket-monitor-trigger2-alert1', + monitor_name: 'bucket-monitor', + trigger_id: 'bucket-monitor-trigger2-id', + trigger_name: 'bucket-monitor-trigger2', + state: 'ACTIVE', + start_time: 1638361800000, + end_time: null, + agg_alert_content: { + parent_bucket_path: 'composite_agg', + bucket_keys: ['groupBy1KeywordValue', 'groupBy2KeywordValue'], + bucket: { + doc_count: 10, + field1: { + value: 100, + }, + field2: { + value: 200, + }, + key: { + groupBy1: 'groupBy1Keyword', + groupBy2: 'groupBy2Keyword', + }, + }, + }, + }, + ], + trigger_name: 'bucket-monitor-trigger2', + start_time: 1638361800000, + monitor_name: 'bucket-monitor', + triggerID: 'bucket-monitor-trigger2-id', + }, + { + ACTIVE: 1, + ACKNOWLEDGED: 0, + ERROR: 0, + total: 1, + alerts: [ + { + id: 'query-monitor-trigger1-alert1', + monitor_name: 'query-monitor', + trigger_id: 'query-monitor-trigger1-id', + trigger_name: 'query-monitor-trigger1', + state: 'ACTIVE', + start_time: 1638360000000, + end_time: null, + }, + ], + trigger_name: 'query-monitor-trigger1', + start_time: 1638360000000, + monitor_name: 'query-monitor', + triggerID: 'query-monitor-trigger1-id', + }, + { + ACTIVE: 1, + ACKNOWLEDGED: 0, + ERROR: 0, + total: 1, + alerts: [ + { + id: 'query-monitor-trigger2-alert1', + monitor_name: 'query-monitor', + trigger_id: 'query-monitor-trigger2-id', + trigger_name: 'query-monitor-trigger2', + state: 'ACTIVE', + start_time: 1638361800000, + end_time: null, + }, + ], + trigger_name: 'query-monitor-trigger2', + start_time: 1638361800000, + monitor_name: 'query-monitor', + triggerID: 'query-monitor-trigger2-id', + }, + ]; + expect(addAlert(alertsList, alert)).toEqual(expectedOutput); + }); + }); + + describe('renderEmptyValue', () => { + test('with empty value', () => { + const value = {}; + expect(renderEmptyValue(value)).toEqual(value); + }); + + test('with undefined value', () => { + const value = undefined; + expect(renderEmptyValue(value)).toEqual(DEFAULT_EMPTY_DATA); + }); + + test('with defined value', () => { + const value = { key: 'value' }; + expect(renderEmptyValue(value)).toEqual(value); + }); + }); + + describe('insertGroupByColumn', () => { + test('with empty groupBy list', () => { + const groupBy = []; + expect(insertGroupByColumn(groupBy)).toEqual(bucketColumns); + }); + + test('with undefined groupBy list', () => { + const groupBy = undefined; + expect(insertGroupByColumn(groupBy)).toEqual(bucketColumns); + }); + + test('with valid groupBy list', () => { + const groupBy = ['keyword1', 'keyword2', 'keyword3']; + const expectedOutput = _.cloneDeep(bucketColumns); + expectedOutput.unshift( + { + field: 'agg_alert_content.bucket.key.keyword3', + name: 'keyword3', + render: renderEmptyValue, + sortable: false, + truncateText: false, + }, + { + field: 'agg_alert_content.bucket.key.keyword2', + name: 'keyword2', + render: renderEmptyValue, + sortable: false, + truncateText: false, + }, + { + field: 'agg_alert_content.bucket.key.keyword1', + name: 'keyword1', + render: renderEmptyValue, + sortable: false, + truncateText: false, + } + ); + expect(insertGroupByColumn(groupBy)).toEqual(expectedOutput); + }); + }); + + describe('removeColumns', () => { + describe('with empty allColumns parameter', () => { + const allColumns = []; + test('with empty columnFieldNames', () => { + const columnFieldNames = []; + const expectedOutput = []; + expect(removeColumns(columnFieldNames, allColumns)).toEqual(expectedOutput); + }); + + test('with undefined columnFieldNames', () => { + const columnFieldNames = undefined; + const expectedOutput = []; + expect(removeColumns(columnFieldNames, allColumns)).toEqual(expectedOutput); + }); + + test('with valid columnFieldNames', () => { + const columnFieldNames = ['first column', 'third column']; + const expectedOutput = []; + expect(removeColumns(columnFieldNames, allColumns)).toEqual(expectedOutput); + }); + }); + describe('with undefined allColumns parameter', () => { + const allColumns = undefined; + test('with empty columnFieldNames', () => { + const columnFieldNames = []; + const expectedOutput = []; + expect(removeColumns(columnFieldNames, allColumns)).toEqual(expectedOutput); + }); + + test('with undefined columnFieldNames', () => { + const columnFieldNames = undefined; + const expectedOutput = []; + expect(removeColumns(columnFieldNames, allColumns)).toEqual(expectedOutput); + }); + + test('with valid columnFieldNames', () => { + const columnFieldNames = ['first column', 'third column']; + const expectedOutput = []; + expect(removeColumns(columnFieldNames, allColumns)).toEqual(expectedOutput); + }); + }); + describe('with valid allColumns parameter', () => { + const allColumns = [ + { field: 'first column' }, + { field: 'second column' }, + { field: 'third column' }, + { field: 'fourth column' }, + { field: 'fifth column' }, + ]; + + test('with empty columnFieldNames', () => { + const columnFieldNames = []; + expect(removeColumns(columnFieldNames, allColumns)).toEqual(allColumns); + }); + + test('with undefined columnFieldNames', () => { + const columnFieldNames = undefined; + expect(removeColumns(columnFieldNames, allColumns)).toEqual(allColumns); + }); + + test('with valid columnFieldNames', () => { + const columnFieldNames = ['first column', 'third column']; + const expectedOutput = [ + { field: 'second column' }, + { field: 'fourth column' }, + { field: 'fifth column' }, + ]; + expect(removeColumns(columnFieldNames, allColumns)).toEqual(expectedOutput); + }); + }); + }); + + describe('getInitialSize', () => { + describe('when perAlertView is false', () => { + const perAlertView = false; + test('when defaultSize is undefined', () => { + const defaultSize = undefined; + expect(getInitialSize(perAlertView, defaultSize)).toEqual(MAX_ALERT_COUNT); + }); + test('when defaultSize is 0', () => { + const defaultSize = 0; + expect(getInitialSize(perAlertView, defaultSize)).toEqual(MAX_ALERT_COUNT); + }); + test('when defaultSize is greater than 0', () => { + const defaultSize = 10; + expect(getInitialSize(perAlertView, defaultSize)).toEqual(MAX_ALERT_COUNT); + }); + test('when defaultSize is less than 0', () => { + const defaultSize = -10; + expect(getInitialSize(perAlertView, defaultSize)).toEqual(MAX_ALERT_COUNT); + }); + }); + describe('when perAlertView is true', () => { + const perAlertView = test('when defaultSize is undefined', () => { + const defaultSize = undefined; + expect(getInitialSize(perAlertView, defaultSize)).toEqual( + DEFAULT_GET_ALERTS_QUERY_PARAMS.size + ); + }); + test('when defaultSize is 0', () => { + const defaultSize = 0; + expect(getInitialSize(perAlertView, defaultSize)).toEqual(defaultSize); + }); + test('when defaultSize is greater than 0', () => { + const defaultSize = 10; + expect(getInitialSize(perAlertView, defaultSize)).toEqual(defaultSize); + }); + test('when defaultSize is less than 0', () => { + const defaultSize = -10; + expect(getInitialSize(perAlertView, defaultSize)).toEqual(MAX_ALERT_COUNT); + }); + }); + describe('when perAlertView is undefined', () => { + const perAlertView = undefined; + test('when defaultSize is undefined', () => { + const defaultSize = undefined; + expect(getInitialSize(perAlertView, defaultSize)).toEqual(MAX_ALERT_COUNT); + }); + test('when defaultSize is 0', () => { + const defaultSize = 0; + expect(getInitialSize(perAlertView, defaultSize)).toEqual(MAX_ALERT_COUNT); + }); + test('when defaultSize is greater than 0', () => { + const defaultSize = 10; + expect(getInitialSize(perAlertView, defaultSize)).toEqual(MAX_ALERT_COUNT); + }); + test('when defaultSize is less than 0', () => { + const defaultSize = -10; + expect(getInitialSize(perAlertView, defaultSize)).toEqual(MAX_ALERT_COUNT); + }); + }); + }); + + describe('displayAcknowledgedAlertsToast', () => { + let notifications; + beforeEach(() => { + notifications = _.cloneDeep(coreMock.notifications); + }); + test('when successfulCount is less than 1', () => { + displayAcknowledgedAlertsToast(notifications, 0); + expect(notifications.toasts.addSuccess).toHaveBeenCalledTimes(0); + }); + test('when successfulCount is undefined', () => { + displayAcknowledgedAlertsToast(notifications, undefined); + expect(notifications.toasts.addSuccess).toHaveBeenCalledTimes(0); + }); + test('when successfulCount is 1', () => { + displayAcknowledgedAlertsToast(notifications, 1); + expect(notifications.toasts.addSuccess).toHaveBeenCalledWith( + 'Successfully acknowledged 1 alert.' + ); + }); + test('when successfulCount is greater than 1', () => { + displayAcknowledgedAlertsToast(notifications, 10); + expect(notifications.toasts.addSuccess).toHaveBeenCalledWith( + 'Successfully acknowledged 10 alerts.' + ); + }); + }); + + describe('filterActiveAlerts', () => { + test('with empty alerts', () => { + const alerts = []; + expect(filterActiveAlerts(alerts)).toEqual([]); + }); + test('with undefined alerts', () => { + const alerts = undefined; + expect(filterActiveAlerts(alerts)).toEqual([]); + }); + test('with valid alerts', () => { + const alerts = [ + { state: ALERT_STATE.ACKNOWLEDGED }, + { state: ALERT_STATE.ACTIVE }, + { state: ALERT_STATE.ACTIVE }, + { state: ALERT_STATE.COMPLETED }, + { state: ALERT_STATE.DELETED }, + { state: ALERT_STATE.ERROR }, + ]; + const expectedOutput = [{ state: ALERT_STATE.ACTIVE }, { state: ALERT_STATE.ACTIVE }]; + expect(filterActiveAlerts(alerts)).toEqual(expectedOutput); + }); + }); + + test('getQueryObjectFromState', () => { + const expectedOutput = { + page: 1, + size: 10, + search: '', + sortField: 'start_time', + sortDirection: 'desc', + severityLevel: 'ALL', + alertState: 'ALL', + monitorIds: ['monitor1', 'monitor2', 'monitor3'], + flyoutIsOpen: true, + }; + const state = { + ...expectedOutput, + extraField1: 'extraField1', + extraField2: 'extraField2', + extraField3: 'extraField3', + }; + expect(getQueryObjectFromState(state)).toEqual(expectedOutput); + }); + + test('getURLQueryParams', () => { + const location = { + pathname: '/dashboard', + search: + '?alertState=ACKNOWLEDGED&from=0&search=searchTerm&severityLevel=5&size=10000&sortDirection=desc&sortField=start_time', + hash: '', + }; + const expectedOutput = { + from: 0, + size: 10000, + search: 'searchTerm', + sortField: 'start_time', + sortDirection: 'desc', + severityLevel: '5', + alertState: ALERT_STATE.ACKNOWLEDGED, + }; + expect(getURLQueryParams(location)).toEqual(expectedOutput); + }); +}); diff --git a/public/pages/Dashboard/utils/tableUtils.js b/public/pages/Dashboard/utils/tableUtils.js index a3ebf1129..8fc877db1 100644 --- a/public/pages/Dashboard/utils/tableUtils.js +++ b/public/pages/Dashboard/utils/tableUtils.js @@ -28,7 +28,6 @@ import React from 'react'; import _ from 'lodash'; import { EuiLink } from '@elastic/eui'; import moment from 'moment'; - import { ALERT_STATE, DEFAULT_EMPTY_DATA } from '../../../utils/constants'; import { PLUGIN_NAME } from '../../../../utils/constants'; @@ -142,19 +141,21 @@ export const alertColumns = ( location, monitors, notifications, - setFlyout + setFlyout, + openFlyout, + closeFlyout, + refreshDashboard ) => [ { field: 'total', name: 'Alerts', sortable: true, truncateText: false, - render: (total, alert) => ( - { - setFlyout({ - type: 'alertsDashboard', - payload: { + render: (total, alert) => { + return ( + { + openFlyout({ ...alert, history, httpClient, @@ -163,13 +164,16 @@ export const alertColumns = ( monitors, notifications, setFlyout, - }, - }); - }} - > - {`${total} alerts`} - - ), + closeFlyout, + refreshDashboard, + }); + }} + data-test-subj={`euiLink_${alert.trigger_name}`} + > + {`${total} alerts`} + + ); + }, }, { field: 'ACTIVE', diff --git a/public/pages/Monitors/containers/Monitors/Monitors.js b/public/pages/Monitors/containers/Monitors/Monitors.js index 910a69429..234fd4b55 100644 --- a/public/pages/Monitors/containers/Monitors/Monitors.js +++ b/public/pages/Monitors/containers/Monitors/Monitors.js @@ -10,25 +10,24 @@ */ /* - * Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * Copyright 2021 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 + * 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 + * 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. + * 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, { Component } from 'react'; import _ from 'lodash'; import queryString from 'query-string'; import { EuiBasicTable, EuiHorizontalRule } from '@elastic/eui'; - import AcknowledgeModal from '../../components/AcknowledgeModal'; import ContentPanel from '../../../../components/ContentPanel'; import MonitorActions from '../../components/MonitorActions'; @@ -39,6 +38,7 @@ import { getURLQueryParams } from './utils/helpers'; import { columns as staticColumns } from './utils/tableUtils'; import { MONITOR_ACTIONS } from '../../../../utils/constants'; import { backendErrorNotification } from '../../../../utils/helpers'; +import { displayAcknowledgedAlertsToast } from '../../../Dashboard/utils/helpers'; const MAX_MONITOR_COUNT = 1000; @@ -265,6 +265,9 @@ export default class Monitors extends Component { .then((resp) => { if (!resp.ok) { backendErrorNotification(notifications, 'acknowledge', 'alert', resp.resp); + } else { + const successfulCount = _.get(resp, 'resp.success', []).length; + displayAcknowledgedAlertsToast(notifications, successfulCount); } }) .catch((error) => error) From d84df6cd1cbae1c31c4daf2ee0abb0d7fe14b277 Mon Sep 17 00:00:00 2001 From: AWSHurneyt Date: Mon, 10 Jan 2022 11:27:14 -0800 Subject: [PATCH 2/7] Removed an unused test variable. Signed-off-by: AWSHurneyt --- public/pages/Dashboard/utils/helpers.test.js | 146 ------------------- 1 file changed, 146 deletions(-) diff --git a/public/pages/Dashboard/utils/helpers.test.js b/public/pages/Dashboard/utils/helpers.test.js index a4618243a..8188572c8 100644 --- a/public/pages/Dashboard/utils/helpers.test.js +++ b/public/pages/Dashboard/utils/helpers.test.js @@ -561,152 +561,6 @@ describe('Dashboard/utils/helpers', () => { monitor_name: 'bucket-monitor', triggerID: 'bucket-monitor-trigger1-id', }; - const test = [ - { - ACTIVE: 2, - ACKNOWLEDGED: 0, - ERROR: 0, - total: 2, - alerts: [ - { - id: 'bucket-monitor-trigger1-alert1', - monitor_name: 'bucket-monitor', - trigger_id: 'bucket-monitor-trigger1-id', - trigger_name: 'bucket-monitor-trigger1', - state: 'ACTIVE', - start_time: 1638360000000, - end_time: null, - agg_alert_content: { - parent_bucket_path: 'composite_agg', - bucket_keys: ['groupBy1KeywordValue', 'groupBy2KeywordValue'], - bucket: { - doc_count: 10, - field1: { - value: 100, - }, - field2: { - value: 200, - }, - key: { - groupBy1: 'groupBy1Keyword', - groupBy2: 'groupBy2Keyword', - }, - }, - }, - }, - { - id: 'bucket-monitor-trigger1-alert2', - monitor_name: 'bucket-monitor', - trigger_id: 'bucket-monitor-trigger1-id', - trigger_name: 'bucket-monitor-trigger1', - state: 'ACTIVE', - start_time: 1638360000000, - end_time: null, - agg_alert_content: { - parent_bucket_path: 'composite_agg', - bucket_keys: ['groupBy1KeywordValue', 'groupBy2KeywordValue'], - bucket: { - doc_count: 10, - field1: { - value: 100, - }, - field2: { - value: 200, - }, - key: { - groupBy1: 'groupBy1Keyword', - groupBy2: 'groupBy2Keyword', - }, - }, - }, - }, - ], - trigger_name: 'bucket-monitor-trigger1', - start_time: 1638360000000, - monitor_name: 'bucket-monitor', - triggerID: 'bucket-monitor-trigger1-id', - }, - { - ACTIVE: 1, - ACKNOWLEDGED: 0, - ERROR: 0, - total: 1, - alerts: [ - { - id: 'bucket-monitor-trigger2-alert1', - monitor_name: 'bucket-monitor', - trigger_id: 'bucket-monitor-trigger2-id', - trigger_name: 'bucket-monitor-trigger2', - state: 'ACTIVE', - start_time: 1638361800000, - end_time: null, - agg_alert_content: { - parent_bucket_path: 'composite_agg', - bucket_keys: ['groupBy1KeywordValue', 'groupBy2KeywordValue'], - bucket: { - doc_count: 10, - field1: { - value: 100, - }, - field2: { - value: 200, - }, - key: { - groupBy1: 'groupBy1Keyword', - groupBy2: 'groupBy2Keyword', - }, - }, - }, - }, - ], - trigger_name: 'bucket-monitor-trigger2', - start_time: 1638361800000, - monitor_name: 'bucket-monitor', - triggerID: 'bucket-monitor-trigger2-id', - }, - { - ACTIVE: 1, - ACKNOWLEDGED: 0, - ERROR: 0, - total: 1, - alerts: [ - { - id: 'query-monitor-trigger1-alert1', - monitor_name: 'query-monitor', - trigger_id: 'query-monitor-trigger1-id', - trigger_name: 'query-monitor-trigger1', - state: 'ACTIVE', - start_time: 1638360000000, - end_time: null, - }, - ], - trigger_name: 'query-monitor-trigger1', - start_time: 1638360000000, - monitor_name: 'query-monitor', - triggerID: 'query-monitor-trigger1-id', - }, - { - ACTIVE: 1, - ACKNOWLEDGED: 0, - ERROR: 0, - total: 1, - alerts: [ - { - id: 'query-monitor-trigger2-alert1', - monitor_name: 'query-monitor', - trigger_id: 'query-monitor-trigger2-id', - trigger_name: 'query-monitor-trigger2', - state: 'ACTIVE', - start_time: 1638361800000, - end_time: null, - }, - ], - trigger_name: 'query-monitor-trigger2', - start_time: 1638361800000, - monitor_name: 'query-monitor', - triggerID: 'query-monitor-trigger2-id', - }, - ]; expect(addAlert(alertsList, alert)).toEqual(expectedOutput); }); }); From ea0fb250bc7b6e917670ce5e7c07035aca7c53d4 Mon Sep 17 00:00:00 2001 From: AWSHurneyt Date: Mon, 10 Jan 2022 13:34:45 -0800 Subject: [PATCH 3/7] Removed debug logs. Signed-off-by: AWSHurneyt --- .../flyouts/components/AlertsDashboardFlyoutComponent.js | 5 +---- public/pages/Dashboard/containers/Dashboard.js | 5 +---- 2 files changed, 2 insertions(+), 8 deletions(-) diff --git a/public/components/Flyout/flyouts/components/AlertsDashboardFlyoutComponent.js b/public/components/Flyout/flyouts/components/AlertsDashboardFlyoutComponent.js index 77ef4eb82..d8c1619b7 100644 --- a/public/components/Flyout/flyouts/components/AlertsDashboardFlyoutComponent.js +++ b/public/components/Flyout/flyouts/components/AlertsDashboardFlyoutComponent.js @@ -234,7 +234,7 @@ export default class AlertsDashboardFlyoutComponent extends Component { return monitorAlerts; }, {}); - const promises = Object.entries(monitorAlerts).map(([monitorId, alerts]) => + Object.entries(monitorAlerts).map(([monitorId, alerts]) => httpClient .post(`../api/alerting/monitors/${monitorId}/_acknowledge/alerts`, { body: JSON.stringify({ alerts }), @@ -250,9 +250,6 @@ export default class AlertsDashboardFlyoutComponent extends Component { .catch((error) => error) ); - const values = await Promise.all(promises); - console.log('values:', values); - // // TODO: Show which values failed, succeeded, etc. const { page, size, diff --git a/public/pages/Dashboard/containers/Dashboard.js b/public/pages/Dashboard/containers/Dashboard.js index 931183123..444f2994f 100644 --- a/public/pages/Dashboard/containers/Dashboard.js +++ b/public/pages/Dashboard/containers/Dashboard.js @@ -232,7 +232,7 @@ export default class Dashboard extends Component { return monitorAlerts; }, {}); - const promises = Object.entries(monitorAlerts).map(([monitorId, alerts]) => + Object.entries(monitorAlerts).map(([monitorId, alerts]) => httpClient .post(`../api/alerting/monitors/${monitorId}/_acknowledge/alerts`, { body: JSON.stringify({ alerts }), @@ -248,9 +248,6 @@ export default class Dashboard extends Component { .catch((error) => error) ); - const values = await Promise.all(promises); - console.log('values:', values); - // // TODO: Show which values failed, succeeded, etc. const { page, size, From 9535be094f4e93dacaf2a85891163b8fad749670 Mon Sep 17 00:00:00 2001 From: AWSHurneyt Date: Fri, 14 Jan 2022 11:06:14 -0800 Subject: [PATCH 4/7] Implemented unit test. Refactored integration tests to use fewer wait periods. Signed-off-by: AWSHurneyt --- .../alerts_dashboard_flyout_spec.js | 151 +++++++++--------- .../__snapshots__/flyouts.test.js.snap | 40 +++++ .../components/Flyout/flyouts/flyouts.test.js | 7 + 3 files changed, 124 insertions(+), 74 deletions(-) diff --git a/cypress/integration/alerts_dashboard_flyout_spec.js b/cypress/integration/alerts_dashboard_flyout_spec.js index 87feb174b..dea7d2c2a 100644 --- a/cypress/integration/alerts_dashboard_flyout_spec.js +++ b/cypress/integration/alerts_dashboard_flyout_spec.js @@ -34,6 +34,8 @@ const BUCKET_TRIGGER = 'sample_alerts_flyout_bucket_level_trigger'; const QUERY_MONITOR = 'sample_alerts_flyout_query_level_monitor'; const QUERY_TRIGGER = 'sample_alerts_flyout_query_level_trigger'; +const TWENTY_SECONDS = 20000; + describe('Alerts by trigger flyout', () => { before(() => { // Delete any existing monitors @@ -45,63 +47,63 @@ describe('Alerts by trigger flyout', () => { // Create the test monitors cy.createMonitor(sampleAlertsFlyoutBucketMonitor); cy.createMonitor(sampleAlertsFlyoutQueryMonitor); - cy.wait(10000); // Visit Alerting OpenSearch Dashboards cy.visit(`${Cypress.env('opensearch_dashboards')}/app/${PLUGIN_NAME}#/monitors`); // Confirm test monitors were created successfully - cy.contains(BUCKET_MONITOR); - cy.contains(QUERY_MONITOR); + cy.contains(BUCKET_MONITOR, { timeout: TWENTY_SECONDS }); + cy.contains(QUERY_MONITOR, { timeout: TWENTY_SECONDS }); - // Wait 1.5 minute for the test monitors to trigger alerts, then go to the 'Alerts by trigger' dashboard page to view alerts - cy.wait(90000); - cy.visit(`${Cypress.env('opensearch_dashboards')}/app/${PLUGIN_NAME}#/dashboard`); + // Wait 1 minutes for the test monitors to trigger alerts, then go to the 'Alerts by trigger' dashboard page to view alerts + cy.wait(60000); }); beforeEach(() => { // Reloading the page to close any flyouts that were not closed by other tests that had failures. - cy.reload(); - cy.wait(10000); + cy.visit(`${Cypress.env('opensearch_dashboards')}/app/${PLUGIN_NAME}#/dashboard`); + + // Confirm dashboard is displaying rows for the test monitors. + cy.contains(BUCKET_MONITOR, { timeout: TWENTY_SECONDS }); + cy.contains(QUERY_MONITOR, { timeout: TWENTY_SECONDS }); }); it('Bucket-level monitor flyout test', () => { // Click the link for the flyout. cy.get(`[data-test-subj="euiLink_${BUCKET_TRIGGER}"]`).click(); - // Wait for the flyout to load the trigger-specific alerts. - cy.wait(10000); - // Perform the test checks within the flyout component. - cy.get(`[data-test-subj="alertsDashboardFlyout_${BUCKET_TRIGGER}"]`).within(() => { + cy.get(`[data-test-subj="alertsDashboardFlyout_${BUCKET_TRIGGER}"]`, { + timeout: TWENTY_SECONDS, + }).within(() => { // Confirm flyout header contains expected text. - cy.get(`[data-test-subj="alertsDashboardFlyout_header_${BUCKET_TRIGGER}"]`).contains( - `Alerts by ${BUCKET_TRIGGER}` - ); + cy.get( + `[data-test-subj="alertsDashboardFlyout_header_${BUCKET_TRIGGER}"]` + ).contains(`Alerts by ${BUCKET_TRIGGER}`, { timeout: TWENTY_SECONDS }); // Confirm 'Trigger name' sections renders as expected. cy.get(`[data-test-subj="alertsDashboardFlyout_triggerName_${BUCKET_TRIGGER}"]`).contains( 'Trigger name' ); - cy.get(`[data-test-subj="alertsDashboardFlyout_triggerName_${BUCKET_TRIGGER}"]`).contains( - BUCKET_TRIGGER - ); + cy.get( + `[data-test-subj="alertsDashboardFlyout_triggerName_${BUCKET_TRIGGER}"]` + ).contains(BUCKET_TRIGGER, { timeout: TWENTY_SECONDS }); // Confirm 'Severity' sections renders as expected. cy.get(`[data-test-subj="alertsDashboardFlyout_severity_${BUCKET_TRIGGER}"]`).contains( 'Severity' ); - cy.get(`[data-test-subj="alertsDashboardFlyout_severity_${BUCKET_TRIGGER}"]`).contains( - '4 (Low)' - ); + cy.get( + `[data-test-subj="alertsDashboardFlyout_severity_${BUCKET_TRIGGER}"]` + ).contains('4 (Low)', { timeout: TWENTY_SECONDS }); // Confirm 'Monitor' sections renders as expected. cy.get(`[data-test-subj="alertsDashboardFlyout_monitor_${BUCKET_TRIGGER}"]`).contains( 'Monitor' ); - cy.get(`[data-test-subj="alertsDashboardFlyout_monitor_${BUCKET_TRIGGER}"]`).contains( - BUCKET_MONITOR - ); + cy.get( + `[data-test-subj="alertsDashboardFlyout_monitor_${BUCKET_TRIGGER}"]` + ).contains(BUCKET_MONITOR, { timeout: TWENTY_SECONDS }); // Confirm 'Conditions' sections renders as expected. cy.get(`[data-test-subj="alertsDashboardFlyout_conditions_${BUCKET_TRIGGER}"]`).contains( @@ -112,40 +114,41 @@ describe('Alerts by trigger flyout', () => { ['params._count < 10000', 'OR', 'params.avg_products_price == 10'].forEach((entry) => cy .get(`[data-test-subj="alertsDashboardFlyout_conditions_${BUCKET_TRIGGER}"]`) - .contains(entry) + .contains(entry, { timeout: TWENTY_SECONDS }) ); // Confirm 'Time range for the last' sections renders as expected. cy.get(`[data-test-subj="alertsDashboardFlyout_timeRange_${BUCKET_TRIGGER}"]`).contains( 'Time range for the last' ); - cy.get(`[data-test-subj="alertsDashboardFlyout_timeRange_${BUCKET_TRIGGER}"]`).contains( - '10 day(s)' - ); + cy.get( + `[data-test-subj="alertsDashboardFlyout_timeRange_${BUCKET_TRIGGER}"]` + ).contains('10 day(s)', { timeout: TWENTY_SECONDS }); // Confirm 'Filters' sections renders as expected. cy.get(`[data-test-subj="alertsDashboardFlyout_filters_${BUCKET_TRIGGER}"]`).contains( 'Filters' ); - cy.get(`[data-test-subj="alertsDashboardFlyout_filters_${BUCKET_TRIGGER}"]`).contains( - 'All fields are included' - ); + cy.get( + `[data-test-subj="alertsDashboardFlyout_filters_${BUCKET_TRIGGER}"]` + ).contains('All fields are included', { timeout: TWENTY_SECONDS }); // Confirm 'Group by' sections renders as expected. cy.get(`[data-test-subj="alertsDashboardFlyout_groupBy_${BUCKET_TRIGGER}"]`).contains( 'Group by' ); - cy.get(`[data-test-subj="alertsDashboardFlyout_groupBy_${BUCKET_TRIGGER}"]`).contains( - 'customer_gender, user' - ); + cy.get( + `[data-test-subj="alertsDashboardFlyout_groupBy_${BUCKET_TRIGGER}"]` + ).contains('customer_gender, user', { timeout: TWENTY_SECONDS }); // Set the 'severity' filter to only display ACTIVE alerts. cy.get('[data-test-subj="dashboardAlertStateFilter"]').select('Active'); - cy.wait(10000); // This monitor configuration consistently returns 46 alerts when testing locally. // Confirm the flyout dashboard contains more than 1 ACTIVE alert. - cy.get('tbody > tr').should(($tr) => expect($tr).to.have.length.greaterThan(1)); + cy.get('tbody > tr', { timeout: TWENTY_SECONDS }).should(($tr) => + expect($tr).to.have.length.greaterThan(1) + ); // Select the first and last alerts in the table. cy.get('input[data-test-subj^="checkboxSelectRow-"]').first().click(); @@ -156,19 +159,17 @@ describe('Alerts by trigger flyout', () => { }); // Confirm acknowledge alerts toast displays expected text. - cy.contains('Successfully acknowledged 2 alerts.'); + cy.contains('Successfully acknowledged 2 alerts.', { timeout: TWENTY_SECONDS }); // Confirm alerts were acknowledged as expected. cy.get(`[data-test-subj="alertsDashboardFlyout_${BUCKET_TRIGGER}"]`).within(() => { - // Wait for GetAlerts API call to complete. - cy.wait(10000); - // Set the 'severity' filter to only display ACKNOWLEDGED alerts. cy.get('[data-test-subj="dashboardAlertStateFilter"]').select('Acknowledged'); - cy.wait(10000); // Confirm the table displays 2 acknowledged alerts. - cy.get('tbody > tr').should(($tr) => expect($tr).to.have.length(2)); + cy.get('tbody > tr', { timeout: TWENTY_SECONDS }).should(($tr) => + expect($tr).to.have.length(2) + ); }); // Confirm close button hides the flyout. @@ -180,74 +181,78 @@ describe('Alerts by trigger flyout', () => { // Click the link for the flyout. cy.get(`[data-test-subj="euiLink_${QUERY_TRIGGER}"]`).click(); - // Wait for the flyout to load the trigger-specific alerts. - cy.wait(10000); - // Perform the test checks within the flyout component. - cy.get(`[data-test-subj="alertsDashboardFlyout_${QUERY_TRIGGER}"]`).within(() => { + cy.get(`[data-test-subj="alertsDashboardFlyout_${QUERY_TRIGGER}"]`, { + timeout: TWENTY_SECONDS, + }).within(() => { // Confirm flyout header contains expected text. - cy.get(`[data-test-subj="alertsDashboardFlyout_header_${QUERY_TRIGGER}"]`).contains( - `Alerts by ${QUERY_TRIGGER}` - ); + cy.get( + `[data-test-subj="alertsDashboardFlyout_header_${QUERY_TRIGGER}"]` + ).contains(`Alerts by ${QUERY_TRIGGER}`, { timeout: TWENTY_SECONDS }); // Confirm 'Trigger name' sections renders as expected. cy.get(`[data-test-subj="alertsDashboardFlyout_triggerName_${QUERY_TRIGGER}"]`).contains( 'Trigger name' ); - cy.get(`[data-test-subj="alertsDashboardFlyout_triggerName_${QUERY_TRIGGER}"]`).contains( - QUERY_TRIGGER - ); + cy.get( + `[data-test-subj="alertsDashboardFlyout_triggerName_${QUERY_TRIGGER}"]` + ).contains(QUERY_TRIGGER, { timeout: TWENTY_SECONDS }); // Confirm 'Severity' sections renders as expected. cy.get(`[data-test-subj="alertsDashboardFlyout_severity_${QUERY_TRIGGER}"]`).contains( 'Severity' ); - cy.get(`[data-test-subj="alertsDashboardFlyout_severity_${QUERY_TRIGGER}"]`).contains( - '2 (High)' - ); + cy.get( + `[data-test-subj="alertsDashboardFlyout_severity_${QUERY_TRIGGER}"]` + ).contains('2 (High)', { timeout: TWENTY_SECONDS }); // Confirm 'Monitor' sections renders as expected. cy.get(`[data-test-subj="alertsDashboardFlyout_monitor_${QUERY_TRIGGER}"]`).contains( 'Monitor' ); - cy.get(`[data-test-subj="alertsDashboardFlyout_monitor_${QUERY_TRIGGER}"]`).contains( - QUERY_MONITOR - ); + cy.get( + `[data-test-subj="alertsDashboardFlyout_monitor_${QUERY_TRIGGER}"]` + ).contains(QUERY_MONITOR, { timeout: TWENTY_SECONDS }); // Confirm 'Conditions' sections renders as expected. cy.get(`[data-test-subj="alertsDashboardFlyout_conditions_${QUERY_TRIGGER}"]`).contains( 'Condition' ); - cy.get(`[data-test-subj="alertsDashboardFlyout_conditions_${QUERY_TRIGGER}"]`).contains( - `ctx.results[0].hits.total.value < 10000` - ); + cy.get( + `[data-test-subj="alertsDashboardFlyout_conditions_${QUERY_TRIGGER}"]` + ).contains(`ctx.results[0].hits.total.value < 10000`, { timeout: TWENTY_SECONDS }); // Confirm 'Time range for the last' sections renders as expected. cy.get(`[data-test-subj="alertsDashboardFlyout_timeRange_${QUERY_TRIGGER}"]`).contains( 'Time range for the last' ); - cy.get(`[data-test-subj="alertsDashboardFlyout_timeRange_${QUERY_TRIGGER}"]`).contains( - '10 day(s)' - ); + cy.get( + `[data-test-subj="alertsDashboardFlyout_timeRange_${QUERY_TRIGGER}"]` + ).contains('10 day(s)', { timeout: TWENTY_SECONDS }); // Confirm 'Filters' sections renders as expected. cy.get(`[data-test-subj="alertsDashboardFlyout_filters_${QUERY_TRIGGER}"]`).contains( 'Filters' ); - cy.get(`[data-test-subj="alertsDashboardFlyout_filters_${QUERY_TRIGGER}"]`).contains('-'); + cy.get(`[data-test-subj="alertsDashboardFlyout_filters_${QUERY_TRIGGER}"]`).contains('-', { + timeout: TWENTY_SECONDS, + }); // Confirm 'Group by' sections renders as expected. cy.get(`[data-test-subj="alertsDashboardFlyout_groupBy_${QUERY_TRIGGER}"]`).contains( 'Group by' ); - cy.get(`[data-test-subj="alertsDashboardFlyout_groupBy_${QUERY_TRIGGER}"]`).contains('user'); + cy.get(`[data-test-subj="alertsDashboardFlyout_groupBy_${QUERY_TRIGGER}"]`).contains('user', { + timeout: TWENTY_SECONDS, + }); // Set the 'severity' filter to only display ACTIVE alerts. cy.get('[data-test-subj="dashboardAlertStateFilter"]').select('Active'); - cy.wait(10000); // Confirm the flyout dashboard contains 1 alert. - cy.get('tbody > tr').should(($tr) => expect($tr).to.have.length(1)); + cy.get('tbody > tr', { timeout: TWENTY_SECONDS }).should(($tr) => + expect($tr).to.have.length(1) + ); // Select the alert. cy.get('input[data-test-subj^="checkboxSelectRow-"]').first().click(); @@ -257,19 +262,17 @@ describe('Alerts by trigger flyout', () => { }); // Confirm acknowledge alerts toast displays expected text. - cy.contains('Successfully acknowledged 1 alert.'); + cy.contains('Successfully acknowledged 1 alert.', { timeout: TWENTY_SECONDS }); // Confirm alerts were acknowledged as expected. cy.get(`[data-test-subj="alertsDashboardFlyout_${QUERY_TRIGGER}"]`).within(() => { - // Wait for GetAlerts API call to complete. - cy.wait(10000); - // Set the 'severity' filter to only display ACKNOWLEDGED alerts. cy.get('[data-test-subj="dashboardAlertStateFilter"]').select('Acknowledged'); - cy.wait(10000); // Confirm the table displays 1 acknowledged alert. - cy.get('tbody > tr').should(($tr) => expect($tr).to.have.length(1)); + cy.get('tbody > tr', { timeout: TWENTY_SECONDS }).should(($tr) => + expect($tr).to.have.length(1) + ); }); // Confirm close button hides the flyout. diff --git a/public/components/Flyout/flyouts/__snapshots__/flyouts.test.js.snap b/public/components/Flyout/flyouts/__snapshots__/flyouts.test.js.snap index 0fc5f3de4..4e8e406d6 100644 --- a/public/components/Flyout/flyouts/__snapshots__/flyouts.test.js.snap +++ b/public/components/Flyout/flyouts/__snapshots__/flyouts.test.js.snap @@ -1,5 +1,45 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP +exports[`Flyouts.alertsDashboard generates message JSON 1`] = ` +Object { + "body": , + "flyoutProps": Object { + "aria-labelledby": "alertsDashboardFlyout", + "data-test-subj": "alertsDashboardFlyout_undefined", + "hideCloseButton": true, + "size": "m", + }, + "footer": + Close + , + "footerProps": Object { + "style": Object { + "backgroundColor": "#F5F7FA", + }, + }, + "header": +

+ Alerts by undefined +

+
, + "headerProps": Object { + "hasBorder": true, + }, +} +`; + exports[`Flyouts.message generates message JSON 1`] = ` Object { "body": { expect(json).toMatchSnapshot(); }); }); + +describe('Flyouts.alertsDashboard', () => { + test('generates message JSON', () => { + const json = Flyouts.alertsDashboard({}); + expect(json).toMatchSnapshot(); + }); +}); From 3164b0e5bf0fe9172c405c00a486254168711890 Mon Sep 17 00:00:00 2001 From: AWSHurneyt Date: Fri, 14 Jan 2022 13:49:56 -0800 Subject: [PATCH 5/7] Examining flakiness in cypress test. Signed-off-by: AWSHurneyt --- .../integration/alerts_dashboard_flyout_spec.js | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/cypress/integration/alerts_dashboard_flyout_spec.js b/cypress/integration/alerts_dashboard_flyout_spec.js index dea7d2c2a..cbdc2bf8b 100644 --- a/cypress/integration/alerts_dashboard_flyout_spec.js +++ b/cypress/integration/alerts_dashboard_flyout_spec.js @@ -62,10 +62,27 @@ describe('Alerts by trigger flyout', () => { beforeEach(() => { // Reloading the page to close any flyouts that were not closed by other tests that had failures. cy.visit(`${Cypress.env('opensearch_dashboards')}/app/${PLUGIN_NAME}#/dashboard`); + // cy.wait(5000); // Confirm dashboard is displaying rows for the test monitors. cy.contains(BUCKET_MONITOR, { timeout: TWENTY_SECONDS }); cy.contains(QUERY_MONITOR, { timeout: TWENTY_SECONDS }); + + cy.contains('[data-test-subj="globalLoadingIndicator-hidden"]', { timeout: TWENTY_SECONDS }); + // .should('not.exist'); + // cy.contains('[class="euiLoadingSpinner"]', { timeout: TWENTY_SECONDS }).should('not.exist'); + + // cy.contains(EuiLoadingSpinner); + + // console.log(`hurneyt CONSOLE TEST`) + // cy.get('tbody > tr', { timeout: TWENTY_SECONDS }).should(($tr) => { + // expect($tr).to.have.length.greaterThan(0); + // // console.log(`hurneyt tr = ${JSON.stringify($tr)}`) + // $tr.each(($el) => { + // if ($el.trigger_name === BUCKET_TRIGGER || $el.trigger_name === QUERY_TRIGGER) + // expect($el.ACTIVE).to.be.greaterThan(0); + // }); + // }); }); it('Bucket-level monitor flyout test', () => { From f744feb5488ace5168684230868abaf445d3a01b Mon Sep 17 00:00:00 2001 From: AWSHurneyt Date: Mon, 24 Jan 2022 11:24:23 -0800 Subject: [PATCH 6/7] Added short wait period to flyout cypress tests to alleviate flakiness. Signed-off-by: AWSHurneyt --- .../alerts_dashboard_flyout_spec.js | 21 ++++--------------- 1 file changed, 4 insertions(+), 17 deletions(-) diff --git a/cypress/integration/alerts_dashboard_flyout_spec.js b/cypress/integration/alerts_dashboard_flyout_spec.js index cbdc2bf8b..a26305bb9 100644 --- a/cypress/integration/alerts_dashboard_flyout_spec.js +++ b/cypress/integration/alerts_dashboard_flyout_spec.js @@ -62,27 +62,14 @@ describe('Alerts by trigger flyout', () => { beforeEach(() => { // Reloading the page to close any flyouts that were not closed by other tests that had failures. cy.visit(`${Cypress.env('opensearch_dashboards')}/app/${PLUGIN_NAME}#/dashboard`); - // cy.wait(5000); + + // Waiting 5 seconds for alerts to finish loading. + // This short wait period alleviates flakiness observed during these tests. + cy.wait(5000); // Confirm dashboard is displaying rows for the test monitors. cy.contains(BUCKET_MONITOR, { timeout: TWENTY_SECONDS }); cy.contains(QUERY_MONITOR, { timeout: TWENTY_SECONDS }); - - cy.contains('[data-test-subj="globalLoadingIndicator-hidden"]', { timeout: TWENTY_SECONDS }); - // .should('not.exist'); - // cy.contains('[class="euiLoadingSpinner"]', { timeout: TWENTY_SECONDS }).should('not.exist'); - - // cy.contains(EuiLoadingSpinner); - - // console.log(`hurneyt CONSOLE TEST`) - // cy.get('tbody > tr', { timeout: TWENTY_SECONDS }).should(($tr) => { - // expect($tr).to.have.length.greaterThan(0); - // // console.log(`hurneyt tr = ${JSON.stringify($tr)}`) - // $tr.each(($el) => { - // if ($el.trigger_name === BUCKET_TRIGGER || $el.trigger_name === QUERY_TRIGGER) - // expect($el.ACTIVE).to.be.greaterThan(0); - // }); - // }); }); it('Bucket-level monitor flyout test', () => { From d5454340aa0c9551795a7df874b2f68a2cf5387c Mon Sep 17 00:00:00 2001 From: AWSHurneyt Date: Mon, 24 Jan 2022 16:46:43 -0800 Subject: [PATCH 7/7] Refactored flyout cypress tests to use aliases. Signed-off-by: AWSHurneyt --- .../alerts_dashboard_flyout_spec.js | 128 +++++++----------- 1 file changed, 49 insertions(+), 79 deletions(-) diff --git a/cypress/integration/alerts_dashboard_flyout_spec.js b/cypress/integration/alerts_dashboard_flyout_spec.js index a26305bb9..4c107ef71 100644 --- a/cypress/integration/alerts_dashboard_flyout_spec.js +++ b/cypress/integration/alerts_dashboard_flyout_spec.js @@ -86,64 +86,49 @@ describe('Alerts by trigger flyout', () => { ).contains(`Alerts by ${BUCKET_TRIGGER}`, { timeout: TWENTY_SECONDS }); // Confirm 'Trigger name' sections renders as expected. - cy.get(`[data-test-subj="alertsDashboardFlyout_triggerName_${BUCKET_TRIGGER}"]`).contains( - 'Trigger name' + cy.get(`[data-test-subj="alertsDashboardFlyout_triggerName_${BUCKET_TRIGGER}"]`).as( + 'triggerName' ); - cy.get( - `[data-test-subj="alertsDashboardFlyout_triggerName_${BUCKET_TRIGGER}"]` - ).contains(BUCKET_TRIGGER, { timeout: TWENTY_SECONDS }); + cy.get('@triggerName').contains('Trigger name'); + cy.get('@triggerName').contains(BUCKET_TRIGGER, { timeout: TWENTY_SECONDS }); // Confirm 'Severity' sections renders as expected. - cy.get(`[data-test-subj="alertsDashboardFlyout_severity_${BUCKET_TRIGGER}"]`).contains( - 'Severity' - ); - cy.get( - `[data-test-subj="alertsDashboardFlyout_severity_${BUCKET_TRIGGER}"]` - ).contains('4 (Low)', { timeout: TWENTY_SECONDS }); + cy.get(`[data-test-subj="alertsDashboardFlyout_severity_${BUCKET_TRIGGER}"]`).as('severity'); + cy.get('@severity').contains('Severity'); + cy.get('@severity').contains('4 (Low)', { timeout: TWENTY_SECONDS }); // Confirm 'Monitor' sections renders as expected. - cy.get(`[data-test-subj="alertsDashboardFlyout_monitor_${BUCKET_TRIGGER}"]`).contains( - 'Monitor' - ); - cy.get( - `[data-test-subj="alertsDashboardFlyout_monitor_${BUCKET_TRIGGER}"]` - ).contains(BUCKET_MONITOR, { timeout: TWENTY_SECONDS }); + cy.get(`[data-test-subj="alertsDashboardFlyout_monitor_${BUCKET_TRIGGER}"]`).as('monitor'); + cy.get('@monitor').contains('Monitor'); + cy.get('@monitor').contains(BUCKET_MONITOR, { timeout: TWENTY_SECONDS }); // Confirm 'Conditions' sections renders as expected. - cy.get(`[data-test-subj="alertsDashboardFlyout_conditions_${BUCKET_TRIGGER}"]`).contains( - 'Conditions' + cy.get(`[data-test-subj="alertsDashboardFlyout_conditions_${BUCKET_TRIGGER}"]`).as( + 'conditions' ); + cy.get('@conditions').contains('Conditions'); // Confirm the 'Conditions' sections renders with all of the expected conditions. ['params._count < 10000', 'OR', 'params.avg_products_price == 10'].forEach((entry) => - cy - .get(`[data-test-subj="alertsDashboardFlyout_conditions_${BUCKET_TRIGGER}"]`) - .contains(entry, { timeout: TWENTY_SECONDS }) + cy.get('@conditions').contains(entry, { timeout: TWENTY_SECONDS }) ); // Confirm 'Time range for the last' sections renders as expected. - cy.get(`[data-test-subj="alertsDashboardFlyout_timeRange_${BUCKET_TRIGGER}"]`).contains( - 'Time range for the last' + cy.get(`[data-test-subj="alertsDashboardFlyout_timeRange_${BUCKET_TRIGGER}"]`).as( + 'timeRange' ); - cy.get( - `[data-test-subj="alertsDashboardFlyout_timeRange_${BUCKET_TRIGGER}"]` - ).contains('10 day(s)', { timeout: TWENTY_SECONDS }); + cy.get('@timeRange').contains('Time range for the last'); + cy.get('@timeRange').contains('10 day(s)', { timeout: TWENTY_SECONDS }); // Confirm 'Filters' sections renders as expected. - cy.get(`[data-test-subj="alertsDashboardFlyout_filters_${BUCKET_TRIGGER}"]`).contains( - 'Filters' - ); - cy.get( - `[data-test-subj="alertsDashboardFlyout_filters_${BUCKET_TRIGGER}"]` - ).contains('All fields are included', { timeout: TWENTY_SECONDS }); + cy.get(`[data-test-subj="alertsDashboardFlyout_filters_${BUCKET_TRIGGER}"]`).as('filters'); + cy.get('@filters').contains('Filters'); + cy.get('@filters').contains('All fields are included', { timeout: TWENTY_SECONDS }); // Confirm 'Group by' sections renders as expected. - cy.get(`[data-test-subj="alertsDashboardFlyout_groupBy_${BUCKET_TRIGGER}"]`).contains( - 'Group by' - ); - cy.get( - `[data-test-subj="alertsDashboardFlyout_groupBy_${BUCKET_TRIGGER}"]` - ).contains('customer_gender, user', { timeout: TWENTY_SECONDS }); + cy.get(`[data-test-subj="alertsDashboardFlyout_groupBy_${BUCKET_TRIGGER}"]`).as('groupBy'); + cy.get('@groupBy').contains('Group by'); + cy.get('@groupBy').contains('customer_gender, user', { timeout: TWENTY_SECONDS }); // Set the 'severity' filter to only display ACTIVE alerts. cy.get('[data-test-subj="dashboardAlertStateFilter"]').select('Active'); @@ -195,60 +180,45 @@ describe('Alerts by trigger flyout', () => { ).contains(`Alerts by ${QUERY_TRIGGER}`, { timeout: TWENTY_SECONDS }); // Confirm 'Trigger name' sections renders as expected. - cy.get(`[data-test-subj="alertsDashboardFlyout_triggerName_${QUERY_TRIGGER}"]`).contains( - 'Trigger name' + cy.get(`[data-test-subj="alertsDashboardFlyout_triggerName_${QUERY_TRIGGER}"]`).as( + 'triggerName' ); - cy.get( - `[data-test-subj="alertsDashboardFlyout_triggerName_${QUERY_TRIGGER}"]` - ).contains(QUERY_TRIGGER, { timeout: TWENTY_SECONDS }); + cy.get('@triggerName').contains('Trigger name'); + cy.get('@triggerName').contains(QUERY_TRIGGER, { timeout: TWENTY_SECONDS }); // Confirm 'Severity' sections renders as expected. - cy.get(`[data-test-subj="alertsDashboardFlyout_severity_${QUERY_TRIGGER}"]`).contains( - 'Severity' - ); - cy.get( - `[data-test-subj="alertsDashboardFlyout_severity_${QUERY_TRIGGER}"]` - ).contains('2 (High)', { timeout: TWENTY_SECONDS }); + cy.get(`[data-test-subj="alertsDashboardFlyout_severity_${QUERY_TRIGGER}"]`).as('severity'); + cy.get('@severity').contains('Severity'); + cy.get('@severity').contains('2 (High)', { timeout: TWENTY_SECONDS }); // Confirm 'Monitor' sections renders as expected. - cy.get(`[data-test-subj="alertsDashboardFlyout_monitor_${QUERY_TRIGGER}"]`).contains( - 'Monitor' - ); - cy.get( - `[data-test-subj="alertsDashboardFlyout_monitor_${QUERY_TRIGGER}"]` - ).contains(QUERY_MONITOR, { timeout: TWENTY_SECONDS }); + cy.get(`[data-test-subj="alertsDashboardFlyout_monitor_${QUERY_TRIGGER}"]`).as('monitor'); + cy.get('@monitor').contains('Monitor'); + cy.get('@monitor').contains(QUERY_MONITOR, { timeout: TWENTY_SECONDS }); // Confirm 'Conditions' sections renders as expected. - cy.get(`[data-test-subj="alertsDashboardFlyout_conditions_${QUERY_TRIGGER}"]`).contains( - 'Condition' + cy.get(`[data-test-subj="alertsDashboardFlyout_conditions_${QUERY_TRIGGER}"]`).as( + 'conditions' ); - cy.get( - `[data-test-subj="alertsDashboardFlyout_conditions_${QUERY_TRIGGER}"]` - ).contains(`ctx.results[0].hits.total.value < 10000`, { timeout: TWENTY_SECONDS }); + cy.get('@conditions').contains('Condition'); + cy.get('@conditions').contains(`ctx.results[0].hits.total.value < 10000`, { + timeout: TWENTY_SECONDS, + }); // Confirm 'Time range for the last' sections renders as expected. - cy.get(`[data-test-subj="alertsDashboardFlyout_timeRange_${QUERY_TRIGGER}"]`).contains( - 'Time range for the last' - ); - cy.get( - `[data-test-subj="alertsDashboardFlyout_timeRange_${QUERY_TRIGGER}"]` - ).contains('10 day(s)', { timeout: TWENTY_SECONDS }); + cy.get(`[data-test-subj="alertsDashboardFlyout_timeRange_${QUERY_TRIGGER}"]`).as('timeRange'); + cy.get('@timeRange').contains('Time range for the last'); + cy.get('@timeRange').contains('10 day(s)', { timeout: TWENTY_SECONDS }); // Confirm 'Filters' sections renders as expected. - cy.get(`[data-test-subj="alertsDashboardFlyout_filters_${QUERY_TRIGGER}"]`).contains( - 'Filters' - ); - cy.get(`[data-test-subj="alertsDashboardFlyout_filters_${QUERY_TRIGGER}"]`).contains('-', { - timeout: TWENTY_SECONDS, - }); + cy.get(`[data-test-subj="alertsDashboardFlyout_filters_${QUERY_TRIGGER}"]`).as('filters'); + cy.get('@filters').contains('Filters'); + cy.get('@filters').contains('-', { timeout: TWENTY_SECONDS }); // Confirm 'Group by' sections renders as expected. - cy.get(`[data-test-subj="alertsDashboardFlyout_groupBy_${QUERY_TRIGGER}"]`).contains( - 'Group by' - ); - cy.get(`[data-test-subj="alertsDashboardFlyout_groupBy_${QUERY_TRIGGER}"]`).contains('user', { - timeout: TWENTY_SECONDS, - }); + cy.get(`[data-test-subj="alertsDashboardFlyout_groupBy_${QUERY_TRIGGER}"]`).as('groupBy'); + cy.get('@groupBy').contains('Group by'); + cy.get('@groupBy').contains('user', { timeout: TWENTY_SECONDS }); // Set the 'severity' filter to only display ACTIVE alerts. cy.get('[data-test-subj="dashboardAlertStateFilter"]').select('Active');