From c9f271aa0b802e813c0e97ad66c3f74cda17356e Mon Sep 17 00:00:00 2001 From: AWSHurneyt Date: Wed, 4 May 2022 10:26:02 -0700 Subject: [PATCH 01/17] Renamed FindingsDashboard/utils to FindingsDashboard/findingsUtils for clarity. Signed-off-by: AWSHurneyt --- .../Flyout/flyouts/components/AlertsDashboardFlyoutComponent.js | 2 +- .../DocumentLevelMonitorQueries/DocumentLevelQuery.js | 2 +- .../CreateMonitor/containers/CreateMonitor/utils/constants.js | 2 +- .../CreateMonitor/containers/DefineMonitor/DefineMonitor.js | 2 +- .../components/FindingsDashboard/{utils.js => findingsUtils.js} | 0 public/pages/Dashboard/containers/FindingsDashboard.js | 2 +- public/pages/MonitorDetails/containers/MonitorDetails.js | 2 +- 7 files changed, 6 insertions(+), 6 deletions(-) rename public/pages/Dashboard/components/FindingsDashboard/{utils.js => findingsUtils.js} (100%) diff --git a/public/components/Flyout/flyouts/components/AlertsDashboardFlyoutComponent.js b/public/components/Flyout/flyouts/components/AlertsDashboardFlyoutComponent.js index 13871ea71..b2c861527 100644 --- a/public/components/Flyout/flyouts/components/AlertsDashboardFlyoutComponent.js +++ b/public/components/Flyout/flyouts/components/AlertsDashboardFlyoutComponent.js @@ -50,7 +50,7 @@ import { SEVERITY_OPTIONS } from '../../../../pages/CreateTrigger/utils/constant import { getAlertsFindingColumn, TABLE_TAB_IDS, -} from '../../../../pages/Dashboard/components/FindingsDashboard/utils'; +} from '../../../../pages/Dashboard/components/FindingsDashboard/findingsUtils'; import FindingsDashboard from '../../../../pages/Dashboard/containers/FindingsDashboard'; export const DEFAULT_NUM_FLYOUT_ROWS = 10; diff --git a/public/pages/CreateMonitor/components/DocumentLevelMonitorQueries/DocumentLevelQuery.js b/public/pages/CreateMonitor/components/DocumentLevelMonitorQueries/DocumentLevelQuery.js index 2b9eeb434..e6df28762 100644 --- a/public/pages/CreateMonitor/components/DocumentLevelMonitorQueries/DocumentLevelQuery.js +++ b/public/pages/CreateMonitor/components/DocumentLevelMonitorQueries/DocumentLevelQuery.js @@ -15,7 +15,7 @@ import { } from '../../../../utils/validate'; import ConfigureDocumentLevelQueryTags from './ConfigureDocumentLevelQueryTags'; import { getIndexFields } from '../MonitorExpressions/expressions/utils/dataTypes'; -import { QUERY_OPERATORS } from '../../../Dashboard/components/FindingsDashboard/utils'; +import { QUERY_OPERATORS } from '../../../Dashboard/components/FindingsDashboard/findingsUtils'; const ALLOWED_DATA_TYPES = ['number', 'text', 'keyword', 'boolean']; diff --git a/public/pages/CreateMonitor/containers/CreateMonitor/utils/constants.js b/public/pages/CreateMonitor/containers/CreateMonitor/utils/constants.js index 1197e06c9..acd51d282 100644 --- a/public/pages/CreateMonitor/containers/CreateMonitor/utils/constants.js +++ b/public/pages/CreateMonitor/containers/CreateMonitor/utils/constants.js @@ -5,7 +5,7 @@ import { OPERATORS_MAP } from '../../../components/MonitorExpressions/expressions/utils/constants'; import { MONITOR_TYPE } from '../../../../../utils/constants'; -import { QUERY_OPERATORS } from '../../../../Dashboard/components/FindingsDashboard/utils'; +import { QUERY_OPERATORS } from '../../../../Dashboard/components/FindingsDashboard/findingsUtils'; export const BUCKET_COUNT = 5; diff --git a/public/pages/CreateMonitor/containers/DefineMonitor/DefineMonitor.js b/public/pages/CreateMonitor/containers/DefineMonitor/DefineMonitor.js index 3d6d41097..5c464f6a5 100644 --- a/public/pages/CreateMonitor/containers/DefineMonitor/DefineMonitor.js +++ b/public/pages/CreateMonitor/containers/DefineMonitor/DefineMonitor.js @@ -29,7 +29,7 @@ import { FORMIK_INITIAL_VALUES } from '../CreateMonitor/utils/constants'; import { API_TYPES } from '../../components/ClusterMetricsMonitor/utils/clusterMetricsMonitorConstants'; import ConfigureDocumentLevelQueries from '../../components/DocumentLevelMonitorQueries/ConfigureDocumentLevelQueries'; import FindingsDashboard from '../../../Dashboard/containers/FindingsDashboard'; -import { validDocLevelGraphQueries } from '../../../Dashboard/components/FindingsDashboard/utils'; +import { validDocLevelGraphQueries } from '../../../Dashboard/components/FindingsDashboard/findingsUtils'; function renderEmptyMessage(message) { return ( diff --git a/public/pages/Dashboard/components/FindingsDashboard/utils.js b/public/pages/Dashboard/components/FindingsDashboard/findingsUtils.js similarity index 100% rename from public/pages/Dashboard/components/FindingsDashboard/utils.js rename to public/pages/Dashboard/components/FindingsDashboard/findingsUtils.js diff --git a/public/pages/Dashboard/containers/FindingsDashboard.js b/public/pages/Dashboard/containers/FindingsDashboard.js index fb1810c55..c9e44e60f 100644 --- a/public/pages/Dashboard/containers/FindingsDashboard.js +++ b/public/pages/Dashboard/containers/FindingsDashboard.js @@ -27,7 +27,7 @@ import { findingsColumnTypes, getFindings, parseFindingsForPreview, -} from '../components/FindingsDashboard/utils'; +} from '../components/FindingsDashboard/findingsUtils'; export const GET_FINDINGS_PREVIEW_PARAMS = { id: DEFAULT_GET_FINDINGS_PARAMS.id, diff --git a/public/pages/MonitorDetails/containers/MonitorDetails.js b/public/pages/MonitorDetails/containers/MonitorDetails.js index dc9c897c7..893745ca0 100644 --- a/public/pages/MonitorDetails/containers/MonitorDetails.js +++ b/public/pages/MonitorDetails/containers/MonitorDetails.js @@ -45,7 +45,7 @@ import { getUnwrappedTriggers } from './Triggers/Triggers'; import { formikToMonitor } from '../../CreateMonitor/containers/CreateMonitor/utils/formikToMonitor'; import monitorToFormik from '../../CreateMonitor/containers/CreateMonitor/utils/monitorToFormik'; import FindingsDashboard from '../../Dashboard/containers/FindingsDashboard'; -import { TABLE_TAB_IDS } from '../../Dashboard/components/FindingsDashboard/utils'; +import { TABLE_TAB_IDS } from '../../Dashboard/components/FindingsDashboard/findingsUtils'; export default class MonitorDetails extends Component { constructor(props) { From 2c0d0891934e75436e74e4f88a5dc9b57363cc18 Mon Sep 17 00:00:00 2001 From: AWSHurneyt Date: Thu, 5 May 2022 11:18:34 -0700 Subject: [PATCH 02/17] Removed an unused helper method. Refactored various helper methods to have default values. Refactored validation logic for doc level queries. Implemented unit tests for various doc level monitor functions. Signed-off-by: AWSHurneyt --- .../FindingsDashboard/findingsUtils.js | 31 +- .../FindingsDashboard/findingsUtils.test.js | 356 ++++++++++++++++++ 2 files changed, 368 insertions(+), 19 deletions(-) create mode 100644 public/pages/Dashboard/components/FindingsDashboard/findingsUtils.test.js diff --git a/public/pages/Dashboard/components/FindingsDashboard/findingsUtils.js b/public/pages/Dashboard/components/FindingsDashboard/findingsUtils.js index 5de5a2807..edc198ce8 100644 --- a/public/pages/Dashboard/components/FindingsDashboard/findingsUtils.js +++ b/public/pages/Dashboard/components/FindingsDashboard/findingsUtils.js @@ -112,7 +112,7 @@ export const findingsColumnTypes = (isAlertsFlyout = false) => [ }, ]; -export const getFindingsForMonitor = (findings, monitorId) => { +export const getFindingsForMonitor = (findings = [], monitorId = '') => { const monitorFindings = []; findings.map((finding) => { const findingId = _.keys(finding)[0]; @@ -124,7 +124,7 @@ export const getFindingsForMonitor = (findings, monitorId) => { return { findings: monitorFindings, totalFindings: monitorFindings.length }; }; -export const parseFindingsForPreview = (previewResponse, index, queries = []) => { +export const parseFindingsForPreview = (previewResponse = {}, index = '', queries = []) => { // TODO FIXME: ExecuteMonitor API currently only returns a list of query names/IDs and the relevant docIds. // As a result, the preview dashboard cannot display document contents. const findings = []; @@ -160,24 +160,17 @@ export const parseFindingsForPreview = (previewResponse, index, queries = []) => return findings; }; -export const getPreviewResponseDocIds = (response) => { - const docIds = []; - _.keys(response).map((queryId) => { - const docIdsList = _.get(response, queryId, []); - docIdsList.forEach((docId) => { - if (!_.includes(docIds, docId)) docIds.push(docId); - }); - }); - return docIds; -}; - -export const validDocLevelGraphQueries = (queries) => { - // The 'queryName' and 'query' fields are required to execute a doc level query. - // If either are undefined for any queries, the monitor cannot be executed. - const allQueriesDefined = queries.find( - (query) => !_.isEmpty(query.queryName) && !_.isEmpty(query.query) +export const validDocLevelGraphQueries = (queries = []) => { + // The 'queryName', 'field', 'operator', and 'query' fields are required to execute a doc level query. + // If any of those fields are undefined for any queries, the monitor cannot be executed. + const definedQueries = queries.find( + (query) => + _.isEmpty(query.queryName) || + _.isEmpty(query.field) || + _.isEmpty(query.operator) || + _.isEmpty(query.query) ); - return !_.isEmpty(allQueriesDefined); + return !_.isEmpty(queries) && _.isEmpty(definedQueries); }; export async function getFindings({ diff --git a/public/pages/Dashboard/components/FindingsDashboard/findingsUtils.test.js b/public/pages/Dashboard/components/FindingsDashboard/findingsUtils.test.js new file mode 100644 index 000000000..a634b134c --- /dev/null +++ b/public/pages/Dashboard/components/FindingsDashboard/findingsUtils.test.js @@ -0,0 +1,356 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +import _ from 'lodash'; +import { + getFindingsForMonitor, + parseFindingsForPreview, + QUERY_OPERATORS, + validDocLevelGraphQueries, +} from './findingsUtils'; + +describe('findingsUtils', () => { + describe('getFindingsForMonitor', () => { + test('when findings array is empty', () => { + const findings = []; + const monitorId = 'monitorId'; + const expectedOutput = { findings: [], totalFindings: 0 }; + expect(getFindingsForMonitor(findings, monitorId)).toEqual(expectedOutput); + }); + + test('when findings array is undefined', () => { + const findings = undefined; + const monitorId = 'monitorId'; + const expectedOutput = { findings: [], totalFindings: 0 }; + expect(getFindingsForMonitor(findings, monitorId)).toEqual(expectedOutput); + }); + + describe('when findings array contains findings and', () => { + const findings = [ + { + finding1: { + finding: { + id: 'finding1', + monitor_id: 'monitorId1', + }, + document_list: ['doc1'], + }, + }, + { + finding2: { + finding: { + id: 'finding2', + monitor_id: 'monitorId2', + }, + document_list: ['doc2'], + }, + }, + { + finding3: { + finding: { + id: 'finding3', + monitor_id: 'monitorId2', + }, + document_list: ['doc3'], + }, + }, + { + finding4: { + finding: { + id: 'finding4', + monitor_id: 'monitorId2', + }, + document_list: ['doc4'], + }, + }, + ]; + + test('when monitorId is blank', () => { + const monitorId = ''; + const expectedOutput = { findings: [], totalFindings: 0 }; + expect(getFindingsForMonitor(findings, monitorId)).toEqual(expectedOutput); + }); + + test('when monitorId is undefined', () => { + const monitorId = undefined; + const expectedOutput = { findings: [], totalFindings: 0 }; + expect(getFindingsForMonitor(findings, monitorId)).toEqual(expectedOutput); + }); + + test('when monitorId has matching findings', () => { + const monitorId = 'monitorId2'; + const expectedOutput = { + findings: [ + { + id: 'finding2', + monitor_id: 'monitorId2', + document_list: ['doc2'], + }, + { + id: 'finding3', + monitor_id: 'monitorId2', + document_list: ['doc3'], + }, + { + id: 'finding4', + monitor_id: 'monitorId2', + document_list: ['doc4'], + }, + ], + totalFindings: 3, + }; + const output = getFindingsForMonitor(findings, monitorId); + expect(output).toEqual(expectedOutput); + }); + + test('when monitorId has no matching findings', () => { + const monitorId = 'unknownMonitorId'; + const expectedOutput = { findings: [], totalFindings: 0 }; + expect(getFindingsForMonitor(findings, monitorId)).toEqual(expectedOutput); + }); + }); + }); + + describe('parseFindingsForPreview', () => { + const index = 'indexName'; + describe('when previewResponse contains no results and', () => { + const previewResponse = {}; + + test('when there are no queries', () => { + const queries = []; + const expectedOutput = []; + expect(parseFindingsForPreview(previewResponse, index, queries)).toEqual(expectedOutput); + }); + + test('when there are no matching queries', () => { + const queries = [ + { + id: 'unknownQuery1', + queryName: 'unknownQuery1', + field: 'field.name', + operator: QUERY_OPERATORS[0].value, + query: 'value1', + tags: ['tag1', 'tag2'], + }, + { + id: 'unknownQuery2', + queryName: 'unknownQuery2', + field: 'another.field.name', + operator: QUERY_OPERATORS[0].value, + query: 'value2', + tags: ['tag3'], + }, + ]; + const expectedOutput = []; + expect(parseFindingsForPreview(previewResponse, index, queries)).toEqual(expectedOutput); + }); + }); + + describe('when previewResponse contains results and', () => { + const previewResponse = { + query1: ['docId1', 'docId2', 'docId3'], + query2: ['docId4', 'docId5', 'docId6'], + }; + + test('when there are no queries', () => { + const queries = []; + const expectedOutput = []; + expect(parseFindingsForPreview(previewResponse, index, queries)).toEqual(expectedOutput); + }); + + test('when there are no matching queries', () => { + const queries = [ + { + id: 'unknownQuery1', + queryName: 'unknownQuery1', + field: 'field.name', + operator: QUERY_OPERATORS[0].value, + query: 'value1', + tags: ['tag1', 'tag2'], + }, + { + id: 'unknownQuery2', + queryName: 'unknownQuery2', + field: 'another.field.name', + operator: QUERY_OPERATORS[0].value, + query: 'value2', + tags: ['tag3'], + }, + ]; + const expectedOutput = []; + expect(parseFindingsForPreview(previewResponse, index, queries)).toEqual(expectedOutput); + }); + + test('when there are matching queries', () => { + const queries = [ + { + id: 'unknownQuery1', + queryName: 'unknownQuery1', + field: 'field.name', + operator: QUERY_OPERATORS[0].value, + query: 'value1', + tags: ['tag1', 'tag2'], + }, + { + id: 'query2', + queryName: 'query2', + field: 'another.field.name', + operator: QUERY_OPERATORS[0].value, + query: 'value2', + tags: ['tag3'], + }, + ]; + + const expectedOutput = [ + { + index: index, + related_doc_id: 'docId4', + queries: [{ name: 'query2', query: 'another.field.name is value2' }], + timestamp: '', + }, + { + index: index, + related_doc_id: 'docId5', + queries: [{ name: 'query2', query: 'another.field.name is value2' }], + timestamp: '', + }, + { + index: index, + related_doc_id: 'docId6', + queries: [{ name: 'query2', query: 'another.field.name is value2' }], + timestamp: '', + }, + ]; + const output = parseFindingsForPreview(previewResponse, index, queries); + expectedOutput.forEach((expectedQuery) => { + const matchingQuery = _.find(output, { + index: expectedQuery.index, + queries: expectedQuery.queries, + related_doc_id: expectedQuery.related_doc_id, + }); + expect(_.isEmpty(matchingQuery)).toEqual(false); + }); + expect(output.length).toEqual(expectedOutput.length); + }); + }); + }); + + describe('validDocLevelGraphQueries', () => { + test('when no queries are supplied', () => { + const queries = []; + expect(validDocLevelGraphQueries(queries)).toEqual(false); + }); + + test('when a query does not have a queryName value', () => { + const queries = [ + { + id: 'query1', + queryName: '', + field: 'field.name', + operator: QUERY_OPERATORS[0].value, + query: 'value1', + tags: ['tag1', 'tag2'], + }, + { + id: 'query2', + queryName: 'query2', + field: 'another.field.name', + operator: QUERY_OPERATORS[0].value, + query: 'value2', + tags: ['tag3'], + }, + ]; + expect(validDocLevelGraphQueries(queries)).toEqual(false); + }); + + test('when a query does not have a field value', () => { + const queries = [ + { + id: 'query1', + queryName: 'query2', + field: '', + operator: QUERY_OPERATORS[0].value, + query: 'value1', + tags: ['tag1', 'tag2'], + }, + { + id: 'query2', + queryName: 'query2', + field: 'another.field.name', + operator: QUERY_OPERATORS[0].value, + query: 'value2', + tags: ['tag3'], + }, + ]; + expect(validDocLevelGraphQueries(queries)).toEqual(false); + }); + + test('when a query does not have an operator value', () => { + const queries = [ + { + id: 'query1', + queryName: 'query2', + field: 'field.name', + operator: '', + query: 'value1', + tags: ['tag1', 'tag2'], + }, + { + id: 'query2', + queryName: 'query2', + field: 'another.field.name', + operator: QUERY_OPERATORS[0].value, + query: 'value2', + tags: ['tag3'], + }, + ]; + expect(validDocLevelGraphQueries(queries)).toEqual(false); + }); + + test('when a query does not have a query value', () => { + const queries = [ + { + id: 'query1', + queryName: 'query1', + field: 'field.name', + operator: QUERY_OPERATORS[0].value, + query: '', + tags: ['tag1', 'tag2'], + }, + { + id: 'query2', + queryName: 'query2', + field: 'another.field.name', + operator: QUERY_OPERATORS[0].value, + query: 'value2', + tags: ['tag3'], + }, + ]; + expect(validDocLevelGraphQueries(queries)).toEqual(false); + }); + + test('when all queries are defined', () => { + const queries = [ + { + id: 'query1', + queryName: 'query2', + field: 'field.name', + operator: QUERY_OPERATORS[0].value, + query: 'value1', + tags: ['tag1', 'tag2'], + }, + { + id: 'query2', + queryName: 'query2', + field: 'another.field.name', + operator: QUERY_OPERATORS[0].value, + query: 'value2', + tags: ['tag3'], + }, + ]; + expect(validDocLevelGraphQueries(queries)).toEqual(true); + }); + }); +}); From 4b9be31f3b2ddf4e4ebba764d37a6b11cce28430 Mon Sep 17 00:00:00 2001 From: AWSHurneyt Date: Thu, 5 May 2022 12:42:51 -0700 Subject: [PATCH 03/17] Implemented FindingPopover snapshot test. Signed-off-by: AWSHurneyt --- .../FindingsDashboard/FindingsPopover.test.js | 20 +++++++++++++++++++ .../FindingsPopover.test.js.snap | 18 +++++++++++++++++ 2 files changed, 38 insertions(+) create mode 100644 public/pages/Dashboard/components/FindingsDashboard/FindingsPopover.test.js create mode 100644 public/pages/Dashboard/components/FindingsDashboard/__snapshots__/FindingsPopover.test.js.snap diff --git a/public/pages/Dashboard/components/FindingsDashboard/FindingsPopover.test.js b/public/pages/Dashboard/components/FindingsDashboard/FindingsPopover.test.js new file mode 100644 index 000000000..f1306f2bb --- /dev/null +++ b/public/pages/Dashboard/components/FindingsDashboard/FindingsPopover.test.js @@ -0,0 +1,20 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +import React from 'react'; +import { render } from 'enzyme'; +import { Formik } from 'formik'; +import FindingsPopover from './FindingsPopover'; + +describe('FindingsPopover', () => { + test('renders', () => { + const component = ( + + + + ); + expect(render(component)).toMatchSnapshot(); + }); +}); diff --git a/public/pages/Dashboard/components/FindingsDashboard/__snapshots__/FindingsPopover.test.js.snap b/public/pages/Dashboard/components/FindingsDashboard/__snapshots__/FindingsPopover.test.js.snap new file mode 100644 index 000000000..838541587 --- /dev/null +++ b/public/pages/Dashboard/components/FindingsDashboard/__snapshots__/FindingsPopover.test.js.snap @@ -0,0 +1,18 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`FindingsPopover renders 1`] = ` +
+
+ +
+
+`; From 67c1da15f66ce173073a3ee7f81fa5b552df993b Mon Sep 17 00:00:00 2001 From: AWSHurneyt Date: Thu, 12 May 2022 10:00:19 -0700 Subject: [PATCH 04/17] Implemented FindingFlyout snapshot test. Signed-off-by: AWSHurneyt --- .../FindingsDashboard/FindingFlyout.js | 1 + .../FindingsDashboard/FindingFlyout.test.js | 20 +++++++++++++++++++ .../__snapshots__/FindingFlyout.test.js.snap | 12 +++++++++++ 3 files changed, 33 insertions(+) create mode 100644 public/pages/Dashboard/components/FindingsDashboard/FindingFlyout.test.js create mode 100644 public/pages/Dashboard/components/FindingsDashboard/__snapshots__/FindingFlyout.test.js.snap diff --git a/public/pages/Dashboard/components/FindingsDashboard/FindingFlyout.js b/public/pages/Dashboard/components/FindingsDashboard/FindingFlyout.js index ef9032d6b..c2ae8b0e8 100644 --- a/public/pages/Dashboard/components/FindingsDashboard/FindingFlyout.js +++ b/public/pages/Dashboard/components/FindingsDashboard/FindingFlyout.js @@ -4,6 +4,7 @@ */ import React, { Component } from 'react'; +import _ from 'lodash'; import { EuiButtonEmpty, EuiCodeBlock, diff --git a/public/pages/Dashboard/components/FindingsDashboard/FindingFlyout.test.js b/public/pages/Dashboard/components/FindingsDashboard/FindingFlyout.test.js new file mode 100644 index 000000000..98399c055 --- /dev/null +++ b/public/pages/Dashboard/components/FindingsDashboard/FindingFlyout.test.js @@ -0,0 +1,20 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +import React from 'react'; +import { render } from 'enzyme'; +import { Formik } from 'formik'; +import FindingFlyout from './FindingFlyout'; + +describe('FindingFlyout', () => { + test('renders', () => { + const component = ( + + + + ); + expect(render(component)).toMatchSnapshot(); + }); +}); diff --git a/public/pages/Dashboard/components/FindingsDashboard/__snapshots__/FindingFlyout.test.js.snap b/public/pages/Dashboard/components/FindingsDashboard/__snapshots__/FindingFlyout.test.js.snap new file mode 100644 index 000000000..bc2ca9662 --- /dev/null +++ b/public/pages/Dashboard/components/FindingsDashboard/__snapshots__/FindingFlyout.test.js.snap @@ -0,0 +1,12 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`FindingFlyout renders 1`] = ` +
+ +
+`; From 9817cbb5f1e3d1e38439376874ae1bf8e74b6b5b Mon Sep 17 00:00:00 2001 From: AWSHurneyt Date: Thu, 12 May 2022 17:06:48 -0700 Subject: [PATCH 05/17] Refactored DocumentLevelTriggerExpression to require selections, and removed redundant code. Signed-off-by: AWSHurneyt --- .../DocumentLevelTriggerExpression.js | 87 ++++++++----------- 1 file changed, 36 insertions(+), 51 deletions(-) diff --git a/public/pages/CreateTrigger/containers/DefineDocumentLevelTrigger/DocumentLevelTriggerExpression.js b/public/pages/CreateTrigger/containers/DefineDocumentLevelTrigger/DocumentLevelTriggerExpression.js index 19ad3f4c2..399c71605 100644 --- a/public/pages/CreateTrigger/containers/DefineDocumentLevelTrigger/DocumentLevelTriggerExpression.js +++ b/public/pages/CreateTrigger/containers/DefineDocumentLevelTrigger/DocumentLevelTriggerExpression.js @@ -5,9 +5,10 @@ import React, { Component } from 'react'; import _ from 'lodash'; -import { EuiButton, EuiFlexGroup, EuiFlexItem } from '@elastic/eui'; +import { EuiButton, EuiFlexGroup, EuiFlexItem, EuiFormRow } from '@elastic/eui'; import { FormikComboBox, FormikSelect } from '../../../../components/FormControls'; import { AND_OR_CONDITION_OPTIONS } from '../../utils/constants'; +import { hasError, isInvalid, required } from '../../../../utils/validate'; class DocumentLevelTriggerExpression extends Component { constructor(props) { @@ -27,61 +28,40 @@ class DocumentLevelTriggerExpression extends Component { const isFirstCondition = index === 0; if (index > 0) values['andOrCondition'] = values.andOrCondition || AND_OR_CONDITION_OPTIONS[0].value; - return isFirstCondition ? ( - form.setFieldValue(field.name, e[0].value), - isClearable: false, - singleSelection: { asPlainText: true }, - options: [ - { label: 'Queries', options: querySelectOptions }, - { label: 'Tags', options: tagSelectOptions }, - ], - selectedOptions: - !_.isEmpty(values.query) && !_.isEmpty(values.query.queryName) - ? [ - { - value: values.query.queryName, - label: values.query.queryName, - query: values.query, - }, - ] - : undefined, - }} - /> - ) : ( - - - field.onChange(e), - options: AND_OR_CONDITION_OPTIONS, - }} - /> - + return ( + + {/* Do not display AND/OR selector for the first condition */} + {!isFirstCondition && ( + + field.onChange(e), + options: AND_OR_CONDITION_OPTIONS, + }} + /> + + )} form.setFieldValue(field.name, e[0].value), + onBlur: (e, field, form) => form.setFieldTouched(field.name, true), isClearable: false, singleSelection: { asPlainText: true }, options: [ @@ -102,11 +82,16 @@ class DocumentLevelTriggerExpression extends Component { /> - - arrayHelpers.remove(index)}> - Remove condition - - + {/* Do not display the button for the first condition */} + {!isFirstCondition && ( + + + arrayHelpers.remove(index)}> + Remove condition + + + + )} ); } From b6ff522a01f32de02909ca4e98db80ea12ea9d4f Mon Sep 17 00:00:00 2001 From: AWSHurneyt Date: Fri, 13 May 2022 16:41:16 -0700 Subject: [PATCH 06/17] Implementing integration tests Signed-off-by: AWSHurneyt --- .../sample_document_level_monitor.json | 56 +++++ .../document_level_monitor_spec.js | 217 ++++++++++++++++++ .../ConfigureDocumentLevelQueries.js | 1 + .../ConfigureDocumentLevelQueryTags.js | 2 + .../DocumentLevelQuery.js | 11 +- .../DocumentLevelQueryTag.js | 5 +- .../DefineDocumentLevelTrigger.js | 1 + .../DocumentLevelTriggerExpression.js | 9 +- 8 files changed, 299 insertions(+), 3 deletions(-) create mode 100644 cypress/fixtures/sample_document_level_monitor.json create mode 100644 cypress/integration/document_level_monitor_spec.js diff --git a/cypress/fixtures/sample_document_level_monitor.json b/cypress/fixtures/sample_document_level_monitor.json new file mode 100644 index 000000000..b2ad999ba --- /dev/null +++ b/cypress/fixtures/sample_document_level_monitor.json @@ -0,0 +1,56 @@ +{ + "type": "monitor", + "monitor_type": "doc_level_monitor", + "name": "sample_document_level_monitor", + "enabled": true, + "createdBy": "chip", + "schedule": { + "period": { + "interval": 1, + "unit": "MINUTES" + } + }, + "inputs": [ + { + "doc_level_input": { + "description": "windows-powershell", + "indices": ["document-level-monitor-test-index"], + "queries": [ + { + "id": "sigma-123", + "name": "sigma-123", + "query": "region:\"us-west-2\"", + "tags": ["MITRE:8500"] + }, + { + "id": "sigma-456", + "name": "sigma-456", + "query": "region:\"us-east-1\"", + "tags": ["MITRE:8600"] + }, + { + "id": "sigma-789", + "name": "sigma-789", + "query": "message:\"This is an error from IAD region\"", + "tags": ["MITRE:8700"] + } + ] + } + } + ], + "triggers": [ + { + "document_level_trigger": { + "name": "sample_trigger", + "severity": "1", + "condition": { + "script": { + "source": "query[name=sigma-123] || query[name=sigma-456] || query[name=sigma-789]", + "lang": "painless" + } + }, + "actions": [] + } + } + ] +} diff --git a/cypress/integration/document_level_monitor_spec.js b/cypress/integration/document_level_monitor_spec.js new file mode 100644 index 000000000..6b2e55fcd --- /dev/null +++ b/cypress/integration/document_level_monitor_spec.js @@ -0,0 +1,217 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +import { PLUGIN_NAME } from '../support/constants'; +import sampleDocumentLevelMonitor from '../fixtures/sample_document_level_monitor.json'; + +const TESTING_INDEX = 'document-level-monitor-test-index'; +const SAMPLE_EXTRACTION_QUERY_MONITOR = 'sample_extraction_query_document_level_monitor'; +const SAMPLE_VISUAL_EDITOR_MONITOR = 'sample_visual_editor_document_level_monitor'; +const SAMPLE_DOCUMENT_LEVEL_MONITOR = 'sample_document_level_monitor'; + +const addDocumentsToTestIndex = (indexName = '', numOfDocs = 0) => { + for (let i = 0; i < numOfDocs; i++) { + const docBody = { + message: 'This is an error from IAD region', + date: '2020-06-04T18:57:12', + region: 'us-west-2', + }; + cy.insertDocumentToIndex(indexName, undefined, docBody); + } +}; + +describe('DocumentLevelMonitor', () => { + before(() => { + // Load sample data + cy.createIndexByName(TESTING_INDEX); + addDocumentsToTestIndex(TESTING_INDEX, 5); + }); + beforeEach(() => { + // Set welcome screen tracking to false + localStorage.setItem('home:welcome:show', 'false'); + + // Visit Alerting OpenSearch Dashboards + cy.visit(`${Cypress.env('opensearch_dashboards')}/app/${PLUGIN_NAME}#/monitors`); + + // Common text to wait for to confirm page loaded, give up to 20 seconds for initial load + cy.contains('Create monitor', { timeout: 20000 }); + }); + + describe('can be created', () => { + beforeEach(() => { + // Delete existing monitors + cy.deleteAllMonitors(); + cy.reload(); + + // Confirm empty monitor list is loaded + cy.contains('There are no existing monitors'); + + // Go to create monitor page + cy.contains('Create monitor').click(); + + // Select the Document-Level Monitor type + cy.get('[data-test-subj="docLevelMonitorRadioCard"]').click(); + }); + + it('by extraction query editor', () => { + // Select extraction query for method of definition + cy.get('[data-test-subj="extractionQueryEditorRadioCard"]').click(); + + // Wait for input to load and then type in the monitor name + cy.get('input[name="name"]').type(SAMPLE_EXTRACTION_QUERY_MONITOR); + + // Wait for input to load and then type in the index name + cy.get('#index').type(`${TESTING_INDEX}{enter}`, { force: true }); + + // Input extraction query + cy.get('[data-test-subj="extractionQueryCodeEditor"]').within(() => { + cy.get('.ace_text-input') + .focus() + .clear({ force: true }) + .type(JSON.stringify(sampleDocumentLevelMonitor.inputs[0].doc_level_input), { + force: true, + parseSpecialCharSequences: false, + delay: 5, + timeout: 20000, + }) + .trigger('blur', { force: true }); + }); + + // Add a trigger + cy.contains('Add trigger').click({ force: true }); + + // Type in the trigger name + cy.get('input[name="triggerDefinitions[0].name"]').type( + sampleDocumentLevelMonitor.triggers[0].document_level_trigger.name + ); + + // Clear the default trigger condition source, and type the sample source + cy.get('[data-test-subj="triggerQueryCodeEditor"]').within(() => { + cy.get('.ace_text-input') + .focus() + .clear({ force: true }) + .type( + JSON.stringify( + sampleDocumentLevelMonitor.triggers[0].document_level_trigger.condition.script.source + ), + { + force: true, + parseSpecialCharSequences: false, + delay: 5, + timeout: 20000, + } + ) + .trigger('blur', { force: true }); + }); + + // TODO: Test with Notifications plugin + + // Click the create button + cy.get('button').contains('Create').click(); + + // Confirm we can see only one row in the trigger list by checking element + cy.contains('This table contains 1 row'); + + // Confirm we can see the new trigger + cy.contains(sampleDocumentLevelMonitor.triggers[0].document_level_trigger.name); + + // Go back to the Monitors list + cy.get('a').contains('Monitors').click(); + + // Confirm we can see the created monitor in the list + cy.contains(SAMPLE_EXTRACTION_QUERY_MONITOR); + }); + + it('by visual editor', () => { + // Select visual editor for method of definition + cy.get('[data-test-subj="visualEditorRadioCard"]').click(); + + // Wait for input to load and then type in the monitor name + cy.get('input[name="name"]').type(SAMPLE_VISUAL_EDITOR_MONITOR); + + // Wait for input to load and then type in the index name + cy.get('#index').type(`${TESTING_INDEX}{enter}`, { force: true }); + + // Enter query name + cy.get('[data-test-subj="documentLevelQuery_queryName0"]').type( + sampleDocumentLevelMonitor.inputs[0].doc_level_input.queries[0].name + ); + + // Enter query field + cy.get('[data-test-subj="documentLevelQuery_field0"]').type('region{downarrow}{enter}'); + + // Enter query operator + cy.get('[data-test-subj="documentLevelQuery_operator0"]').type('is{enter}'); + + // Enter query + cy.get('[data-test-subj="documentLevelQuery_query0"]').type('us-west-2'); + + // Enter query tags + cy.get('[data-test-subj="addDocLevelQueryTagButton_query0"]').click(); + cy.get('[data-test-subj="documentLevelQueryTag_text_field_query0_tag0"]').type( + sampleDocumentLevelMonitor.inputs[0].doc_level_input.queries[0].tags[0] + ); + + // Add a trigger + cy.contains('Add trigger').click({ force: true }); + + // Type in the trigger name + cy.get('input[name="triggerDefinitions[0].name"]').type( + sampleDocumentLevelMonitor.triggers[0].document_level_trigger.name + ); + + // Define the first condition + cy.get( + '[data-test-subj="documentLevelTriggerExpression_query_triggerDefinitions[0].triggerConditions.0"]' + ).type(sampleDocumentLevelMonitor.inputs[0].doc_level_input.queries[0].tags[0]); + + // Add another condition + cy.get('[data-test-subj="addTriggerConditionButton"]').click(); + + // Define a second condition + cy.get( + '[data-test-subj="documentLevelTriggerExpression_andOr_triggerDefinitions[0].triggerConditions.1"]' + ).type('or{enter}'); + + cy.get( + '[data-test-subj="documentLevelTriggerExpression_query_triggerDefinitions[0].triggerConditions.1"]' + ).type(sampleDocumentLevelMonitor.inputs[0].doc_level_input.queries[0].tags[0]); + + // TODO: Test with Notifications plugin + + // Click the create button + cy.get('button').contains('Create').click(); + + // Confirm we can see only one row in the trigger list by checking element + cy.contains('This table contains 1 row'); + + // Confirm we can see the new trigger + cy.contains(sampleDocumentLevelMonitor.triggers[0].document_level_trigger.name); + + // Go back to the Monitors list + cy.get('a').contains('Monitors').click(); + + // Confirm we can see the created monitor in the list + cy.contains(SAMPLE_VISUAL_EDITOR_MONITOR); + }); + }); + + describe('can be updated', () => { + beforeEach(() => { + cy.deleteAllMonitors(); + }); + + // todo hurney + describe('when defined by visual editor', () => {}); + }); + + after(() => { + // Delete all monitors and destinations + cy.deleteAllMonitors(); + + // Delete sample data + cy.deleteIndexByName(TESTING_INDEX); + }); +}); diff --git a/public/pages/CreateMonitor/components/DocumentLevelMonitorQueries/ConfigureDocumentLevelQueries.js b/public/pages/CreateMonitor/components/DocumentLevelMonitorQueries/ConfigureDocumentLevelQueries.js index c4965de37..8aad21512 100644 --- a/public/pages/CreateMonitor/components/DocumentLevelMonitorQueries/ConfigureDocumentLevelQueries.js +++ b/public/pages/CreateMonitor/components/DocumentLevelMonitorQueries/ConfigureDocumentLevelQueries.js @@ -50,6 +50,7 @@ class ConfigureDocumentLevelQueries extends Component { arrayHelpers.push(_.cloneDeep(FORMIK_INITIAL_DOCUMENT_LEVEL_QUERY_VALUES)) } disabled={numOfQueries >= MAX_QUERIES} + data-test-subj={'addDocLevelQueryButton'} > {numOfQueries === 0 ? 'Add query' : 'Add another query'} diff --git a/public/pages/CreateMonitor/components/DocumentLevelMonitorQueries/ConfigureDocumentLevelQueryTags.js b/public/pages/CreateMonitor/components/DocumentLevelMonitorQueries/ConfigureDocumentLevelQueryTags.js index 176883f73..15da04ddc 100644 --- a/public/pages/CreateMonitor/components/DocumentLevelMonitorQueries/ConfigureDocumentLevelQueryTags.js +++ b/public/pages/CreateMonitor/components/DocumentLevelMonitorQueries/ConfigureDocumentLevelQueryTags.js @@ -58,6 +58,7 @@ class ConfigureDocumentLevelQueryTags extends Component { @@ -72,6 +73,7 @@ class ConfigureDocumentLevelQueryTags extends Component { onClick={() => arrayHelpers.push('')} disabled={numOfTags >= MAX_TAGS} style={{ paddingTop: '5px' }} + data-test-subj={`addDocLevelQueryTagButton_query${queryIndex}`} > + Add tag diff --git a/public/pages/CreateMonitor/components/DocumentLevelMonitorQueries/DocumentLevelQuery.js b/public/pages/CreateMonitor/components/DocumentLevelMonitorQueries/DocumentLevelQuery.js index e6df28762..8fa288e60 100644 --- a/public/pages/CreateMonitor/components/DocumentLevelMonitorQueries/DocumentLevelQuery.js +++ b/public/pages/CreateMonitor/components/DocumentLevelMonitorQueries/DocumentLevelQuery.js @@ -49,13 +49,18 @@ class DocumentLevelQuery extends Component { inputProps={{ placeholder: 'Enter a name for the query', isInvalid, + 'data-test-subj': `documentLevelQuery_queryName${queryIndex}`, }} /> {queryIndex > 0 && ( - queriesArrayHelpers.remove(queryIndex)}> + queriesArrayHelpers.remove(queryIndex)} + data-test-subj={`documentLevelQuery_removeQueryButton${queryIndex}`} + > Remove query @@ -82,6 +87,8 @@ class DocumentLevelQuery extends Component { onChange: (e, field, form) => form.setFieldValue(field.name, e[0].label), onBlur: (e, field, form) => form.setFieldTouched(field.name, true), singleSelection: { asPlainText: true }, + isClearable: false, + 'data-test-subj': `documentLevelQuery_field${queryIndex}`, }} /> @@ -94,6 +101,7 @@ class DocumentLevelQuery extends Component { inputProps={{ onChange: (e, field) => field.onChange(e), options: QUERY_OPERATORS, + 'data-test-subj': `documentLevelQuery_operator${queryIndex}`, }} /> @@ -113,6 +121,7 @@ class DocumentLevelQuery extends Component { placeholder: 'Enter the search term', fullWidth: true, isInvalid, + 'data-test-subj': `documentLevelQuery_query${queryIndex}`, }} /> diff --git a/public/pages/CreateMonitor/components/DocumentLevelMonitorQueries/DocumentLevelQueryTag.js b/public/pages/CreateMonitor/components/DocumentLevelMonitorQueries/DocumentLevelQueryTag.js index e64c77a09..b20e83fe2 100644 --- a/public/pages/CreateMonitor/components/DocumentLevelMonitorQueries/DocumentLevelQueryTag.js +++ b/public/pages/CreateMonitor/components/DocumentLevelMonitorQueries/DocumentLevelQueryTag.js @@ -48,7 +48,7 @@ class DocumentLevelQueryTag extends Component { } renderPopover() { - const { formFieldName } = this.props; + const { formFieldName, queryIndex, tagIndex } = this.props; return (
@@ -95,6 +96,7 @@ class DocumentLevelQueryTag extends Component { formik: { errors }, arrayHelpers, formFieldName, + queryIndex, tag = '', tagIndex = 0, } = this.props; @@ -119,6 +121,7 @@ class DocumentLevelQueryTag extends Component { iconOnClickAriaLabel={'Remove tag'} onClick={this.openPopover} onClickAriaLabel={'Edit tag'} + data-test-subj={`documentLevelQueryTag_badge_query${queryIndex}_tag${tagIndex}`} > {_.isEmpty(tag) ? TAG_PLACEHOLDER_TEXT : tag} diff --git a/public/pages/CreateTrigger/containers/DefineDocumentLevelTrigger/DefineDocumentLevelTrigger.js b/public/pages/CreateTrigger/containers/DefineDocumentLevelTrigger/DefineDocumentLevelTrigger.js index 6366d871b..1bc831765 100644 --- a/public/pages/CreateTrigger/containers/DefineDocumentLevelTrigger/DefineDocumentLevelTrigger.js +++ b/public/pages/CreateTrigger/containers/DefineDocumentLevelTrigger/DefineDocumentLevelTrigger.js @@ -222,6 +222,7 @@ class DefineDocumentLevelTrigger extends Component { } disabled={disableAddTriggerConditionButton} size={'xs'} + data-test-subj={`addTriggerConditionButton`} > + Add condition diff --git a/public/pages/CreateTrigger/containers/DefineDocumentLevelTrigger/DocumentLevelTriggerExpression.js b/public/pages/CreateTrigger/containers/DefineDocumentLevelTrigger/DocumentLevelTriggerExpression.js index 399c71605..6bcb58735 100644 --- a/public/pages/CreateTrigger/containers/DefineDocumentLevelTrigger/DocumentLevelTriggerExpression.js +++ b/public/pages/CreateTrigger/containers/DefineDocumentLevelTrigger/DocumentLevelTriggerExpression.js @@ -28,6 +28,7 @@ class DocumentLevelTriggerExpression extends Component { const isFirstCondition = index === 0; if (index > 0) values['andOrCondition'] = values.andOrCondition || AND_OR_CONDITION_OPTIONS[0].value; + console.info(`hurneyt formFieldName = ${formFieldName}`); return ( {/* Do not display AND/OR selector for the first condition */} @@ -42,6 +43,7 @@ class DocumentLevelTriggerExpression extends Component { inputProps={{ onChange: (e, field) => field.onChange(e), options: AND_OR_CONDITION_OPTIONS, + 'data-test-subj': `documentLevelTriggerExpression_andOr_${formFieldName}`, }} /> @@ -78,6 +80,7 @@ class DocumentLevelTriggerExpression extends Component { }, ] : undefined, + 'data-test-subj': `documentLevelTriggerExpression_query_${formFieldName}`, }} /> @@ -86,7 +89,11 @@ class DocumentLevelTriggerExpression extends Component { {!isFirstCondition && ( - arrayHelpers.remove(index)}> + arrayHelpers.remove(index)} + data-test-subj={`documentLevelTriggerExpression_removeConditionButton_${formFieldName}`} + > Remove condition From f9e0ea32bb3aa9cee1a54a1ba7d5ca5273f7b551 Mon Sep 17 00:00:00 2001 From: AWSHurneyt Date: Tue, 10 May 2022 17:40:17 -0700 Subject: [PATCH 07/17] Implemented an example trigger condition for doc level monitors that are defined using the extraction query editor. Implemented additional form-reset logic when changing monitor types. Moved getDefaultScript to a separate helper class, and refactored the unit tests, to accommodate other monitor types. Signed-off-by: AWSHurneyt --- .../utils/clusterMetricsMonitorHelpers.js | 20 +---- .../clusterMetricsMonitorHelpers.test.js | 59 ------------- .../components/MonitorType/MonitorType.js | 27 ++---- .../ConfigureTriggers/ConfigureTriggers.js | 33 ++++---- .../CreateTrigger/utils/constants.js | 5 ++ public/pages/CreateTrigger/utils/helper.js | 27 +++++- .../pages/CreateTrigger/utils/helper.test.js | 84 +++++++++++++++++++ 7 files changed, 140 insertions(+), 115 deletions(-) create mode 100644 public/pages/CreateTrigger/utils/helper.test.js diff --git a/public/pages/CreateMonitor/components/ClusterMetricsMonitor/utils/clusterMetricsMonitorHelpers.js b/public/pages/CreateMonitor/components/ClusterMetricsMonitor/utils/clusterMetricsMonitorHelpers.js index 929f64aad..55ea7752d 100644 --- a/public/pages/CreateMonitor/components/ClusterMetricsMonitor/utils/clusterMetricsMonitorHelpers.js +++ b/public/pages/CreateMonitor/components/ClusterMetricsMonitor/utils/clusterMetricsMonitorHelpers.js @@ -6,16 +6,13 @@ import _ from 'lodash'; import { formikToClusterMetricsInput } from '../../../containers/CreateMonitor/utils/formikToMonitor'; import { - DEFAULT_CLUSTER_METRICS_SCRIPT, + API_TYPES, + GET_API_TYPE_DEBUG_TEXT, ILLEGAL_PATH_PARAMETER_CHARACTERS, + NO_PATH_PARAMS_PLACEHOLDER_TEXT, PATH_PARAMETER_ILLEGAL_CHARACTER_TEXT, PATH_PARAMETERS_REQUIRED_TEXT, - API_TYPES, - NO_PATH_PARAMS_PLACEHOLDER_TEXT, - GET_API_TYPE_DEBUG_TEXT, } from './clusterMetricsMonitorConstants'; -import { SEARCH_TYPE } from '../../../../../utils/constants'; -import { FORMIK_INITIAL_TRIGGER_VALUES } from '../../../../CreateTrigger/containers/CreateTrigger/utils/constants'; import { FORMIK_INITIAL_VALUES } from '../../../containers/CreateMonitor/utils/constants'; export function buildClusterMetricsRequest(values) { @@ -66,17 +63,6 @@ export const getApiTypesRequiringPathParams = () => { return apiList; }; -export const getDefaultScript = (monitorValues) => { - const searchType = _.get(monitorValues, 'searchType', FORMIK_INITIAL_VALUES.searchType); - switch (searchType) { - case SEARCH_TYPE.CLUSTER_METRICS: - const apiType = _.get(monitorValues, 'uri.api_type'); - return _.get(API_TYPES, `${apiType}.defaultCondition`, DEFAULT_CLUSTER_METRICS_SCRIPT); - default: - return FORMIK_INITIAL_TRIGGER_VALUES.script; - } -}; - export const getExamplePathParams = (apiType) => { if (_.isEmpty(apiType)) return NO_PATH_PARAMS_PLACEHOLDER_TEXT; let exampleText = _.get(API_TYPES, `${apiType}.exampleText`, ''); diff --git a/public/pages/CreateMonitor/components/ClusterMetricsMonitor/utils/clusterMetricsMonitorHelpers.test.js b/public/pages/CreateMonitor/components/ClusterMetricsMonitor/utils/clusterMetricsMonitorHelpers.test.js index f2c2cf35f..ee48e8a1e 100644 --- a/public/pages/CreateMonitor/components/ClusterMetricsMonitor/utils/clusterMetricsMonitorHelpers.test.js +++ b/public/pages/CreateMonitor/components/ClusterMetricsMonitor/utils/clusterMetricsMonitorHelpers.test.js @@ -6,7 +6,6 @@ import _ from 'lodash'; import { API_TYPES, - DEFAULT_CLUSTER_METRICS_SCRIPT, GET_API_TYPE_DEBUG_TEXT, ILLEGAL_PATH_PARAMETER_CHARACTERS, NO_PATH_PARAMS_PLACEHOLDER_TEXT, @@ -19,15 +18,12 @@ import { getApiPath, getApiType, getApiTypesRequiringPathParams, - getDefaultScript, getExamplePathParams, isInvalidApiPathParameter, pathParamsContainIllegalCharacters, validateApiPathParameter, } from './clusterMetricsMonitorHelpers'; import { FORMIK_INITIAL_VALUES } from '../../../containers/CreateMonitor/utils/constants'; -import { SEARCH_TYPE } from '../../../../../utils/constants'; -import { FORMIK_INITIAL_TRIGGER_VALUES } from '../../../../CreateTrigger/containers/CreateTrigger/utils/constants'; describe('clusterMetricsMonitorHelpers', () => { describe('buildClusterMetricsRequest', () => { @@ -273,61 +269,6 @@ describe('clusterMetricsMonitorHelpers', () => { }); }); - describe('getDefaultScript', () => { - test('when searchType is undefined', () => { - const monitorValues = undefined; - expect(getDefaultScript(monitorValues)).toEqual(FORMIK_INITIAL_TRIGGER_VALUES.script); - }); - test('when searchType is clusterMetrics and api_type is undefined', () => { - const monitorValues = { - searchType: SEARCH_TYPE.CLUSTER_METRICS, - uri: undefined, - }; - expect(getDefaultScript(monitorValues)).toEqual(DEFAULT_CLUSTER_METRICS_SCRIPT); - }); - test('when searchType is clusterMetrics and api_type is empty', () => { - const monitorValues = { - searchType: SEARCH_TYPE.CLUSTER_METRICS, - uri: { - api_type: '', - }, - }; - expect(getDefaultScript(monitorValues)).toEqual(DEFAULT_CLUSTER_METRICS_SCRIPT); - }); - test('when searchType is clusterMetrics and api_type does not have a default condition', () => { - const monitorValues = { - searchType: SEARCH_TYPE.CLUSTER_METRICS, - uri: { - api_type: 'unknownApi', - }, - }; - expect(getDefaultScript(monitorValues)).toEqual(DEFAULT_CLUSTER_METRICS_SCRIPT); - }); - - _.keys(SEARCH_TYPE).forEach((searchType) => { - test(`when searchType is ${searchType}`, () => { - if (SEARCH_TYPE[searchType] !== SEARCH_TYPE.CLUSTER_METRICS) { - const monitorValues = { searchType: searchType }; - expect(getDefaultScript(monitorValues)).toEqual(FORMIK_INITIAL_TRIGGER_VALUES.script); - } - }); - }); - - _.keys(API_TYPES).forEach((apiType) => { - test(`when searchType is clusterMetrics and api_type is ${apiType}`, () => { - const monitorValues = { - searchType: SEARCH_TYPE.CLUSTER_METRICS, - uri: { - api_type: apiType, - }, - }; - const expectedOutput = _.get(API_TYPES, `${apiType}.defaultCondition`); - if (!_.isEmpty(expectedOutput)) - expect(getDefaultScript(monitorValues)).toEqual(expectedOutput); - }); - }); - }); - describe('getExamplePathParams', () => { test('when apiType has no example text', () => { const apiType = 'apiTypeWithoutExampleText'; diff --git a/public/pages/CreateMonitor/components/MonitorType/MonitorType.js b/public/pages/CreateMonitor/components/MonitorType/MonitorType.js index 983b3472f..1ba2a1ef9 100644 --- a/public/pages/CreateMonitor/components/MonitorType/MonitorType.js +++ b/public/pages/CreateMonitor/components/MonitorType/MonitorType.js @@ -4,7 +4,6 @@ */ import React from 'react'; -import _ from 'lodash'; import { EuiFlexGrid, EuiFlexItem, EuiText } from '@elastic/eui'; import FormikCheckableCard from '../../../../components/FormControls/FormikCheckableCard'; import { MONITOR_TYPE, SEARCH_TYPE } from '../../../../utils/constants'; @@ -23,8 +22,12 @@ const onChangeDefinition = (e, form) => { // Clearing various form fields when changing monitor types. // TODO: Implement modal that confirms the change before clearing. form.setFieldValue('index', FORMIK_INITIAL_VALUES.index); + form.setFieldValue('searchType', FORMIK_INITIAL_VALUES.searchType); form.setFieldValue('triggerDefinitions', FORMIK_INITIAL_TRIGGER_VALUES.triggerConditions); switch (type) { + case MONITOR_TYPE.CLUSTER_METRICS: + form.setFieldValue('searchType', SEARCH_TYPE.CLUSTER_METRICS); + break; case MONITOR_TYPE.DOC_LEVEL: form.setFieldValue('query', DEFAULT_DOCUMENT_LEVEL_QUERY); break; @@ -71,9 +74,7 @@ const MonitorType = ({ values }) => ( label: 'Per query monitor', checked: values.monitor_type === MONITOR_TYPE.QUERY_LEVEL, value: MONITOR_TYPE.QUERY_LEVEL, - onChange: (e, field, form) => { - onChangeDefinition(e, form); - }, + onChange: (e, field, form) => onChangeDefinition(e, form), children: queryLevelDescription, 'data-test-subj': 'queryLevelMonitorRadioCard', }} @@ -89,14 +90,7 @@ const MonitorType = ({ values }) => ( label: 'Per bucket monitor', checked: values.monitor_type === MONITOR_TYPE.BUCKET_LEVEL, value: MONITOR_TYPE.BUCKET_LEVEL, - onChange: (e, field, form) => { - const searchType = _.get(values, 'searchType'); - // Setting search type to graph when changing monitor type from query-level to bucket-level, - // and the search type is not supported by bucket-level monitors. - if (searchType !== SEARCH_TYPE.GRAPH || searchType !== SEARCH_TYPE.QUERY) - form.setFieldValue('searchType', SEARCH_TYPE.GRAPH); - onChangeDefinition(e, form); - }, + onChange: (e, field, form) => onChangeDefinition(e, form), children: bucketLevelDescription, 'data-test-subj': 'bucketLevelMonitorRadioCard', }} @@ -112,10 +106,7 @@ const MonitorType = ({ values }) => ( label: 'Per cluster metrics monitor', checked: values.monitor_type === MONITOR_TYPE.CLUSTER_METRICS, value: MONITOR_TYPE.CLUSTER_METRICS, - onChange: (e, field, form) => { - form.setFieldValue('searchType', SEARCH_TYPE.CLUSTER_METRICS); - onChangeDefinition(e, form); - }, + onChange: (e, field, form) => onChangeDefinition(e, form), children: clusterMetricsDescription, 'data-test-subj': 'clusterMetricsMonitorRadioCard', }} @@ -131,9 +122,7 @@ const MonitorType = ({ values }) => ( label: 'Per document monitor', checked: values.monitor_type === MONITOR_TYPE.DOC_LEVEL, value: MONITOR_TYPE.DOC_LEVEL, - onChange: (e, field, form) => { - onChangeDefinition(e, form); - }, + onChange: (e, field, form) => onChangeDefinition(e, form), children: documentLevelDescription, 'data-test-subj': 'docLevelMonitorRadioCard', }} diff --git a/public/pages/CreateTrigger/containers/ConfigureTriggers/ConfigureTriggers.js b/public/pages/CreateTrigger/containers/ConfigureTriggers/ConfigureTriggers.js index be72b6afb..90afc3cd4 100644 --- a/public/pages/CreateTrigger/containers/ConfigureTriggers/ConfigureTriggers.js +++ b/public/pages/CreateTrigger/containers/ConfigureTriggers/ConfigureTriggers.js @@ -23,9 +23,9 @@ import DefineDocumentLevelTrigger from '../DefineDocumentLevelTrigger/DefineDocu import { buildClusterMetricsRequest, canExecuteClusterMetricsMonitor, - getDefaultScript, } from '../../../CreateMonitor/components/ClusterMetricsMonitor/utils/clusterMetricsMonitorHelpers'; import { FORMIK_INITIAL_VALUES } from '../../../CreateMonitor/containers/CreateMonitor/utils/constants'; +import { getDefaultScript } from '../../utils/helper'; class ConfigureTriggers extends React.Component { constructor(props) { @@ -85,12 +85,8 @@ class ConfigureTriggers extends React.Component { FORMIK_INITIAL_VALUES.uri.api_type ); if (prevSearchType !== currSearchType || prevApiType !== currApiType) { - switch (currSearchType) { - case SEARCH_TYPE.CLUSTER_METRICS: - _.set(this.state, 'addTriggerButton', this.prepareAddTriggerButton()); - _.set(this.state, 'triggerEmptyPrompt', this.prepareTriggerEmptyPrompt()); - break; - } + this.setState({ addTriggerButton: this.prepareAddTriggerButton() }); + this.setState({ triggerEmptyPrompt: this.prepareTriggerEmptyPrompt() }); } const prevInputs = prevProps.monitor.inputs[0]; @@ -318,6 +314,7 @@ class ConfigureTriggers extends React.Component { renderTriggers = (triggerArrayHelpers) => { const { monitorValues, triggerValues } = this.props; + const { triggerEmptyPrompt } = this.state; const hasTriggers = !_.isEmpty(_.get(triggerValues, 'triggerDefinitions')); const triggerContent = (arrayHelpers, index) => { @@ -331,18 +328,16 @@ class ConfigureTriggers extends React.Component { } }; - return hasTriggers ? ( - triggerValues.triggerDefinitions.map((trigger, index) => { - return ( -
- {triggerContent(triggerArrayHelpers, index)} - -
- ); - }) - ) : ( - - ); + return hasTriggers + ? triggerValues.triggerDefinitions.map((trigger, index) => { + return ( +
+ {triggerContent(triggerArrayHelpers, index)} + +
+ ); + }) + : triggerEmptyPrompt; }; render() { diff --git a/public/pages/CreateTrigger/containers/CreateTrigger/utils/constants.js b/public/pages/CreateTrigger/containers/CreateTrigger/utils/constants.js index 414fffd49..8f92c4636 100644 --- a/public/pages/CreateTrigger/containers/CreateTrigger/utils/constants.js +++ b/public/pages/CreateTrigger/containers/CreateTrigger/utils/constants.js @@ -73,6 +73,11 @@ export const FORMIK_INITIAL_TRIGGER_VALUES = { actions: undefined, }; +export const FORMIK_INITIAL_DOC_LEVEL_SCRIPT = { + lang: FORMIK_INITIAL_TRIGGER_VALUES.script.lang, + source: '(query[name=] || query[name=]) && query[tag=]', +}; + export const HITS_TOTAL_RESULTS_PATH = 'ctx.results[0].hits.total.value'; export const AGGREGATION_RESULTS_PATH = 'ctx.results[0].aggregations.metric.value'; export const ANOMALY_GRADE_RESULT_PATH = 'ctx.results[0].aggregations.max_anomaly_grade.value'; diff --git a/public/pages/CreateTrigger/utils/helper.js b/public/pages/CreateTrigger/utils/helper.js index d89523f76..c4a100ef6 100644 --- a/public/pages/CreateTrigger/utils/helper.js +++ b/public/pages/CreateTrigger/utils/helper.js @@ -3,8 +3,18 @@ * SPDX-License-Identifier: Apache-2.0 */ +import _ from 'lodash'; import { DESTINATION_TYPE } from '../../Destinations/utils/constants'; -import { BACKEND_CHANNEL_TYPE } from '../../../utils/constants'; +import { BACKEND_CHANNEL_TYPE, MONITOR_TYPE } from '../../../utils/constants'; +import { FORMIK_INITIAL_VALUES } from '../../CreateMonitor/containers/CreateMonitor/utils/constants'; +import { + API_TYPES, + DEFAULT_CLUSTER_METRICS_SCRIPT, +} from '../../CreateMonitor/components/ClusterMetricsMonitor/utils/clusterMetricsMonitorConstants'; +import { + FORMIK_INITIAL_DOC_LEVEL_SCRIPT, + FORMIK_INITIAL_TRIGGER_VALUES, +} from '../containers/CreateTrigger/utils/constants'; export const getChannelOptions = (channels, allowedTypes) => allowedTypes.map((type) => ({ @@ -21,3 +31,18 @@ export const toChannelType = (type) => { return type; }; + +export const getDefaultScript = (monitorValues) => { + const monitorType = _.get(monitorValues, 'monitor_type', FORMIK_INITIAL_VALUES.monitor_type); + switch (monitorType) { + case MONITOR_TYPE.BUCKET_LEVEL: + return FORMIK_INITIAL_TRIGGER_VALUES.bucketSelector; + case MONITOR_TYPE.CLUSTER_METRICS: + const apiType = _.get(monitorValues, 'uri.api_type'); + return _.get(API_TYPES, `${apiType}.defaultCondition`, DEFAULT_CLUSTER_METRICS_SCRIPT); + case MONITOR_TYPE.DOC_LEVEL: + return FORMIK_INITIAL_DOC_LEVEL_SCRIPT; + default: + return FORMIK_INITIAL_TRIGGER_VALUES.script; + } +}; diff --git a/public/pages/CreateTrigger/utils/helper.test.js b/public/pages/CreateTrigger/utils/helper.test.js new file mode 100644 index 000000000..24f9c0687 --- /dev/null +++ b/public/pages/CreateTrigger/utils/helper.test.js @@ -0,0 +1,84 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +import _ from 'lodash'; +import { getDefaultScript } from './helper'; +import { MONITOR_TYPE } from '../../../utils/constants'; +import { + FORMIK_INITIAL_DOC_LEVEL_SCRIPT, + FORMIK_INITIAL_TRIGGER_VALUES, +} from '../containers/CreateTrigger/utils/constants'; +import { + API_TYPES, + DEFAULT_CLUSTER_METRICS_SCRIPT, +} from '../../CreateMonitor/components/ClusterMetricsMonitor/utils/clusterMetricsMonitorConstants'; + +describe('CreateTrigger/utils/helper', () => { + describe('getDefaultScript', () => { + test('when monitor_type is undefined', () => { + const monitorValues = undefined; + expect(getDefaultScript(monitorValues)).toEqual(FORMIK_INITIAL_TRIGGER_VALUES.script); + }); + + test(`when monitor_type is ${MONITOR_TYPE.BUCKET_LEVEL}`, () => { + const monitorValues = { monitor_type: MONITOR_TYPE.BUCKET_LEVEL }; + expect(getDefaultScript(monitorValues)).toEqual(FORMIK_INITIAL_TRIGGER_VALUES.bucketSelector); + }); + + test(`when monitor_type is ${MONITOR_TYPE.DOC_LEVEL}`, () => { + const monitorValues = { monitor_type: MONITOR_TYPE.DOC_LEVEL }; + expect(getDefaultScript(monitorValues)).toEqual(FORMIK_INITIAL_DOC_LEVEL_SCRIPT); + }); + + test(`when monitor_type is ${MONITOR_TYPE.QUERY_LEVEL}`, () => { + const monitorValues = { monitor_type: MONITOR_TYPE.QUERY_LEVEL }; + expect(getDefaultScript(monitorValues)).toEqual(FORMIK_INITIAL_TRIGGER_VALUES.script); + }); + + describe(`when monitor_type is ${MONITOR_TYPE.CLUSTER_METRICS}`, () => { + test('and api_type is undefined', () => { + const monitorValues = { + monitor_type: MONITOR_TYPE.CLUSTER_METRICS, + uri: undefined, + }; + expect(getDefaultScript(monitorValues)).toEqual(DEFAULT_CLUSTER_METRICS_SCRIPT); + }); + + test('and api_type is empty', () => { + const monitorValues = { + monitor_type: MONITOR_TYPE.CLUSTER_METRICS, + uri: { + api_type: '', + }, + }; + expect(getDefaultScript(monitorValues)).toEqual(DEFAULT_CLUSTER_METRICS_SCRIPT); + }); + + test('and api_type does not have a default condition', () => { + const monitorValues = { + monitor_type: MONITOR_TYPE.CLUSTER_METRICS, + uri: { + api_type: 'unknownApi', + }, + }; + expect(getDefaultScript(monitorValues)).toEqual(DEFAULT_CLUSTER_METRICS_SCRIPT); + }); + + _.keys(API_TYPES).forEach((apiType) => { + test(`and api_type is ${apiType}`, () => { + const monitorValues = { + monitor_type: MONITOR_TYPE.CLUSTER_METRICS, + uri: { + api_type: apiType, + }, + }; + const expectedOutput = _.get(API_TYPES, `${apiType}.defaultCondition`); + if (!_.isEmpty(expectedOutput)) + expect(getDefaultScript(monitorValues)).toEqual(expectedOutput); + }); + }); + }); + }); +}); From 236d07c1a21f4893988c041eee309ef8bcea635f Mon Sep 17 00:00:00 2001 From: AWSHurneyt Date: Tue, 10 May 2022 17:41:11 -0700 Subject: [PATCH 08/17] Refactored actions component for doc level monitors to support configuration action execution options. Signed-off-by: AWSHurneyt --- .../Action/__snapshots__/Action.test.js.snap | 198 ++++++++++++++++-- .../components/Action/actions/Message.js | 145 ++++++------- .../__snapshots__/Message.test.js.snap | 99 ++++++++- .../CreateTrigger/utils/formikToTrigger.js | 3 +- .../CreateTrigger/utils/triggerToFormik.js | 6 +- 5 files changed, 338 insertions(+), 113 deletions(-) diff --git a/public/pages/CreateTrigger/components/Action/__snapshots__/Action.test.js.snap b/public/pages/CreateTrigger/components/Action/__snapshots__/Action.test.js.snap index f7c795f04..7b7429067 100644 --- a/public/pages/CreateTrigger/components/Action/__snapshots__/Action.test.js.snap +++ b/public/pages/CreateTrigger/components/Action/__snapshots__/Action.test.js.snap @@ -397,24 +397,101 @@ exports[`Action renders with Notifications plugin installed 1`] = `
-
+
-
- + Perform action - -
- Per monitor execution + + +
+
+
+
+
+
+
+ +
+ +
+
+
+
+
+
+
+
+ +
+ +
+
+
-
-
+
-
- + Perform action - -
- Per monitor execution + + +
+
+
+
+
+
+
+ +
+ +
+
+
+
+
+
+
+
+ +
+ +
+
+
-
setDisplayPreview(e.target.checked); - const isBucketLevelMonitor = - _.get(context, 'ctx.monitor.monitor_type', MONITOR_TYPE.QUERY_LEVEL) === - MONITOR_TYPE.BUCKET_LEVEL; + const monitorType = _.get(context, 'ctx.monitor.monitor_type', MONITOR_TYPE.QUERY_LEVEL); + const editableActionExecutionPolicy = + monitorType === MONITOR_TYPE.BUCKET_LEVEL || MONITOR_TYPE.DOC_LEVEL; + const actionPath = `${fieldPath}actions.${index}`; - const actionExecutionPolicyPath = isBucketLevelMonitor + const actionExecutionPolicyPath = editableActionExecutionPolicy ? `${actionPath}.action_execution_policy` : actionPath; + const actionableAlertsSelectionsPath = `${actionExecutionPolicyPath}.action_execution_scope.${NOTIFY_OPTIONS_VALUES.PER_ALERT}.actionable_alerts`; - let actionExecutionScopeId = isBucketLevelMonitor + let actionExecutionScopeId = editableActionExecutionPolicy ? _.get( action, 'action_execution_policy.action_execution_scope', @@ -157,24 +159,31 @@ export default function Message( if (!_.isString(actionExecutionScopeId)) actionExecutionScopeId = _.keys(actionExecutionScopeId)[0]; - let actionableAlertsSelections = _.get( - values, - `${actionExecutionPolicyPath}.action_execution_scope.${NOTIFY_OPTIONS_VALUES.PER_ALERT}.actionable_alerts` - ); + let actionableAlertsSelections; + let displayActionableAlertsOptions; + let displayThrottlingSettings; + switch (monitorType) { + case MONITOR_TYPE.BUCKET_LEVEL: + displayActionableAlertsOptions = true; + actionableAlertsSelections = _.get(values, actionableAlertsSelectionsPath); + break; + case MONITOR_TYPE.DOC_LEVEL: + displayActionableAlertsOptions = false; + displayThrottlingSettings = false; + actionableAlertsSelections = []; + break; + default: + displayActionableAlertsOptions = false; + displayThrottlingSettings = actionExecutionScopeId !== NOTIFY_OPTIONS_VALUES.PER_EXECUTION; + } if (actionExecutionScopeId === NOTIFY_OPTIONS_VALUES.PER_ALERT) { - if (_.get(values, `${actionPath}.throttle.value`) === undefined) { + if (_.get(values, `${actionPath}.throttle.value`) === undefined) _.set(values, `${actionPath}.throttle.value`, 10); - } - if (actionableAlertsSelections === undefined) { - _.set( - values, - `${actionExecutionPolicyPath}.action_execution_scope.${NOTIFY_OPTIONS_VALUES.PER_ALERT}.actionable_alerts`, - DEFAULT_ACTIONABLE_ALERTS_SELECTIONS - ); + if (actionableAlertsSelections === undefined) actionableAlertsSelections = DEFAULT_ACTIONABLE_ALERTS_SELECTIONS; - } + _.set(values, actionableAlertsSelectionsPath, actionableAlertsSelections); } let preview = ''; @@ -237,7 +246,7 @@ export default function Message( {renderSendTestMessageButton( index, sendTestMessage, - isBucketLevelMonitor, + monitorType === MONITOR_TYPE.BUCKET_LEVEL, displayPreview, onDisplayPreviewChange, fieldPath @@ -264,7 +273,7 @@ export default function Message( - {isBucketLevelMonitor ? ( + {editableActionExecutionPolicy ? ( Perform action} style={{ maxWidth: '100%' }} @@ -279,9 +288,7 @@ export default function Message( value: NOTIFY_OPTIONS_VALUES.PER_EXECUTION, checked: actionExecutionScopeId === NOTIFY_OPTIONS_VALUES.PER_EXECUTION, label: NOTIFY_OPTIONS_LABELS.PER_EXECUTION, - onChange: (e, field, form) => { - field.onChange(e); - }, + onChange: (e, field, form) => field.onChange(e), }} /> @@ -294,63 +301,49 @@ export default function Message( value: NOTIFY_OPTIONS_VALUES.PER_ALERT, checked: actionExecutionScopeId === NOTIFY_OPTIONS_VALUES.PER_ALERT, label: NOTIFY_OPTIONS_LABELS.PER_ALERT, - onChange: (e, field, form) => { - field.onChange(e); - }, + onChange: (e, field, form) => field.onChange(e), }} /> - - {actionExecutionScopeId === NOTIFY_OPTIONS_VALUES.PER_ALERT ? ( - - + + - { - form.setFieldTouched( - `${actionExecutionPolicyPath}.action_execution_scope.${NOTIFY_OPTIONS_VALUES.PER_ALERT}.actionable_alerts`, - true - ); - }, - onChange: (options, field, form) => { - form.setFieldValue( - `${actionExecutionPolicyPath}.action_execution_scope.${NOTIFY_OPTIONS_VALUES.PER_ALERT}.actionable_alerts`, - options - ); - }, - isClearable: true, - selectedOptions: actionableAlertsSelections, - }} - /> - - - ) : null} - + inputProps={{ + placeholder: 'Select alert options', + options: ACTIONABLE_ALERTS_OPTIONS, + onBlur: (e, field, form) => { + form.setFieldTouched(actionableAlertsSelectionsPath, true); + }, + onChange: (options, field, form) => { + form.setFieldValue(actionableAlertsSelectionsPath, options); + }, + isClearable: true, + selectedOptions: actionableAlertsSelections, + }} + /> + + + ) : null} ) : ( @@ -360,7 +353,7 @@ export default function Message(
)} - {actionExecutionScopeId !== NOTIFY_OPTIONS_VALUES.PER_EXECUTION ? ( + {displayThrottlingSettings ? ( diff --git a/public/pages/CreateTrigger/components/Action/actions/__snapshots__/Message.test.js.snap b/public/pages/CreateTrigger/components/Action/actions/__snapshots__/Message.test.js.snap index 2d689a0b2..e11394570 100644 --- a/public/pages/CreateTrigger/components/Action/actions/__snapshots__/Message.test.js.snap +++ b/public/pages/CreateTrigger/components/Action/actions/__snapshots__/Message.test.js.snap @@ -170,24 +170,101 @@ exports[`Message renders 1`] = `
-
+
-
- + Perform action - -
- Per monitor execution + + +
+
+
+
+
+
+
+ +
+ +
+
+
+
+
+
+
+
+ +
+ +
+
+
-
{ const actionExecutionPolicy = _.get(action, `${executionPolicyPath}`); From ba4f22f967db278005d11aea9dde586fd2231b57 Mon Sep 17 00:00:00 2001 From: AWSHurneyt Date: Tue, 17 May 2022 08:04:24 -0700 Subject: [PATCH 09/17] Refactored actions component to refresh the list of channels on blur. Signed-off-by: AWSHurneyt --- .../pages/CreateTrigger/components/Action/Action.js | 13 ++++++++++++- .../containers/ConfigureActions/ConfigureActions.js | 3 ++- 2 files changed, 14 insertions(+), 2 deletions(-) diff --git a/public/pages/CreateTrigger/components/Action/Action.js b/public/pages/CreateTrigger/components/Action/Action.js index 70d9d3e20..bb1ae8d9f 100644 --- a/public/pages/CreateTrigger/components/Action/Action.js +++ b/public/pages/CreateTrigger/components/Action/Action.js @@ -3,7 +3,7 @@ * SPDX-License-Identifier: Apache-2.0 */ -import React from 'react'; +import React, { useState } from 'react'; import _ from 'lodash'; import { EuiAccordion, @@ -36,7 +36,9 @@ const Action = ({ fieldPath, values, hasNotificationPlugin, + loadDestinations, }) => { + const [loadingDestinations, setLoadingDestinations] = useState(false); const selectedDestination = flattenedDestinations.filter( (item) => item.value === action.destination_id ); @@ -47,6 +49,12 @@ const Action = ({ const manageChannelsUrl = httpClient.basePath.prepend(MANAGE_CHANNELS_PATH); const isFirstAction = index !== undefined && index === 0; + async function refreshDestinations() { + setLoadingDestinations(true); + await loadDestinations(); + setLoadingDestinations(false); + } + const renderChannels = () => { return (
@@ -74,6 +82,7 @@ const Action = ({ }); }, onBlur: (e, field, form) => { + refreshDestinations(); form.setFieldTouched(`${fieldPath}actions.${index}.destination_id`, true); }, singleSelection: { asPlainText: true }, @@ -87,6 +96,8 @@ const Action = ({ ), rowHeight: 45, + async: true, + isLoading: loadingDestinations, }} /> diff --git a/public/pages/CreateTrigger/containers/ConfigureActions/ConfigureActions.js b/public/pages/CreateTrigger/containers/ConfigureActions/ConfigureActions.js index 0b2ebd89a..082f28092 100644 --- a/public/pages/CreateTrigger/containers/ConfigureActions/ConfigureActions.js +++ b/public/pages/CreateTrigger/containers/ConfigureActions/ConfigureActions.js @@ -278,6 +278,7 @@ class ConfigureActions extends React.Component { fieldPath={fieldPath} values={values} hasNotificationPlugin={hasNotificationPlugin} + loadDestinations={this.loadDestinations} /> )) ) : ( @@ -305,7 +306,7 @@ class ConfigureActions extends React.Component { Define actions when trigger conditions are met. - {loadingDestinations ? ( + {loadingDestinations && numOfActions < 1 ? (
Loading Destinations...
) : ( this.renderActions(arrayHelpers) From 6423ed9aae0ee2f0924a3057e1a298c0d8119d65 Mon Sep 17 00:00:00 2001 From: AWSHurneyt Date: Tue, 17 May 2022 08:07:05 -0700 Subject: [PATCH 10/17] Fixed a bug that was causing the finding flyout triggered by the alerts table to allow multiple flyouts to be open at once. Signed-off-by: AWSHurneyt --- .../AcknowledgeAlertsModal.js | 15 +++++++-- .../FindingsDashboard/FindingFlyout.js | 19 +++++++++--- .../FindingsDashboard/findingsUtils.js | 12 +++++-- .../pages/Dashboard/containers/Dashboard.js | 31 +++++++++++++------ 4 files changed, 58 insertions(+), 19 deletions(-) diff --git a/public/pages/Dashboard/components/AcknowledgeAlertsModal/AcknowledgeAlertsModal.js b/public/pages/Dashboard/components/AcknowledgeAlertsModal/AcknowledgeAlertsModal.js index 794514397..7bfd9e7e9 100644 --- a/public/pages/Dashboard/components/AcknowledgeAlertsModal/AcknowledgeAlertsModal.js +++ b/public/pages/Dashboard/components/AcknowledgeAlertsModal/AcknowledgeAlertsModal.js @@ -44,7 +44,7 @@ import DashboardControls from '../DashboardControls'; import ContentPanel from '../../../../components/ContentPanel'; import { queryColumns } from '../../utils/tableUtils'; import DashboardEmptyPrompt from '../DashboardEmptyPrompt'; -import { getAlertsFindingColumn } from '../FindingsDashboard/utils'; +import { getAlertsFindingColumn } from '../FindingsDashboard/findingsUtils'; export const DEFAULT_NUM_MODAL_ROWS = 10; @@ -66,6 +66,7 @@ export default class AcknowledgeAlertsModal extends Component { this.state = { alerts: [], alertState: alertState, + flyoutIsOpen: false, loading: true, monitors: [], monitorIds: [monitor_id], @@ -324,6 +325,7 @@ export default class AcknowledgeAlertsModal extends Component { const { alerts = [], alertState, + flyoutIsOpen, loading, page, search, @@ -347,7 +349,16 @@ export default class AcknowledgeAlertsModal extends Component { columns.splice( 0, 0, - getAlertsFindingColumn(httpClient, history, false, location, notifications) + getAlertsFindingColumn( + httpClient, + history, + false, + location, + notifications, + flyoutIsOpen, + () => this.setState({ flyoutIsOpen: true }), + () => this.setState({ flyoutIsOpen: false }) + ) ); break; default: diff --git a/public/pages/Dashboard/components/FindingsDashboard/FindingFlyout.js b/public/pages/Dashboard/components/FindingsDashboard/FindingFlyout.js index c2ae8b0e8..ef15234d0 100644 --- a/public/pages/Dashboard/components/FindingsDashboard/FindingFlyout.js +++ b/public/pages/Dashboard/components/FindingsDashboard/FindingFlyout.js @@ -19,8 +19,7 @@ import { EuiText, EuiTitle, } from '@elastic/eui'; -import _ from 'lodash'; -import { getFindings } from './utils'; +import { getFindings } from './findingsUtils'; import { DEFAULT_GET_FINDINGS_PARAMS } from '../../../../../server/services/FindingService'; export const NO_FINDING_DOC_ID_TEXT = 'No document ID'; @@ -34,7 +33,6 @@ export default class FindingFlyout extends Component { docList: alertDocList || document_list, flyout: undefined, finding: finding, - flyoutHeight: undefined, isFlyoutOpen: false, }; } @@ -61,7 +59,12 @@ export default class FindingFlyout extends Component { } onClick = () => { + const { dashboardFlyoutIsOpen = false, openFlyout, closeFlyout } = this.props; const { isFlyoutOpen } = this.state; + if (typeof openFlyout === 'function' && typeof closeFlyout === 'function') { + if (dashboardFlyoutIsOpen) closeFlyout(); + else openFlyout(); + } this.setState({ isFlyoutOpen: !isFlyoutOpen }); }; @@ -170,11 +173,17 @@ export default class FindingFlyout extends Component { } render() { + const { dashboardFlyoutIsOpen } = this.props; const { docList, flyout, isFlyoutOpen } = this.state; + const openFlyout = _.isUndefined(dashboardFlyoutIsOpen) + ? isFlyoutOpen + : dashboardFlyoutIsOpen && isFlyoutOpen; + let docId = _.get(docList, '0.id', NO_FINDING_DOC_ID_TEXT); + docId = _.split(docId, '|')[0]; return (
- {_.get(docList, '0.id', NO_FINDING_DOC_ID_TEXT)} - {isFlyoutOpen && flyout} + {docId} + {openFlyout && flyout}
); } diff --git a/public/pages/Dashboard/components/FindingsDashboard/findingsUtils.js b/public/pages/Dashboard/components/FindingsDashboard/findingsUtils.js index edc198ce8..8237210d4 100644 --- a/public/pages/Dashboard/components/FindingsDashboard/findingsUtils.js +++ b/public/pages/Dashboard/components/FindingsDashboard/findingsUtils.js @@ -43,7 +43,10 @@ export const getAlertsFindingColumn = ( history, isAlertsFlyout = false, location, - notifications + notifications, + flyoutIsOpen, + openFlyout, + closeFlyout ) => { return { field: 'related_doc_ids', @@ -61,6 +64,9 @@ export const getAlertsFindingColumn = ( history={history} location={location} notifications={notifications} + dashboardFlyoutIsOpen={flyoutIsOpen} + openFlyout={openFlyout} + closeFlyout={closeFlyout} /> ); }, @@ -163,14 +169,14 @@ export const parseFindingsForPreview = (previewResponse = {}, index = '', querie export const validDocLevelGraphQueries = (queries = []) => { // The 'queryName', 'field', 'operator', and 'query' fields are required to execute a doc level query. // If any of those fields are undefined for any queries, the monitor cannot be executed. - const definedQueries = queries.find( + const incompleteQueries = queries.find( (query) => _.isEmpty(query.queryName) || _.isEmpty(query.field) || _.isEmpty(query.operator) || _.isEmpty(query.query) ); - return !_.isEmpty(queries) && _.isEmpty(definedQueries); + return !_.isEmpty(queries) && _.isEmpty(incompleteQueries); }; export async function getFindings({ diff --git a/public/pages/Dashboard/containers/Dashboard.js b/public/pages/Dashboard/containers/Dashboard.js index 8f98446e4..09784ad33 100644 --- a/public/pages/Dashboard/containers/Dashboard.js +++ b/public/pages/Dashboard/containers/Dashboard.js @@ -29,7 +29,7 @@ import { import { DEFAULT_PAGE_SIZE_OPTIONS } from '../../Monitors/containers/Monitors/utils/constants'; import { MAX_ALERT_COUNT } from '../utils/constants'; import AcknowledgeAlertsModal from '../components/AcknowledgeAlertsModal'; -import { getAlertsFindingColumn } from '../components/FindingsDashboard/utils'; +import { getAlertsFindingColumn } from '../components/FindingsDashboard/findingsUtils'; export default class Dashboard extends Component { constructor(props) { @@ -276,16 +276,19 @@ export default class Dashboard extends Component { }; openFlyout = (payload) => { - this.setState({ ...this.state, flyoutIsOpen: true }); - this.props.setFlyout({ - type: 'alertsDashboard', - payload: { ...payload }, - }); + this.setState({ flyoutIsOpen: true }); + if (!_.isEmpty(payload)) { + this.props.setFlyout({ + type: 'alertsDashboard', + payload: { ...payload }, + }); + } }; closeFlyout = () => { - this.props.setFlyout(null); - this.setState({ ...this.state, flyoutIsOpen: false }); + const { setFlyout } = this.props; + if (typeof setFlyout === 'function') setFlyout(null); + this.setState({ flyoutIsOpen: false }); }; refreshDashboard = () => { @@ -345,6 +348,7 @@ export default class Dashboard extends Component { alerts, alertsByTriggers, alertState, + flyoutIsOpen, loadingMonitors, monitors, page, @@ -385,7 +389,16 @@ export default class Dashboard extends Component { columnType.splice( 0, 0, - getAlertsFindingColumn(httpClient, history, false, location, notifications) + getAlertsFindingColumn( + httpClient, + history, + false, + location, + notifications, + flyoutIsOpen, + this.openFlyout, + this.closeFlyout + ) ); break; default: From 5800a71e63d71e9c2d19a3ef56c3be3411b424f7 Mon Sep 17 00:00:00 2001 From: AWSHurneyt Date: Tue, 17 May 2022 08:07:29 -0700 Subject: [PATCH 11/17] Removed development comment. Signed-off-by: AWSHurneyt --- .../containers/CreateTrigger/utils/formikToTrigger.js | 1 - 1 file changed, 1 deletion(-) diff --git a/public/pages/CreateTrigger/containers/CreateTrigger/utils/formikToTrigger.js b/public/pages/CreateTrigger/containers/CreateTrigger/utils/formikToTrigger.js index 7378261ee..299ba6ee6 100644 --- a/public/pages/CreateTrigger/containers/CreateTrigger/utils/formikToTrigger.js +++ b/public/pages/CreateTrigger/containers/CreateTrigger/utils/formikToTrigger.js @@ -73,7 +73,6 @@ export function formikToBucketLevelTrigger(values, monitorUiMetadata) { export function formikToDocumentLevelTrigger(values, monitorUiMetadata) { const condition = formikToDocumentLevelTriggerCondition(values, monitorUiMetadata); - // const actions = formikToAction(values); // todo hurneyt const actions = formikToBucketLevelTriggerAction(values); return { document_level_trigger: { From d62434fa90a022be9adf6b6d71f821c5b326f896 Mon Sep 17 00:00:00 2001 From: AWSHurneyt Date: Tue, 17 May 2022 08:09:45 -0700 Subject: [PATCH 12/17] Fixed a bug that prevented sorting the performance preview for doc level monitors defined with the visual editor. Signed-off-by: AWSHurneyt --- .../containers/DefineMonitor/DefineMonitor.js | 15 +++- .../Dashboard/containers/FindingsDashboard.js | 69 ++++++++++++------- 2 files changed, 58 insertions(+), 26 deletions(-) diff --git a/public/pages/CreateMonitor/containers/DefineMonitor/DefineMonitor.js b/public/pages/CreateMonitor/containers/DefineMonitor/DefineMonitor.js index 5c464f6a5..3e60cd8b8 100644 --- a/public/pages/CreateMonitor/containers/DefineMonitor/DefineMonitor.js +++ b/public/pages/CreateMonitor/containers/DefineMonitor/DefineMonitor.js @@ -225,7 +225,13 @@ class DefineMonitor extends Component { renderGraph() { const { errors, history, httpClient, location, notifications, values } = this.props; - const { response, performanceResponse, formikSnapshot, dataTypes } = this.state; + const { + response, + performanceResponse, + formikSnapshot, + dataTypes, + loadingResponse, + } = this.state; const aggregations = _.get(values, 'aggregations'); const monitorExpressions = () => { @@ -278,6 +284,8 @@ class DefineMonitor extends Component { ? renderEmptyMessage( 'Invalid input in data filter. Remove data filter or adjust filter ' ) + : loadingResponse + ? renderEmptyMessage() : previewContent()} @@ -286,7 +294,6 @@ class DefineMonitor extends Component { } async onRunQuery() { - this.setState({ loadingResponse: true }); const { httpClient, values, notifications } = this.props; const { monitor_type, searchType } = values; @@ -294,8 +301,10 @@ class DefineMonitor extends Component { switch (monitor_type) { case MONITOR_TYPE.DOC_LEVEL: const { queries } = values; - if (SEARCH_TYPE.GRAPH && !validDocLevelGraphQueries(queries)) return; + const canExecute = searchType === SEARCH_TYPE.GRAPH && validDocLevelGraphQueries(queries); + if (!canExecute) return; } + this.setState({ loadingResponse: true }); const formikSnapshot = _.cloneDeep(values); let requests; diff --git a/public/pages/Dashboard/containers/FindingsDashboard.js b/public/pages/Dashboard/containers/FindingsDashboard.js index c9e44e60f..b029eb361 100644 --- a/public/pages/Dashboard/containers/FindingsDashboard.js +++ b/public/pages/Dashboard/containers/FindingsDashboard.js @@ -72,9 +72,13 @@ export default class FindingsDashboard extends Component { } componentDidUpdate(prevProps, prevState) { + const { isPreview = false } = this.props; const prevQuery = this.getQueryObjectFromState(prevState); const currQuery = this.getQueryObjectFromState(this.state); - if (!_.isEqual(prevQuery, currQuery)) this.getFindings(); + if (!_.isEqual(prevQuery, currQuery)) { + if (isPreview) this.sortPreviewFindings(prevState.sortDirection, this.state.sortDirection); + else this.getFindings(); + } } getURLQueryParams() { @@ -137,8 +141,25 @@ export default class FindingsDashboard extends Component { }); } + sortPreviewFindings(prevSortDirection, currSortDirection) { + const { findings, sortField } = this.state; + let sortedFindings; + switch (sortField) { + case 'document_list': + sortedFindings = _.sortBy(findings, `related_doc_id`); + break; + case 'queries': + sortedFindings = _.sortBy(findings, `queries`); + break; + default: + sortedFindings = _.sortBy(findings, `timestamp`); + } + if (prevSortDirection !== currSortDirection) sortedFindings = _.reverse(sortedFindings); + this.setState({ findings: sortedFindings }); + } + onTableChange = ({ page: tablePage = {}, sort = {} }) => { - const { index: page, size } = tablePage; + const { index: page = 0, size = 10 } = tablePage; const { field: sortField, direction: sortDirection } = sort; this.setState({ page, size, sortField, sortDirection }); }; @@ -184,27 +205,29 @@ export default class FindingsDashboard extends Component { titleSize={'s'} bodyStyles={{ padding: 'initial' }} > - - - { - this.setState({ page: 0, search: selection.target.value }); - this.getFindings(); - }} - value={search} - /> - - - - this.setState({ page: page })} - /> - - + {!isPreview && ( + + + { + this.setState({ page: 0, search: selection.target.value }); + this.getFindings(); + }} + value={search} + /> + + + + this.setState({ page: page })} + /> + + + )} From 3a8218ed14464e6fc8ee542c60cf6977316362cb Mon Sep 17 00:00:00 2001 From: AWSHurneyt Date: Tue, 17 May 2022 08:10:21 -0700 Subject: [PATCH 13/17] Fixed a bug that allowed doc level monitors to be defined with blank queries/tags when using the visual editor. Signed-off-by: AWSHurneyt --- .../DocumentLevelTriggerExpression.js | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/public/pages/CreateTrigger/containers/DefineDocumentLevelTrigger/DocumentLevelTriggerExpression.js b/public/pages/CreateTrigger/containers/DefineDocumentLevelTrigger/DocumentLevelTriggerExpression.js index 6bcb58735..43b63de9f 100644 --- a/public/pages/CreateTrigger/containers/DefineDocumentLevelTrigger/DocumentLevelTriggerExpression.js +++ b/public/pages/CreateTrigger/containers/DefineDocumentLevelTrigger/DocumentLevelTriggerExpression.js @@ -28,7 +28,6 @@ class DocumentLevelTriggerExpression extends Component { const isFirstCondition = index === 0; if (index > 0) values['andOrCondition'] = values.andOrCondition || AND_OR_CONDITION_OPTIONS[0].value; - console.info(`hurneyt formFieldName = ${formFieldName}`); return ( {/* Do not display AND/OR selector for the first condition */} @@ -85,7 +84,7 @@ class DocumentLevelTriggerExpression extends Component { /> - {/* Do not display the button for the first condition */} + {/* Do not display this button for the first condition */} {!isFirstCondition && ( From 53c93df29a02fde7302b0eb3e64e435d66f3eae3 Mon Sep 17 00:00:00 2001 From: AWSHurneyt Date: Tue, 17 May 2022 08:11:20 -0700 Subject: [PATCH 14/17] Refactored the backend formatting used for 'is not' queries. Signed-off-by: AWSHurneyt --- .../containers/CreateMonitor/utils/formikToMonitor.js | 4 +--- .../containers/CreateMonitor/utils/monitorToFormik.js | 10 +++++----- 2 files changed, 6 insertions(+), 8 deletions(-) diff --git a/public/pages/CreateMonitor/containers/CreateMonitor/utils/formikToMonitor.js b/public/pages/CreateMonitor/containers/CreateMonitor/utils/formikToMonitor.js index 2b63661a6..a610d9abb 100644 --- a/public/pages/CreateMonitor/containers/CreateMonitor/utils/formikToMonitor.js +++ b/public/pages/CreateMonitor/containers/CreateMonitor/utils/formikToMonitor.js @@ -231,9 +231,7 @@ export function formikToDocLevelInput(values) { const formikToQuery = query.operator === '==' ? `${query.field}:\"${query.query}\"` - : JSON.stringify({ - bool: { must_not: { term: { [query.field]: `\"${query.query}\"` } } }, - }); + : `NOT (${query.field}:\"${query.query}\")`; return { // id: query.id, // TODO FIXME: Refactor to this assignment logic once backend generates its own ID value id: query.queryName, diff --git a/public/pages/CreateMonitor/containers/CreateMonitor/utils/monitorToFormik.js b/public/pages/CreateMonitor/containers/CreateMonitor/utils/monitorToFormik.js index 250ef48a1..f76f3bab6 100644 --- a/public/pages/CreateMonitor/containers/CreateMonitor/utils/monitorToFormik.js +++ b/public/pages/CreateMonitor/containers/CreateMonitor/utils/monitorToFormik.js @@ -86,14 +86,14 @@ export function queriesToFormik(queries) { } const parsedQuerySource = {}; - const usesIsNotOperator = _.has(querySource, 'bool'); + const usesIsNotOperator = _.startsWith(querySource, 'NOT (') && _.endsWith(querySource, ')'); const operator = usesIsNotOperator ? '!=' : '=='; if (usesIsNotOperator) { - const term = _.get(querySource, 'bool.must_not.term'); - const field = _.keys(term)[0]; - parsedQuerySource['field'] = _.trim(field, '":'); - parsedQuerySource['query'] = _.trim(term[field], '"'); + querySource = querySource.substring(5, querySource.length - 1); + querySource = _.split(querySource, ':'); + parsedQuerySource['field'] = _.trim(querySource[0], '"'); + parsedQuerySource['query'] = _.trim(querySource[1], '"'); } else { const splitQuery = _.split(querySource, '"'); parsedQuerySource['field'] = _.trim(splitQuery[0], '":'); From 1832004af10011a364615beb4eb1ba4ca194e019 Mon Sep 17 00:00:00 2001 From: AWSHurneyt Date: Tue, 17 May 2022 08:11:40 -0700 Subject: [PATCH 15/17] Implemented additional integration tests. Signed-off-by: AWSHurneyt --- .../sample_document_level_monitor.json | 59 ++++++- .../document_level_monitor_spec.js | 145 +++++++++++++++++- 2 files changed, 197 insertions(+), 7 deletions(-) diff --git a/cypress/fixtures/sample_document_level_monitor.json b/cypress/fixtures/sample_document_level_monitor.json index b2ad999ba..5d59af440 100644 --- a/cypress/fixtures/sample_document_level_monitor.json +++ b/cypress/fixtures/sample_document_level_monitor.json @@ -52,5 +52,62 @@ "actions": [] } } - ] + ], + "ui_metadata": { + "schedule": { + "timezone": null, + "frequency": "interval", + "period": { + "interval": 1, + "unit": "MINUTES" + }, + "daily": 0, + "weekly": { + "mon": false, + "tue": false, + "wed": false, + "thur": false, + "fri": false, + "sat": false, + "sun": false + }, + "monthly": { + "type": "day", + "day": 1 + }, + "cronExpression": "0 */1 * * *" + }, + "monitor_type": "doc_level_monitor", + "doc_level_input": { + "queries": [ + { + "id": "sigma-123", + "queryName": "sigma-123", + "field": "region", + "operator": "==", + "query": "us-west-2", + "tags": ["MITRE:8500"] + }, + { + "id": "sigma-456", + "queryName": "sigma-456", + "field": "region", + "operator": "==", + "query": "us-east-1", + "tags": ["MITRE:8600"] + }, + { + "id": "sigma-789", + "queryName": "sigma-789", + "field": "message", + "operator": "==", + "query": "This is an error from IAD region", + "tags": ["MITRE:8700"] + } + ] + }, + "search": { + "searchType": "graph" + } + } } diff --git a/cypress/integration/document_level_monitor_spec.js b/cypress/integration/document_level_monitor_spec.js index 6b2e55fcd..472065d35 100644 --- a/cypress/integration/document_level_monitor_spec.js +++ b/cypress/integration/document_level_monitor_spec.js @@ -3,6 +3,7 @@ * SPDX-License-Identifier: Apache-2.0 */ +import _ from 'lodash'; import { PLUGIN_NAME } from '../support/constants'; import sampleDocumentLevelMonitor from '../fixtures/sample_document_level_monitor.json'; @@ -149,7 +150,7 @@ describe('DocumentLevelMonitor', () => { cy.get('[data-test-subj="documentLevelQuery_query0"]').type('us-west-2'); // Enter query tags - cy.get('[data-test-subj="addDocLevelQueryTagButton_query0"]').click(); + cy.get('[data-test-subj="addDocLevelQueryTagButton_query0"]').click().click(); cy.get('[data-test-subj="documentLevelQueryTag_text_field_query0_tag0"]').type( sampleDocumentLevelMonitor.inputs[0].doc_level_input.queries[0].tags[0] ); @@ -165,10 +166,12 @@ describe('DocumentLevelMonitor', () => { // Define the first condition cy.get( '[data-test-subj="documentLevelTriggerExpression_query_triggerDefinitions[0].triggerConditions.0"]' - ).type(sampleDocumentLevelMonitor.inputs[0].doc_level_input.queries[0].tags[0]); + ).type( + `${sampleDocumentLevelMonitor.inputs[0].doc_level_input.queries[0].tags[0]}{downarrow}{enter}` + ); // Add another condition - cy.get('[data-test-subj="addTriggerConditionButton"]').click(); + cy.get('[data-test-subj="addTriggerConditionButton"]').click().click(); // Define a second condition cy.get( @@ -177,7 +180,9 @@ describe('DocumentLevelMonitor', () => { cy.get( '[data-test-subj="documentLevelTriggerExpression_query_triggerDefinitions[0].triggerConditions.1"]' - ).type(sampleDocumentLevelMonitor.inputs[0].doc_level_input.queries[0].tags[0]); + ).type( + `${sampleDocumentLevelMonitor.inputs[0].doc_level_input.queries[0].name}{downarrow}{enter}` + ); // TODO: Test with Notifications plugin @@ -203,8 +208,136 @@ describe('DocumentLevelMonitor', () => { cy.deleteAllMonitors(); }); - // todo hurney - describe('when defined by visual editor', () => {}); + describe('when defined with extraction query editor', () => { + it('with a new trigger', () => { + // Removing ui-metadata so the UX will use the extraction query editor when editing the monitor + const extractionQueryMonitor = _.omit(_.cloneDeep(sampleDocumentLevelMonitor), [ + 'ui_metadata', + ]); + + // Creating the test monitor + cy.createMonitor(extractionQueryMonitor); + cy.reload(); + + // Confirm the created monitor can be seen + cy.contains(SAMPLE_DOCUMENT_LEVEL_MONITOR); + + // Select the monitor + cy.get('a').contains(SAMPLE_DOCUMENT_LEVEL_MONITOR).click({ force: true }); + + // Click Edit button + cy.contains('Edit').click({ force: true }); + + // Add a trigger + cy.contains('Add another trigger').click({ force: true }); + + // Expand the accordion + cy.contains('New trigger').click(); + + // Type in the trigger name + const newTriggerName = 'new-extraction-query-trigger'; + cy.get('input[name="triggerDefinitions[1].name"]').type(newTriggerName); + + // Clear the default trigger condition source, and type the sample source + cy.get('[data-test-subj="triggerQueryCodeEditor"]') + .last() + .within(() => { + cy.get('.ace_text-input') + .focus() + .clear({ force: true }) + .type( + JSON.stringify( + sampleDocumentLevelMonitor.triggers[0].document_level_trigger.condition.script + .source + ), + { + force: true, + parseSpecialCharSequences: false, + delay: 5, + timeout: 20000, + } + ) + .trigger('blur', { force: true }); + }); + + // TODO: Test with Notifications plugin + + // Click the create button + cy.get('button').contains('Update').last().click(); + + // Confirm we can see only one row in the trigger list by checking element + cy.contains('This table contains 2 rows'); + + // Confirm we can see the new trigger + cy.contains(newTriggerName); + }); + }); + + describe('when defined with visual editor', () => { + it('with a new query and a new trigger', () => { + // Creating the test monitor + cy.createMonitor(sampleDocumentLevelMonitor); + cy.reload(); + + // Confirm the created monitor can be seen + cy.contains(SAMPLE_DOCUMENT_LEVEL_MONITOR); + + // Select the monitor + cy.get('a').contains(SAMPLE_DOCUMENT_LEVEL_MONITOR).click({ force: true }); + + // Click Edit button + cy.contains('Edit').click({ force: true }); + + // Add another query + cy.contains('Add another query').click({ force: true }); + + // Enter query name + const newQueryName = 'new-visual-editor-query'; + cy.get('[data-test-subj="documentLevelQuery_queryName3"]').type(newQueryName); + + // Enter query field + cy.get('[data-test-subj="documentLevelQuery_field3"]').type('message{downarrow}{enter}'); + + // Enter query operator + cy.get('[data-test-subj="documentLevelQuery_operator3"]').type('is not{enter}'); + + // Enter query + cy.get('[data-test-subj="documentLevelQuery_query3"]').type('Unknown message'); + + // Enter query tags + cy.get('[data-test-subj="addDocLevelQueryTagButton_query3"]').click().click(); + cy.get('[data-test-subj="documentLevelQueryTag_text_field_query3_tag0"]').type('sev1'); + + // Remove existing trigger + cy.contains('Remove trigger').click({ force: true }); + + // Add a trigger + cy.contains('Add another trigger').click({ force: true }); + + // Expand the accordion + cy.contains('New trigger').click(); + + // Type in the trigger name + const newTriggerName = 'new-visual-editor-trigger'; + cy.get('input[name="triggerDefinitions[0].name"]').type(newTriggerName); + + // Define the triggere condition + cy.get( + '[data-test-subj="documentLevelTriggerExpression_query_triggerDefinitions[0].triggerConditions.0"]' + ).type(`${newQueryName}{downarrow}{enter}`); + + // TODO: Test with Notifications plugin + + // Click the create button + cy.get('button').contains('Update').last().click(); + + // Confirm we can see only one row in the trigger list by checking element + cy.contains('This table contains 1 row'); + + // Confirm we can see the new trigger + cy.contains(newTriggerName); + }); + }); }); after(() => { From 0cf1a31a2e29e57abcdc5ff368315265b10861be Mon Sep 17 00:00:00 2001 From: AWSHurneyt Date: Tue, 17 May 2022 10:01:45 -0700 Subject: [PATCH 16/17] Refactored the default notify option for doc level monitors. Signed-off-by: AWSHurneyt --- .../components/Action/actions/Message.js | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/public/pages/CreateTrigger/components/Action/actions/Message.js b/public/pages/CreateTrigger/components/Action/actions/Message.js index f0be21bac..5d3a01e79 100644 --- a/public/pages/CreateTrigger/components/Action/actions/Message.js +++ b/public/pages/CreateTrigger/components/Action/actions/Message.js @@ -149,12 +149,16 @@ export default function Message( : actionPath; const actionableAlertsSelectionsPath = `${actionExecutionPolicyPath}.action_execution_scope.${NOTIFY_OPTIONS_VALUES.PER_ALERT}.actionable_alerts`; + let defaultNotifyOption; + switch (monitorType) { + case MONITOR_TYPE.DOC_LEVEL: + defaultNotifyOption = NOTIFY_OPTIONS_VALUES.PER_EXECUTION; + break; + default: + defaultNotifyOption = NOTIFY_OPTIONS_VALUES.PER_ALERT; + } let actionExecutionScopeId = editableActionExecutionPolicy - ? _.get( - action, - 'action_execution_policy.action_execution_scope', - NOTIFY_OPTIONS_VALUES.PER_ALERT - ) + ? _.get(action, 'action_execution_policy.action_execution_scope', defaultNotifyOption) : ''; if (!_.isString(actionExecutionScopeId)) actionExecutionScopeId = _.keys(actionExecutionScopeId)[0]; From 19cc3ba48d37c79246d598a92882fb0b5b4a1d45 Mon Sep 17 00:00:00 2001 From: AWSHurneyt Date: Tue, 17 May 2022 10:06:50 -0700 Subject: [PATCH 17/17] Removed index creation step from test as ingesting data will perform that automatically. Signed-off-by: AWSHurneyt --- cypress/integration/document_level_monitor_spec.js | 1 - 1 file changed, 1 deletion(-) diff --git a/cypress/integration/document_level_monitor_spec.js b/cypress/integration/document_level_monitor_spec.js index 472065d35..8e6636db2 100644 --- a/cypress/integration/document_level_monitor_spec.js +++ b/cypress/integration/document_level_monitor_spec.js @@ -26,7 +26,6 @@ const addDocumentsToTestIndex = (indexName = '', numOfDocs = 0) => { describe('DocumentLevelMonitor', () => { before(() => { // Load sample data - cy.createIndexByName(TESTING_INDEX); addDocumentsToTestIndex(TESTING_INDEX, 5); }); beforeEach(() => {