@oneuptime/common 10.0.36 → 10.0.37

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (46) hide show
  1. package/Models/DatabaseModels/Index.ts +2 -0
  2. package/Models/DatabaseModels/WorkspaceNotificationSummary.ts +819 -0
  3. package/Server/API/StatusPageAPI.ts +7 -0
  4. package/Server/API/WorkspaceNotificationSummaryAPI.ts +67 -0
  5. package/Server/Infrastructure/Postgres/SchemaMigrations/1774355321449-MigrationName.ts +51 -0
  6. package/Server/Infrastructure/Postgres/SchemaMigrations/1774357353502-MigrationName.ts +29 -0
  7. package/Server/Infrastructure/Postgres/SchemaMigrations/Index.ts +4 -0
  8. package/Server/Middleware/MasterAdminAuthorization.ts +55 -0
  9. package/Server/Services/Index.ts +2 -0
  10. package/Server/Services/WorkspaceNotificationSummaryService.ts +1450 -0
  11. package/Server/Utils/Greenlock/Greenlock.ts +1 -0
  12. package/Types/Permission.ts +42 -0
  13. package/Types/Workspace/NotificationSummary/WorkspaceNotificationSummaryItem.ts +13 -0
  14. package/Types/Workspace/NotificationSummary/WorkspaceNotificationSummaryType.ts +8 -0
  15. package/UI/Components/GanttChart/Bar/Index.tsx +23 -5
  16. package/build/dist/Models/DatabaseModels/Index.js +2 -0
  17. package/build/dist/Models/DatabaseModels/Index.js.map +1 -1
  18. package/build/dist/Models/DatabaseModels/WorkspaceNotificationSummary.js +857 -0
  19. package/build/dist/Models/DatabaseModels/WorkspaceNotificationSummary.js.map +1 -0
  20. package/build/dist/Server/API/StatusPageAPI.js +2 -0
  21. package/build/dist/Server/API/StatusPageAPI.js.map +1 -1
  22. package/build/dist/Server/API/WorkspaceNotificationSummaryAPI.js +40 -0
  23. package/build/dist/Server/API/WorkspaceNotificationSummaryAPI.js.map +1 -0
  24. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1774355321449-MigrationName.js +24 -0
  25. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1774355321449-MigrationName.js.map +1 -0
  26. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1774357353502-MigrationName.js +16 -0
  27. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1774357353502-MigrationName.js.map +1 -0
  28. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js +4 -0
  29. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js.map +1 -1
  30. package/build/dist/Server/Middleware/MasterAdminAuthorization.js +25 -0
  31. package/build/dist/Server/Middleware/MasterAdminAuthorization.js.map +1 -0
  32. package/build/dist/Server/Services/Index.js +2 -0
  33. package/build/dist/Server/Services/Index.js.map +1 -1
  34. package/build/dist/Server/Services/WorkspaceNotificationSummaryService.js +1122 -0
  35. package/build/dist/Server/Services/WorkspaceNotificationSummaryService.js.map +1 -0
  36. package/build/dist/Server/Utils/Greenlock/Greenlock.js +1 -0
  37. package/build/dist/Server/Utils/Greenlock/Greenlock.js.map +1 -1
  38. package/build/dist/Types/Permission.js +36 -0
  39. package/build/dist/Types/Permission.js.map +1 -1
  40. package/build/dist/Types/Workspace/NotificationSummary/WorkspaceNotificationSummaryItem.js +14 -0
  41. package/build/dist/Types/Workspace/NotificationSummary/WorkspaceNotificationSummaryItem.js.map +1 -0
  42. package/build/dist/Types/Workspace/NotificationSummary/WorkspaceNotificationSummaryType.js +9 -0
  43. package/build/dist/Types/Workspace/NotificationSummary/WorkspaceNotificationSummaryType.js.map +1 -0
  44. package/build/dist/UI/Components/GanttChart/Bar/Index.js +15 -3
  45. package/build/dist/UI/Components/GanttChart/Bar/Index.js.map +1 -1
  46. package/package.json +1 -1
@@ -0,0 +1,1122 @@
1
+ var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
2
+ var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
3
+ if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
4
+ else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
5
+ return c > 3 && r && Object.defineProperty(target, key, r), r;
6
+ };
7
+ var __metadata = (this && this.__metadata) || function (k, v) {
8
+ if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
9
+ };
10
+ import ObjectID from "../../Types/ObjectID";
11
+ import DatabaseService from "./DatabaseService";
12
+ import WorkspaceNotificationSummary from "../../Models/DatabaseModels/WorkspaceNotificationSummary";
13
+ import WorkspaceNotificationSummaryType from "../../Types/Workspace/NotificationSummary/WorkspaceNotificationSummaryType";
14
+ import WorkspaceNotificationSummaryItem from "../../Types/Workspace/NotificationSummary/WorkspaceNotificationSummaryItem";
15
+ import BadDataException from "../../Types/Exception/BadDataException";
16
+ import IncidentService from "./IncidentService";
17
+ import AlertService from "./AlertService";
18
+ import IncidentEpisodeService from "./IncidentEpisodeService";
19
+ import AlertEpisodeService from "./AlertEpisodeService";
20
+ import IncidentStateTimelineService from "./IncidentStateTimelineService";
21
+ import AlertStateTimelineService from "./AlertStateTimelineService";
22
+ import WorkspaceNotificationLogService from "./WorkspaceNotificationLogService";
23
+ import WorkspaceNotificationStatus from "../../Types/Workspace/WorkspaceNotificationStatus";
24
+ import WorkspaceNotificationActionType from "../../Types/Workspace/WorkspaceNotificationActionType";
25
+ import logger from "../Utils/Logger";
26
+ import OneUptimeDate from "../../Types/Date";
27
+ import QueryHelper from "../Types/Database/QueryHelper";
28
+ import WorkspaceUtil from "../Utils/Workspace/Workspace";
29
+ import CaptureSpan from "../Utils/Telemetry/CaptureSpan";
30
+ import URL from "../../Types/API/URL";
31
+ import DatabaseConfig from "../DatabaseConfig";
32
+ import { NotificationRuleConditionCheckOn, } from "../../Types/Workspace/NotificationRules/NotificationRuleCondition";
33
+ import FilterCondition from "../../Types/Filter/FilterCondition";
34
+ import { WorkspaceNotificationRuleUtil } from "../../Types/Workspace/NotificationRules/NotificationRuleUtil";
35
+ export class Service extends DatabaseService {
36
+ constructor() {
37
+ super(WorkspaceNotificationSummary);
38
+ }
39
+ async testSummary(data) {
40
+ await this.sendSummary({ summaryId: data.summaryId, isTest: true });
41
+ }
42
+ async sendSummary(data) {
43
+ const summary = await this.findOneById({
44
+ id: data.summaryId,
45
+ select: {
46
+ projectId: true,
47
+ name: true,
48
+ workspaceType: true,
49
+ summaryType: true,
50
+ recurringInterval: true,
51
+ numberOfDaysOfData: true,
52
+ channelNames: true,
53
+ teamName: true,
54
+ summaryItems: true,
55
+ filters: true,
56
+ filterCondition: true,
57
+ },
58
+ props: {
59
+ isRoot: true,
60
+ },
61
+ });
62
+ if (!summary) {
63
+ throw new BadDataException("Summary not found");
64
+ }
65
+ if (!summary.projectId) {
66
+ throw new BadDataException("Summary project ID not found");
67
+ }
68
+ if (!summary.channelNames || summary.channelNames.length === 0) {
69
+ throw new BadDataException("No channel names configured for summary");
70
+ }
71
+ if (!summary.summaryItems || summary.summaryItems.length === 0) {
72
+ throw new BadDataException("No summary items selected");
73
+ }
74
+ const messageBlocks = await this.buildSummaryMessageBlocks({ summary });
75
+ const messagePayload = {
76
+ _type: "WorkspaceMessagePayload",
77
+ channelNames: summary.channelNames,
78
+ channelIds: [],
79
+ messageBlocks: messageBlocks,
80
+ workspaceType: summary.workspaceType,
81
+ teamId: summary.teamName || undefined,
82
+ };
83
+ try {
84
+ await WorkspaceUtil.postMessageToAllWorkspaceChannelsAsBot({
85
+ projectId: summary.projectId,
86
+ messagePayloadsByWorkspace: [messagePayload],
87
+ });
88
+ // Log successful send
89
+ for (const channelName of summary.channelNames) {
90
+ await WorkspaceNotificationLogService.createWorkspaceLog({
91
+ projectId: summary.projectId,
92
+ workspaceType: summary.workspaceType,
93
+ channelName: channelName,
94
+ actionType: WorkspaceNotificationActionType.SendMessage,
95
+ status: WorkspaceNotificationStatus.Success,
96
+ statusMessage: data.isTest
97
+ ? "Test summary sent successfully"
98
+ : "Summary sent successfully",
99
+ message: `${summary.summaryType || ""} summary "${summary.name || "Untitled"}" sent to channel "${channelName}"`,
100
+ }, { isRoot: true });
101
+ }
102
+ }
103
+ catch (err) {
104
+ // Log failed send
105
+ for (const channelName of summary.channelNames) {
106
+ await WorkspaceNotificationLogService.createWorkspaceLog({
107
+ projectId: summary.projectId,
108
+ workspaceType: summary.workspaceType,
109
+ channelName: channelName,
110
+ actionType: WorkspaceNotificationActionType.SendMessage,
111
+ status: WorkspaceNotificationStatus.Error,
112
+ statusMessage: err instanceof Error ? err.message : "Unknown error",
113
+ message: `Failed to send ${summary.summaryType || ""} summary "${summary.name || "Untitled"}" to channel "${channelName}"`,
114
+ }, { isRoot: true }).catch((logErr) => {
115
+ logger.error("Failed to create workspace notification log for summary send failure");
116
+ logger.error(logErr);
117
+ });
118
+ }
119
+ throw err; // Re-throw so caller knows it failed
120
+ }
121
+ if (!data.isTest) {
122
+ await this.updateOneById({
123
+ id: data.summaryId,
124
+ data: {
125
+ lastSentAt: OneUptimeDate.getCurrentDate(),
126
+ },
127
+ props: {
128
+ isRoot: true,
129
+ },
130
+ });
131
+ }
132
+ }
133
+ // ───────────────────────── helpers ─────────────────────────
134
+ static divider() {
135
+ return { _type: "WorkspacePayloadDivider" };
136
+ }
137
+ static header(text) {
138
+ return { _type: "WorkspacePayloadHeader", text };
139
+ }
140
+ static md(text) {
141
+ return { _type: "WorkspacePayloadMarkdown", text };
142
+ }
143
+ static bold(text) {
144
+ return `**${text}**`;
145
+ }
146
+ static link(url, text) {
147
+ return `[${text}](${url})`;
148
+ }
149
+ static formatDuration(totalMinutes) {
150
+ if (totalMinutes < 1) {
151
+ return "< 1m";
152
+ }
153
+ const days = Math.floor(totalMinutes / 1440);
154
+ const hours = Math.floor((totalMinutes % 1440) / 60);
155
+ const mins = Math.round(totalMinutes % 60);
156
+ const parts = [];
157
+ if (days > 0) {
158
+ parts.push(`${days}d`);
159
+ }
160
+ if (hours > 0) {
161
+ parts.push(`${hours}h`);
162
+ }
163
+ if (mins > 0 || parts.length === 0) {
164
+ parts.push(`${mins}m`);
165
+ }
166
+ return parts.join(" ");
167
+ }
168
+ static formatDate(date) {
169
+ return OneUptimeDate.getDateAsLocalFormattedString(date, true);
170
+ }
171
+ static has(items, item) {
172
+ return items.includes(item);
173
+ }
174
+ // Check if an item matches the summary's filter conditions
175
+ static matchesFilters(data) {
176
+ if (!data.filters || data.filters.length === 0) {
177
+ return true; // no filters = include everything
178
+ }
179
+ const rule = {
180
+ filters: data.filters,
181
+ filterCondition: data.filterCondition || FilterCondition.Any,
182
+ };
183
+ return WorkspaceNotificationRuleUtil.isRuleMatching({
184
+ notificationRule: rule,
185
+ values: data.values,
186
+ });
187
+ }
188
+ // Build values map for an incident
189
+ static buildIncidentValues(incident) {
190
+ var _a, _b, _c, _d, _e, _f;
191
+ return {
192
+ [NotificationRuleConditionCheckOn.IncidentTitle]: incident.title || "",
193
+ [NotificationRuleConditionCheckOn.IncidentDescription]: incident.description || "",
194
+ [NotificationRuleConditionCheckOn.IncidentSeverity]: ((_b = (_a = incident.incidentSeverity) === null || _a === void 0 ? void 0 : _a._id) === null || _b === void 0 ? void 0 : _b.toString()) || "",
195
+ [NotificationRuleConditionCheckOn.IncidentState]: ((_d = (_c = incident.currentIncidentState) === null || _c === void 0 ? void 0 : _c._id) === null || _d === void 0 ? void 0 : _d.toString()) || "",
196
+ [NotificationRuleConditionCheckOn.IncidentLabels]: ((_e = incident.labels) === null || _e === void 0 ? void 0 : _e.map((l) => {
197
+ var _a;
198
+ return ((_a = l._id) === null || _a === void 0 ? void 0 : _a.toString()) || "";
199
+ })) || [],
200
+ [NotificationRuleConditionCheckOn.Monitors]: ((_f = incident.monitors) === null || _f === void 0 ? void 0 : _f.map((m) => {
201
+ var _a;
202
+ return ((_a = m._id) === null || _a === void 0 ? void 0 : _a.toString()) || "";
203
+ })) || [],
204
+ // unused for incidents
205
+ [NotificationRuleConditionCheckOn.MonitorName]: undefined,
206
+ [NotificationRuleConditionCheckOn.MonitorType]: undefined,
207
+ [NotificationRuleConditionCheckOn.MonitorStatus]: undefined,
208
+ [NotificationRuleConditionCheckOn.AlertTitle]: undefined,
209
+ [NotificationRuleConditionCheckOn.AlertDescription]: undefined,
210
+ [NotificationRuleConditionCheckOn.AlertSeverity]: undefined,
211
+ [NotificationRuleConditionCheckOn.AlertState]: undefined,
212
+ [NotificationRuleConditionCheckOn.AlertLabels]: undefined,
213
+ [NotificationRuleConditionCheckOn.ScheduledMaintenanceTitle]: undefined,
214
+ [NotificationRuleConditionCheckOn.ScheduledMaintenanceDescription]: undefined,
215
+ [NotificationRuleConditionCheckOn.ScheduledMaintenanceState]: undefined,
216
+ [NotificationRuleConditionCheckOn.ScheduledMaintenanceLabels]: undefined,
217
+ [NotificationRuleConditionCheckOn.MonitorLabels]: undefined,
218
+ [NotificationRuleConditionCheckOn.OnCallDutyPolicyName]: undefined,
219
+ [NotificationRuleConditionCheckOn.OnCallDutyPolicyDescription]: undefined,
220
+ [NotificationRuleConditionCheckOn.OnCallDutyPolicyLabels]: undefined,
221
+ [NotificationRuleConditionCheckOn.AlertEpisodeTitle]: undefined,
222
+ [NotificationRuleConditionCheckOn.AlertEpisodeDescription]: undefined,
223
+ [NotificationRuleConditionCheckOn.AlertEpisodeSeverity]: undefined,
224
+ [NotificationRuleConditionCheckOn.AlertEpisodeState]: undefined,
225
+ [NotificationRuleConditionCheckOn.AlertEpisodeLabels]: undefined,
226
+ [NotificationRuleConditionCheckOn.IncidentEpisodeTitle]: undefined,
227
+ [NotificationRuleConditionCheckOn.IncidentEpisodeDescription]: undefined,
228
+ [NotificationRuleConditionCheckOn.IncidentEpisodeSeverity]: undefined,
229
+ [NotificationRuleConditionCheckOn.IncidentEpisodeState]: undefined,
230
+ [NotificationRuleConditionCheckOn.IncidentEpisodeLabels]: undefined,
231
+ };
232
+ }
233
+ // Build values map for an alert
234
+ static buildAlertValues(alert) {
235
+ var _a, _b, _c, _d, _e, _f;
236
+ return {
237
+ [NotificationRuleConditionCheckOn.AlertTitle]: alert.title || "",
238
+ [NotificationRuleConditionCheckOn.AlertDescription]: alert.description || "",
239
+ [NotificationRuleConditionCheckOn.AlertSeverity]: ((_b = (_a = alert.alertSeverity) === null || _a === void 0 ? void 0 : _a._id) === null || _b === void 0 ? void 0 : _b.toString()) || "",
240
+ [NotificationRuleConditionCheckOn.AlertState]: ((_d = (_c = alert.currentAlertState) === null || _c === void 0 ? void 0 : _c._id) === null || _d === void 0 ? void 0 : _d.toString()) || "",
241
+ [NotificationRuleConditionCheckOn.AlertLabels]: ((_e = alert.labels) === null || _e === void 0 ? void 0 : _e.map((l) => {
242
+ var _a;
243
+ return ((_a = l._id) === null || _a === void 0 ? void 0 : _a.toString()) || "";
244
+ })) || [],
245
+ [NotificationRuleConditionCheckOn.Monitors]: ((_f = alert.monitor) === null || _f === void 0 ? void 0 : _f._id)
246
+ ? [alert.monitor._id.toString()]
247
+ : [],
248
+ // unused for alerts
249
+ [NotificationRuleConditionCheckOn.MonitorName]: undefined,
250
+ [NotificationRuleConditionCheckOn.MonitorType]: undefined,
251
+ [NotificationRuleConditionCheckOn.MonitorStatus]: undefined,
252
+ [NotificationRuleConditionCheckOn.IncidentTitle]: undefined,
253
+ [NotificationRuleConditionCheckOn.IncidentDescription]: undefined,
254
+ [NotificationRuleConditionCheckOn.IncidentSeverity]: undefined,
255
+ [NotificationRuleConditionCheckOn.IncidentState]: undefined,
256
+ [NotificationRuleConditionCheckOn.IncidentLabels]: undefined,
257
+ [NotificationRuleConditionCheckOn.ScheduledMaintenanceTitle]: undefined,
258
+ [NotificationRuleConditionCheckOn.ScheduledMaintenanceDescription]: undefined,
259
+ [NotificationRuleConditionCheckOn.ScheduledMaintenanceState]: undefined,
260
+ [NotificationRuleConditionCheckOn.ScheduledMaintenanceLabels]: undefined,
261
+ [NotificationRuleConditionCheckOn.MonitorLabels]: undefined,
262
+ [NotificationRuleConditionCheckOn.OnCallDutyPolicyName]: undefined,
263
+ [NotificationRuleConditionCheckOn.OnCallDutyPolicyDescription]: undefined,
264
+ [NotificationRuleConditionCheckOn.OnCallDutyPolicyLabels]: undefined,
265
+ [NotificationRuleConditionCheckOn.AlertEpisodeTitle]: undefined,
266
+ [NotificationRuleConditionCheckOn.AlertEpisodeDescription]: undefined,
267
+ [NotificationRuleConditionCheckOn.AlertEpisodeSeverity]: undefined,
268
+ [NotificationRuleConditionCheckOn.AlertEpisodeState]: undefined,
269
+ [NotificationRuleConditionCheckOn.AlertEpisodeLabels]: undefined,
270
+ [NotificationRuleConditionCheckOn.IncidentEpisodeTitle]: undefined,
271
+ [NotificationRuleConditionCheckOn.IncidentEpisodeDescription]: undefined,
272
+ [NotificationRuleConditionCheckOn.IncidentEpisodeSeverity]: undefined,
273
+ [NotificationRuleConditionCheckOn.IncidentEpisodeState]: undefined,
274
+ [NotificationRuleConditionCheckOn.IncidentEpisodeLabels]: undefined,
275
+ };
276
+ }
277
+ // Build values map for an incident episode
278
+ static buildIncidentEpisodeValues(episode) {
279
+ var _a, _b, _c, _d, _e;
280
+ return {
281
+ [NotificationRuleConditionCheckOn.IncidentEpisodeTitle]: episode.title || "",
282
+ [NotificationRuleConditionCheckOn.IncidentEpisodeDescription]: episode.description || "",
283
+ [NotificationRuleConditionCheckOn.IncidentEpisodeSeverity]: ((_b = (_a = episode.incidentSeverity) === null || _a === void 0 ? void 0 : _a._id) === null || _b === void 0 ? void 0 : _b.toString()) || "",
284
+ [NotificationRuleConditionCheckOn.IncidentEpisodeState]: ((_d = (_c = episode.currentIncidentState) === null || _c === void 0 ? void 0 : _c._id) === null || _d === void 0 ? void 0 : _d.toString()) || "",
285
+ [NotificationRuleConditionCheckOn.IncidentEpisodeLabels]: ((_e = episode.labels) === null || _e === void 0 ? void 0 : _e.map((l) => {
286
+ var _a;
287
+ return ((_a = l._id) === null || _a === void 0 ? void 0 : _a.toString()) || "";
288
+ })) || [],
289
+ // unused for incident episodes
290
+ [NotificationRuleConditionCheckOn.IncidentTitle]: undefined,
291
+ [NotificationRuleConditionCheckOn.IncidentDescription]: undefined,
292
+ [NotificationRuleConditionCheckOn.IncidentSeverity]: undefined,
293
+ [NotificationRuleConditionCheckOn.IncidentState]: undefined,
294
+ [NotificationRuleConditionCheckOn.IncidentLabels]: undefined,
295
+ [NotificationRuleConditionCheckOn.AlertTitle]: undefined,
296
+ [NotificationRuleConditionCheckOn.AlertDescription]: undefined,
297
+ [NotificationRuleConditionCheckOn.AlertSeverity]: undefined,
298
+ [NotificationRuleConditionCheckOn.AlertState]: undefined,
299
+ [NotificationRuleConditionCheckOn.AlertLabels]: undefined,
300
+ [NotificationRuleConditionCheckOn.AlertEpisodeTitle]: undefined,
301
+ [NotificationRuleConditionCheckOn.AlertEpisodeDescription]: undefined,
302
+ [NotificationRuleConditionCheckOn.AlertEpisodeSeverity]: undefined,
303
+ [NotificationRuleConditionCheckOn.AlertEpisodeState]: undefined,
304
+ [NotificationRuleConditionCheckOn.AlertEpisodeLabels]: undefined,
305
+ [NotificationRuleConditionCheckOn.Monitors]: undefined,
306
+ [NotificationRuleConditionCheckOn.MonitorName]: undefined,
307
+ [NotificationRuleConditionCheckOn.MonitorType]: undefined,
308
+ [NotificationRuleConditionCheckOn.MonitorStatus]: undefined,
309
+ [NotificationRuleConditionCheckOn.MonitorLabels]: undefined,
310
+ [NotificationRuleConditionCheckOn.ScheduledMaintenanceTitle]: undefined,
311
+ [NotificationRuleConditionCheckOn.ScheduledMaintenanceDescription]: undefined,
312
+ [NotificationRuleConditionCheckOn.ScheduledMaintenanceState]: undefined,
313
+ [NotificationRuleConditionCheckOn.ScheduledMaintenanceLabels]: undefined,
314
+ [NotificationRuleConditionCheckOn.OnCallDutyPolicyName]: undefined,
315
+ [NotificationRuleConditionCheckOn.OnCallDutyPolicyDescription]: undefined,
316
+ [NotificationRuleConditionCheckOn.OnCallDutyPolicyLabels]: undefined,
317
+ };
318
+ }
319
+ // Build values map for an alert episode
320
+ static buildAlertEpisodeValues(episode) {
321
+ var _a, _b, _c, _d, _e;
322
+ return {
323
+ [NotificationRuleConditionCheckOn.AlertEpisodeTitle]: episode.title || "",
324
+ [NotificationRuleConditionCheckOn.AlertEpisodeDescription]: episode.description || "",
325
+ [NotificationRuleConditionCheckOn.AlertEpisodeSeverity]: ((_b = (_a = episode.alertSeverity) === null || _a === void 0 ? void 0 : _a._id) === null || _b === void 0 ? void 0 : _b.toString()) || "",
326
+ [NotificationRuleConditionCheckOn.AlertEpisodeState]: ((_d = (_c = episode.currentAlertState) === null || _c === void 0 ? void 0 : _c._id) === null || _d === void 0 ? void 0 : _d.toString()) || "",
327
+ [NotificationRuleConditionCheckOn.AlertEpisodeLabels]: ((_e = episode.labels) === null || _e === void 0 ? void 0 : _e.map((l) => {
328
+ var _a;
329
+ return ((_a = l._id) === null || _a === void 0 ? void 0 : _a.toString()) || "";
330
+ })) || [],
331
+ // unused for alert episodes
332
+ [NotificationRuleConditionCheckOn.IncidentTitle]: undefined,
333
+ [NotificationRuleConditionCheckOn.IncidentDescription]: undefined,
334
+ [NotificationRuleConditionCheckOn.IncidentSeverity]: undefined,
335
+ [NotificationRuleConditionCheckOn.IncidentState]: undefined,
336
+ [NotificationRuleConditionCheckOn.IncidentLabels]: undefined,
337
+ [NotificationRuleConditionCheckOn.IncidentEpisodeTitle]: undefined,
338
+ [NotificationRuleConditionCheckOn.IncidentEpisodeDescription]: undefined,
339
+ [NotificationRuleConditionCheckOn.IncidentEpisodeSeverity]: undefined,
340
+ [NotificationRuleConditionCheckOn.IncidentEpisodeState]: undefined,
341
+ [NotificationRuleConditionCheckOn.IncidentEpisodeLabels]: undefined,
342
+ [NotificationRuleConditionCheckOn.AlertTitle]: undefined,
343
+ [NotificationRuleConditionCheckOn.AlertDescription]: undefined,
344
+ [NotificationRuleConditionCheckOn.AlertSeverity]: undefined,
345
+ [NotificationRuleConditionCheckOn.AlertState]: undefined,
346
+ [NotificationRuleConditionCheckOn.AlertLabels]: undefined,
347
+ [NotificationRuleConditionCheckOn.Monitors]: undefined,
348
+ [NotificationRuleConditionCheckOn.MonitorName]: undefined,
349
+ [NotificationRuleConditionCheckOn.MonitorType]: undefined,
350
+ [NotificationRuleConditionCheckOn.MonitorStatus]: undefined,
351
+ [NotificationRuleConditionCheckOn.MonitorLabels]: undefined,
352
+ [NotificationRuleConditionCheckOn.ScheduledMaintenanceTitle]: undefined,
353
+ [NotificationRuleConditionCheckOn.ScheduledMaintenanceDescription]: undefined,
354
+ [NotificationRuleConditionCheckOn.ScheduledMaintenanceState]: undefined,
355
+ [NotificationRuleConditionCheckOn.ScheduledMaintenanceLabels]: undefined,
356
+ [NotificationRuleConditionCheckOn.OnCallDutyPolicyName]: undefined,
357
+ [NotificationRuleConditionCheckOn.OnCallDutyPolicyDescription]: undefined,
358
+ [NotificationRuleConditionCheckOn.OnCallDutyPolicyLabels]: undefined,
359
+ };
360
+ }
361
+ // ───────────────────────── main builder ─────────────────────────
362
+ async buildSummaryMessageBlocks(data) {
363
+ const { summary } = data;
364
+ const blocks = [];
365
+ const items = summary.summaryItems;
366
+ const days = summary.numberOfDaysOfData || 7;
367
+ const type = summary.summaryType;
368
+ const fromDate = OneUptimeDate.addRemoveDays(OneUptimeDate.getCurrentDate(), -days);
369
+ const fromDateStr = Service.formatDate(fromDate);
370
+ const toDateStr = Service.formatDate(OneUptimeDate.getCurrentDate());
371
+ // Title
372
+ blocks.push(Service.header(`${type} Summary — ${fromDateStr} to ${toDateStr}`));
373
+ blocks.push(Service.md(`_Reporting period: ${Service.bold(String(days))} day${days !== 1 ? "s" : ""}_`));
374
+ blocks.push(Service.divider());
375
+ // Build type-specific content
376
+ if (type === WorkspaceNotificationSummaryType.Incident ||
377
+ type === WorkspaceNotificationSummaryType.IncidentEpisode) {
378
+ await this.buildIncidentBlocks({
379
+ blocks,
380
+ items,
381
+ type,
382
+ fromDate,
383
+ projectId: summary.projectId,
384
+ filters: summary.filters || undefined,
385
+ filterCondition: summary.filterCondition || undefined,
386
+ });
387
+ }
388
+ else {
389
+ await this.buildAlertBlocks({
390
+ blocks,
391
+ items,
392
+ type,
393
+ fromDate,
394
+ projectId: summary.projectId,
395
+ filters: summary.filters || undefined,
396
+ filterCondition: summary.filterCondition || undefined,
397
+ });
398
+ }
399
+ // Footer
400
+ blocks.push(Service.divider());
401
+ blocks.push(Service.md(`_Sent by OneUptime • ${summary.name || "Untitled"}_`));
402
+ return blocks;
403
+ }
404
+ // ───────────────────────── Incidents ─────────────────────────
405
+ async buildIncidentBlocks(data) {
406
+ var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o, _p;
407
+ if (data.type === WorkspaceNotificationSummaryType.IncidentEpisode) {
408
+ await this.buildIncidentEpisodeBlocks(data);
409
+ return;
410
+ }
411
+ const { blocks, items, fromDate, projectId } = data;
412
+ let incidents = await IncidentService.findAllBy({
413
+ query: {
414
+ projectId,
415
+ createdAt: QueryHelper.greaterThanEqualTo(fromDate),
416
+ },
417
+ select: {
418
+ _id: true,
419
+ title: true,
420
+ description: true,
421
+ incidentNumber: true,
422
+ incidentNumberWithPrefix: true,
423
+ incidentSeverity: { name: true, _id: true },
424
+ currentIncidentState: {
425
+ name: true,
426
+ _id: true,
427
+ isResolvedState: true,
428
+ isAcknowledgedState: true,
429
+ },
430
+ labels: { _id: true, name: true },
431
+ monitors: { name: true, _id: true },
432
+ createdAt: true,
433
+ declaredAt: true,
434
+ },
435
+ props: { isRoot: true },
436
+ });
437
+ // Apply filters
438
+ if (data.filters && data.filters.length > 0) {
439
+ incidents = incidents.filter((inc) => {
440
+ return Service.matchesFilters({
441
+ filters: data.filters,
442
+ filterCondition: data.filterCondition,
443
+ values: Service.buildIncidentValues(inc),
444
+ });
445
+ });
446
+ }
447
+ const dashboardUrl = await DatabaseConfig.getDashboardUrl();
448
+ // Overview stats
449
+ if (Service.has(items, WorkspaceNotificationSummaryItem.TotalCount)) {
450
+ const resolved = incidents.filter((i) => {
451
+ var _a;
452
+ return (_a = i.currentIncidentState) === null || _a === void 0 ? void 0 : _a.isResolvedState;
453
+ }).length;
454
+ const open = incidents.length - resolved;
455
+ blocks.push(Service.md(`${Service.bold("Total:")} ${incidents.length} incident${incidents.length !== 1 ? "s" : ""} · ` +
456
+ `${Service.bold("Open:")} ${open} · ${Service.bold("Resolved:")} ${resolved}`));
457
+ }
458
+ // Severity breakdown
459
+ if (Service.has(items, WorkspaceNotificationSummaryItem.SeverityBreakdown)) {
460
+ const map = new Map();
461
+ for (const i of incidents) {
462
+ const s = ((_a = i.incidentSeverity) === null || _a === void 0 ? void 0 : _a.name) || "Unknown";
463
+ map.set(s, (map.get(s) || 0) + 1);
464
+ }
465
+ if (map.size > 0) {
466
+ const parts = [];
467
+ for (const [sev, count] of map) {
468
+ parts.push(`${sev}: ${Service.bold(String(count))}`);
469
+ }
470
+ blocks.push(Service.md(`${Service.bold("By Severity:")} ${parts.join(" · ")}`));
471
+ }
472
+ }
473
+ // State breakdown
474
+ if (Service.has(items, WorkspaceNotificationSummaryItem.StateBreakdown)) {
475
+ const map = new Map();
476
+ for (const i of incidents) {
477
+ const s = ((_b = i.currentIncidentState) === null || _b === void 0 ? void 0 : _b.name) || "Unknown";
478
+ map.set(s, (map.get(s) || 0) + 1);
479
+ }
480
+ if (map.size > 0) {
481
+ const parts = [];
482
+ for (const [state, count] of map) {
483
+ parts.push(`${state}: ${Service.bold(String(count))}`);
484
+ }
485
+ blocks.push(Service.md(`${Service.bold("By State:")} ${parts.join(" · ")}`));
486
+ }
487
+ }
488
+ // Timeline data for MTTA/MTTR/who
489
+ const needTimeline = Service.has(items, WorkspaceNotificationSummaryItem.WhoAcknowledged) ||
490
+ Service.has(items, WorkspaceNotificationSummaryItem.WhoResolved) ||
491
+ Service.has(items, WorkspaceNotificationSummaryItem.TimeToAcknowledge) ||
492
+ Service.has(items, WorkspaceNotificationSummaryItem.TimeToResolve);
493
+ const tlMap = new Map();
494
+ if (needTimeline && incidents.length > 0) {
495
+ const ids = incidents
496
+ .filter((i) => {
497
+ return i._id;
498
+ })
499
+ .map((i) => {
500
+ return new ObjectID(i._id.toString());
501
+ });
502
+ const timelines = await IncidentStateTimelineService.findAllBy({
503
+ query: {
504
+ projectId,
505
+ incidentId: QueryHelper.any(ids),
506
+ },
507
+ select: {
508
+ incidentId: true,
509
+ incidentState: {
510
+ isAcknowledgedState: true,
511
+ isResolvedState: true,
512
+ },
513
+ createdByUser: { name: true, email: true },
514
+ createdAt: true,
515
+ },
516
+ props: { isRoot: true },
517
+ });
518
+ for (const tl of timelines) {
519
+ const id = ((_c = tl.incidentId) === null || _c === void 0 ? void 0 : _c.toString()) || "";
520
+ if (!tlMap.has(id)) {
521
+ tlMap.set(id, {});
522
+ }
523
+ const td = tlMap.get(id);
524
+ const userName = ((_e = (_d = tl.createdByUser) === null || _d === void 0 ? void 0 : _d.name) === null || _e === void 0 ? void 0 : _e.toString()) ||
525
+ ((_g = (_f = tl.createdByUser) === null || _f === void 0 ? void 0 : _f.email) === null || _g === void 0 ? void 0 : _g.toString()) ||
526
+ "System";
527
+ if (((_h = tl.incidentState) === null || _h === void 0 ? void 0 : _h.isAcknowledgedState) && !td.ackAt) {
528
+ td.ackBy = userName;
529
+ td.ackAt = tl.createdAt;
530
+ }
531
+ if (((_j = tl.incidentState) === null || _j === void 0 ? void 0 : _j.isResolvedState) && !td.resolvedAt) {
532
+ td.resolvedBy = userName;
533
+ td.resolvedAt = tl.createdAt;
534
+ }
535
+ }
536
+ for (const inc of incidents) {
537
+ const id = ((_k = inc._id) === null || _k === void 0 ? void 0 : _k.toString()) || "";
538
+ if (!tlMap.has(id)) {
539
+ tlMap.set(id, {});
540
+ }
541
+ tlMap.get(id).declaredAt = inc.declaredAt || inc.createdAt;
542
+ }
543
+ }
544
+ // MTTA
545
+ if (Service.has(items, WorkspaceNotificationSummaryItem.TimeToAcknowledge)) {
546
+ const { avg, count } = this.computeAvg(tlMap, "ack");
547
+ blocks.push(Service.md(count > 0
548
+ ? `${Service.bold("MTTA (Mean Time to Acknowledge):")} ${Service.bold(Service.formatDuration(avg))} _(${count} acknowledged)_`
549
+ : `${Service.bold("MTTA (Mean Time to Acknowledge):")} _No incidents acknowledged_`));
550
+ }
551
+ // MTTR
552
+ if (Service.has(items, WorkspaceNotificationSummaryItem.TimeToResolve)) {
553
+ const { avg, count } = this.computeAvg(tlMap, "resolve");
554
+ blocks.push(Service.md(count > 0
555
+ ? `${Service.bold("MTTR (Mean Time to Resolve):")} ${Service.bold(Service.formatDuration(avg))} _(${count} resolved)_`
556
+ : `${Service.bold("MTTR (Mean Time to Resolve):")} _No incidents resolved_`));
557
+ }
558
+ // Resources affected
559
+ if (Service.has(items, WorkspaceNotificationSummaryItem.ResourcesAffected)) {
560
+ const names = new Set();
561
+ for (const inc of incidents) {
562
+ if (inc.monitors) {
563
+ for (const m of inc.monitors) {
564
+ if (m.name) {
565
+ names.add(m.name);
566
+ }
567
+ }
568
+ }
569
+ }
570
+ if (names.size > 0) {
571
+ blocks.push(Service.md(`${Service.bold(`Resources Affected (${names.size}):`)} ${Array.from(names).join(", ")}`));
572
+ }
573
+ }
574
+ // Detailed list
575
+ if (Service.has(items, WorkspaceNotificationSummaryItem.ListWithLinks)) {
576
+ blocks.push(Service.divider());
577
+ if (incidents.length === 0) {
578
+ blocks.push(Service.md(`_No incidents reported in this period._`));
579
+ return;
580
+ }
581
+ for (const inc of incidents) {
582
+ const id = ((_l = inc._id) === null || _l === void 0 ? void 0 : _l.toString()) || "";
583
+ const display = inc.incidentNumberWithPrefix || `#${inc.incidentNumber || ""}`;
584
+ const linkUrl = URL.fromString(dashboardUrl.toString())
585
+ .addRoute(`/${projectId.toString()}/incidents/${id}`)
586
+ .toString();
587
+ const td = tlMap.get(id);
588
+ // Title line with link
589
+ let text = `${Service.bold(Service.link(linkUrl, `${display} — ${inc.title || "Untitled"}`))}`;
590
+ // Meta line
591
+ const meta = [];
592
+ if ((_m = inc.incidentSeverity) === null || _m === void 0 ? void 0 : _m.name) {
593
+ meta.push(`Severity: ${Service.bold(inc.incidentSeverity.name)}`);
594
+ }
595
+ if ((_o = inc.currentIncidentState) === null || _o === void 0 ? void 0 : _o.name) {
596
+ meta.push(`State: ${Service.bold(inc.currentIncidentState.name)}`);
597
+ }
598
+ if (inc.declaredAt) {
599
+ meta.push(`Declared: ${Service.formatDate(inc.declaredAt)}`);
600
+ }
601
+ if (meta.length > 0) {
602
+ text += `\n${meta.join(" · ")}`;
603
+ }
604
+ // Ack & resolve line
605
+ const ackResolve = [];
606
+ if (Service.has(items, WorkspaceNotificationSummaryItem.WhoAcknowledged)) {
607
+ if ((td === null || td === void 0 ? void 0 : td.ackBy) && (td === null || td === void 0 ? void 0 : td.ackAt)) {
608
+ ackResolve.push(`Ack: ${Service.bold(td.ackBy)} in ${Service.formatDuration(OneUptimeDate.getMinutesBetweenTwoDates(td.declaredAt || inc.createdAt, td.ackAt))}`);
609
+ }
610
+ else {
611
+ ackResolve.push(`_Not yet acknowledged_`);
612
+ }
613
+ }
614
+ if (Service.has(items, WorkspaceNotificationSummaryItem.WhoResolved)) {
615
+ if ((td === null || td === void 0 ? void 0 : td.resolvedBy) && (td === null || td === void 0 ? void 0 : td.resolvedAt)) {
616
+ ackResolve.push(`Resolved: ${Service.bold(td.resolvedBy)} in ${Service.formatDuration(OneUptimeDate.getMinutesBetweenTwoDates(td.declaredAt || inc.createdAt, td.resolvedAt))}`);
617
+ }
618
+ else if (!((_p = inc.currentIncidentState) === null || _p === void 0 ? void 0 : _p.isResolvedState)) {
619
+ ackResolve.push(`_Not yet resolved_`);
620
+ }
621
+ }
622
+ if (ackResolve.length > 0) {
623
+ text += `\n${ackResolve.join(" · ")}`;
624
+ }
625
+ blocks.push(Service.md(text));
626
+ }
627
+ }
628
+ }
629
+ // ───────────────────────── Incident Episodes ─────────────────────────
630
+ async buildIncidentEpisodeBlocks(data) {
631
+ var _a, _b, _c, _d, _e;
632
+ const { blocks, items, fromDate, projectId } = data;
633
+ let episodes = await IncidentEpisodeService.findAllBy({
634
+ query: {
635
+ projectId,
636
+ createdAt: QueryHelper.greaterThanEqualTo(fromDate),
637
+ },
638
+ select: {
639
+ _id: true,
640
+ title: true,
641
+ description: true,
642
+ incidentSeverity: { name: true, _id: true },
643
+ currentIncidentState: {
644
+ name: true,
645
+ _id: true,
646
+ isResolvedState: true,
647
+ },
648
+ labels: { _id: true, name: true },
649
+ createdAt: true,
650
+ resolvedAt: true,
651
+ },
652
+ props: { isRoot: true },
653
+ });
654
+ // Apply filters
655
+ if (data.filters && data.filters.length > 0) {
656
+ episodes = episodes.filter((ep) => {
657
+ return Service.matchesFilters({
658
+ filters: data.filters,
659
+ filterCondition: data.filterCondition,
660
+ values: Service.buildIncidentEpisodeValues(ep),
661
+ });
662
+ });
663
+ }
664
+ const dashboardUrl = await DatabaseConfig.getDashboardUrl();
665
+ if (Service.has(items, WorkspaceNotificationSummaryItem.TotalCount)) {
666
+ const resolved = episodes.filter((e) => {
667
+ var _a;
668
+ return (_a = e.currentIncidentState) === null || _a === void 0 ? void 0 : _a.isResolvedState;
669
+ }).length;
670
+ blocks.push(Service.md(`${Service.bold("Total:")} ${episodes.length} episode${episodes.length !== 1 ? "s" : ""} · ` +
671
+ `${Service.bold("Open:")} ${episodes.length - resolved} · ${Service.bold("Resolved:")} ${resolved}`));
672
+ }
673
+ if (Service.has(items, WorkspaceNotificationSummaryItem.SeverityBreakdown)) {
674
+ const map = new Map();
675
+ for (const e of episodes) {
676
+ const s = ((_a = e.incidentSeverity) === null || _a === void 0 ? void 0 : _a.name) || "Unknown";
677
+ map.set(s, (map.get(s) || 0) + 1);
678
+ }
679
+ if (map.size > 0) {
680
+ const parts = [];
681
+ for (const [sev, c] of map) {
682
+ parts.push(`${sev}: ${Service.bold(String(c))}`);
683
+ }
684
+ blocks.push(Service.md(`${Service.bold("By Severity:")} ${parts.join(" · ")}`));
685
+ }
686
+ }
687
+ if (Service.has(items, WorkspaceNotificationSummaryItem.StateBreakdown)) {
688
+ const map = new Map();
689
+ for (const e of episodes) {
690
+ const s = ((_b = e.currentIncidentState) === null || _b === void 0 ? void 0 : _b.name) || "Unknown";
691
+ map.set(s, (map.get(s) || 0) + 1);
692
+ }
693
+ if (map.size > 0) {
694
+ const parts = [];
695
+ for (const [state, c] of map) {
696
+ parts.push(`${state}: ${Service.bold(String(c))}`);
697
+ }
698
+ blocks.push(Service.md(`${Service.bold("By State:")} ${parts.join(" · ")}`));
699
+ }
700
+ }
701
+ if (Service.has(items, WorkspaceNotificationSummaryItem.TimeToResolve)) {
702
+ let total = 0;
703
+ let count = 0;
704
+ for (const e of episodes) {
705
+ if (e.resolvedAt && e.createdAt) {
706
+ total += OneUptimeDate.getMinutesBetweenTwoDates(e.createdAt, e.resolvedAt);
707
+ count++;
708
+ }
709
+ }
710
+ blocks.push(Service.md(count > 0
711
+ ? `${Service.bold("MTTR (Mean Time to Resolve):")} ${Service.bold(Service.formatDuration(Math.round(total / count)))} _(${count} resolved)_`
712
+ : `${Service.bold("MTTR (Mean Time to Resolve):")} _No episodes resolved_`));
713
+ }
714
+ if (Service.has(items, WorkspaceNotificationSummaryItem.ListWithLinks)) {
715
+ blocks.push(Service.divider());
716
+ if (episodes.length === 0) {
717
+ blocks.push(Service.md(`_No incident episodes in this period._`));
718
+ return;
719
+ }
720
+ for (const ep of episodes) {
721
+ const id = ((_c = ep._id) === null || _c === void 0 ? void 0 : _c.toString()) || "";
722
+ const linkUrl = URL.fromString(dashboardUrl.toString())
723
+ .addRoute(`/${projectId.toString()}/incidents/episodes/${id}`)
724
+ .toString();
725
+ let text = `${Service.bold(Service.link(linkUrl, ep.title || "Untitled Episode"))}`;
726
+ const meta = [];
727
+ if ((_d = ep.incidentSeverity) === null || _d === void 0 ? void 0 : _d.name) {
728
+ meta.push(`Severity: ${Service.bold(ep.incidentSeverity.name)}`);
729
+ }
730
+ if ((_e = ep.currentIncidentState) === null || _e === void 0 ? void 0 : _e.name) {
731
+ meta.push(`State: ${Service.bold(ep.currentIncidentState.name)}`);
732
+ }
733
+ if (ep.createdAt) {
734
+ meta.push(`Created: ${Service.formatDate(ep.createdAt)}`);
735
+ }
736
+ if (ep.resolvedAt && ep.createdAt) {
737
+ meta.push(`Resolved in ${Service.bold(Service.formatDuration(OneUptimeDate.getMinutesBetweenTwoDates(ep.createdAt, ep.resolvedAt)))}`);
738
+ }
739
+ if (meta.length > 0) {
740
+ text += `\n${meta.join(" · ")}`;
741
+ }
742
+ blocks.push(Service.md(text));
743
+ }
744
+ }
745
+ }
746
+ // ───────────────────────── Alerts ─────────────────────────
747
+ async buildAlertBlocks(data) {
748
+ var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o, _p, _q;
749
+ if (data.type === WorkspaceNotificationSummaryType.AlertEpisode) {
750
+ await this.buildAlertEpisodeBlocks(data);
751
+ return;
752
+ }
753
+ const { blocks, items, fromDate, projectId } = data;
754
+ let alerts = await AlertService.findAllBy({
755
+ query: {
756
+ projectId,
757
+ createdAt: QueryHelper.greaterThanEqualTo(fromDate),
758
+ },
759
+ select: {
760
+ _id: true,
761
+ title: true,
762
+ description: true,
763
+ alertNumber: true,
764
+ alertNumberWithPrefix: true,
765
+ alertSeverity: { name: true, _id: true },
766
+ currentAlertState: {
767
+ name: true,
768
+ _id: true,
769
+ isResolvedState: true,
770
+ isAcknowledgedState: true,
771
+ },
772
+ labels: { _id: true, name: true },
773
+ monitor: { name: true, _id: true },
774
+ createdAt: true,
775
+ },
776
+ props: { isRoot: true },
777
+ });
778
+ // Apply filters
779
+ if (data.filters && data.filters.length > 0) {
780
+ alerts = alerts.filter((alert) => {
781
+ return Service.matchesFilters({
782
+ filters: data.filters,
783
+ filterCondition: data.filterCondition,
784
+ values: Service.buildAlertValues(alert),
785
+ });
786
+ });
787
+ }
788
+ const dashboardUrl = await DatabaseConfig.getDashboardUrl();
789
+ if (Service.has(items, WorkspaceNotificationSummaryItem.TotalCount)) {
790
+ const resolved = alerts.filter((a) => {
791
+ var _a;
792
+ return (_a = a.currentAlertState) === null || _a === void 0 ? void 0 : _a.isResolvedState;
793
+ }).length;
794
+ blocks.push(Service.md(`${Service.bold("Total:")} ${alerts.length} alert${alerts.length !== 1 ? "s" : ""} · ` +
795
+ `${Service.bold("Open:")} ${alerts.length - resolved} · ${Service.bold("Resolved:")} ${resolved}`));
796
+ }
797
+ if (Service.has(items, WorkspaceNotificationSummaryItem.SeverityBreakdown)) {
798
+ const map = new Map();
799
+ for (const a of alerts) {
800
+ const s = ((_a = a.alertSeverity) === null || _a === void 0 ? void 0 : _a.name) || "Unknown";
801
+ map.set(s, (map.get(s) || 0) + 1);
802
+ }
803
+ if (map.size > 0) {
804
+ const parts = [];
805
+ for (const [sev, c] of map) {
806
+ parts.push(`${sev}: ${Service.bold(String(c))}`);
807
+ }
808
+ blocks.push(Service.md(`${Service.bold("By Severity:")} ${parts.join(" · ")}`));
809
+ }
810
+ }
811
+ if (Service.has(items, WorkspaceNotificationSummaryItem.StateBreakdown)) {
812
+ const map = new Map();
813
+ for (const a of alerts) {
814
+ const s = ((_b = a.currentAlertState) === null || _b === void 0 ? void 0 : _b.name) || "Unknown";
815
+ map.set(s, (map.get(s) || 0) + 1);
816
+ }
817
+ if (map.size > 0) {
818
+ const parts = [];
819
+ for (const [state, c] of map) {
820
+ parts.push(`${state}: ${Service.bold(String(c))}`);
821
+ }
822
+ blocks.push(Service.md(`${Service.bold("By State:")} ${parts.join(" · ")}`));
823
+ }
824
+ }
825
+ // Timeline data
826
+ const needTimeline = Service.has(items, WorkspaceNotificationSummaryItem.WhoAcknowledged) ||
827
+ Service.has(items, WorkspaceNotificationSummaryItem.WhoResolved) ||
828
+ Service.has(items, WorkspaceNotificationSummaryItem.TimeToAcknowledge) ||
829
+ Service.has(items, WorkspaceNotificationSummaryItem.TimeToResolve);
830
+ const tlMap = new Map();
831
+ if (needTimeline && alerts.length > 0) {
832
+ const ids = alerts
833
+ .filter((a) => {
834
+ return a._id;
835
+ })
836
+ .map((a) => {
837
+ return new ObjectID(a._id.toString());
838
+ });
839
+ const timelines = await AlertStateTimelineService.findAllBy({
840
+ query: { projectId, alertId: QueryHelper.any(ids) },
841
+ select: {
842
+ alertId: true,
843
+ alertState: {
844
+ isAcknowledgedState: true,
845
+ isResolvedState: true,
846
+ },
847
+ createdByUser: { name: true, email: true },
848
+ createdAt: true,
849
+ },
850
+ props: { isRoot: true },
851
+ });
852
+ for (const tl of timelines) {
853
+ const id = ((_c = tl.alertId) === null || _c === void 0 ? void 0 : _c.toString()) || "";
854
+ if (!tlMap.has(id)) {
855
+ tlMap.set(id, {});
856
+ }
857
+ const td = tlMap.get(id);
858
+ const userName = ((_e = (_d = tl.createdByUser) === null || _d === void 0 ? void 0 : _d.name) === null || _e === void 0 ? void 0 : _e.toString()) ||
859
+ ((_g = (_f = tl.createdByUser) === null || _f === void 0 ? void 0 : _f.email) === null || _g === void 0 ? void 0 : _g.toString()) ||
860
+ "System";
861
+ if (((_h = tl.alertState) === null || _h === void 0 ? void 0 : _h.isAcknowledgedState) && !td.ackAt) {
862
+ td.ackBy = userName;
863
+ td.ackAt = tl.createdAt;
864
+ }
865
+ if (((_j = tl.alertState) === null || _j === void 0 ? void 0 : _j.isResolvedState) && !td.resolvedAt) {
866
+ td.resolvedBy = userName;
867
+ td.resolvedAt = tl.createdAt;
868
+ }
869
+ }
870
+ for (const a of alerts) {
871
+ const id = ((_k = a._id) === null || _k === void 0 ? void 0 : _k.toString()) || "";
872
+ if (!tlMap.has(id)) {
873
+ tlMap.set(id, {});
874
+ }
875
+ tlMap.get(id).declaredAt = a.createdAt;
876
+ }
877
+ }
878
+ if (Service.has(items, WorkspaceNotificationSummaryItem.TimeToAcknowledge)) {
879
+ const { avg, count } = this.computeAvg(tlMap, "ack");
880
+ blocks.push(Service.md(count > 0
881
+ ? `${Service.bold("MTTA (Mean Time to Acknowledge):")} ${Service.bold(Service.formatDuration(avg))} _(${count} acknowledged)_`
882
+ : `${Service.bold("MTTA (Mean Time to Acknowledge):")} _No alerts acknowledged_`));
883
+ }
884
+ if (Service.has(items, WorkspaceNotificationSummaryItem.TimeToResolve)) {
885
+ const { avg, count } = this.computeAvg(tlMap, "resolve");
886
+ blocks.push(Service.md(count > 0
887
+ ? `${Service.bold("MTTR (Mean Time to Resolve):")} ${Service.bold(Service.formatDuration(avg))} _(${count} resolved)_`
888
+ : `${Service.bold("MTTR (Mean Time to Resolve):")} _No alerts resolved_`));
889
+ }
890
+ if (Service.has(items, WorkspaceNotificationSummaryItem.ResourcesAffected)) {
891
+ const names = new Set();
892
+ for (const a of alerts) {
893
+ if ((_l = a.monitor) === null || _l === void 0 ? void 0 : _l.name) {
894
+ names.add(a.monitor.name);
895
+ }
896
+ }
897
+ if (names.size > 0) {
898
+ blocks.push(Service.md(`${Service.bold(`Resources Affected (${names.size}):`)} ${Array.from(names).join(", ")}`));
899
+ }
900
+ }
901
+ if (Service.has(items, WorkspaceNotificationSummaryItem.ListWithLinks)) {
902
+ blocks.push(Service.divider());
903
+ if (alerts.length === 0) {
904
+ blocks.push(Service.md(`_No alerts reported in this period._`));
905
+ return;
906
+ }
907
+ for (const a of alerts) {
908
+ const id = ((_m = a._id) === null || _m === void 0 ? void 0 : _m.toString()) || "";
909
+ const display = a.alertNumberWithPrefix || `#${a.alertNumber || ""}`;
910
+ const linkUrl = URL.fromString(dashboardUrl.toString())
911
+ .addRoute(`/${projectId.toString()}/alerts/${id}`)
912
+ .toString();
913
+ const td = tlMap.get(id);
914
+ let text = `${Service.bold(Service.link(linkUrl, `${display} — ${a.title || "Untitled"}`))}`;
915
+ const meta = [];
916
+ if ((_o = a.alertSeverity) === null || _o === void 0 ? void 0 : _o.name) {
917
+ meta.push(`Severity: ${Service.bold(a.alertSeverity.name)}`);
918
+ }
919
+ if ((_p = a.currentAlertState) === null || _p === void 0 ? void 0 : _p.name) {
920
+ meta.push(`State: ${Service.bold(a.currentAlertState.name)}`);
921
+ }
922
+ if (a.createdAt) {
923
+ meta.push(`Created: ${Service.formatDate(a.createdAt)}`);
924
+ }
925
+ if (meta.length > 0) {
926
+ text += `\n${meta.join(" · ")}`;
927
+ }
928
+ const ackResolve = [];
929
+ if (Service.has(items, WorkspaceNotificationSummaryItem.WhoAcknowledged)) {
930
+ if ((td === null || td === void 0 ? void 0 : td.ackBy) && (td === null || td === void 0 ? void 0 : td.ackAt)) {
931
+ ackResolve.push(`Ack: ${Service.bold(td.ackBy)} in ${Service.formatDuration(OneUptimeDate.getMinutesBetweenTwoDates(td.declaredAt || a.createdAt, td.ackAt))}`);
932
+ }
933
+ else {
934
+ ackResolve.push(`_Not yet acknowledged_`);
935
+ }
936
+ }
937
+ if (Service.has(items, WorkspaceNotificationSummaryItem.WhoResolved)) {
938
+ if ((td === null || td === void 0 ? void 0 : td.resolvedBy) && (td === null || td === void 0 ? void 0 : td.resolvedAt)) {
939
+ ackResolve.push(`Resolved: ${Service.bold(td.resolvedBy)} in ${Service.formatDuration(OneUptimeDate.getMinutesBetweenTwoDates(td.declaredAt || a.createdAt, td.resolvedAt))}`);
940
+ }
941
+ else if (!((_q = a.currentAlertState) === null || _q === void 0 ? void 0 : _q.isResolvedState)) {
942
+ ackResolve.push(`_Not yet resolved_`);
943
+ }
944
+ }
945
+ if (ackResolve.length > 0) {
946
+ text += `\n${ackResolve.join(" · ")}`;
947
+ }
948
+ blocks.push(Service.md(text));
949
+ }
950
+ }
951
+ }
952
+ // ───────────────────────── Alert Episodes ─────────────────────────
953
+ async buildAlertEpisodeBlocks(data) {
954
+ var _a, _b, _c, _d, _e;
955
+ const { blocks, items, fromDate, projectId } = data;
956
+ let episodes = await AlertEpisodeService.findAllBy({
957
+ query: {
958
+ projectId,
959
+ createdAt: QueryHelper.greaterThanEqualTo(fromDate),
960
+ },
961
+ select: {
962
+ _id: true,
963
+ title: true,
964
+ description: true,
965
+ alertSeverity: { name: true, _id: true },
966
+ currentAlertState: { name: true, _id: true, isResolvedState: true },
967
+ labels: { _id: true, name: true },
968
+ createdAt: true,
969
+ resolvedAt: true,
970
+ },
971
+ props: { isRoot: true },
972
+ });
973
+ // Apply filters
974
+ if (data.filters && data.filters.length > 0) {
975
+ episodes = episodes.filter((ep) => {
976
+ return Service.matchesFilters({
977
+ filters: data.filters,
978
+ filterCondition: data.filterCondition,
979
+ values: Service.buildAlertEpisodeValues(ep),
980
+ });
981
+ });
982
+ }
983
+ const dashboardUrl = await DatabaseConfig.getDashboardUrl();
984
+ if (Service.has(items, WorkspaceNotificationSummaryItem.TotalCount)) {
985
+ const resolved = episodes.filter((e) => {
986
+ var _a;
987
+ return (_a = e.currentAlertState) === null || _a === void 0 ? void 0 : _a.isResolvedState;
988
+ }).length;
989
+ blocks.push(Service.md(`${Service.bold("Total:")} ${episodes.length} episode${episodes.length !== 1 ? "s" : ""} · ` +
990
+ `${Service.bold("Open:")} ${episodes.length - resolved} · ${Service.bold("Resolved:")} ${resolved}`));
991
+ }
992
+ if (Service.has(items, WorkspaceNotificationSummaryItem.SeverityBreakdown)) {
993
+ const map = new Map();
994
+ for (const e of episodes) {
995
+ const s = ((_a = e.alertSeverity) === null || _a === void 0 ? void 0 : _a.name) || "Unknown";
996
+ map.set(s, (map.get(s) || 0) + 1);
997
+ }
998
+ if (map.size > 0) {
999
+ const parts = [];
1000
+ for (const [sev, c] of map) {
1001
+ parts.push(`${sev}: ${Service.bold(String(c))}`);
1002
+ }
1003
+ blocks.push(Service.md(`${Service.bold("By Severity:")} ${parts.join(" · ")}`));
1004
+ }
1005
+ }
1006
+ if (Service.has(items, WorkspaceNotificationSummaryItem.StateBreakdown)) {
1007
+ const map = new Map();
1008
+ for (const e of episodes) {
1009
+ const s = ((_b = e.currentAlertState) === null || _b === void 0 ? void 0 : _b.name) || "Unknown";
1010
+ map.set(s, (map.get(s) || 0) + 1);
1011
+ }
1012
+ if (map.size > 0) {
1013
+ const parts = [];
1014
+ for (const [state, c] of map) {
1015
+ parts.push(`${state}: ${Service.bold(String(c))}`);
1016
+ }
1017
+ blocks.push(Service.md(`${Service.bold("By State:")} ${parts.join(" · ")}`));
1018
+ }
1019
+ }
1020
+ if (Service.has(items, WorkspaceNotificationSummaryItem.TimeToResolve)) {
1021
+ let total = 0;
1022
+ let count = 0;
1023
+ for (const e of episodes) {
1024
+ if (e.resolvedAt && e.createdAt) {
1025
+ total += OneUptimeDate.getMinutesBetweenTwoDates(e.createdAt, e.resolvedAt);
1026
+ count++;
1027
+ }
1028
+ }
1029
+ blocks.push(Service.md(count > 0
1030
+ ? `${Service.bold("MTTR (Mean Time to Resolve):")} ${Service.bold(Service.formatDuration(Math.round(total / count)))} _(${count} resolved)_`
1031
+ : `${Service.bold("MTTR (Mean Time to Resolve):")} _No episodes resolved_`));
1032
+ }
1033
+ if (Service.has(items, WorkspaceNotificationSummaryItem.ListWithLinks)) {
1034
+ blocks.push(Service.divider());
1035
+ if (episodes.length === 0) {
1036
+ blocks.push(Service.md(`_No alert episodes in this period._`));
1037
+ return;
1038
+ }
1039
+ for (const ep of episodes) {
1040
+ const id = ((_c = ep._id) === null || _c === void 0 ? void 0 : _c.toString()) || "";
1041
+ const linkUrl = URL.fromString(dashboardUrl.toString())
1042
+ .addRoute(`/${projectId.toString()}/alerts/episodes/${id}`)
1043
+ .toString();
1044
+ let text = `${Service.bold(Service.link(linkUrl, ep.title || "Untitled Episode"))}`;
1045
+ const meta = [];
1046
+ if ((_d = ep.alertSeverity) === null || _d === void 0 ? void 0 : _d.name) {
1047
+ meta.push(`Severity: ${Service.bold(ep.alertSeverity.name)}`);
1048
+ }
1049
+ if ((_e = ep.currentAlertState) === null || _e === void 0 ? void 0 : _e.name) {
1050
+ meta.push(`State: ${Service.bold(ep.currentAlertState.name)}`);
1051
+ }
1052
+ if (ep.createdAt) {
1053
+ meta.push(`Created: ${Service.formatDate(ep.createdAt)}`);
1054
+ }
1055
+ if (ep.resolvedAt && ep.createdAt) {
1056
+ meta.push(`Resolved in ${Service.bold(Service.formatDuration(OneUptimeDate.getMinutesBetweenTwoDates(ep.createdAt, ep.resolvedAt)))}`);
1057
+ }
1058
+ if (meta.length > 0) {
1059
+ text += `\n${meta.join(" · ")}`;
1060
+ }
1061
+ blocks.push(Service.md(text));
1062
+ }
1063
+ }
1064
+ }
1065
+ // ───────────────────────── Utilities ─────────────────────────
1066
+ computeAvg(tlMap, kind) {
1067
+ let total = 0;
1068
+ let count = 0;
1069
+ for (const [, td] of tlMap) {
1070
+ const eventTime = kind === "ack" ? td.ackAt : td.resolvedAt;
1071
+ if (eventTime && td.declaredAt) {
1072
+ total += OneUptimeDate.getMinutesBetweenTwoDates(td.declaredAt, eventTime);
1073
+ count++;
1074
+ }
1075
+ }
1076
+ return { avg: count > 0 ? Math.round(total / count) : 0, count };
1077
+ }
1078
+ }
1079
+ __decorate([
1080
+ CaptureSpan(),
1081
+ __metadata("design:type", Function),
1082
+ __metadata("design:paramtypes", [Object]),
1083
+ __metadata("design:returntype", Promise)
1084
+ ], Service.prototype, "testSummary", null);
1085
+ __decorate([
1086
+ CaptureSpan(),
1087
+ __metadata("design:type", Function),
1088
+ __metadata("design:paramtypes", [Object]),
1089
+ __metadata("design:returntype", Promise)
1090
+ ], Service.prototype, "sendSummary", null);
1091
+ __decorate([
1092
+ CaptureSpan(),
1093
+ __metadata("design:type", Function),
1094
+ __metadata("design:paramtypes", [Object]),
1095
+ __metadata("design:returntype", Promise)
1096
+ ], Service.prototype, "buildSummaryMessageBlocks", null);
1097
+ __decorate([
1098
+ CaptureSpan(),
1099
+ __metadata("design:type", Function),
1100
+ __metadata("design:paramtypes", [Object]),
1101
+ __metadata("design:returntype", Promise)
1102
+ ], Service.prototype, "buildIncidentBlocks", null);
1103
+ __decorate([
1104
+ CaptureSpan(),
1105
+ __metadata("design:type", Function),
1106
+ __metadata("design:paramtypes", [Object]),
1107
+ __metadata("design:returntype", Promise)
1108
+ ], Service.prototype, "buildIncidentEpisodeBlocks", null);
1109
+ __decorate([
1110
+ CaptureSpan(),
1111
+ __metadata("design:type", Function),
1112
+ __metadata("design:paramtypes", [Object]),
1113
+ __metadata("design:returntype", Promise)
1114
+ ], Service.prototype, "buildAlertBlocks", null);
1115
+ __decorate([
1116
+ CaptureSpan(),
1117
+ __metadata("design:type", Function),
1118
+ __metadata("design:paramtypes", [Object]),
1119
+ __metadata("design:returntype", Promise)
1120
+ ], Service.prototype, "buildAlertEpisodeBlocks", null);
1121
+ export default new Service();
1122
+ //# sourceMappingURL=WorkspaceNotificationSummaryService.js.map