From f4459e4e60cdf4d679e50bdb3411c9c7ea76acd7 Mon Sep 17 00:00:00 2001 From: Drew Davis Date: Thu, 16 Apr 2026 11:15:49 -0400 Subject: [PATCH] review: Update alert template and add tests --- .../renderAlertTemplate.test.ts.snap | 284 +++++++++++++ .../checkAlerts/__tests__/checkAlerts.test.ts | 8 +- .../__tests__/renderAlertTemplate.test.ts | 402 ++++++++++++++++++ .../__tests__/singleInvocationAlert.test.ts | 2 +- .../api/src/tasks/checkAlerts/template.ts | 21 +- 5 files changed, 692 insertions(+), 25 deletions(-) create mode 100644 packages/api/src/tasks/checkAlerts/__tests__/__snapshots__/renderAlertTemplate.test.ts.snap create mode 100644 packages/api/src/tasks/checkAlerts/__tests__/renderAlertTemplate.test.ts diff --git a/packages/api/src/tasks/checkAlerts/__tests__/__snapshots__/renderAlertTemplate.test.ts.snap b/packages/api/src/tasks/checkAlerts/__tests__/__snapshots__/renderAlertTemplate.test.ts.snap new file mode 100644 index 00000000..66833b94 --- /dev/null +++ b/packages/api/src/tasks/checkAlerts/__tests__/__snapshots__/renderAlertTemplate.test.ts.snap @@ -0,0 +1,284 @@ +// Jest Snapshot v1, https://jestjs.io/docs/snapshot-testing + +exports[`buildAlertMessageTemplateTitle saved search alerts ALERT state above threshold=5 alertValue=10 1`] = `"🚨 Alert for "My Search" - 10 lines found"`; + +exports[`buildAlertMessageTemplateTitle saved search alerts ALERT state above_exclusive threshold=5 alertValue=10 1`] = `"🚨 Alert for "My Search" - 10 lines found"`; + +exports[`buildAlertMessageTemplateTitle saved search alerts ALERT state below threshold=5 alertValue=2 1`] = `"🚨 Alert for "My Search" - 2 lines found"`; + +exports[`buildAlertMessageTemplateTitle saved search alerts ALERT state below_or_equal threshold=5 alertValue=3 1`] = `"🚨 Alert for "My Search" - 3 lines found"`; + +exports[`buildAlertMessageTemplateTitle saved search alerts ALERT state equal threshold=5 alertValue=5 1`] = `"🚨 Alert for "My Search" - 5 lines found"`; + +exports[`buildAlertMessageTemplateTitle saved search alerts ALERT state not_equal threshold=5 alertValue=10 1`] = `"🚨 Alert for "My Search" - 10 lines found"`; + +exports[`buildAlertMessageTemplateTitle saved search alerts OK state (resolved) above threshold=5 okValue=3 1`] = `"✅ Alert for "My Search" - 3 lines found"`; + +exports[`buildAlertMessageTemplateTitle saved search alerts OK state (resolved) above_exclusive threshold=5 okValue=3 1`] = `"✅ Alert for "My Search" - 3 lines found"`; + +exports[`buildAlertMessageTemplateTitle saved search alerts OK state (resolved) below threshold=5 okValue=10 1`] = `"✅ Alert for "My Search" - 10 lines found"`; + +exports[`buildAlertMessageTemplateTitle saved search alerts OK state (resolved) below_or_equal threshold=5 okValue=10 1`] = `"✅ Alert for "My Search" - 10 lines found"`; + +exports[`buildAlertMessageTemplateTitle saved search alerts OK state (resolved) equal threshold=5 okValue=10 1`] = `"✅ Alert for "My Search" - 10 lines found"`; + +exports[`buildAlertMessageTemplateTitle saved search alerts OK state (resolved) not_equal threshold=5 okValue=5 1`] = `"✅ Alert for "My Search" - 5 lines found"`; + +exports[`buildAlertMessageTemplateTitle tile alerts ALERT state above threshold=5 alertValue=10 1`] = `"🚨 Alert for "Test Chart" in "My Dashboard" - 10 meets or exceeds 5"`; + +exports[`buildAlertMessageTemplateTitle tile alerts ALERT state above_exclusive threshold=5 alertValue=10 1`] = `"🚨 Alert for "Test Chart" in "My Dashboard" - 10 exceeds 5"`; + +exports[`buildAlertMessageTemplateTitle tile alerts ALERT state below threshold=5 alertValue=2 1`] = `"🚨 Alert for "Test Chart" in "My Dashboard" - 2 falls below 5"`; + +exports[`buildAlertMessageTemplateTitle tile alerts ALERT state below_or_equal threshold=5 alertValue=3 1`] = `"🚨 Alert for "Test Chart" in "My Dashboard" - 3 falls to or below 5"`; + +exports[`buildAlertMessageTemplateTitle tile alerts ALERT state decimal threshold 1`] = `"🚨 Alert for "Test Chart" in "My Dashboard" - 10.1 meets or exceeds 1.5"`; + +exports[`buildAlertMessageTemplateTitle tile alerts ALERT state equal threshold=5 alertValue=5 1`] = `"🚨 Alert for "Test Chart" in "My Dashboard" - 5 equals 5"`; + +exports[`buildAlertMessageTemplateTitle tile alerts ALERT state integer threshold rounds value 1`] = `"🚨 Alert for "Test Chart" in "My Dashboard" - 11 meets or exceeds 5"`; + +exports[`buildAlertMessageTemplateTitle tile alerts ALERT state not_equal threshold=5 alertValue=10 1`] = `"🚨 Alert for "Test Chart" in "My Dashboard" - 10 does not equal 5"`; + +exports[`buildAlertMessageTemplateTitle tile alerts OK state (resolved) above threshold=5 okValue=3 1`] = `"✅ Alert for "Test Chart" in "My Dashboard" - 3 falls below 5"`; + +exports[`buildAlertMessageTemplateTitle tile alerts OK state (resolved) above_exclusive threshold=5 okValue=3 1`] = `"✅ Alert for "Test Chart" in "My Dashboard" - 3 falls to or below 5"`; + +exports[`buildAlertMessageTemplateTitle tile alerts OK state (resolved) below threshold=5 okValue=10 1`] = `"✅ Alert for "Test Chart" in "My Dashboard" - 10 meets or exceeds 5"`; + +exports[`buildAlertMessageTemplateTitle tile alerts OK state (resolved) below_or_equal threshold=5 okValue=10 1`] = `"✅ Alert for "Test Chart" in "My Dashboard" - 10 exceeds 5"`; + +exports[`buildAlertMessageTemplateTitle tile alerts OK state (resolved) equal threshold=5 okValue=10 1`] = `"✅ Alert for "Test Chart" in "My Dashboard" - 10 does not equal 5"`; + +exports[`buildAlertMessageTemplateTitle tile alerts OK state (resolved) not_equal threshold=5 okValue=5 1`] = `"✅ Alert for "Test Chart" in "My Dashboard" - 5 equals 5"`; + +exports[`renderAlertTemplate saved search alerts ALERT state above threshold=5 alertValue=10 1`] = ` +" +10 lines found, which meets or exceeds the threshold of 5 lines +Time Range (UTC): [Mar 17 10:10:00 PM - Mar 17 10:15:00 PM) + +\`\`\` +"2023-03-17 22:14:01","error","Failed to connect to database" +"2023-03-17 22:13:45","error","Connection timeout after 30s" +"2023-03-17 22:12:30","error","Retry limit exceeded" +\`\`\`" +`; + +exports[`renderAlertTemplate saved search alerts ALERT state above_exclusive threshold=5 alertValue=10 1`] = ` +" +10 lines found, which exceeds the threshold of 5 lines +Time Range (UTC): [Mar 17 10:10:00 PM - Mar 17 10:15:00 PM) + +\`\`\` +"2023-03-17 22:14:01","error","Failed to connect to database" +"2023-03-17 22:13:45","error","Connection timeout after 30s" +"2023-03-17 22:12:30","error","Retry limit exceeded" +\`\`\`" +`; + +exports[`renderAlertTemplate saved search alerts ALERT state below threshold=5 alertValue=2 1`] = ` +" +2 lines found, which falls below the threshold of 5 lines +Time Range (UTC): [Mar 17 10:10:00 PM - Mar 17 10:15:00 PM) + +\`\`\` +"2023-03-17 22:14:01","error","Failed to connect to database" +"2023-03-17 22:13:45","error","Connection timeout after 30s" +"2023-03-17 22:12:30","error","Retry limit exceeded" +\`\`\`" +`; + +exports[`renderAlertTemplate saved search alerts ALERT state below_or_equal threshold=5 alertValue=3 1`] = ` +" +3 lines found, which falls to or below the threshold of 5 lines +Time Range (UTC): [Mar 17 10:10:00 PM - Mar 17 10:15:00 PM) + +\`\`\` +"2023-03-17 22:14:01","error","Failed to connect to database" +"2023-03-17 22:13:45","error","Connection timeout after 30s" +"2023-03-17 22:12:30","error","Retry limit exceeded" +\`\`\`" +`; + +exports[`renderAlertTemplate saved search alerts ALERT state equal threshold=5 alertValue=5 1`] = ` +" +5 lines found, which equals the threshold of 5 lines +Time Range (UTC): [Mar 17 10:10:00 PM - Mar 17 10:15:00 PM) + +\`\`\` +"2023-03-17 22:14:01","error","Failed to connect to database" +"2023-03-17 22:13:45","error","Connection timeout after 30s" +"2023-03-17 22:12:30","error","Retry limit exceeded" +\`\`\`" +`; + +exports[`renderAlertTemplate saved search alerts ALERT state not_equal threshold=5 alertValue=10 1`] = ` +" +10 lines found, which does not equal the threshold of 5 lines +Time Range (UTC): [Mar 17 10:10:00 PM - Mar 17 10:15:00 PM) + +\`\`\` +"2023-03-17 22:14:01","error","Failed to connect to database" +"2023-03-17 22:13:45","error","Connection timeout after 30s" +"2023-03-17 22:12:30","error","Retry limit exceeded" +\`\`\`" +`; + +exports[`renderAlertTemplate saved search alerts ALERT state with group 1`] = ` +"Group: "http" +10 lines found, which meets or exceeds the threshold of 5 lines +Time Range (UTC): [Mar 17 10:10:00 PM - Mar 17 10:15:00 PM) + +\`\`\` +"2023-03-17 22:14:01","error","Failed to connect to database" +"2023-03-17 22:13:45","error","Connection timeout after 30s" +"2023-03-17 22:12:30","error","Retry limit exceeded" +\`\`\`" +`; + +exports[`renderAlertTemplate saved search alerts OK state (resolved) above threshold=5 okValue=3 1`] = ` +"The alert has been resolved. +Time Range (UTC): [Mar 17 10:10:00 PM - Mar 17 10:15:00 PM) +" +`; + +exports[`renderAlertTemplate saved search alerts OK state (resolved) above_exclusive threshold=5 okValue=3 1`] = ` +"The alert has been resolved. +Time Range (UTC): [Mar 17 10:10:00 PM - Mar 17 10:15:00 PM) +" +`; + +exports[`renderAlertTemplate saved search alerts OK state (resolved) below threshold=5 okValue=10 1`] = ` +"The alert has been resolved. +Time Range (UTC): [Mar 17 10:10:00 PM - Mar 17 10:15:00 PM) +" +`; + +exports[`renderAlertTemplate saved search alerts OK state (resolved) below_or_equal threshold=5 okValue=10 1`] = ` +"The alert has been resolved. +Time Range (UTC): [Mar 17 10:10:00 PM - Mar 17 10:15:00 PM) +" +`; + +exports[`renderAlertTemplate saved search alerts OK state (resolved) equal threshold=5 okValue=10 1`] = ` +"The alert has been resolved. +Time Range (UTC): [Mar 17 10:10:00 PM - Mar 17 10:15:00 PM) +" +`; + +exports[`renderAlertTemplate saved search alerts OK state (resolved) not_equal threshold=5 okValue=5 1`] = ` +"The alert has been resolved. +Time Range (UTC): [Mar 17 10:10:00 PM - Mar 17 10:15:00 PM) +" +`; + +exports[`renderAlertTemplate saved search alerts OK state (resolved) with group 1`] = ` +"Group: "http" - The alert has been resolved. +Time Range (UTC): [Mar 17 10:10:00 PM - Mar 17 10:15:00 PM) +" +`; + +exports[`renderAlertTemplate tile alerts ALERT state above threshold=5 alertValue=10 1`] = ` +" +10 meets or exceeds 5 +Time Range (UTC): [Mar 17 10:10:00 PM - Mar 17 10:15:00 PM) +" +`; + +exports[`renderAlertTemplate tile alerts ALERT state above_exclusive threshold=5 alertValue=10 1`] = ` +" +10 exceeds 5 +Time Range (UTC): [Mar 17 10:10:00 PM - Mar 17 10:15:00 PM) +" +`; + +exports[`renderAlertTemplate tile alerts ALERT state below threshold=5 alertValue=2 1`] = ` +" +2 falls below 5 +Time Range (UTC): [Mar 17 10:10:00 PM - Mar 17 10:15:00 PM) +" +`; + +exports[`renderAlertTemplate tile alerts ALERT state below_or_equal threshold=5 alertValue=3 1`] = ` +" +3 falls to or below 5 +Time Range (UTC): [Mar 17 10:10:00 PM - Mar 17 10:15:00 PM) +" +`; + +exports[`renderAlertTemplate tile alerts ALERT state decimal threshold 1`] = ` +" +10.1 meets or exceeds 1.5 +Time Range (UTC): [Mar 17 10:10:00 PM - Mar 17 10:15:00 PM) +" +`; + +exports[`renderAlertTemplate tile alerts ALERT state equal threshold=5 alertValue=5 1`] = ` +" +5 equals 5 +Time Range (UTC): [Mar 17 10:10:00 PM - Mar 17 10:15:00 PM) +" +`; + +exports[`renderAlertTemplate tile alerts ALERT state integer threshold rounds value 1`] = ` +" +11 meets or exceeds 5 +Time Range (UTC): [Mar 17 10:10:00 PM - Mar 17 10:15:00 PM) +" +`; + +exports[`renderAlertTemplate tile alerts ALERT state not_equal threshold=5 alertValue=10 1`] = ` +" +10 does not equal 5 +Time Range (UTC): [Mar 17 10:10:00 PM - Mar 17 10:15:00 PM) +" +`; + +exports[`renderAlertTemplate tile alerts ALERT state with group 1`] = ` +"Group: "us-east-1" +10 meets or exceeds 5 +Time Range (UTC): [Mar 17 10:10:00 PM - Mar 17 10:15:00 PM) +" +`; + +exports[`renderAlertTemplate tile alerts OK state (resolved) above threshold=5 okValue=3 1`] = ` +"The alert has been resolved. +Time Range (UTC): [Mar 17 10:10:00 PM - Mar 17 10:15:00 PM) +" +`; + +exports[`renderAlertTemplate tile alerts OK state (resolved) above_exclusive threshold=5 okValue=3 1`] = ` +"The alert has been resolved. +Time Range (UTC): [Mar 17 10:10:00 PM - Mar 17 10:15:00 PM) +" +`; + +exports[`renderAlertTemplate tile alerts OK state (resolved) below threshold=5 okValue=10 1`] = ` +"The alert has been resolved. +Time Range (UTC): [Mar 17 10:10:00 PM - Mar 17 10:15:00 PM) +" +`; + +exports[`renderAlertTemplate tile alerts OK state (resolved) below_or_equal threshold=5 okValue=10 1`] = ` +"The alert has been resolved. +Time Range (UTC): [Mar 17 10:10:00 PM - Mar 17 10:15:00 PM) +" +`; + +exports[`renderAlertTemplate tile alerts OK state (resolved) equal threshold=5 okValue=10 1`] = ` +"The alert has been resolved. +Time Range (UTC): [Mar 17 10:10:00 PM - Mar 17 10:15:00 PM) +" +`; + +exports[`renderAlertTemplate tile alerts OK state (resolved) not_equal threshold=5 okValue=5 1`] = ` +"The alert has been resolved. +Time Range (UTC): [Mar 17 10:10:00 PM - Mar 17 10:15:00 PM) +" +`; + +exports[`renderAlertTemplate tile alerts OK state (resolved) with group 1`] = ` +"Group: "us-east-1" - The alert has been resolved. +Time Range (UTC): [Mar 17 10:10:00 PM - Mar 17 10:15:00 PM) +" +`; diff --git a/packages/api/src/tasks/checkAlerts/__tests__/checkAlerts.test.ts b/packages/api/src/tasks/checkAlerts/__tests__/checkAlerts.test.ts index c180a7f4..49305c68 100644 --- a/packages/api/src/tasks/checkAlerts/__tests__/checkAlerts.test.ts +++ b/packages/api/src/tasks/checkAlerts/__tests__/checkAlerts.test.ts @@ -952,7 +952,7 @@ describe('checkAlerts', () => { text: [ '**', 'Group: "http"', - '10 lines found, expected less than 1 lines', + '10 lines found, which meets or exceeds the threshold of 1 lines', 'Time Range (UTC): [Mar 17 10:13:03 PM - Mar 17 10:13:59 PM)', 'Custom body ', '```', @@ -1012,7 +1012,7 @@ describe('checkAlerts', () => { text: [ '**', 'Group: "http"', - '10 lines found, expected less than 1 lines', + '10 lines found, which meets or exceeds the threshold of 1 lines', 'Time Range (UTC): [Mar 17 10:13:03 PM - Mar 17 10:13:59 PM)', 'Custom body ', '```', @@ -1119,7 +1119,7 @@ describe('checkAlerts', () => { text: [ '**', 'Group: "http"', - '10 lines found, expected less than 1 lines', + '10 lines found, which meets or exceeds the threshold of 1 lines', 'Time Range (UTC): [Mar 17 10:13:03 PM - Mar 17 10:13:59 PM)', '', ' Runbook URL: https://example.com', @@ -1148,7 +1148,7 @@ describe('checkAlerts', () => { text: [ '**', 'Group: "http"', - '10 lines found, expected less than 1 lines', + '10 lines found, which meets or exceeds the threshold of 1 lines', 'Time Range (UTC): [Mar 17 10:13:03 PM - Mar 17 10:13:59 PM)', '', ' Runbook URL: https://example.com', diff --git a/packages/api/src/tasks/checkAlerts/__tests__/renderAlertTemplate.test.ts b/packages/api/src/tasks/checkAlerts/__tests__/renderAlertTemplate.test.ts new file mode 100644 index 00000000..07d8f496 --- /dev/null +++ b/packages/api/src/tasks/checkAlerts/__tests__/renderAlertTemplate.test.ts @@ -0,0 +1,402 @@ +import { + AlertState, + AlertThresholdType, + SourceKind, +} from '@hyperdx/common-utils/dist/types'; +import mongoose from 'mongoose'; + +import { makeTile } from '@/fixtures'; +import { AlertSource } from '@/models/alert'; +import { loadProvider } from '@/tasks/checkAlerts/providers'; +import { + AlertMessageTemplateDefaultView, + buildAlertMessageTemplateTitle, + renderAlertTemplate, +} from '@/tasks/checkAlerts/template'; + +let alertProvider: any; + +beforeAll(async () => { + alertProvider = await loadProvider(); +}); + +// eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion +const mockMetadata = { + getColumn: jest.fn().mockImplementation(({ column }) => { + const columnMap = { + Timestamp: { name: 'Timestamp', type: 'DateTime' }, + Body: { name: 'Body', type: 'String' }, + SeverityText: { name: 'SeverityText', type: 'String' }, + ServiceName: { name: 'ServiceName', type: 'String' }, + }; + return Promise.resolve(columnMap[column]); + }), + getColumns: jest.fn().mockResolvedValue([]), + getMapKeys: jest.fn().mockResolvedValue([]), + getMapValues: jest.fn().mockResolvedValue([]), + getAllFields: jest.fn().mockResolvedValue([]), + getTableMetadata: jest.fn().mockResolvedValue({}), + getClickHouseSettings: jest.fn().mockReturnValue({}), + setClickHouseSettings: jest.fn(), + getSkipIndices: jest.fn().mockResolvedValue([]), + getSetting: jest.fn().mockResolvedValue(undefined), +} as any; + +const sampleLogsCsv = [ + '"2023-03-17 22:14:01","error","Failed to connect to database"', + '"2023-03-17 22:13:45","error","Connection timeout after 30s"', + '"2023-03-17 22:12:30","error","Retry limit exceeded"', +].join('\n'); + +// eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion +const mockClickhouseClient = { + query: jest.fn().mockResolvedValue({ + json: jest.fn().mockResolvedValue({ data: [] }), + text: jest.fn().mockResolvedValue(sampleLogsCsv), + }), +} as any; + +const startTime = new Date('2023-03-17T22:10:00.000Z'); +const endTime = new Date('2023-03-17T22:15:00.000Z'); + +const makeSearchView = ( + overrides: Partial & { + thresholdType?: AlertThresholdType; + threshold?: number; + value?: number; + group?: string; + } = {}, +): AlertMessageTemplateDefaultView => ({ + alert: { + thresholdType: overrides.thresholdType ?? AlertThresholdType.ABOVE, + threshold: overrides.threshold ?? 5, + source: AlertSource.SAVED_SEARCH, + channel: { type: null }, + interval: '1m', + }, + source: { + id: 'fake-source-id', + kind: SourceKind.Log, + team: 'team-123', + from: { databaseName: 'default', tableName: 'otel_logs' }, + timestampValueExpression: 'Timestamp', + connection: 'connection-123', + name: 'Logs', + defaultTableSelectExpression: 'Timestamp, Body', + }, + savedSearch: { + _id: 'fake-saved-search-id' as any, + team: 'team-123' as any, + id: 'fake-saved-search-id', + name: 'My Search', + select: 'Body', + where: 'Body: "error"', + whereLanguage: 'lucene', + orderBy: 'timestamp', + source: 'fake-source-id' as any, + tags: ['test'], + createdAt: new Date(), + updatedAt: new Date(), + }, + attributes: {}, + granularity: '1m', + group: overrides.group, + isGroupedAlert: false, + startTime, + endTime, + value: overrides.value ?? 10, +}); + +const testTile = makeTile({ id: 'test-tile-id' }); +const makeTileView = ( + overrides: Partial & { + thresholdType?: AlertThresholdType; + threshold?: number; + value?: number; + group?: string; + } = {}, +): AlertMessageTemplateDefaultView => ({ + alert: { + thresholdType: overrides.thresholdType ?? AlertThresholdType.ABOVE, + threshold: overrides.threshold ?? 5, + source: AlertSource.TILE, + channel: { type: null }, + interval: '1m', + tileId: 'test-tile-id', + }, + dashboard: { + _id: new mongoose.Types.ObjectId(), + id: 'id-123', + name: 'My Dashboard', + tiles: [testTile], + team: 'team-123' as any, + tags: ['test'], + createdAt: new Date(), + updatedAt: new Date(), + }, + attributes: {}, + granularity: '5 minute', + group: overrides.group, + isGroupedAlert: false, + startTime, + endTime, + value: overrides.value ?? 10, +}); + +const render = (view: AlertMessageTemplateDefaultView, state: AlertState) => + renderAlertTemplate({ + alertProvider, + clickhouseClient: mockClickhouseClient, + metadata: mockMetadata, + state, + template: null, + title: 'Test Alert Title', + view, + teamWebhooksById: new Map(), + }); + +interface AlertCase { + thresholdType: AlertThresholdType; + threshold: number; + alertValue: number; // value that would trigger the alert + okValue: number; // value that would resolve the alert +} + +const alertCases: AlertCase[] = [ + { + thresholdType: AlertThresholdType.ABOVE, + threshold: 5, + alertValue: 10, + okValue: 3, + }, + { + thresholdType: AlertThresholdType.ABOVE_EXCLUSIVE, + threshold: 5, + alertValue: 10, + okValue: 3, + }, + { + thresholdType: AlertThresholdType.BELOW, + threshold: 5, + alertValue: 2, + okValue: 10, + }, + { + thresholdType: AlertThresholdType.BELOW_OR_EQUAL, + threshold: 5, + alertValue: 3, + okValue: 10, + }, + { + thresholdType: AlertThresholdType.EQUAL, + threshold: 5, + alertValue: 5, + okValue: 10, + }, + { + thresholdType: AlertThresholdType.NOT_EQUAL, + threshold: 5, + alertValue: 10, + okValue: 5, + }, +]; + +describe('renderAlertTemplate', () => { + describe('saved search alerts', () => { + describe('ALERT state', () => { + it.each(alertCases)( + '$thresholdType threshold=$threshold alertValue=$alertValue', + async ({ thresholdType, threshold, alertValue }) => { + const result = await render( + makeSearchView({ thresholdType, threshold, value: alertValue }), + AlertState.ALERT, + ); + expect(result).toMatchSnapshot(); + }, + ); + + it('with group', async () => { + const result = await render( + makeSearchView({ group: 'http' }), + AlertState.ALERT, + ); + expect(result).toMatchSnapshot(); + }); + }); + + describe('OK state (resolved)', () => { + it.each(alertCases)( + '$thresholdType threshold=$threshold okValue=$okValue', + async ({ thresholdType, threshold, okValue }) => { + const result = await render( + makeSearchView({ thresholdType, threshold, value: okValue }), + AlertState.OK, + ); + expect(result).toMatchSnapshot(); + }, + ); + + it('with group', async () => { + const result = await render( + makeSearchView({ group: 'http' }), + AlertState.OK, + ); + expect(result).toMatchSnapshot(); + }); + }); + }); + + describe('tile alerts', () => { + describe('ALERT state', () => { + it.each(alertCases)( + '$thresholdType threshold=$threshold alertValue=$alertValue', + async ({ thresholdType, threshold, alertValue }) => { + const result = await render( + makeTileView({ thresholdType, threshold, value: alertValue }), + AlertState.ALERT, + ); + expect(result).toMatchSnapshot(); + }, + ); + + it('with group', async () => { + const result = await render( + makeTileView({ group: 'us-east-1' }), + AlertState.ALERT, + ); + expect(result).toMatchSnapshot(); + }); + + it('decimal threshold', async () => { + const result = await render( + makeTileView({ + thresholdType: AlertThresholdType.ABOVE, + threshold: 1.5, + value: 10.123, + }), + AlertState.ALERT, + ); + expect(result).toMatchSnapshot(); + }); + + it('integer threshold rounds value', async () => { + const result = await render( + makeTileView({ + thresholdType: AlertThresholdType.ABOVE, + threshold: 5, + value: 10.789, + }), + AlertState.ALERT, + ); + expect(result).toMatchSnapshot(); + }); + }); + + describe('OK state (resolved)', () => { + it.each(alertCases)( + '$thresholdType threshold=$threshold okValue=$okValue', + async ({ thresholdType, threshold, okValue }) => { + const result = await render( + makeTileView({ thresholdType, threshold, value: okValue }), + AlertState.OK, + ); + expect(result).toMatchSnapshot(); + }, + ); + + it('with group', async () => { + const result = await render( + makeTileView({ group: 'us-east-1' }), + AlertState.OK, + ); + expect(result).toMatchSnapshot(); + }); + }); + }); +}); + +describe('buildAlertMessageTemplateTitle', () => { + describe('saved search alerts', () => { + describe('ALERT state', () => { + it.each(alertCases)( + '$thresholdType threshold=$threshold alertValue=$alertValue', + ({ thresholdType, threshold, alertValue }) => { + const result = buildAlertMessageTemplateTitle({ + view: makeSearchView({ + thresholdType, + threshold, + value: alertValue, + }), + state: AlertState.ALERT, + }); + expect(result).toMatchSnapshot(); + }, + ); + }); + + describe('OK state (resolved)', () => { + it.each(alertCases)( + '$thresholdType threshold=$threshold okValue=$okValue', + ({ thresholdType, threshold, okValue }) => { + const result = buildAlertMessageTemplateTitle({ + view: makeSearchView({ thresholdType, threshold, value: okValue }), + state: AlertState.OK, + }); + expect(result).toMatchSnapshot(); + }, + ); + }); + }); + + describe('tile alerts', () => { + describe('ALERT state', () => { + it.each(alertCases)( + '$thresholdType threshold=$threshold alertValue=$alertValue', + ({ thresholdType, threshold, alertValue }) => { + const result = buildAlertMessageTemplateTitle({ + view: makeTileView({ thresholdType, threshold, value: alertValue }), + state: AlertState.ALERT, + }); + expect(result).toMatchSnapshot(); + }, + ); + + it('decimal threshold', () => { + const result = buildAlertMessageTemplateTitle({ + view: makeTileView({ + thresholdType: AlertThresholdType.ABOVE, + threshold: 1.5, + value: 10.123, + }), + state: AlertState.ALERT, + }); + expect(result).toMatchSnapshot(); + }); + + it('integer threshold rounds value', () => { + const result = buildAlertMessageTemplateTitle({ + view: makeTileView({ + thresholdType: AlertThresholdType.ABOVE, + threshold: 5, + value: 10.789, + }), + state: AlertState.ALERT, + }); + expect(result).toMatchSnapshot(); + }); + }); + + describe('OK state (resolved)', () => { + it.each(alertCases)( + '$thresholdType threshold=$threshold okValue=$okValue', + ({ thresholdType, threshold, okValue }) => { + const result = buildAlertMessageTemplateTitle({ + view: makeTileView({ thresholdType, threshold, value: okValue }), + state: AlertState.OK, + }); + expect(result).toMatchSnapshot(); + }, + ); + }); + }); +}); diff --git a/packages/api/src/tasks/checkAlerts/__tests__/singleInvocationAlert.test.ts b/packages/api/src/tasks/checkAlerts/__tests__/singleInvocationAlert.test.ts index 6118e451..02d5f2bc 100644 --- a/packages/api/src/tasks/checkAlerts/__tests__/singleInvocationAlert.test.ts +++ b/packages/api/src/tasks/checkAlerts/__tests__/singleInvocationAlert.test.ts @@ -248,7 +248,7 @@ describe('Single Invocation Alert Test', () => { // Verify the message body contains the search link const messageBody = webhookPayload.blocks[0].text.text; expect(messageBody).toContain('lines found'); - expect(messageBody).toContain('expected less than 1 lines'); + expect(messageBody).toContain('meets or exceeds the threshold of 1 lines'); expect(messageBody).toContain('http://app:8080/search/'); expect(messageBody).toContain('from='); expect(messageBody).toContain('to='); diff --git a/packages/api/src/tasks/checkAlerts/template.ts b/packages/api/src/tasks/checkAlerts/template.ts index cb0c2d48..9587ba22 100644 --- a/packages/api/src/tasks/checkAlerts/template.ts +++ b/packages/api/src/tasks/checkAlerts/template.ts @@ -81,25 +81,6 @@ const describeThresholdResolution = ( } }; -const describeThresholdExpectation = ( - thresholdType: AlertThresholdType, -): string => { - switch (thresholdType) { - case AlertThresholdType.ABOVE: - return 'less than'; - case AlertThresholdType.BELOW: - return 'at least'; - case AlertThresholdType.ABOVE_EXCLUSIVE: - return 'at most'; - case AlertThresholdType.BELOW_OR_EQUAL: - return 'greater than'; - case AlertThresholdType.EQUAL: - return 'not equal to'; - case AlertThresholdType.NOT_EQUAL: - return 'equal to'; - } -}; - const MAX_MESSAGE_LENGTH = 500; const NOTIFY_FN_NAME = '__hdx_notify_channel__'; const IS_MATCH_FN_NAME = 'is_match'; @@ -703,7 +684,7 @@ ${targetTemplate}`; } rawTemplateBody = `${group ? `Group: "${group}"` : ''} -${value} lines found, expected ${describeThresholdExpectation(alert.thresholdType)} ${alert.threshold} lines\n${timeRangeMessage} +${value} lines found, which ${describeThresholdViolation(alert.thresholdType)} the threshold of ${alert.threshold} lines\n${timeRangeMessage} ${targetTemplate} \`\`\` ${truncatedResults}