Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add alert page for indexing and seeing details about alerts #154

Merged
merged 20 commits into from
Dec 28, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,10 @@ dev-lint:
ci-lint:
./docker/ingestor/run_linting.sh && npx nx run-many -t ci:lint

.PHONY: dev-int-build
dev-int-build:
docker compose -p int -f ./docker-compose.ci.yml build

.PHONY: dev-int
dev-int:
docker compose -p int -f ./docker-compose.ci.yml run --rm api dev:int $(FILE)
Expand Down
1 change: 1 addition & 0 deletions packages/api/src/controllers/alerts.ts
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,7 @@ export const createAlert = async (alertInput: AlertInput) => {

// create an update alert function based off of the above create alert function
export const updateAlert = async (id: string, alertInput: AlertInput) => {
// should consider clearing AlertHistory when updating an alert?
return Alert.findByIdAndUpdate(id, makeAlert(alertInput), {
returnDocument: 'after',
});
Expand Down
2 changes: 1 addition & 1 deletion packages/api/src/models/alert.ts
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,7 @@ const AlertSchema = new Schema<IAlert>(
// Log alerts
logView: {
type: mongoose.Schema.Types.ObjectId,
ref: 'Alert',
ref: 'LogView',
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Found this bug...that explains why logView couldn't be populated

required: false,
},
groupBy: {
Expand Down
13 changes: 13 additions & 0 deletions packages/api/src/models/alertHistory.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ export interface IAlertHistory {
counts: number;
createdAt: Date;
state: AlertState;
lastValues: { startTime: Date; count: number }[];
}

const AlertHistorySchema = new Schema<IAlertHistory>({
Expand All @@ -27,6 +28,18 @@ const AlertHistorySchema = new Schema<IAlertHistory>({
enum: Object.values(AlertState),
required: true,
},
lastValues: [
{
startTime: {
type: Date,
required: true,
},
count: {
type: Number,
required: true,
},
},
],
});

AlertHistorySchema.index(
Expand Down
95 changes: 95 additions & 0 deletions packages/api/src/routers/api/__tests__/alerts.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
import {
clearDBCollections,
closeDB,
getLoggedInAgent,
getServer,
} from '@/fixtures';

const randomId = () => Math.random().toString(36).substring(7);

const makeChart = () => ({
id: randomId(),
name: 'Test Chart',
x: 1,
y: 1,
w: 1,
h: 1,
series: [
{
type: 'time',
table: 'metrics',
},
],
});

const makeAlert = ({
dashboardId,
chartId,
}: {
dashboardId: string;
chartId: string;
}) => ({
channel: {
type: 'webhook',
webhookId: 'test-webhook-id',
},
interval: '15m',
threshold: 8,
type: 'presence',
source: 'CHART',
dashboardId,
chartId,
});

const MOCK_DASHBOARD = {
name: 'Test Dashboard',
charts: [makeChart(), makeChart(), makeChart(), makeChart(), makeChart()],
query: 'test query',
};

describe('alerts router', () => {
const server = getServer();

it('index has alerts attached to dashboards', async () => {
const { agent } = await getLoggedInAgent(server);

await agent.post('/dashboards').send(MOCK_DASHBOARD).expect(200);
const initialDashboards = await agent.get('/dashboards').expect(200);

// Create alerts for all charts
const dashboard = initialDashboards.body.data[0];
await Promise.all(
dashboard.charts.map(chart =>
agent
.post('/alerts')
.send(
makeAlert({
dashboardId: dashboard._id,
chartId: chart.id,
}),
)
.expect(200),
),
);

const alerts = await agent.get(`/alerts`).expect(200);
expect(alerts.body.data.length).toBe(5);
for (const alert of alerts.body.data) {
expect(alert.chartId).toBeDefined();
expect(alert.dashboard).toBeDefined();
}
});

beforeAll(async () => {
await server.start();
});

afterEach(async () => {
await clearDBCollections();
});

afterAll(async () => {
await server.closeHttpServer();
await closeDB();
});
});
73 changes: 66 additions & 7 deletions packages/api/src/routers/api/alerts.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import express from 'express';
import express, { NextFunction, Request, Response } from 'express';
import _ from 'lodash';
import { z } from 'zod';
import { validateRequest } from 'zod-express-middleware';

Expand All @@ -9,6 +10,9 @@ import {
} from '@/controllers/alerts';
import { getTeam } from '@/controllers/team';
import Alert from '@/models/alert';
import AlertHistory from '@/models/alertHistory';
import { IDashboard } from '@/models/dashboard';
import { ILogView } from '@/models/logView';

const router = express.Router();

Expand Down Expand Up @@ -41,10 +45,12 @@ const zAlert = z
})
.and(zLogAlert.or(zChartAlert));

const zAlertInput = zAlert;

// Validate groupBy property
const validateGroupBy = async (req, res, next) => {
const validateGroupBy = async (
req: Request,
res: Response,
next: NextFunction,
) => {
const { groupBy, source } = req.body || {};
if (source === 'LOG' && groupBy) {
const teamId = req.user?.team;
Expand All @@ -70,10 +76,63 @@ const validateGroupBy = async (req, res, next) => {
next();
};

// Routes
router.get('/', async (req, res, next) => {
try {
const teamId = req.user?.team;
if (teamId == null) {
return res.sendStatus(403);
}
const alerts = await Alert.find({ team: teamId }).populate<{
logView: ILogView;
dashboardId: IDashboard;
}>(['logView', 'dashboardId']);

const data = await Promise.all(
alerts.map(async alert => {
const history = await AlertHistory.find(
{
alert: alert._id,
team: teamId,
},
{
__v: 0,
_id: 0,
alert: 0,
},
)
.sort({ createdAt: -1 })
.limit(20);

return {
history,
dashboard: alert.dashboardId,
..._.pick(alert, [
'_id',
'channel',
'interval',
'threshold',
'state',
'type',
'source',
'logView',
'chartId',
'createdAt',
'updatedAt',
]),
};
}),
);
res.json({
data,
});
} catch (e) {
next(e);
}
});

router.post(
'/',
validateRequest({ body: zAlertInput }),
validateRequest({ body: zAlert }),
validateGroupBy,
async (req, res, next) => {
try {
Expand All @@ -89,7 +148,7 @@ router.post(

router.put(
'/:id',
validateRequest({ body: zAlertInput }),
validateRequest({ body: zAlert }),
validateGroupBy,
async (req, res, next) => {
try {
Expand Down
21 changes: 11 additions & 10 deletions packages/api/src/tasks/__tests__/checkAlerts.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -360,16 +360,17 @@ describe('checkAlerts', () => {
createdAt: 1,
});
expect(alertHistories.length).toBe(2);
expect(alertHistories[0].state).toBe('ALERT');
expect(alertHistories[0].counts).toBe(1);
expect(alertHistories[0].createdAt).toEqual(
new Date('2023-11-16T22:10:00.000Z'),
);
expect(alertHistories[1].state).toBe('OK');
expect(alertHistories[1].counts).toBe(0);
expect(alertHistories[1].createdAt).toEqual(
new Date('2023-11-16T22:15:00.000Z'),
);
const [history1, history2] = alertHistories;
expect(history1.state).toBe('ALERT');
expect(history1.counts).toBe(1);
expect(history1.createdAt).toEqual(new Date('2023-11-16T22:10:00.000Z'));
expect(history1.lastValues.length).toBe(1);
expect(history1.lastValues.length).toBeGreaterThan(0);
expect(history1.lastValues[0].count).toBeGreaterThanOrEqual(1);

expect(history2.state).toBe('OK');
expect(history2.counts).toBe(0);
expect(history2.createdAt).toEqual(new Date('2023-11-16T22:15:00.000Z'));

// check if getLogsChart query + webhook were triggered
expect(clickhouse.getLogsChart).toHaveBeenNthCalledWith(1, {
Expand Down
3 changes: 2 additions & 1 deletion packages/api/src/tasks/checkAlerts.ts
Original file line number Diff line number Diff line change
Expand Up @@ -476,6 +476,7 @@ export const processAlert = async (now: Date, alert: AlertDocument) => {
const totalCount = isString(checkData.data)
? parseInt(checkData.data)
: checkData.data;
const bucketStart = new Date(checkData.ts_bucket * 1000);
if (doesExceedThreshold(alert, totalCount)) {
alertState = AlertState.ALERT;
logger.info({
Expand All @@ -484,7 +485,6 @@ export const processAlert = async (now: Date, alert: AlertDocument) => {
totalCount,
checkData,
});
const bucketStart = new Date(checkData.ts_bucket * 1000);

await fireChannelEvent({
alert,
Expand All @@ -498,6 +498,7 @@ export const processAlert = async (now: Date, alert: AlertDocument) => {
});
history.counts += 1;
}
history.lastValues.push({ count: totalCount, startTime: bucketStart });
}

history.state = alertState;
Expand Down
3 changes: 3 additions & 0 deletions packages/app/pages/alerts.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
import AlertsPage from '../src/AlertsPage';

export default AlertsPage;
Loading
Loading