diff --git a/cypress/fixtures/sample_composite_level_monitor.json b/cypress/fixtures/sample_composite_level_monitor.json index 9b1f67bf4..8e6d09a13 100644 --- a/cypress/fixtures/sample_composite_level_monitor.json +++ b/cypress/fixtures/sample_composite_level_monitor.json @@ -48,7 +48,7 @@ "name": "sample_channel", "destination_id": "6dYFw4gB2qeAWe54NgyL", "message_template": { - "source": "Monitor {{ctx.monitor.name}} just entered alert status. Please investigate the issue.\n - Trigger: {{ctx.trigger.name}}\n - Severity: {{ctx.trigger.severity}}\n - Period start: {{ctx.periodStart}}\n - Period end: {{ctx.periodEnd}}", + "source": "Monitor {{ctx.monitor.name}} just entered alert status. Please investigate the issue.\n - Trigger: {{ctx.trigger.name}}\n - Severity: {{ctx.trigger.severity}}\n - Period start: {{ctx.periodStart}} UTC\n - Period end: {{ctx.periodEnd}} UTC", "lang": "mustache" }, "throttle_enabled": false, diff --git a/cypress/integration/acknowledge_alerts_modal_spec.js b/cypress/integration/acknowledge_alerts_modal_spec.js index 9c2d69452..5efc6c17f 100644 --- a/cypress/integration/acknowledge_alerts_modal_spec.js +++ b/cypress/integration/acknowledge_alerts_modal_spec.js @@ -15,6 +15,8 @@ const QUERY_TRIGGER = 'sample_alerts_flyout_query_level_trigger'; const TWENTY_SECONDS = 20000; +const SIXTY_SECONDS = 60000; + describe('AcknowledgeAlertsModal', () => { before(() => { // Delete any existing monitors @@ -32,11 +34,14 @@ describe('AcknowledgeAlertsModal', () => { cy.wait(60000); // Visit Alerting OpenSearch Dashboards - cy.visit(`${Cypress.env('opensearch_dashboards')}/app/${PLUGIN_NAME}#/monitors`); + cy.visit(`${Cypress.env('opensearch_dashboards')}/app/${PLUGIN_NAME}#/monitors`, { timeout: SIXTY_SECONDS }); // Confirm test monitors were created successfully - cy.contains(BUCKET_MONITOR, { timeout: TWENTY_SECONDS }); - cy.contains(QUERY_MONITOR, { timeout: TWENTY_SECONDS }); + cy.contains(BUCKET_MONITOR, { timeout: SIXTY_SECONDS }); + cy.contains(QUERY_MONITOR, { timeout: SIXTY_SECONDS }); + + // Wait 1 minute for the test monitors to trigger alerts, then go to the 'Alerts by trigger' dashboard page to view alerts + cy.wait(60000); }); beforeEach(() => { @@ -44,8 +49,8 @@ describe('AcknowledgeAlertsModal', () => { cy.visit(`${Cypress.env('opensearch_dashboards')}/app/${PLUGIN_NAME}#/dashboard`); // Confirm dashboard is displaying rows for the test monitors. - cy.contains(BUCKET_MONITOR, { timeout: TWENTY_SECONDS }); - cy.contains(QUERY_MONITOR, { timeout: TWENTY_SECONDS }); + cy.contains(BUCKET_MONITOR, { timeout: SIXTY_SECONDS }); + cy.contains(QUERY_MONITOR, { timeout: SIXTY_SECONDS }); }); it('Acknowledge button disabled when more than 1 trigger selected', () => { diff --git a/cypress/support/commands.js b/cypress/support/commands.js index e05711de0..e9aa4f739 100644 --- a/cypress/support/commands.js +++ b/cypress/support/commands.js @@ -71,7 +71,12 @@ Cypress.Commands.overwrite('request', (originalFn, ...args) => { }); Cypress.Commands.add('createMonitor', (monitorJSON) => { - cy.request('POST', `${Cypress.env('opensearch')}${API.MONITOR_BASE}`, monitorJSON); + cy.request({ + method: 'POST', + url: `${Cypress.env('opensearch')}${API.MONITOR_BASE}`, + body: monitorJSON, + timeout: 60000 + }); }); Cypress.Commands.add('createAndExecuteMonitor', (monitorJSON) => { @@ -182,6 +187,7 @@ Cypress.Commands.add('loadSampleEcommerceData', () => { method: 'POST', headers: { 'osd-xsrf': 'opensearch-dashboards' }, url: `${Cypress.env('opensearch_dashboards')}/api/sample_data/ecommerce`, + timeout: 60000, }); }); diff --git a/public/pages/CreateTrigger/components/Action/actions/Message.test.js b/public/pages/CreateTrigger/components/Action/actions/Message.test.js index 047a2bec5..0d08d7e09 100644 --- a/public/pages/CreateTrigger/components/Action/actions/Message.test.js +++ b/public/pages/CreateTrigger/components/Action/actions/Message.test.js @@ -19,7 +19,7 @@ function getRenderWrapper(customProps = {}) { action={{ message_template: { source: - 'Monitor {{ctx.monitor.name}} just entered alert status. Please investigate the issue.\n- Trigger: {{ctx.trigger.name}}\n- Severity: {{ctx.trigger.severity}}\n- Period start: {{ctx.periodStart}}\n- Period end: {{ctx.periodEnd}}', + 'Monitor {{ctx.monitor.name}} just entered alert status. Please investigate the issue.\n- Trigger: {{ctx.trigger.name}}\n- Severity: {{ctx.trigger.severity}}\n- Period start: {{ctx.periodStart}} UTC\n- Period end: {{ctx.periodEnd}} UTC', lang: 'mustache', }, }} diff --git a/public/pages/CreateTrigger/utils/constants.js b/public/pages/CreateTrigger/utils/constants.js index dd8a59165..7c6af02e3 100644 --- a/public/pages/CreateTrigger/utils/constants.js +++ b/public/pages/CreateTrigger/utils/constants.js @@ -11,8 +11,8 @@ export const DEFAULT_MESSAGE_SOURCE = { Monitor {{ctx.monitor.name}} just entered alert status. Please investigate the issue. - Trigger: {{ctx.trigger.name}} - Severity: {{ctx.trigger.severity}} - - Period start: {{ctx.periodStart}} - - Period end: {{ctx.periodEnd}} + - Period start: {{ctx.periodStart}} UTC + - Period end: {{ctx.periodEnd}} UTC - Deduped Alerts: {{#ctx.dedupedAlerts}} @@ -33,8 +33,8 @@ export const DEFAULT_MESSAGE_SOURCE = { Monitor {{ctx.monitor.name}} just entered alert status. Please investigate the issue. - Trigger: {{ctx.trigger.name}} - Severity: {{ctx.trigger.severity}} - - Period start: {{ctx.periodStart}} - - Period end: {{ctx.periodEnd}} + - Period start: {{ctx.periodStart}} UTC + - Period end: {{ctx.periodEnd}} UTC `.trim(), }; diff --git a/public/pages/CreateTrigger/utils/helper.js b/public/pages/CreateTrigger/utils/helper.js index b8c91bdc6..7c201bf7f 100644 --- a/public/pages/CreateTrigger/utils/helper.js +++ b/public/pages/CreateTrigger/utils/helper.js @@ -106,6 +106,8 @@ export const conditionToExpressions = (condition = '', monitors) => { }; export function getTimeZone() { - const detectedTimeZone = getUISettings().get('dateFormat:tz', 'Browser'); - return detectedTimeZone === 'Browser' ? (moment.tz.guess() || moment.format('Z')) : detectedTimeZone; + // TODO: Include support to configure timezones rather than using the default UTC as requested here - https://github.com/opensearch-project/alerting/issues/1744 + // const detectedTimeZone = getUISettings().get('dateFormat:tz', 'Browser'); + // return detectedTimeZone === 'Browser' ? (moment.tz.guess() || moment.format('Z')) : detectedTimeZone; + return "UTC"; } diff --git a/public/pages/CreateTrigger/utils/helper.test.js b/public/pages/CreateTrigger/utils/helper.test.js index 24f9c0687..60fc032f8 100644 --- a/public/pages/CreateTrigger/utils/helper.test.js +++ b/public/pages/CreateTrigger/utils/helper.test.js @@ -4,7 +4,7 @@ */ import _ from 'lodash'; -import { getDefaultScript } from './helper'; +import { getDefaultScript, getTriggerContext, getTimeZone } from './helper'; import { MONITOR_TYPE } from '../../../utils/constants'; import { FORMIK_INITIAL_DOC_LEVEL_SCRIPT, @@ -14,6 +14,8 @@ import { API_TYPES, DEFAULT_CLUSTER_METRICS_SCRIPT, } from '../../CreateMonitor/components/ClusterMetricsMonitor/utils/clusterMetricsMonitorConstants'; +import moment from 'moment-timezone'; +import { formikToTrigger } from '../containers/CreateTrigger/utils/formikToTrigger'; describe('CreateTrigger/utils/helper', () => { describe('getDefaultScript', () => { @@ -81,4 +83,167 @@ describe('CreateTrigger/utils/helper', () => { }); }); }); + + describe('getTriggerContext', () => { + test("Should return a proper trigger context", () => { + const mockMonitor = { + "name": "Test", + "type": "monitor", + "monitor_type": "query_level_monitor", + "enabled": true, + "schedule": { + "period": { + "interval": 1, + "unit": "MINUTES" + } + }, + "inputs": [ + { + "search": { + "indices": [ + "opensearch_dashboards_sample_data_ecommerce" + ], + "query": { + "size": 0, + "aggregations": {}, + "query": { + "bool": { + "filter": [ + { + "range": { + "order_date": { + "gte": "{{period_end}}||-1h", + "lte": "{{period_end}}", + "format": "epoch_millis" + } + } + } + ] + } + } + } + } + } + ], + "triggers": [], + "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": "query_level_monitor", + "search": { + "searchType": "graph", + "timeField": "order_date", + "aggregations": [], + "groupBy": [], + "bucketValue": 1, + "bucketUnitOfTime": "h", + "filters": [] + } + } + }; + const mockInputResults = { + results: [ + { + "_shards": { + "total": 1, + "failed": 0, + "successful": 1, + "skipped": 0 + }, + "hits": { + "hits": [], + "total": { + "value": 33, + "relation": "eq" + }, + "max_score": null + }, + "took": 3, + "timed_out": false, + "aggregations": { + "over": { + "buckets": [ + { + "key_as_string": "2025-01-06T08:00:00.000-08:00", + "doc_count": 8, + "key": 1736179200000 + }, + { + "key_as_string": "2025-01-06T09:00:00.000-08:00", + "doc_count": 4, + "key": 1736182800000 + }, + { + "key_as_string": "2025-01-06T10:00:00.000-08:00", + "doc_count": 8, + "key": 1736186400000 + }, + { + "key_as_string": "2025-01-06T11:00:00.000-08:00", + "doc_count": 7, + "key": 1736190000000 + }, + { + "key_as_string": "2025-01-06T12:00:00.000-08:00", + "doc_count": 5, + "key": 1736193600000 + }, + { + "key_as_string": "2025-01-06T13:00:00.000-08:00", + "doc_count": 1, + "key": 1736197200000 + } + ] + } + } + } + ] + }; + const mockExecuteResponse = { + period_start: "2025-01-06T21:11:41Z", + period_end: "2025-01-06T21:12:41Z", + input_results: mockInputResults, + }; + const mockValues = { + name: "Test", + severity: "1", + condition: { + script: "ctx.results[0].hits.total.value > 10000" + } + }; + const mockTrigger = formikToTrigger(mockValues, _.get(mockMonitor, 'ui_metadata', {})); + if (_.isArray(mockTrigger) && triggerIndex >= 0) mockTrigger = mockTrigger[triggerIndex]; + const result = getTriggerContext(mockExecuteResponse, mockMonitor, mockValues); + expect(result).toEqual({ + periodStart: moment.utc(_.get(mockExecuteResponse, 'period_start', Date.now())).tz(getTimeZone()).format(), + periodEnd: moment.utc(_.get(mockExecuteResponse, 'period_end', Date.now())).tz(getTimeZone()).format(), + results: [_.get(mockExecuteResponse, 'input_results.results[0]')].filter((result) => !!result), + trigger: mockTrigger, + alert: null, + error: null, + monitor: mockMonitor + }); + }); + }); }); diff --git a/public/pages/Dashboard/components/AcknowledgeAlertsModal/AcknowledgeAlertsModal.js b/public/pages/Dashboard/components/AcknowledgeAlertsModal/AcknowledgeAlertsModal.js index e9dae71f0..93f1bb21c 100644 --- a/public/pages/Dashboard/components/AcknowledgeAlertsModal/AcknowledgeAlertsModal.js +++ b/public/pages/Dashboard/components/AcknowledgeAlertsModal/AcknowledgeAlertsModal.js @@ -42,10 +42,10 @@ import { MAX_ALERT_COUNT } from '../../utils/constants'; import { DEFAULT_PAGE_SIZE_OPTIONS } from '../../../Monitors/containers/Monitors/utils/constants'; import DashboardControls from '../DashboardControls'; import ContentPanel from '../../../../components/ContentPanel'; -import { appendCommentsAction, queryColumns } from '../../utils/tableUtils'; +import { queryColumns } from '../../utils/tableUtils'; import DashboardEmptyPrompt from '../DashboardEmptyPrompt'; import { getAlertsFindingColumn } from '../FindingsDashboard/findingsUtils'; -import { getDataSourceId, getIsCommentsEnabled } from '../../../utils/helpers'; +import { appendCommentsAction, getDataSourceId, getIsCommentsEnabled } from '../../../utils/helpers'; export const DEFAULT_NUM_MODAL_ROWS = 10;