Skip to content

Commit

Permalink
feat: alert template message pt4 (#338)
Browse files Browse the repository at this point in the history
Introduce conditional alert routing helper `#is_match`
  • Loading branch information
wrn14897 authored Mar 11, 2024
1 parent 0e365bf commit b454003
Show file tree
Hide file tree
Showing 5 changed files with 262 additions and 17 deletions.
6 changes: 6 additions & 0 deletions .changeset/green-bulldogs-behave.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
'@hyperdx/api': patch
'@hyperdx/app': patch
---

feat: introduce conditional alert routing helper #is_match
9 changes: 9 additions & 0 deletions packages/api/src/clickhouse/__tests__/clickhouse.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1271,20 +1271,29 @@ Array [
expect(data).toMatchInlineSnapshot(`
Array [
Object {
"attributes": Object {
"testGroup": "group2",
},
"data": 777,
"group": Array [
"group2",
],
"ts_bucket": 1641340800,
},
Object {
"attributes": Object {
"testGroup": "group1",
},
"data": 77,
"group": Array [
"group1",
],
"ts_bucket": 1641340800,
},
Object {
"attributes": Object {
"testGroup": "group1",
},
"data": 7,
"group": Array [
"group1",
Expand Down
40 changes: 32 additions & 8 deletions packages/api/src/clickhouse/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1818,21 +1818,39 @@ export const getMultiSeriesChartLegacyFormat = async ({

const flatData = result.data.flatMap(row => {
if (seriesReturnType === 'column') {
return series.map((_, i) => {
return series.map((s, i) => {
const groupBy =
s.type === 'number' ? [] : 'groupBy' in s ? s.groupBy : [];
const attributes = groupBy.reduce((acc, curVal, curIndex) => {
acc[curVal] = row.group[curIndex];
return acc;
}, {} as Record<string, string>);
return {
ts_bucket: row.ts_bucket,
group: row.group,
attributes,
data: row[`series_${i}.data`],
group: row.group,
ts_bucket: row.ts_bucket,
};
});
}

// Ratio only has 1 series
const groupBy =
series[0].type === 'number'
? []
: 'groupBy' in series[0]
? series[0].groupBy
: [];
const attributes = groupBy.reduce((acc, curVal, curIndex) => {
acc[curVal] = row.group[curIndex];
return acc;
}, {} as Record<string, string>);
return [
{
ts_bucket: row.ts_bucket,
group: row.group,
attributes,
data: row['series_0.data'],
group: row.group,
ts_bucket: row.ts_bucket,
},
];
});
Expand Down Expand Up @@ -2550,8 +2568,9 @@ export const checkAlert = async ({
`
SELECT
?
count(*) as data,
toUnixTimestamp(toStartOfInterval(timestamp, INTERVAL ?)) as ts_bucket
count(*) AS data,
any(_string_attributes) AS attributes,
toUnixTimestamp(toStartOfInterval(timestamp, INTERVAL ?)) AS ts_bucket
FROM ??
WHERE ? AND (?)
GROUP BY ?
Expand Down Expand Up @@ -2596,7 +2615,12 @@ export const checkAlert = async ({
},
});
const result = await rows.json<
ResponseJSON<{ data: string; group?: string; ts_bucket: number }>
ResponseJSON<{
data: string;
group?: string;
ts_bucket: number;
attributes: Record<string, string>;
}>
>();
logger.info({
message: 'checkAlert',
Expand Down
183 changes: 182 additions & 1 deletion packages/api/src/tasks/__tests__/checkAlerts.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -283,9 +283,16 @@ describe('checkAlerts', () => {
).toMatchInlineSnapshot(
`"{{__hdx_notify_channel__ channel=\\"action\\" id=\\"id-with-multiple-dashes\\"}}"`,
);

// custom template id
expect(
translateExternalActionsToInternal('@action-{{action_id}}'),
).toMatchInlineSnapshot(
`"{{__hdx_notify_channel__ channel=\\"action\\" id=\\"{{action_id}}\\"}}"`,
);
});

it('renderAlertTemplate', async () => {
it('renderAlertTemplate - custom body with single action', async () => {
jest
.spyOn(slack, 'postMessageToWebhook')
.mockResolvedValueOnce(null as any);
Expand Down Expand Up @@ -356,6 +363,180 @@ describe('checkAlerts', () => {
},
);
});

it('renderAlertTemplate - single action with custom action id', async () => {
jest
.spyOn(slack, 'postMessageToWebhook')
.mockResolvedValueOnce(null as any);
jest.spyOn(clickhouse, 'getLogBatch').mockResolvedValueOnce({
data: [
{
timestamp: '2023-11-16T22:10:00.000Z',
severity_text: 'error',
body: 'Oh no! Something went wrong!',
},
{
timestamp: '2023-11-16T22:15:00.000Z',
severity_text: 'info',
body: 'All good!',
},
],
} as any);

const team = await createTeam({ name: 'My Team' });
await new Webhook({
team: team._id,
service: 'slack',
url: 'https://hooks.slack.com/services/123',
name: 'My_Webhook',
}).save();

await renderAlertTemplate({
template: 'Custom body @slack_webhook-{{attributes.webhookName}}', // partial name should work
view: {
...defaultSearchView,
alert: {
...defaultSearchView.alert,
channel: {
type: null, // using template instead
},
},
attributes: {
webhookName: 'My_Webhook',
},
team: {
id: team._id.toString(),
logStreamTableVersion: team.logStreamTableVersion,
},
},
title: 'Alert for "My Search" - 10 lines found',
});

expect(slack.postMessageToWebhook).toHaveBeenNthCalledWith(
1,
'https://hooks.slack.com/services/123',
{
text: 'Alert for "My Search" - 10 lines found',
blocks: [
{
text: {
text: [
'*<http://localhost:9090/search/id-123?from=1679091183103&to=1679091239103&q=level%3Aerror+span_name%3A%22http%22 | Alert for "My Search" - 10 lines found>*',
'Group: "http"',
'10 lines found, expected less than 1 lines',
'Custom body ',
'```',
'Nov 16 22:10:00Z [error] Oh no! Something went wrong!',
'Nov 16 22:15:00Z [info] All good!',
'```',
].join('\n'),
type: 'mrkdwn',
},
type: 'section',
},
],
},
);
});

it('renderAlertTemplate - #is_match with single action', async () => {
jest
.spyOn(slack, 'postMessageToWebhook')
.mockResolvedValueOnce(null as any);
jest.spyOn(clickhouse, 'getLogBatch').mockResolvedValueOnce({
data: [
{
timestamp: '2023-11-16T22:10:00.000Z',
severity_text: 'error',
body: 'Oh no! Something went wrong!',
},
{
timestamp: '2023-11-16T22:15:00.000Z',
severity_text: 'info',
body: 'All good!',
},
],
} as any);

const team = await createTeam({ name: 'My Team' });
await new Webhook({
team: team._id,
service: 'slack',
url: 'https://hooks.slack.com/services/123',
name: 'My_Webhook',
}).save();

await renderAlertTemplate({
template:
'{{#is_match "host" "web"}} @slack_webhook-My_Web {{/is_match}}', // partial name should work
view: {
...defaultSearchView,
alert: {
...defaultSearchView.alert,
channel: {
type: null, // using template instead
},
},
team: {
id: team._id.toString(),
logStreamTableVersion: team.logStreamTableVersion,
},
attributes: {
host: 'web',
},
},
title: 'Alert for "My Search" - 10 lines found',
});

// @slack_webhook should not be called
await renderAlertTemplate({
template:
'{{#is_match "host" "web"}} @slack_webhook-My_Web {{/is_match}}', // partial name should work
view: {
...defaultSearchView,
alert: {
...defaultSearchView.alert,
channel: {
type: null, // using template instead
},
},
team: {
id: team._id.toString(),
logStreamTableVersion: team.logStreamTableVersion,
},
attributes: {
host: 'web2',
},
},
title: 'Alert for "My Search" - 10 lines found',
});

expect(slack.postMessageToWebhook).toHaveBeenNthCalledWith(
1,
'https://hooks.slack.com/services/123',
{
text: 'Alert for "My Search" - 10 lines found',
blocks: [
{
text: {
text: [
'*<http://localhost:9090/search/id-123?from=1679091183103&to=1679091239103&q=level%3Aerror+span_name%3A%22http%22 | Alert for "My Search" - 10 lines found>*',
'Group: "http"',
'10 lines found, expected less than 1 lines',
'',
'```',
'Nov 16 22:10:00Z [error] Oh no! Something went wrong!',
'Nov 16 22:15:00Z [info] All good!',
'```',
].join('\n'),
type: 'mrkdwn',
},
type: 'section',
},
],
},
);
});
});

describe('processAlert', () => {
Expand Down
Loading

0 comments on commit b454003

Please sign in to comment.