@oneuptime/common 9.2.9 → 9.2.10

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 (51) hide show
  1. package/Models/DatabaseModels/Index.ts +4 -0
  2. package/Models/DatabaseModels/StatusPageSubscriberNotificationTemplate.ts +461 -0
  3. package/Models/DatabaseModels/StatusPageSubscriberNotificationTemplateStatusPage.ts +400 -0
  4. package/Server/Infrastructure/Postgres/SchemaMigrations/1765195603978-MigrationName.ts +107 -0
  5. package/Server/Infrastructure/Postgres/SchemaMigrations/Index.ts +2 -0
  6. package/Server/Services/Index.ts +4 -0
  7. package/Server/Services/ScheduledMaintenanceService.ts +184 -63
  8. package/Server/Services/StatusPageSubscriberNotificationTemplateService.ts +272 -0
  9. package/Server/Services/StatusPageSubscriberNotificationTemplateStatusPageService.ts +10 -0
  10. package/Tests/UI/Components/Loader.test.tsx +2 -2
  11. package/Types/Email/EmailTemplateType.ts +1 -0
  12. package/Types/Permission.ts +82 -0
  13. package/Types/StatusPage/StatusPageSubscriberNotificationEventType.ts +22 -0
  14. package/Types/StatusPage/StatusPageSubscriberNotificationMethod.ts +14 -0
  15. package/UI/Components/Forms/BasicForm.tsx +2 -0
  16. package/UI/Components/Forms/Types/Field.ts +1 -0
  17. package/UI/Components/Loader/Loader.tsx +6 -6
  18. package/build/dist/Models/DatabaseModels/Index.js +4 -0
  19. package/build/dist/Models/DatabaseModels/Index.js.map +1 -1
  20. package/build/dist/Models/DatabaseModels/StatusPageSubscriberNotificationTemplate.js +482 -0
  21. package/build/dist/Models/DatabaseModels/StatusPageSubscriberNotificationTemplate.js.map +1 -0
  22. package/build/dist/Models/DatabaseModels/StatusPageSubscriberNotificationTemplateStatusPage.js +413 -0
  23. package/build/dist/Models/DatabaseModels/StatusPageSubscriberNotificationTemplateStatusPage.js.map +1 -0
  24. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1765195603978-MigrationName.js +42 -0
  25. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1765195603978-MigrationName.js.map +1 -0
  26. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js +2 -0
  27. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js.map +1 -1
  28. package/build/dist/Server/Services/Index.js +4 -0
  29. package/build/dist/Server/Services/Index.js.map +1 -1
  30. package/build/dist/Server/Services/ScheduledMaintenanceService.js +125 -50
  31. package/build/dist/Server/Services/ScheduledMaintenanceService.js.map +1 -1
  32. package/build/dist/Server/Services/StatusPageSubscriberNotificationTemplateService.js +236 -0
  33. package/build/dist/Server/Services/StatusPageSubscriberNotificationTemplateService.js.map +1 -0
  34. package/build/dist/Server/Services/StatusPageSubscriberNotificationTemplateStatusPageService.js +9 -0
  35. package/build/dist/Server/Services/StatusPageSubscriberNotificationTemplateStatusPageService.js.map +1 -0
  36. package/build/dist/Tests/UI/Components/Loader.test.js +2 -2
  37. package/build/dist/Tests/UI/Components/Loader.test.js.map +1 -1
  38. package/build/dist/Types/Email/EmailTemplateType.js +1 -0
  39. package/build/dist/Types/Email/EmailTemplateType.js.map +1 -1
  40. package/build/dist/Types/Permission.js +66 -0
  41. package/build/dist/Types/Permission.js.map +1 -1
  42. package/build/dist/Types/StatusPage/StatusPageSubscriberNotificationEventType.js +20 -0
  43. package/build/dist/Types/StatusPage/StatusPageSubscriberNotificationEventType.js.map +1 -0
  44. package/build/dist/Types/StatusPage/StatusPageSubscriberNotificationMethod.js +14 -0
  45. package/build/dist/Types/StatusPage/StatusPageSubscriberNotificationMethod.js.map +1 -0
  46. package/build/dist/UI/Components/Forms/BasicForm.js +3 -1
  47. package/build/dist/UI/Components/Forms/BasicForm.js.map +1 -1
  48. package/build/dist/UI/Components/Loader/Loader.js +2 -2
  49. package/build/dist/UI/Components/Loader/Loader.js.map +1 -1
  50. package/jest.config.json +1 -1
  51. package/package.json +1 -1
@@ -59,6 +59,12 @@ import NotificationRuleWorkspaceChannel from "../../Types/Workspace/Notification
59
59
  import { MessageBlocksByWorkspaceType } from "./WorkspaceNotificationRuleService";
60
60
  import ScheduledMaintenanceWorkspaceMessages from "../Utils/Workspace/WorkspaceMessages/ScheduledMaintenance";
61
61
  import CaptureSpan from "../Utils/Telemetry/CaptureSpan";
62
+ import StatusPageSubscriberNotificationTemplateService, {
63
+ Service as StatusPageSubscriberNotificationTemplateServiceClass,
64
+ } from "./StatusPageSubscriberNotificationTemplateService";
65
+ import StatusPageSubscriberNotificationTemplate from "../../Models/DatabaseModels/StatusPageSubscriberNotificationTemplate";
66
+ import StatusPageSubscriberNotificationEventType from "../../Types/StatusPage/StatusPageSubscriberNotificationEventType";
67
+ import StatusPageSubscriberNotificationMethod from "../../Types/StatusPage/StatusPageSubscriberNotificationMethod";
62
68
 
63
69
  export class Service extends DatabaseService<Model> {
64
70
  public constructor() {
@@ -209,6 +215,41 @@ export class Service extends DatabaseService<Model> {
209
215
  })
210
216
  .join(", ") || "";
211
217
 
218
+ // Fetch custom templates for each notification method
219
+ const [
220
+ emailTemplate,
221
+ smsTemplate,
222
+ slackTemplate,
223
+ ]: Array<StatusPageSubscriberNotificationTemplate | null> =
224
+ await Promise.all([
225
+ StatusPageSubscriberNotificationTemplateService.getTemplateForStatusPage(
226
+ {
227
+ statusPageId: statuspage.id!,
228
+ eventType:
229
+ StatusPageSubscriberNotificationEventType.SubscriberScheduledMaintenanceCreated,
230
+ notificationMethod:
231
+ StatusPageSubscriberNotificationMethod.Email,
232
+ },
233
+ ),
234
+ StatusPageSubscriberNotificationTemplateService.getTemplateForStatusPage(
235
+ {
236
+ statusPageId: statuspage.id!,
237
+ eventType:
238
+ StatusPageSubscriberNotificationEventType.SubscriberScheduledMaintenanceCreated,
239
+ notificationMethod: StatusPageSubscriberNotificationMethod.SMS,
240
+ },
241
+ ),
242
+ StatusPageSubscriberNotificationTemplateService.getTemplateForStatusPage(
243
+ {
244
+ statusPageId: statuspage.id!,
245
+ eventType:
246
+ StatusPageSubscriberNotificationEventType.SubscriberScheduledMaintenanceCreated,
247
+ notificationMethod:
248
+ StatusPageSubscriberNotificationMethod.Slack,
249
+ },
250
+ ),
251
+ ]);
252
+
212
253
  for (const subscriber of subscribers) {
213
254
  if (!subscriber._id) {
214
255
  continue;
@@ -232,23 +273,45 @@ export class Service extends DatabaseService<Model> {
232
273
  subscriber.id!,
233
274
  ).toString();
234
275
 
235
- if (subscriber.subscriberPhone) {
236
- const sms: SMS = {
237
- message: `
238
- Scheduled Maintenance - ${statusPageName}
239
-
240
- ${event.title || ""}
241
-
242
- ${
243
- resourcesAffected
244
- ? "Resources Affected: " + resourcesAffected
245
- : ""
246
- }
276
+ // Create template variables for custom templates
277
+ const templateVariables: Record<string, string> = {
278
+ statusPageName: statusPageName,
279
+ statusPageUrl: statusPageURL,
280
+ detailsUrl: scheduledEventDetailsUrl,
281
+ scheduledMaintenanceTitle: event.title || "",
282
+ scheduledMaintenanceDescription: event.description || "",
283
+ scheduledStartTime:
284
+ OneUptimeDate.getDateAsUserFriendlyFormattedString(
285
+ event.startsAt!,
286
+ ),
287
+ scheduledEndTime: event.endsAt
288
+ ? OneUptimeDate.getDateAsUserFriendlyFormattedString(event.endsAt)
289
+ : "",
290
+ resourcesAffected: resourcesAffected,
291
+ unsubscribeUrl: unsubscribeUrl,
292
+ };
247
293
 
248
- To view this event, visit ${statusPageURL}
294
+ if (subscriber.subscriberPhone) {
295
+ let smsMessage: string;
296
+
297
+ if (
298
+ smsTemplate &&
299
+ smsTemplate.templateBody &&
300
+ statuspage.callSmsConfig
301
+ ) {
302
+ // Use custom template only when custom Twilio is configured
303
+ smsMessage =
304
+ StatusPageSubscriberNotificationTemplateServiceClass.compileTemplate(
305
+ smsTemplate.templateBody,
306
+ templateVariables,
307
+ );
308
+ } else {
309
+ // Use default template
310
+ smsMessage = `Scheduled Maintenance: ${event.title || ""} on ${statusPageName}.${resourcesAffected ? ` Impact: ${resourcesAffected}.` : ""} Details: ${scheduledEventDetailsUrl}. Unsub: ${unsubscribeUrl}`;
311
+ }
249
312
 
250
- To update notification preferences or unsubscribe, visit ${unsubscribeUrl}
251
- `,
313
+ const sms: SMS = {
314
+ message: smsMessage,
252
315
  to: subscriber.subscriberPhone,
253
316
  };
254
317
 
@@ -266,7 +329,18 @@ export class Service extends DatabaseService<Model> {
266
329
  }
267
330
 
268
331
  if (subscriber.slackIncomingWebhookUrl) {
269
- const slackMessage: string = `## 🔧 Scheduled Maintenance - ${event.title || ""}
332
+ let slackMessage: string;
333
+
334
+ if (slackTemplate && slackTemplate.templateBody) {
335
+ // Use custom template
336
+ slackMessage =
337
+ StatusPageSubscriberNotificationTemplateServiceClass.compileTemplate(
338
+ slackTemplate.templateBody,
339
+ templateVariables,
340
+ );
341
+ } else {
342
+ // Use default template
343
+ slackMessage = `## 🔧 Scheduled Maintenance - ${event.title || ""}
270
344
 
271
345
  **Scheduled Date:** ${OneUptimeDate.getDateAsUserFriendlyFormattedString(event.startsAt!)}
272
346
 
@@ -275,6 +349,7 @@ ${resourcesAffected ? `**Resources Affected:** ${resourcesAffected}` : ""}
275
349
  **Description:** ${event.description || ""}
276
350
 
277
351
  [View Status Page](${statusPageURL}) | [Unsubscribe](${unsubscribeUrl})`;
352
+ }
278
353
 
279
354
  // send Slack notification here.
280
355
  SlackUtil.sendMessageToChannelViaIncomingWebhook({
@@ -290,55 +365,101 @@ ${resourcesAffected ? `**Resources Affected:** ${resourcesAffected}` : ""}
290
365
  const statusPageIdString: string | null =
291
366
  statuspage.id?.toString() || statuspage._id?.toString() || null;
292
367
 
293
- MailService.sendMail(
294
- {
295
- toEmail: subscriber.subscriberEmail,
296
- templateType:
297
- EmailTemplateType.SubscriberScheduledMaintenanceEventCreated,
298
- vars: {
299
- statusPageName: statusPageName,
300
- statusPageUrl: statusPageURL,
301
- detailsUrl: scheduledEventDetailsUrl,
302
- logoUrl:
303
- statuspage.logoFileId && statusPageIdString
304
- ? new URL(httpProtocol, host)
305
- .addRoute(StatusPageApiRoute)
306
- .addRoute(`/logo/${statusPageIdString}`)
307
- .toString()
308
- : "",
309
- isPublicStatusPage: statuspage.isPublicStatusPage
310
- ? "true"
311
- : "false",
312
- subscriberEmailNotificationFooterText:
313
- statuspage.subscriberEmailNotificationFooterText || "",
314
- resourcesAffected: resourcesAffected,
315
- scheduledAt:
316
- OneUptimeDate.getDateAsFormattedHTMLInMultipleTimezones({
317
- date: event.startsAt!,
318
- timezones: statuspage.subscriberTimezones || [],
319
- use12HourFormat: true,
320
- }),
321
- eventTitle: event.title || "",
322
- eventDescription: await Markdown.convertToHTML(
323
- event.description || "",
324
- MarkdownContentType.Email,
368
+ // Prepare email variables
369
+ const emailVars: Record<string, string> = {
370
+ statusPageName: statusPageName,
371
+ statusPageUrl: statusPageURL,
372
+ detailsUrl: scheduledEventDetailsUrl,
373
+ logoUrl:
374
+ statuspage.logoFileId && statusPageIdString
375
+ ? new URL(httpProtocol, host)
376
+ .addRoute(StatusPageApiRoute)
377
+ .addRoute(`/logo/${statusPageIdString}`)
378
+ .toString()
379
+ : "",
380
+ isPublicStatusPage: statuspage.isPublicStatusPage
381
+ ? "true"
382
+ : "false",
383
+ subscriberEmailNotificationFooterText:
384
+ statuspage.subscriberEmailNotificationFooterText || "",
385
+ resourcesAffected: resourcesAffected,
386
+ scheduledAt:
387
+ OneUptimeDate.getDateAsFormattedHTMLInMultipleTimezones({
388
+ date: event.startsAt!,
389
+ timezones: statuspage.subscriberTimezones || [],
390
+ use12HourFormat: true,
391
+ }),
392
+ eventTitle: event.title || "",
393
+ eventDescription: await Markdown.convertToHTML(
394
+ event.description || "",
395
+ MarkdownContentType.Email,
396
+ ),
397
+ unsubscribeUrl: unsubscribeUrl,
398
+ };
399
+
400
+ // Check for custom email template - only use when custom SMTP is configured
401
+ if (
402
+ emailTemplate &&
403
+ emailTemplate.templateBody &&
404
+ statuspage.smtpConfig
405
+ ) {
406
+ // Use custom template with BlankTemplate only when custom SMTP is configured
407
+ const customEmailBody: string =
408
+ StatusPageSubscriberNotificationTemplateServiceClass.compileTemplate(
409
+ emailTemplate.templateBody,
410
+ { ...templateVariables, ...emailVars },
411
+ );
412
+ const customEmailSubject: string = emailTemplate.emailSubject
413
+ ? StatusPageSubscriberNotificationTemplateServiceClass.compileTemplate(
414
+ emailTemplate.emailSubject,
415
+ { ...templateVariables, ...emailVars },
416
+ )
417
+ : "[Scheduled Maintenance] " + (event.title || statusPageName);
418
+
419
+ MailService.sendMail(
420
+ {
421
+ toEmail: subscriber.subscriberEmail,
422
+ templateType: EmailTemplateType.BlankTemplate,
423
+ vars: {
424
+ body: customEmailBody,
425
+ },
426
+ subject: customEmailSubject,
427
+ },
428
+ {
429
+ mailServer: ProjectSmtpConfigService.toEmailServer(
430
+ statuspage.smtpConfig,
325
431
  ),
326
- unsubscribeUrl: unsubscribeUrl,
432
+ projectId: statuspage.projectId!,
433
+ statusPageId: statuspage.id!,
434
+ scheduledMaintenanceId: event.id!,
327
435
  },
328
- subject:
329
- "[Scheduled Maintenance] " + (event.title || statusPageName),
330
- },
331
- {
332
- mailServer: ProjectSmtpConfigService.toEmailServer(
333
- statuspage.smtpConfig,
334
- ),
335
- projectId: statuspage.projectId!,
336
- statusPageId: statuspage.id!,
337
- scheduledMaintenanceId: event.id!,
338
- },
339
- ).catch((err: Error) => {
340
- logger.error(err);
341
- });
436
+ ).catch((err: Error) => {
437
+ logger.error(err);
438
+ });
439
+ } else {
440
+ // Use default hard-coded template
441
+ MailService.sendMail(
442
+ {
443
+ toEmail: subscriber.subscriberEmail,
444
+ templateType:
445
+ EmailTemplateType.SubscriberScheduledMaintenanceEventCreated,
446
+ vars: emailVars,
447
+ subject:
448
+ "[Scheduled Maintenance] " +
449
+ (event.title || statusPageName),
450
+ },
451
+ {
452
+ mailServer: ProjectSmtpConfigService.toEmailServer(
453
+ statuspage.smtpConfig,
454
+ ),
455
+ projectId: statuspage.projectId!,
456
+ statusPageId: statuspage.id!,
457
+ scheduledMaintenanceId: event.id!,
458
+ },
459
+ ).catch((err: Error) => {
460
+ logger.error(err);
461
+ });
462
+ }
342
463
  }
343
464
  }
344
465
  }
@@ -0,0 +1,272 @@
1
+ import DatabaseService from "./DatabaseService";
2
+ import Model from "../../Models/DatabaseModels/StatusPageSubscriberNotificationTemplate";
3
+ import StatusPageSubscriberNotificationTemplateStatusPage from "../../Models/DatabaseModels/StatusPageSubscriberNotificationTemplateStatusPage";
4
+ import ObjectID from "../../Types/ObjectID";
5
+ import StatusPageSubscriberNotificationEventType from "../../Types/StatusPage/StatusPageSubscriberNotificationEventType";
6
+ import StatusPageSubscriberNotificationMethod from "../../Types/StatusPage/StatusPageSubscriberNotificationMethod";
7
+ import StatusPageSubscriberNotificationTemplateStatusPageService from "./StatusPageSubscriberNotificationTemplateStatusPageService";
8
+ import BadDataException from "../../Types/Exception/BadDataException";
9
+
10
+ export class Service extends DatabaseService<Model> {
11
+ public constructor() {
12
+ super(Model);
13
+ }
14
+
15
+ /**
16
+ * Get template for a specific status page, event type, and notification method.
17
+ * Returns null if no custom template is found (caller should use default template).
18
+ */
19
+ public async getTemplateForStatusPage(data: {
20
+ statusPageId: ObjectID;
21
+ eventType: StatusPageSubscriberNotificationEventType;
22
+ notificationMethod: StatusPageSubscriberNotificationMethod;
23
+ }): Promise<Model | null> {
24
+ const { statusPageId, eventType, notificationMethod } = data;
25
+
26
+ // First find the template link for this status page
27
+ const templateLinks: Array<StatusPageSubscriberNotificationTemplateStatusPage> =
28
+ await StatusPageSubscriberNotificationTemplateStatusPageService.findBy({
29
+ query: {
30
+ statusPageId: statusPageId,
31
+ },
32
+ select: {
33
+ statusPageSubscriberNotificationTemplateId: true,
34
+ },
35
+ skip: 0,
36
+ limit: 100,
37
+ props: {
38
+ isRoot: true,
39
+ },
40
+ });
41
+
42
+ if (templateLinks.length === 0) {
43
+ return null;
44
+ }
45
+
46
+ // Get the template IDs
47
+ const templateIds: Array<ObjectID> = templateLinks
48
+ .map((link: StatusPageSubscriberNotificationTemplateStatusPage) => {
49
+ return link.statusPageSubscriberNotificationTemplateId;
50
+ })
51
+ .filter((id: ObjectID | undefined): id is ObjectID => {
52
+ return id !== undefined;
53
+ });
54
+
55
+ if (templateIds.length === 0) {
56
+ return null;
57
+ }
58
+
59
+ // Find the specific template matching the event type and notification method
60
+ const templates: Array<Model> = await this.findBy({
61
+ query: {
62
+ eventType: eventType,
63
+ notificationMethod: notificationMethod,
64
+ },
65
+ select: {
66
+ _id: true,
67
+ templateName: true,
68
+ templateBody: true,
69
+ emailSubject: true,
70
+ eventType: true,
71
+ notificationMethod: true,
72
+ },
73
+ skip: 0,
74
+ limit: 100,
75
+ props: {
76
+ isRoot: true,
77
+ },
78
+ });
79
+
80
+ // Find a template that matches one of the linked template IDs
81
+ for (const template of templates) {
82
+ if (
83
+ templateIds.some((id: ObjectID) => {
84
+ return id.toString() === template._id?.toString();
85
+ })
86
+ ) {
87
+ return template;
88
+ }
89
+ }
90
+
91
+ return null;
92
+ }
93
+
94
+ /**
95
+ * Get available variables for a specific event type.
96
+ * These variables can be used in templates with {{variableName}} syntax.
97
+ */
98
+ public static getAvailableVariablesForEventType(
99
+ eventType: StatusPageSubscriberNotificationEventType,
100
+ ): Array<{ name: string; description: string }> {
101
+ const commonVariables: Array<{ name: string; description: string }> = [
102
+ { name: "statusPageName", description: "Name of the status page" },
103
+ { name: "statusPageUrl", description: "URL of the status page" },
104
+ {
105
+ name: "unsubscribeUrl",
106
+ description: "URL to unsubscribe from notifications",
107
+ },
108
+ { name: "resourcesAffected", description: "List of affected resources" },
109
+ ];
110
+
111
+ switch (eventType) {
112
+ case StatusPageSubscriberNotificationEventType.SubscriberIncidentCreated:
113
+ return [
114
+ ...commonVariables,
115
+ { name: "incidentTitle", description: "Title of the incident" },
116
+ {
117
+ name: "incidentDescription",
118
+ description: "Description of the incident",
119
+ },
120
+ { name: "incidentSeverity", description: "Severity of the incident" },
121
+ { name: "detailsUrl", description: "URL to view incident details" },
122
+ ];
123
+
124
+ case StatusPageSubscriberNotificationEventType.SubscriberIncidentStateChanged:
125
+ return [
126
+ ...commonVariables,
127
+ { name: "incidentTitle", description: "Title of the incident" },
128
+ {
129
+ name: "incidentDescription",
130
+ description: "Description of the incident",
131
+ },
132
+ { name: "incidentSeverity", description: "Severity of the incident" },
133
+ {
134
+ name: "incidentState",
135
+ description: "Current state of the incident",
136
+ },
137
+ { name: "detailsUrl", description: "URL to view incident details" },
138
+ ];
139
+
140
+ case StatusPageSubscriberNotificationEventType.SubscriberIncidentNoteCreated:
141
+ return [
142
+ ...commonVariables,
143
+ { name: "incidentTitle", description: "Title of the incident" },
144
+ { name: "incidentSeverity", description: "Severity of the incident" },
145
+ {
146
+ name: "incidentState",
147
+ description: "Current state of the incident",
148
+ },
149
+ { name: "postedAt", description: "When the note was posted" },
150
+ { name: "note", description: "Content of the note" },
151
+ { name: "detailsUrl", description: "URL to view incident details" },
152
+ ];
153
+
154
+ case StatusPageSubscriberNotificationEventType.SubscriberIncidentPostmortemPublished:
155
+ return [
156
+ ...commonVariables,
157
+ { name: "incidentTitle", description: "Title of the incident" },
158
+ { name: "incidentSeverity", description: "Severity of the incident" },
159
+ {
160
+ name: "postmortemNote",
161
+ description: "Content of the postmortem note",
162
+ },
163
+ { name: "detailsUrl", description: "URL to view incident details" },
164
+ ];
165
+
166
+ case StatusPageSubscriberNotificationEventType.SubscriberAnnouncementCreated:
167
+ return [
168
+ ...commonVariables,
169
+ {
170
+ name: "announcementTitle",
171
+ description: "Title of the announcement",
172
+ },
173
+ {
174
+ name: "announcementDescription",
175
+ description: "Description of the announcement",
176
+ },
177
+ {
178
+ name: "detailsUrl",
179
+ description: "URL to view announcement details",
180
+ },
181
+ ];
182
+
183
+ case StatusPageSubscriberNotificationEventType.SubscriberScheduledMaintenanceCreated:
184
+ return [
185
+ ...commonVariables,
186
+ {
187
+ name: "scheduledMaintenanceTitle",
188
+ description: "Title of the scheduled maintenance",
189
+ },
190
+ {
191
+ name: "scheduledMaintenanceDescription",
192
+ description: "Description of the scheduled maintenance",
193
+ },
194
+ {
195
+ name: "scheduledStartTime",
196
+ description: "When the maintenance is scheduled to start",
197
+ },
198
+ {
199
+ name: "scheduledEndTime",
200
+ description: "When the maintenance is scheduled to end",
201
+ },
202
+ {
203
+ name: "detailsUrl",
204
+ description: "URL to view scheduled maintenance details",
205
+ },
206
+ ];
207
+
208
+ case StatusPageSubscriberNotificationEventType.SubscriberScheduledMaintenanceStateChanged:
209
+ return [
210
+ ...commonVariables,
211
+ {
212
+ name: "scheduledMaintenanceTitle",
213
+ description: "Title of the scheduled maintenance",
214
+ },
215
+ {
216
+ name: "scheduledMaintenanceDescription",
217
+ description: "Description of the scheduled maintenance",
218
+ },
219
+ {
220
+ name: "scheduledMaintenanceState",
221
+ description: "Current state of the scheduled maintenance",
222
+ },
223
+ {
224
+ name: "detailsUrl",
225
+ description: "URL to view scheduled maintenance details",
226
+ },
227
+ ];
228
+
229
+ case StatusPageSubscriberNotificationEventType.SubscriberScheduledMaintenanceNoteCreated:
230
+ return [
231
+ ...commonVariables,
232
+ {
233
+ name: "scheduledMaintenanceTitle",
234
+ description: "Title of the scheduled maintenance",
235
+ },
236
+ {
237
+ name: "scheduledMaintenanceState",
238
+ description: "Current state of the scheduled maintenance",
239
+ },
240
+ { name: "postedAt", description: "When the note was posted" },
241
+ { name: "note", description: "Content of the note" },
242
+ {
243
+ name: "detailsUrl",
244
+ description: "URL to view scheduled maintenance details",
245
+ },
246
+ ];
247
+
248
+ default:
249
+ throw new BadDataException(`Unknown event type: ${eventType}`);
250
+ }
251
+ }
252
+
253
+ /**
254
+ * Compile a template with the given variables.
255
+ * Replaces {{variableName}} with the actual values.
256
+ */
257
+ public static compileTemplate(
258
+ template: string,
259
+ variables: Record<string, string>,
260
+ ): string {
261
+ let compiledTemplate: string = template;
262
+
263
+ for (const [key, value] of Object.entries(variables)) {
264
+ const regex: RegExp = new RegExp(`{{\\s*${key}\\s*}}`, "g");
265
+ compiledTemplate = compiledTemplate.replace(regex, value || "");
266
+ }
267
+
268
+ return compiledTemplate;
269
+ }
270
+ }
271
+
272
+ export default new Service();
@@ -0,0 +1,10 @@
1
+ import DatabaseService from "./DatabaseService";
2
+ import Model from "../../Models/DatabaseModels/StatusPageSubscriberNotificationTemplateStatusPage";
3
+
4
+ export class Service extends DatabaseService<Model> {
5
+ public constructor() {
6
+ super(Model);
7
+ }
8
+ }
9
+
10
+ export default new Service();
@@ -14,7 +14,7 @@ describe("Loader tests", () => {
14
14
  loaderType={LoaderType.Bar}
15
15
  />,
16
16
  );
17
- const barLoader: HTMLElement = screen.getByRole("bar-loader");
17
+ const barLoader: HTMLElement = screen.getByTestId("bar-loader");
18
18
  expect(barLoader).toBeInTheDocument();
19
19
  });
20
20
  test("it should render if beats loader show up", () => {
@@ -25,7 +25,7 @@ describe("Loader tests", () => {
25
25
  loaderType={LoaderType.Beats}
26
26
  />,
27
27
  );
28
- const beatLoader: HTMLElement = screen.getByRole("beat-loader");
28
+ const beatLoader: HTMLElement = screen.getByTestId("beat-loader");
29
29
  expect(beatLoader).toBeInTheDocument();
30
30
  });
31
31
  });
@@ -1,4 +1,5 @@
1
1
  enum EmailTemplateType {
2
+ BlankTemplate = "BlankTemplate.hbs",
2
3
  ForgotPassword = "ForgotPassword.hbs",
3
4
  ProbeOffline = "ProbeOffline.hbs",
4
5
  SignupWelcomeEmail = "SignupWelcomeEmail.hbs",