hyperdx/packages/api/src/routers/api/alerts.ts
Tom Alexander 96f0539e7a
feat: Add silence alerts feature (#1464)
Fixes: HDX-2734
2025-12-12 21:56:09 +00:00

228 lines
5.1 KiB
TypeScript

import express from 'express';
import _ from 'lodash';
import { ObjectId } from 'mongodb';
import { z } from 'zod';
import { validateRequest } from 'zod-express-middleware';
import { getRecentAlertHistories } from '@/controllers/alertHistory';
import {
createAlert,
deleteAlert,
getAlertById,
getAlertsEnhanced,
updateAlert,
} from '@/controllers/alerts';
import { alertSchema, objectIdSchema } from '@/utils/zod';
const router = express.Router();
router.get('/', async (req, res, next) => {
try {
const teamId = req.user?.team;
if (teamId == null) {
return res.sendStatus(403);
}
const alerts = await getAlertsEnhanced(teamId);
const data = await Promise.all(
alerts.map(async alert => {
const history = await getRecentAlertHistories({
alertId: new ObjectId(alert._id),
limit: 20,
});
return {
history,
silenced: alert.silenced
? {
by: alert.silenced.by?.email,
at: alert.silenced.at,
until: alert.silenced.until,
}
: undefined,
createdBy: alert.createdBy
? _.pick(alert.createdBy, ['email', 'name'])
: undefined,
channel: _.pick(alert.channel, ['type']),
...(alert.dashboard && {
dashboardId: alert.dashboard._id,
dashboard: {
tiles: alert.dashboard.tiles
.filter(tile => tile.id === alert.tileId)
.map(tile => _.pick(tile, ['id', 'config.name'])),
..._.pick(alert.dashboard, ['_id', 'name', 'updatedAt', 'tags']),
},
}),
...(alert.savedSearch && {
savedSearchId: alert.savedSearch._id,
savedSearch: _.pick(alert.savedSearch, [
'_id',
'createdAt',
'name',
'updatedAt',
'tags',
]),
}),
..._.pick(alert, [
'_id',
'interval',
'threshold',
'thresholdType',
'state',
'source',
'tileId',
'createdAt',
'updatedAt',
]),
};
}),
);
res.json({
data,
});
} catch (e) {
next(e);
}
});
router.post(
'/',
validateRequest({ body: alertSchema }),
async (req, res, next) => {
const teamId = req.user?.team;
const userId = req.user?._id;
if (teamId == null || userId == null) {
return res.sendStatus(403);
}
try {
const alertInput = req.body;
return res.json({
data: await createAlert(teamId, alertInput, userId),
});
} catch (e) {
next(e);
}
},
);
router.put(
'/:id',
validateRequest({
body: alertSchema,
params: z.object({
id: objectIdSchema,
}),
}),
async (req, res, next) => {
try {
const teamId = req.user?.team;
if (teamId == null) {
return res.sendStatus(403);
}
const { id } = req.params;
const alertInput = req.body;
res.json({
data: await updateAlert(id, teamId, alertInput),
});
} catch (e) {
next(e);
}
},
);
router.post(
'/:id/silenced',
validateRequest({
body: z.object({
mutedUntil: z
.string()
.datetime()
.refine(val => new Date(val) > new Date(), {
message: 'mutedUntil must be in the future',
}),
}),
params: z.object({
id: objectIdSchema,
}),
}),
async (req, res, next) => {
try {
const teamId = req.user?.team;
if (teamId == null || req.user == null) {
return res.sendStatus(403);
}
const alert = await getAlertById(req.params.id, teamId);
if (!alert) {
return res.status(404).json({ error: 'Alert not found' });
}
alert.silenced = {
by: req.user._id,
at: new Date(),
until: new Date(req.body.mutedUntil),
};
await alert.save();
res.sendStatus(200);
} catch (e) {
next(e);
}
},
);
router.delete(
'/:id/silenced',
validateRequest({
params: z.object({
id: objectIdSchema,
}),
}),
async (req, res, next) => {
try {
const teamId = req.user?.team;
if (teamId == null) {
return res.sendStatus(403);
}
const alert = await getAlertById(req.params.id, teamId);
if (!alert) {
return res.status(404).json({ error: 'Alert not found' });
}
alert.silenced = undefined;
await alert.save();
res.sendStatus(200);
} catch (e) {
next(e);
}
},
);
router.delete(
'/:id',
validateRequest({
params: z.object({
id: objectIdSchema,
}),
}),
async (req, res, next) => {
try {
const teamId = req.user?.team;
const { id: alertId } = req.params;
if (teamId == null) {
return res.sendStatus(403);
}
if (!alertId) {
return res.sendStatus(400);
}
await deleteAlert(alertId, teamId);
res.sendStatus(200);
} catch (e) {
next(e);
}
},
);
export default router;