Skip to content

Commit

Permalink
feat: alert template message pt6 (#343)
Browse files Browse the repository at this point in the history
  • Loading branch information
wrn14897 authored Mar 14, 2024
1 parent 9c6b51e commit de9712c
Show file tree
Hide file tree
Showing 2 changed files with 91 additions and 19 deletions.
58 changes: 53 additions & 5 deletions packages/api/src/tasks/__tests__/checkAlerts.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ import {
buildLogSearchLink,
doesExceedThreshold,
escapeJsonValues,
expandToNestedObject,
getDefaultExternalAction,
injectIntoPlaceholders,
processAlert,
Expand Down Expand Up @@ -144,6 +145,47 @@ describe('checkAlerts', () => {
expect(doesExceedThreshold(false, 10, 10)).toBe(false);
});

it('expandToNestedObject', () => {
expect(expandToNestedObject({}).__proto__).toBeUndefined();
expect(expandToNestedObject({})).toEqual({});
expect(expandToNestedObject({ foo: 'bar' })).toEqual({ foo: 'bar' });
expect(expandToNestedObject({ 'foo.bar': 'baz' })).toEqual({
foo: { bar: 'baz' },
});
expect(expandToNestedObject({ 'foo.bar.baz': 'qux' })).toEqual({
foo: { bar: { baz: 'qux' } },
});
// mix
expect(
expandToNestedObject({
'foo.bar.baz': 'qux',
'foo.bar.quux': 'quuz',
'foo1.bar1.baz1': 'qux1',
}),
).toEqual({
foo: { bar: { baz: 'qux', quux: 'quuz' } },
foo1: { bar1: { baz1: 'qux1' } },
});
// overwriting
expect(
expandToNestedObject({ 'foo.bar.baz': 'qux', 'foo.bar': 'quuz' }),
).toEqual({
foo: { bar: 'quuz' },
});
// max depth
expect(
expandToNestedObject(
{
'foo.bar.baz.qux.quuz.quux': 'qux',
},
'.',
3,
),
).toEqual({
foo: { bar: { baz: {} } },
});
});

describe('Alert Templates', () => {
const defaultSearchView: any = {
alert: {
Expand Down Expand Up @@ -532,8 +574,8 @@ describe('checkAlerts', () => {

await renderAlertTemplate({
template: `
{{#is_match "k8s.pod.name" "otel-collector-123"}}
Runbook URL: {{attributes.runbookUrl}}
{{#is_match "attributes.k8s.pod.name" "otel-collector-123"}}
Runbook URL: {{attributes.runbook.url}}
hi i matched
@slack_webhook-My_Web
{{/is_match}}
Expand All @@ -549,8 +591,14 @@ describe('checkAlerts', () => {
},
},
attributes: {
runbookUrl: 'https://example.com',
'k8s.pod.name': 'otel-collector-123',
runbook: {
url: 'https://example.com',
},
k8s: {
pod: {
name: 'otel-collector-123',
},
},
},
},
title: 'Alert for "My Search" - 10 lines found',
Expand All @@ -563,7 +611,7 @@ describe('checkAlerts', () => {
// @slack_webhook should not be called
await renderAlertTemplate({
template:
'{{#is_match "host" "web"}} @slack_webhook-My_Web {{/is_match}}', // partial name should work
'{{#is_match "attributes.host" "web"}} @slack_webhook-My_Web {{/is_match}}', // partial name should work
view: {
...defaultSearchView,
alert: {
Expand Down
52 changes: 38 additions & 14 deletions packages/api/src/tasks/checkAlerts.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
import * as fns from 'date-fns';
import * as fnsTz from 'date-fns-tz';
import Handlebars, { HelperOptions } from 'handlebars';
import _ from 'lodash';
import { escapeRegExp, isString } from 'lodash';
import mongoose from 'mongoose';
import ms from 'ms';
Expand Down Expand Up @@ -107,14 +108,44 @@ export const doesExceedThreshold = (
return false;
};

// transfer keys of attributes with dot into nested object
// ex: { 'a.b': 'c', 'd.e.f': 'g' } -> { a: { b: 'c' }, d: { e: { f: 'g' } } }
export const expandToNestedObject = (
obj: Record<string, string>,
separator = '.',
maxDepth = 10,
) => {
const result: Record<string, any> = Object.create(null); // An object NOT inheriting from `Object.prototype`
for (const key in obj) {
if (Object.prototype.hasOwnProperty.call(obj, key)) {
const keys = key.split(separator);
let nestedObj = result;

for (let i = 0; i < keys.length; i++) {
if (i >= maxDepth) {
break;
}
const nestedKey = keys[i];
if (i === keys.length - 1) {
nestedObj[nestedKey] = obj[key];
} else {
nestedObj[nestedKey] = nestedObj[nestedKey] || {};
nestedObj = nestedObj[nestedKey];
}
}
}
}
return result;
};

// ------------------------------------------------------------
// ----------------- Alert Message Template -------------------
// ------------------------------------------------------------
// should match the external alert schema
type AlertMessageTemplateDefaultView = {
// FIXME: do we want to include groupBy in the external alert schema?
alert: z.infer<typeof externalAlertSchema> & { groupBy?: string };
attributes: Record<string, string>;
attributes: ReturnType<typeof expandToNestedObject>;
dashboard: ReturnType<
typeof translateDashboardDocumentToExternalDashboard
> | null;
Expand Down Expand Up @@ -411,16 +442,8 @@ export const renderAlertTemplate = async ({
logStreamTableVersion?: ITeam['logStreamTableVersion'];
};
}) => {
const {
alert,
attributes,
dashboard,
endTime,
group,
savedSearch,
startTime,
value,
} = view;
const { alert, dashboard, endTime, group, savedSearch, startTime, value } =
view;

const defaultExternalAction = getDefaultExternalAction(alert);
const targetTemplate =
Expand All @@ -430,14 +453,13 @@ export const renderAlertTemplate = async ({
).trim()
: translateExternalActionsToInternal(template ?? '');

const attributesMap = new Map(Object.entries(attributes ?? {}));
const isMatchFn = function (shouldRender: boolean) {
return function (
targetKey: string,
targetValue: string,
options: HelperOptions,
) {
if (attributesMap.get(targetKey) === targetValue) {
if (_.has(view, targetKey) && _.get(view, targetKey) === targetValue) {
if (shouldRender) {
return options.fn(this);
} else {
Expand Down Expand Up @@ -581,12 +603,14 @@ const fireChannelEvent = async ({
if (team == null) {
throw new Error('Team not found');
}

const attributesNested = expandToNestedObject(attributes);
const templateView: AlertMessageTemplateDefaultView = {
alert: {
...translateAlertDocumentToExternalAlert(alert),
groupBy: alert.groupBy,
},
attributes,
attributes: attributesNested,
dashboard: dashboard
? translateDashboardDocumentToExternalDashboard({
_id: dashboard._id,
Expand Down

0 comments on commit de9712c

Please sign in to comment.