@oneuptime/common 9.2.9 → 9.2.11
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.
- package/Models/DatabaseModels/Index.ts +4 -0
- package/Models/DatabaseModels/StatusPageSubscriberNotificationTemplate.ts +461 -0
- package/Models/DatabaseModels/StatusPageSubscriberNotificationTemplateStatusPage.ts +400 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/1765195603978-MigrationName.ts +107 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/Index.ts +2 -0
- package/Server/Services/Index.ts +4 -0
- package/Server/Services/ScheduledMaintenanceService.ts +184 -63
- package/Server/Services/StatusPageSubscriberNotificationTemplateService.ts +272 -0
- package/Server/Services/StatusPageSubscriberNotificationTemplateStatusPageService.ts +10 -0
- package/Server/Types/Workflow/Components/Discord/SendMessageToChannel.ts +108 -0
- package/Server/Types/Workflow/Components/Index.ts +2 -0
- package/Tests/UI/Components/Loader.test.tsx +2 -2
- package/Types/Email/EmailTemplateType.ts +1 -0
- package/Types/Permission.ts +82 -0
- package/Types/StatusPage/StatusPageSubscriberNotificationEventType.ts +22 -0
- package/Types/StatusPage/StatusPageSubscriberNotificationMethod.ts +14 -0
- package/Types/Workflow/ComponentID.ts +1 -0
- package/Types/Workflow/Components/Discord.ts +68 -0
- package/Types/Workflow/Components.ts +7 -0
- package/UI/Components/Forms/BasicForm.tsx +2 -0
- package/UI/Components/Forms/Types/Field.ts +1 -0
- package/UI/Components/Loader/Loader.tsx +6 -6
- package/build/dist/Models/DatabaseModels/Index.js +4 -0
- package/build/dist/Models/DatabaseModels/Index.js.map +1 -1
- package/build/dist/Models/DatabaseModels/StatusPageSubscriberNotificationTemplate.js +482 -0
- package/build/dist/Models/DatabaseModels/StatusPageSubscriberNotificationTemplate.js.map +1 -0
- package/build/dist/Models/DatabaseModels/StatusPageSubscriberNotificationTemplateStatusPage.js +413 -0
- package/build/dist/Models/DatabaseModels/StatusPageSubscriberNotificationTemplateStatusPage.js.map +1 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1765195603978-MigrationName.js +42 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1765195603978-MigrationName.js.map +1 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js +2 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js.map +1 -1
- package/build/dist/Server/Services/Index.js +4 -0
- package/build/dist/Server/Services/Index.js.map +1 -1
- package/build/dist/Server/Services/ScheduledMaintenanceService.js +125 -50
- package/build/dist/Server/Services/ScheduledMaintenanceService.js.map +1 -1
- package/build/dist/Server/Services/StatusPageSubscriberNotificationTemplateService.js +236 -0
- package/build/dist/Server/Services/StatusPageSubscriberNotificationTemplateService.js.map +1 -0
- package/build/dist/Server/Services/StatusPageSubscriberNotificationTemplateStatusPageService.js +9 -0
- package/build/dist/Server/Services/StatusPageSubscriberNotificationTemplateStatusPageService.js.map +1 -0
- package/build/dist/Server/Types/Workflow/Components/Discord/SendMessageToChannel.js +92 -0
- package/build/dist/Server/Types/Workflow/Components/Discord/SendMessageToChannel.js.map +1 -0
- package/build/dist/Server/Types/Workflow/Components/Index.js +2 -0
- package/build/dist/Server/Types/Workflow/Components/Index.js.map +1 -1
- package/build/dist/Tests/UI/Components/Loader.test.js +2 -2
- package/build/dist/Tests/UI/Components/Loader.test.js.map +1 -1
- package/build/dist/Types/Email/EmailTemplateType.js +1 -0
- package/build/dist/Types/Email/EmailTemplateType.js.map +1 -1
- package/build/dist/Types/Permission.js +66 -0
- package/build/dist/Types/Permission.js.map +1 -1
- package/build/dist/Types/StatusPage/StatusPageSubscriberNotificationEventType.js +20 -0
- package/build/dist/Types/StatusPage/StatusPageSubscriberNotificationEventType.js.map +1 -0
- package/build/dist/Types/StatusPage/StatusPageSubscriberNotificationMethod.js +14 -0
- package/build/dist/Types/StatusPage/StatusPageSubscriberNotificationMethod.js.map +1 -0
- package/build/dist/Types/Workflow/ComponentID.js +1 -0
- package/build/dist/Types/Workflow/ComponentID.js.map +1 -1
- package/build/dist/Types/Workflow/Components/Discord.js +61 -0
- package/build/dist/Types/Workflow/Components/Discord.js.map +1 -0
- package/build/dist/Types/Workflow/Components.js +7 -0
- package/build/dist/Types/Workflow/Components.js.map +1 -1
- package/build/dist/UI/Components/Forms/BasicForm.js +3 -1
- package/build/dist/UI/Components/Forms/BasicForm.js.map +1 -1
- package/build/dist/UI/Components/Loader/Loader.js +2 -2
- package/build/dist/UI/Components/Loader/Loader.js.map +1 -1
- package/jest.config.json +1 -1
- 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
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
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
|
-
|
|
432
|
+
projectId: statuspage.projectId!,
|
|
433
|
+
statusPageId: statuspage.id!,
|
|
434
|
+
scheduledMaintenanceId: event.id!,
|
|
327
435
|
},
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
}
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
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();
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
import ComponentCode, { RunOptions, RunReturnType } from "../../ComponentCode";
|
|
2
|
+
import HTTPErrorResponse from "../../../../../Types/API/HTTPErrorResponse";
|
|
3
|
+
import HTTPResponse from "../../../../../Types/API/HTTPResponse";
|
|
4
|
+
import URL from "../../../../../Types/API/URL";
|
|
5
|
+
import APIException from "../../../../../Types/Exception/ApiException";
|
|
6
|
+
import BadDataException from "../../../../../Types/Exception/BadDataException";
|
|
7
|
+
import { JSONObject } from "../../../../../Types/JSON";
|
|
8
|
+
import ComponentMetadata, {
|
|
9
|
+
Port,
|
|
10
|
+
} from "../../../../../Types/Workflow/Component";
|
|
11
|
+
import ComponentID from "../../../../../Types/Workflow/ComponentID";
|
|
12
|
+
import DiscordComponents from "../../../../../Types/Workflow/Components/Discord";
|
|
13
|
+
import API from "../../../../../Utils/API";
|
|
14
|
+
import CaptureSpan from "../../../../Utils/Telemetry/CaptureSpan";
|
|
15
|
+
|
|
16
|
+
export default class SendMessageToChannel extends ComponentCode {
|
|
17
|
+
public constructor() {
|
|
18
|
+
super();
|
|
19
|
+
|
|
20
|
+
const Component: ComponentMetadata | undefined = DiscordComponents.find(
|
|
21
|
+
(i: ComponentMetadata) => {
|
|
22
|
+
return i.id === ComponentID.DiscordSendMessageToChannel;
|
|
23
|
+
},
|
|
24
|
+
);
|
|
25
|
+
|
|
26
|
+
if (!Component) {
|
|
27
|
+
throw new BadDataException("Component not found.");
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
this.setMetadata(Component);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
@CaptureSpan()
|
|
34
|
+
public override async run(
|
|
35
|
+
args: JSONObject,
|
|
36
|
+
options: RunOptions,
|
|
37
|
+
): Promise<RunReturnType> {
|
|
38
|
+
const successPort: Port | undefined = this.getMetadata().outPorts.find(
|
|
39
|
+
(p: Port) => {
|
|
40
|
+
return p.id === "success";
|
|
41
|
+
},
|
|
42
|
+
);
|
|
43
|
+
|
|
44
|
+
if (!successPort) {
|
|
45
|
+
throw options.onError(new BadDataException("Success port not found"));
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
const errorPort: Port | undefined = this.getMetadata().outPorts.find(
|
|
49
|
+
(p: Port) => {
|
|
50
|
+
return p.id === "error";
|
|
51
|
+
},
|
|
52
|
+
);
|
|
53
|
+
|
|
54
|
+
if (!errorPort) {
|
|
55
|
+
throw options.onError(new BadDataException("Error port not found"));
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
if (!args["text"]) {
|
|
59
|
+
throw options.onError(new BadDataException("Discord message not found"));
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
if (!args["webhook-url"]) {
|
|
63
|
+
throw options.onError(
|
|
64
|
+
new BadDataException("Discord Webhook URL not found"),
|
|
65
|
+
);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
args["webhook-url"] = URL.fromString(
|
|
69
|
+
args["webhook-url"]?.toString() as string,
|
|
70
|
+
);
|
|
71
|
+
|
|
72
|
+
let apiResult: HTTPResponse<JSONObject> | HTTPErrorResponse | null = null;
|
|
73
|
+
|
|
74
|
+
try {
|
|
75
|
+
// https://discord.com/developers/docs/resources/webhook#execute-webhook
|
|
76
|
+
apiResult = await API.post({
|
|
77
|
+
url: args["webhook-url"] as URL,
|
|
78
|
+
data: {
|
|
79
|
+
content: args["text"] as string,
|
|
80
|
+
},
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
if (apiResult instanceof HTTPErrorResponse) {
|
|
84
|
+
return Promise.resolve({
|
|
85
|
+
returnValues: {
|
|
86
|
+
error: apiResult.message || "Server Error.",
|
|
87
|
+
},
|
|
88
|
+
executePort: errorPort,
|
|
89
|
+
});
|
|
90
|
+
}
|
|
91
|
+
return Promise.resolve({
|
|
92
|
+
returnValues: {},
|
|
93
|
+
executePort: successPort,
|
|
94
|
+
});
|
|
95
|
+
} catch (err) {
|
|
96
|
+
if (err instanceof HTTPErrorResponse) {
|
|
97
|
+
return Promise.resolve({
|
|
98
|
+
returnValues: {
|
|
99
|
+
error: err.message || "Server Error.",
|
|
100
|
+
},
|
|
101
|
+
executePort: errorPort,
|
|
102
|
+
});
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
throw options.onError(new APIException("Something wrong happened."));
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
}
|