feat(notifications): add Rocket.Chat attachments
This commit is contained in:
1 parent
ee25c6b479
commit
2c780b8101
2 files changed
+71
-77
No files matched your search
@@ -64,7 +64,7 @@ export class RocketChatProvider extends NotificationProvider {
|
||||
|
||||
try {
|
||||
await got.post(notification.address, {
|
||||
json: { text: this.buildText(message) },
|
||||
json: this.buildPayload(message),
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
@@ -83,34 +83,38 @@ export class RocketChatProvider extends NotificationProvider {
|
||||
}
|
||||
}
|
||||
|
||||
private buildText(message: NotificationMessage): string {
|
||||
const lines = [
|
||||
message.content.title,
|
||||
message.content.summary,
|
||||
"",
|
||||
`Monitor: ${message.monitor.name}`,
|
||||
`Type: ${message.monitor.type}`,
|
||||
`Status: ${message.monitor.status}`,
|
||||
`URL: ${message.monitor.url}`,
|
||||
];
|
||||
private buildPayload(message: NotificationMessage): RocketChatPayload {
|
||||
return {
|
||||
text: message.content.summary,
|
||||
attachments: [
|
||||
{
|
||||
color: this.severityColor(message.severity),
|
||||
title: message.content.title,
|
||||
title_link: `${message.clientHost}/infrastructure/${message.monitor.id}`,
|
||||
fields: [
|
||||
{ title: "Monitor", value: message.monitor.name, short: true },
|
||||
{ title: "Type", value: message.monitor.type, short: true },
|
||||
{ title: "Status", value: message.monitor.status, short: true },
|
||||
{ title: "URL", value: message.monitor.url, short: false },
|
||||
],
|
||||
ts: message.content.timestamp.toISOString(),
|
||||
},
|
||||
],
|
||||
};
|
||||
}
|
||||
|
||||
if (message.content.details?.length) {
|
||||
lines.push("", "Details:", ...message.content.details.map((detail) => `- ${detail}`));
|
||||
private severityColor(severity: NotificationMessage["severity"]): string {
|
||||
switch (severity) {
|
||||
case "critical":
|
||||
return "#FF0000";
|
||||
case "warning":
|
||||
return "#FFA500";
|
||||
case "success":
|
||||
return "#00FF00";
|
||||
case "info":
|
||||
return "#0000FF";
|
||||
default:
|
||||
return "#808080";
|
||||
}
|
||||
if (message.content.thresholds?.length) {
|
||||
lines.push(
|
||||
"",
|
||||
"Thresholds:",
|
||||
...message.content.thresholds.map(
|
||||
(threshold) => `- ${threshold.metric.toUpperCase()}: ${threshold.formattedValue} (threshold: ${threshold.threshold}${threshold.unit})`
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
if (message.content.incident) {
|
||||
lines.push("", `Incident: ${message.clientHost}/infrastructure/${message.monitor.id}`);
|
||||
}
|
||||
|
||||
return lines.join("\n");
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,7 @@
|
||||
import { describe, expect, it, jest } from "@jest/globals";
|
||||
import type { NotificationSeverity } from "../../../../src/domain/notifications/notification.type.ts";
|
||||
import { createMockLogger } from "../../../helpers/createMockLogger.ts";
|
||||
import { makeMessage, makeMessageWithIncident, makeMessageWithThresholds, makeNotification } from "../../../helpers/notificationMessage.ts";
|
||||
import { makeMessage, makeNotification } from "../../../helpers/notificationMessage.ts";
|
||||
import { testNotificationProviderContract } from "../../../helpers/notificationProviderContract.ts";
|
||||
|
||||
const mockGotPost = jest.fn().mockResolvedValue({});
|
||||
@@ -84,7 +85,7 @@ describe("RocketChatProvider", () => {
|
||||
);
|
||||
});
|
||||
|
||||
it("sends monitor details as a text-only message", async () => {
|
||||
it("sends a rich monitor attachment without webhook overrides", async () => {
|
||||
const { provider } = createProvider();
|
||||
|
||||
const result = await provider.sendMessage(makeNotification({ type: "rocket_chat" }), makeMessage());
|
||||
@@ -96,22 +97,45 @@ describe("RocketChatProvider", () => {
|
||||
timeout: { request: 30000 },
|
||||
retry: { limit: 0 },
|
||||
json: {
|
||||
text: [
|
||||
"Monitor Down: Test Monitor",
|
||||
'Monitor "Test Monitor" is currently down.',
|
||||
"",
|
||||
"Monitor: Test Monitor",
|
||||
"Type: http",
|
||||
"Status: down",
|
||||
"URL: https://example.com",
|
||||
"",
|
||||
"Details:",
|
||||
"- URL: https://example.com",
|
||||
"- Status: Down",
|
||||
].join("\n"),
|
||||
text: 'Monitor "Test Monitor" is currently down.',
|
||||
attachments: [
|
||||
{
|
||||
color: "#FF0000",
|
||||
title: "Monitor Down: Test Monitor",
|
||||
title_link: "https://app.example.com/infrastructure/mon-1",
|
||||
fields: [
|
||||
{ title: "Monitor", value: "Test Monitor", short: true },
|
||||
{ title: "Type", value: "http", short: true },
|
||||
{ title: "Status", value: "down", short: true },
|
||||
{ title: "URL", value: "https://example.com", short: false },
|
||||
],
|
||||
ts: "2025-01-01T00:00:00.000Z",
|
||||
},
|
||||
],
|
||||
},
|
||||
})
|
||||
);
|
||||
|
||||
const payload = mockGotPost.mock.calls[0][1].json;
|
||||
expect(payload).not.toHaveProperty("alias");
|
||||
expect(payload).not.toHaveProperty("avatar");
|
||||
expect(payload).not.toHaveProperty("emoji");
|
||||
expect(payload).not.toHaveProperty("channel");
|
||||
});
|
||||
|
||||
it.each([
|
||||
["critical", "#FF0000"],
|
||||
["warning", "#FFA500"],
|
||||
["success", "#00FF00"],
|
||||
["info", "#0000FF"],
|
||||
["unknown", "#808080"],
|
||||
])("maps %s severity to %s", async (severity, color) => {
|
||||
const { provider } = createProvider();
|
||||
|
||||
await provider.sendMessage(makeNotification({ type: "rocket_chat" }), makeMessage({ severity: severity as NotificationSeverity }));
|
||||
|
||||
const payload = mockGotPost.mock.calls[0][1].json;
|
||||
expect(payload.attachments[0].color).toBe(color);
|
||||
});
|
||||
|
||||
it("returns false when the message webhook URL is missing", async () => {
|
||||
@@ -123,40 +147,6 @@ describe("RocketChatProvider", () => {
|
||||
expect(mockGotPost).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("includes threshold breaches in the text", async () => {
|
||||
const { provider } = createProvider();
|
||||
|
||||
await provider.sendMessage(makeNotification({ type: "rocket_chat" }), makeMessageWithThresholds());
|
||||
|
||||
const payload = mockGotPost.mock.calls[0][1].json;
|
||||
expect(payload.text).toContain("Thresholds:\n- CPU: 90.0% (threshold: 80%)\n- MEMORY: 85.0% (threshold: 70%)");
|
||||
});
|
||||
|
||||
it("includes a monitor-scoped link when incident data is present", async () => {
|
||||
const { provider } = createProvider();
|
||||
|
||||
await provider.sendMessage(makeNotification({ type: "rocket_chat" }), makeMessageWithIncident());
|
||||
|
||||
const payload = mockGotPost.mock.calls[0][1].json;
|
||||
expect(payload.text).toContain("Incident: https://app.example.com/infrastructure/mon-1");
|
||||
});
|
||||
|
||||
it("omits the incident link when incident data is absent", async () => {
|
||||
const { provider } = createProvider();
|
||||
|
||||
await provider.sendMessage(
|
||||
makeNotification({ type: "rocket_chat" }),
|
||||
makeMessage({
|
||||
type: "monitor_up",
|
||||
severity: "success",
|
||||
monitor: { id: "mon-1", name: "Test Monitor", url: "https://example.com", type: "http", status: "up" },
|
||||
})
|
||||
);
|
||||
|
||||
const payload = mockGotPost.mock.calls[0][1].json;
|
||||
expect(payload.text).not.toContain("Incident:");
|
||||
});
|
||||
|
||||
it("returns false and logs when message delivery fails", async () => {
|
||||
mockGotPost.mockRejectedValue(new Error("network failure"));
|
||||
const { provider, logger } = createProvider();
|
||||
|
||||
Reference in new issue
Block a user