From 41d6c092d48fe231437e343713c66d3935aa40c0 Mon Sep 17 00:00:00 2001 From: sukritraj02 Date: Wed, 28 Jan 2026 18:53:51 +0530 Subject: [PATCH] eat: improve notification grouping email subject and monitor name formatting --- server/package-lock.json | 21 +++++----------- .../notificationProviders/email.ts | 6 ++++- .../infrastructure/notificationsService.ts | 25 ++++++++++--------- 3 files changed, 24 insertions(+), 28 deletions(-) diff --git a/server/package-lock.json b/server/package-lock.json index 3f5b824b0..7643f5f52 100644 --- a/server/package-lock.json +++ b/server/package-lock.json @@ -873,7 +873,6 @@ "integrity": "sha512-H3mcG6ZDLTlYfaSNi0iOKkigqMFvkTKlGUYlD8GW7nNOYRrevuA46iTypPyv+06V3fEmvvazfntkBU34L0azAw==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@babel/code-frame": "^7.28.6", "@babel/generator": "^7.28.6", @@ -1503,7 +1502,6 @@ } ], "license": "MIT", - "peer": true, "engines": { "node": ">=18" }, @@ -1548,7 +1546,6 @@ } ], "license": "MIT", - "peer": true, "engines": { "node": ">=18" } @@ -4228,6 +4225,7 @@ "integrity": "sha512-L7z9BgrNEcYyUYtF+HaEfiS5ebkh9jXqbszz7pC0hRBPaatV0XjSD3+eHrpqFemQfgwiFF0QPIarnIihIDn7OA==", "license": "ISC", "optional": true, + "peer": true, "engines": { "node": ">=10.13.0" } @@ -4404,7 +4402,6 @@ "integrity": "sha512-wGA0NX93b19/dZC1J18tKWVIYWyyF2ZjT9vin/NRu0qzzvfVzWjs04iq2rQ3H65vCTQYlRqs3YHfY7zjdV+9Kw==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@types/body-parser": "*", "@types/express-serve-static-core": "^5.0.0", @@ -4565,7 +4562,6 @@ "resolved": "https://registry.npmjs.org/@types/node/-/node-24.9.1.tgz", "integrity": "sha512-QoiaXANRkSXK6p0Duvt56W208du4P9Uye9hWLWgGMDTEoKPhuenzNcC4vGUmrNkiOKTlIrBoyNQYNpSwfEZXSg==", "license": "MIT", - "peer": true, "dependencies": { "undici-types": "~7.16.0" } @@ -5052,7 +5048,6 @@ "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==", "dev": true, "license": "MIT", - "peer": true, "bin": { "acorn": "bin/acorn" }, @@ -5631,7 +5626,6 @@ } ], "license": "MIT", - "peer": true, "dependencies": { "baseline-browser-mapping": "^2.8.19", "caniuse-lite": "^1.0.30001751", @@ -6517,7 +6511,6 @@ "resolved": "https://registry.npmjs.org/cssnano/-/cssnano-7.1.1.tgz", "integrity": "sha512-fm4D8ti0dQmFPeF8DXSAA//btEmqCOgAc/9Oa3C1LW94h5usNrJEfrON7b4FkPZgnDEn6OUs5NdxiJZmAtGOpQ==", "license": "MIT", - "peer": true, "dependencies": { "cssnano-preset-default": "^7.0.9", "lilconfig": "^3.1.3" @@ -7259,7 +7252,6 @@ "integrity": "sha512-t5aPOpmtJcZcz5UJyY2GbvpDlsK5E8JqRqoKtfiKE3cNh437KIqfJr3A3AKf5k64NPx6d0G3dno6XDY05PqPtw==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@eslint-community/eslint-utils": "^4.8.0", "@eslint-community/regexpp": "^4.12.1", @@ -7614,7 +7606,6 @@ "resolved": "https://registry.npmjs.org/express/-/express-4.22.1.tgz", "integrity": "sha512-F2X8g9P1X7uCPZMA3MVf9wcTqlyNp7IhH5qPCI0izhaOIYXaW9L535tGA3qmjRzpH+bZczqq7hVKxTR4NWnu+g==", "license": "MIT", - "peer": true, "dependencies": { "accepts": "~1.3.8", "array-flatten": "1.1.1", @@ -9250,7 +9241,6 @@ "integrity": "sha512-F26gjC0yWN8uAA5m5Ss8ZQf5nDHWGlN/xWZIh8S5SRbsEKBovwZhxGd6LJlbZYxBgCYOtreSUyb8hpXyGC5O4A==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@jest/core": "30.2.0", "@jest/types": "30.2.0", @@ -11900,7 +11890,6 @@ } ], "license": "MIT", - "peer": true, "dependencies": { "nanoid": "^3.3.11", "picocolors": "^1.1.1", @@ -13752,6 +13741,7 @@ "integrity": "sha512-OoohrmuUlBs8B8o6MB2Aevn+pRIH9zDALSR+6hhqVfa6fRwG/Qw9VUMSMW9VNg2CFc/MTIfabtdOVl9ODIJjpw==", "license": "MIT", "optional": true, + "peer": true, "dependencies": { "@trysound/sax": "0.2.0", "commander": "^7.2.0", @@ -13778,6 +13768,7 @@ "integrity": "sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw==", "license": "MIT", "optional": true, + "peer": true, "engines": { "node": ">= 10" } @@ -13788,6 +13779,7 @@ "integrity": "sha512-6Fv1DV/TYw//QF5IzQdqsNDjx/wc8TrMBZsqjL9eW01tWb7R7k/mq+/VXfJCl7SoD5emsJop9cOByJZfs8hYIw==", "license": "MIT", "optional": true, + "peer": true, "dependencies": { "mdn-data": "2.0.30", "source-map-js": "^1.0.1" @@ -13801,7 +13793,8 @@ "resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.0.30.tgz", "integrity": "sha512-GaqWWShW4kv/G9IEucWScBx9G1/vsFZZJUO+tD26M8J8z3Kw5RDQjaoZe03YAClgeS/SWPOcb4nkFBTEi5DUEA==", "license": "CC0-1.0", - "optional": true + "optional": true, + "peer": true }, "node_modules/swagger-ui-dist": { "version": "5.30.0", @@ -14086,7 +14079,6 @@ "integrity": "sha512-f0FFpIdcHgn8zcPSbf1dRevwt047YMnaiJM3u2w2RewrB+fob/zePZcrOyQoLMMO7aBIddLcQIEK5dYjkLnGrQ==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@cspotcode/source-map-support": "^0.8.0", "@tsconfig/node10": "^1.0.7", @@ -14249,7 +14241,6 @@ "integrity": "sha512-CWBzXQrc/qOkhidw1OzBTQuYRbfyxDXJMVJ1XNwUHGROVmuaeiEm3OslpZ1RV96d7SKKjZKrSJu3+t/xlw3R9A==", "devOptional": true, "license": "Apache-2.0", - "peer": true, "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" diff --git a/server/src/service/infrastructure/notificationProviders/email.ts b/server/src/service/infrastructure/notificationProviders/email.ts index 16e48f5c1..32ccf1859 100644 --- a/server/src/service/infrastructure/notificationProviders/email.ts +++ b/server/src/service/infrastructure/notificationProviders/email.ts @@ -19,7 +19,11 @@ export class EmailProvider implements INotificationProvider { }; async sendAlert(notification: Notification, monitor: Monitor, monitorStatusResponse: MonitorStatusResponse): Promise { - const subject = `Monitor ${monitor.name} infrastructure alerts`; + // For grouped notifications (identified by ":" in name), customize subject to indicate multiple services. + // Example: "2 services: Service A, Service B" becomes "Alert: 2 services are down" + const isGroupedNotification = monitor.name.includes(":"); + const subject = isGroupedNotification ? `Alert: ${monitor.name} are down` : `Monitor ${monitor.name} is down`; + let html; if (monitor.type === "hardware") { html = this.buildHardwareEmail(monitor, monitorStatusResponse); diff --git a/server/src/service/infrastructure/notificationsService.ts b/server/src/service/infrastructure/notificationsService.ts index 9b597e74a..06e8e3cd6 100644 --- a/server/src/service/infrastructure/notificationsService.ts +++ b/server/src/service/infrastructure/notificationsService.ts @@ -194,36 +194,37 @@ export class NotificationsService implements INotificationsService { * To avoid changing email templates, we construct a synthetic Monitor * whose name concisely lists all affected services. The existing * `serverIsDownTemplate` is then reused. + * + * @param notification The email notification to send to + * @param monitors Array of monitors that went down + * @param statusResponses Array of status responses (parallel to monitors) + * @returns true if email was sent successfully, false otherwise */ - private flushEmailGroup = async ( - notification: Notification, - monitors: Monitor[], - statusResponses: MonitorStatusResponse[] - ): Promise => { + private flushEmailGroup = async (notification: Notification, monitors: Monitor[], statusResponses: MonitorStatusResponse[]): Promise => { if (!monitors.length || !statusResponses.length) { return false; } - // Build a combined monitor name, e.g.: - // "2 services are down: A, B" + // Build a combined monitor name listing all affected services. + // Example: "Service A, Service B" (2 services) or "Service A" (1 service) const uniqueNames = Array.from(new Set(monitors.map((m) => m.name))); const servicesCount = uniqueNames.length; - const combinedName = - servicesCount === 1 - ? `${uniqueNames[0]}` - : `${servicesCount} services are down: ${uniqueNames.join(", ")}`; + const servicesList = uniqueNames.join(", "); + + const combinedName = servicesCount === 1 ? servicesList : `${servicesCount} services: ${servicesList}`; // Use the first monitor as a base for URL and other fields. const baseMonitor = monitors[0]; const baseStatus = statusResponses[0]; // Create a shallow clone so we don't mutate the original entity. + // This preserves monitor properties while overriding the name for grouped display. const syntheticMonitor: Monitor = { ...baseMonitor, name: combinedName, }; - // Reuse existing email provider to build and send the email. + // Reuse existing email provider to send grouped notification. return await this.emailProvider.sendAlert(notification, syntheticMonitor, baseStatus); };