@oneuptime/common 9.5.12 → 10.0.0

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 (76) hide show
  1. package/Models/DatabaseModels/GlobalConfig.ts +19 -0
  2. package/Server/API/UserCallAPI.ts +19 -0
  3. package/Server/API/UserEmailAPI.ts +19 -0
  4. package/Server/API/UserPushAPI.ts +66 -22
  5. package/Server/API/UserSmsAPI.ts +19 -0
  6. package/Server/API/UserWhatsAppAPI.ts +19 -0
  7. package/Server/DatabaseConfig.ts +7 -0
  8. package/Server/EnvironmentConfig.ts +15 -0
  9. package/Server/Infrastructure/Postgres/SchemaMigrations/1770834237091-MigrationName.ts +23 -0
  10. package/Server/Infrastructure/Postgres/SchemaMigrations/Index.ts +2 -0
  11. package/Server/Services/ProjectService.ts +14 -0
  12. package/Server/Services/PushNotificationService.ts +136 -12
  13. package/Server/Services/UserNotificationRuleService.ts +153 -111
  14. package/Server/Utils/Monitor/Criteria/DomainMonitorCriteria.ts +142 -0
  15. package/Server/Utils/Monitor/MonitorCriteriaEvaluator.ts +13 -0
  16. package/Server/Utils/Monitor/MonitorTemplateUtil.ts +21 -0
  17. package/Server/Utils/VM/VMRunner.ts +214 -37
  18. package/Types/Monitor/CriteriaFilter.ts +9 -1
  19. package/Types/Monitor/DomainMonitor/DomainMonitorResponse.ts +15 -0
  20. package/Types/Monitor/MonitorCriteriaInstance.ts +67 -0
  21. package/Types/Monitor/MonitorStep.ts +32 -0
  22. package/Types/Monitor/MonitorStepDomainMonitor.ts +33 -0
  23. package/Types/Monitor/MonitorType.ts +66 -2
  24. package/Types/Probe/ProbeMonitorResponse.ts +2 -0
  25. package/UI/Components/CardSelect/CardSelect.tsx +133 -67
  26. package/UI/Components/Forms/Types/Field.ts +7 -2
  27. package/build/dist/Models/DatabaseModels/GlobalConfig.js +21 -0
  28. package/build/dist/Models/DatabaseModels/GlobalConfig.js.map +1 -1
  29. package/build/dist/Server/API/UserCallAPI.js +17 -0
  30. package/build/dist/Server/API/UserCallAPI.js.map +1 -1
  31. package/build/dist/Server/API/UserEmailAPI.js +17 -0
  32. package/build/dist/Server/API/UserEmailAPI.js.map +1 -1
  33. package/build/dist/Server/API/UserPushAPI.js +50 -12
  34. package/build/dist/Server/API/UserPushAPI.js.map +1 -1
  35. package/build/dist/Server/API/UserSmsAPI.js +17 -0
  36. package/build/dist/Server/API/UserSmsAPI.js.map +1 -1
  37. package/build/dist/Server/API/UserWhatsAppAPI.js +17 -0
  38. package/build/dist/Server/API/UserWhatsAppAPI.js.map +1 -1
  39. package/build/dist/Server/DatabaseConfig.js +9 -0
  40. package/build/dist/Server/DatabaseConfig.js.map +1 -1
  41. package/build/dist/Server/EnvironmentConfig.js +5 -0
  42. package/build/dist/Server/EnvironmentConfig.js.map +1 -1
  43. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1770834237091-MigrationName.js +14 -0
  44. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1770834237091-MigrationName.js.map +1 -0
  45. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js +2 -0
  46. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js.map +1 -1
  47. package/build/dist/Server/Services/ProjectService.js +12 -2
  48. package/build/dist/Server/Services/ProjectService.js.map +1 -1
  49. package/build/dist/Server/Services/PushNotificationService.js +74 -12
  50. package/build/dist/Server/Services/PushNotificationService.js.map +1 -1
  51. package/build/dist/Server/Services/UserNotificationRuleService.js +96 -109
  52. package/build/dist/Server/Services/UserNotificationRuleService.js.map +1 -1
  53. package/build/dist/Server/Utils/Monitor/Criteria/DomainMonitorCriteria.js +113 -0
  54. package/build/dist/Server/Utils/Monitor/Criteria/DomainMonitorCriteria.js.map +1 -0
  55. package/build/dist/Server/Utils/Monitor/MonitorCriteriaEvaluator.js +10 -0
  56. package/build/dist/Server/Utils/Monitor/MonitorCriteriaEvaluator.js.map +1 -1
  57. package/build/dist/Server/Utils/Monitor/MonitorTemplateUtil.js +16 -0
  58. package/build/dist/Server/Utils/Monitor/MonitorTemplateUtil.js.map +1 -1
  59. package/build/dist/Server/Utils/VM/VMRunner.js +182 -20
  60. package/build/dist/Server/Utils/VM/VMRunner.js.map +1 -1
  61. package/build/dist/Types/Monitor/CriteriaFilter.js +8 -1
  62. package/build/dist/Types/Monitor/CriteriaFilter.js.map +1 -1
  63. package/build/dist/Types/Monitor/DomainMonitor/DomainMonitorResponse.js +2 -0
  64. package/build/dist/Types/Monitor/DomainMonitor/DomainMonitorResponse.js.map +1 -0
  65. package/build/dist/Types/Monitor/MonitorCriteriaInstance.js +62 -0
  66. package/build/dist/Types/Monitor/MonitorCriteriaInstance.js.map +1 -1
  67. package/build/dist/Types/Monitor/MonitorStep.js +22 -0
  68. package/build/dist/Types/Monitor/MonitorStep.js.map +1 -1
  69. package/build/dist/Types/Monitor/MonitorStepDomainMonitor.js +24 -0
  70. package/build/dist/Types/Monitor/MonitorStepDomainMonitor.js.map +1 -0
  71. package/build/dist/Types/Monitor/MonitorType.js +58 -2
  72. package/build/dist/Types/Monitor/MonitorType.js.map +1 -1
  73. package/build/dist/UI/Components/CardSelect/CardSelect.js +55 -20
  74. package/build/dist/UI/Components/CardSelect/CardSelect.js.map +1 -1
  75. package/build/dist/UI/Components/Forms/Types/Field.js.map +1 -1
  76. package/package.json +2 -1
@@ -10,9 +10,16 @@ import {
10
10
  VapidPublicKey,
11
11
  VapidPrivateKey,
12
12
  VapidSubject,
13
+ ExpoAccessToken,
14
+ PushNotificationRelayUrl,
13
15
  } from "../EnvironmentConfig";
14
16
  import webpush from "web-push";
15
17
  import { Expo, ExpoPushMessage, ExpoPushTicket } from "expo-server-sdk";
18
+ import API from "../../Utils/API";
19
+ import URL from "../../Types/API/URL";
20
+ import HTTPErrorResponse from "../../Types/API/HTTPErrorResponse";
21
+ import HTTPResponse from "../../Types/API/HTTPResponse";
22
+ import { JSONObject } from "../../Types/JSON";
16
23
  import PushNotificationUtil from "../Utils/PushNotificationUtil";
17
24
  import { LIMIT_PER_PROJECT } from "../../Types/Database/LimitMax";
18
25
  import UserPush from "../../Models/DatabaseModels/UserPush";
@@ -43,7 +50,9 @@ export interface PushNotificationOptions {
43
50
 
44
51
  export default class PushNotificationService {
45
52
  public static isWebPushInitialized = false;
46
- private static expoClient: Expo = new Expo();
53
+ private static expoClient: Expo = new Expo(
54
+ ExpoAccessToken ? { accessToken: ExpoAccessToken } : undefined,
55
+ );
47
56
 
48
57
  public static initializeWebPush(): void {
49
58
  if (this.isWebPushInitialized) {
@@ -340,20 +349,33 @@ export default class PushNotificationService {
340
349
  );
341
350
  }
342
351
 
343
- try {
344
- const dataPayload: { [key: string]: string } = {};
345
- if (message.data) {
346
- for (const key of Object.keys(message.data)) {
347
- dataPayload[key] = String(message.data[key]);
348
- }
349
- }
350
- if (message.url || message.clickAction) {
351
- dataPayload["url"] = message.url || message.clickAction || "";
352
+ const dataPayload: { [key: string]: string } = {};
353
+ if (message.data) {
354
+ for (const key of Object.keys(message.data)) {
355
+ dataPayload[key] = String(message.data[key]);
352
356
  }
357
+ }
358
+ if (message.url || message.clickAction) {
359
+ dataPayload["url"] = message.url || message.clickAction || "";
360
+ }
353
361
 
354
- const channelId: string =
355
- deviceType === PushDeviceType.Android ? "oncall_high" : "default";
362
+ const channelId: string =
363
+ deviceType === PushDeviceType.Android ? "oncall_high" : "default";
364
+
365
+ // If EXPO_ACCESS_TOKEN is not set, relay through the push notification gateway
366
+ if (!ExpoAccessToken) {
367
+ await this.sendViaRelay(
368
+ expoPushToken,
369
+ message,
370
+ dataPayload,
371
+ channelId,
372
+ deviceType,
373
+ );
374
+ return;
375
+ }
356
376
 
377
+ // Send directly via Expo SDK
378
+ try {
357
379
  const expoPushMessage: ExpoPushMessage = {
358
380
  to: expoPushToken,
359
381
  title: message.title,
@@ -403,6 +425,108 @@ export default class PushNotificationService {
403
425
  }
404
426
  }
405
427
 
428
+ private static async sendViaRelay(
429
+ expoPushToken: string,
430
+ message: PushNotificationMessage,
431
+ dataPayload: { [key: string]: string },
432
+ channelId: string,
433
+ deviceType: PushDeviceType,
434
+ ): Promise<void> {
435
+ logger.info(
436
+ `Sending ${deviceType} push notification via relay: ${PushNotificationRelayUrl}`,
437
+ );
438
+
439
+ try {
440
+ const response: HTTPErrorResponse | HTTPResponse<JSONObject> =
441
+ await API.post<JSONObject>({
442
+ url: URL.fromString(PushNotificationRelayUrl),
443
+ data: {
444
+ to: expoPushToken,
445
+ title: message.title || "",
446
+ body: message.body || "",
447
+ data: dataPayload,
448
+ sound: "default",
449
+ priority: "high",
450
+ channelId: channelId,
451
+ },
452
+ });
453
+
454
+ if (response instanceof HTTPErrorResponse) {
455
+ throw new Error(
456
+ `Push relay error: ${JSON.stringify(response.jsonData)}`,
457
+ );
458
+ }
459
+
460
+ logger.info(
461
+ `Push notification sent via relay successfully to ${deviceType} device`,
462
+ );
463
+ } catch (error: any) {
464
+ logger.error(
465
+ `Failed to send push notification via relay to ${deviceType} device: ${error.message}`,
466
+ );
467
+ throw error;
468
+ }
469
+ }
470
+
471
+ public static isValidExpoPushToken(token: string): boolean {
472
+ return Expo.isExpoPushToken(token);
473
+ }
474
+
475
+ public static hasExpoAccessToken(): boolean {
476
+ return Boolean(ExpoAccessToken);
477
+ }
478
+
479
+ public static async sendRelayPushNotification(data: {
480
+ to: string;
481
+ title?: string;
482
+ body?: string;
483
+ data?: { [key: string]: string };
484
+ sound?: string;
485
+ priority?: string;
486
+ channelId?: string;
487
+ }): Promise<void> {
488
+ if (!ExpoAccessToken) {
489
+ throw new Error(
490
+ "Push relay is not configured. EXPO_ACCESS_TOKEN is not set on this server.",
491
+ );
492
+ }
493
+
494
+ const expoPushMessage: ExpoPushMessage = {
495
+ to: data.to,
496
+ title: data.title || "",
497
+ body: data.body || "",
498
+ data: data.data || {},
499
+ sound: (data.sound as "default" | null) || "default",
500
+ priority: (data.priority as "default" | "normal" | "high") || "high",
501
+ channelId: data.channelId || "default",
502
+ };
503
+
504
+ const tickets: ExpoPushTicket[] =
505
+ await this.expoClient.sendPushNotificationsAsync([expoPushMessage]);
506
+
507
+ const ticket: ExpoPushTicket | undefined = tickets[0];
508
+
509
+ if (ticket && ticket.status === "error") {
510
+ const errorTicket: ExpoPushTicket & {
511
+ message?: string;
512
+ details?: { error?: string };
513
+ } = ticket as ExpoPushTicket & {
514
+ message?: string;
515
+ details?: { error?: string };
516
+ };
517
+
518
+ logger.error(
519
+ `Push relay: Expo push notification error: ${errorTicket.message}`,
520
+ );
521
+
522
+ throw new Error(
523
+ `Failed to send push notification: ${errorTicket.message}`,
524
+ );
525
+ }
526
+
527
+ logger.info(`Push relay: notification sent successfully to ${data.to}`);
528
+ }
529
+
406
530
  public static async sendPushNotificationToUser(
407
531
  userId: ObjectID,
408
532
  projectId: ObjectID,
@@ -69,6 +69,14 @@ import PushNotificationMessage from "../../Types/PushNotification/PushNotificati
69
69
  import logger from "../Utils/Logger";
70
70
  import CaptureSpan from "../Utils/Telemetry/CaptureSpan";
71
71
 
72
+ export interface NotificationMethodDescriptor {
73
+ userEmailId?: ObjectID;
74
+ userSmsId?: ObjectID;
75
+ userCallId?: ObjectID;
76
+ userWhatsAppId?: ObjectID;
77
+ userPushId?: ObjectID;
78
+ }
79
+
72
80
  export class Service extends DatabaseService<Model> {
73
81
  public constructor() {
74
82
  super(Model);
@@ -2207,13 +2215,89 @@ export class Service extends DatabaseService<Model> {
2207
2215
  }
2208
2216
 
2209
2217
  @CaptureSpan()
2210
- public async addDefaultIncidentNotificationRuleForUser(data: {
2218
+ public async addDefaultNotificationRulesForVerifiedMethod(data: {
2211
2219
  projectId: ObjectID;
2212
2220
  userId: ObjectID;
2213
- userEmail: UserEmail;
2221
+ notificationMethod: NotificationMethodDescriptor;
2214
2222
  }): Promise<void> {
2215
- const { projectId, userId, userEmail } = data;
2223
+ const { projectId, userId, notificationMethod } = data;
2224
+
2225
+ await this.createIncidentOnCallRules(projectId, userId, notificationMethod);
2226
+ await this.createAlertOnCallRules(projectId, userId, notificationMethod);
2227
+ await this.createSingleRule(
2228
+ projectId,
2229
+ userId,
2230
+ notificationMethod,
2231
+ NotificationRuleType.ON_CALL_EXECUTED_ALERT_EPISODE,
2232
+ );
2233
+ await this.createSingleRule(
2234
+ projectId,
2235
+ userId,
2236
+ notificationMethod,
2237
+ NotificationRuleType.ON_CALL_EXECUTED_INCIDENT_EPISODE,
2238
+ );
2239
+ await this.createSingleRule(
2240
+ projectId,
2241
+ userId,
2242
+ notificationMethod,
2243
+ NotificationRuleType.WHEN_USER_GOES_ON_CALL,
2244
+ );
2245
+ await this.createSingleRule(
2246
+ projectId,
2247
+ userId,
2248
+ notificationMethod,
2249
+ NotificationRuleType.WHEN_USER_GOES_OFF_CALL,
2250
+ );
2251
+ }
2252
+
2253
+ private applyNotificationMethod(
2254
+ rule: Model,
2255
+ descriptor: NotificationMethodDescriptor,
2256
+ ): void {
2257
+ if (descriptor.userEmailId) {
2258
+ rule.userEmailId = descriptor.userEmailId;
2259
+ }
2260
+ if (descriptor.userSmsId) {
2261
+ rule.userSmsId = descriptor.userSmsId;
2262
+ }
2263
+ if (descriptor.userCallId) {
2264
+ rule.userCallId = descriptor.userCallId;
2265
+ }
2266
+ if (descriptor.userWhatsAppId) {
2267
+ rule.userWhatsAppId = descriptor.userWhatsAppId;
2268
+ }
2269
+ if (descriptor.userPushId) {
2270
+ rule.userPushId = descriptor.userPushId;
2271
+ }
2272
+ }
2273
+
2274
+ private getNotificationMethodQuery(
2275
+ descriptor: NotificationMethodDescriptor,
2276
+ ): Record<string, ObjectID> {
2277
+ const query: Record<string, ObjectID> = {};
2278
+ if (descriptor.userEmailId) {
2279
+ query["userEmailId"] = descriptor.userEmailId;
2280
+ }
2281
+ if (descriptor.userSmsId) {
2282
+ query["userSmsId"] = descriptor.userSmsId;
2283
+ }
2284
+ if (descriptor.userCallId) {
2285
+ query["userCallId"] = descriptor.userCallId;
2286
+ }
2287
+ if (descriptor.userWhatsAppId) {
2288
+ query["userWhatsAppId"] = descriptor.userWhatsAppId;
2289
+ }
2290
+ if (descriptor.userPushId) {
2291
+ query["userPushId"] = descriptor.userPushId;
2292
+ }
2293
+ return query;
2294
+ }
2216
2295
 
2296
+ private async createIncidentOnCallRules(
2297
+ projectId: ObjectID,
2298
+ userId: ObjectID,
2299
+ notificationMethod: NotificationMethodDescriptor,
2300
+ ): Promise<void> {
2217
2301
  const incidentSeverities: Array<IncidentSeverity> =
2218
2302
  await IncidentSeverityService.findBy({
2219
2303
  query: {
@@ -2229,38 +2313,34 @@ export class Service extends DatabaseService<Model> {
2229
2313
  },
2230
2314
  });
2231
2315
 
2232
- // create for incident severities.
2233
2316
  for (const incidentSeverity of incidentSeverities) {
2234
- //check if this rule already exists.
2235
2317
  const existingRule: Model | null = await this.findOneBy({
2236
2318
  query: {
2237
2319
  projectId,
2238
2320
  userId,
2239
- userEmailId: userEmail.id!,
2321
+ ...this.getNotificationMethodQuery(notificationMethod),
2240
2322
  incidentSeverityId: incidentSeverity.id!,
2241
2323
  ruleType: NotificationRuleType.ON_CALL_EXECUTED_INCIDENT,
2242
- },
2324
+ } as any,
2243
2325
  props: {
2244
2326
  isRoot: true,
2245
2327
  },
2246
2328
  });
2247
2329
 
2248
2330
  if (existingRule) {
2249
- continue; // skip this rule.
2331
+ continue;
2250
2332
  }
2251
2333
 
2252
- const notificationRule: Model = new Model();
2253
-
2254
- notificationRule.projectId = projectId;
2255
- notificationRule.userId = userId;
2256
- notificationRule.userEmailId = userEmail.id!;
2257
- notificationRule.incidentSeverityId = incidentSeverity.id!;
2258
- notificationRule.notifyAfterMinutes = 0;
2259
- notificationRule.ruleType =
2260
- NotificationRuleType.ON_CALL_EXECUTED_INCIDENT;
2334
+ const rule: Model = new Model();
2335
+ rule.projectId = projectId;
2336
+ rule.userId = userId;
2337
+ this.applyNotificationMethod(rule, notificationMethod);
2338
+ rule.incidentSeverityId = incidentSeverity.id!;
2339
+ rule.notifyAfterMinutes = 0;
2340
+ rule.ruleType = NotificationRuleType.ON_CALL_EXECUTED_INCIDENT;
2261
2341
 
2262
2342
  await this.create({
2263
- data: notificationRule,
2343
+ data: rule,
2264
2344
  props: {
2265
2345
  isRoot: true,
2266
2346
  },
@@ -2268,14 +2348,11 @@ export class Service extends DatabaseService<Model> {
2268
2348
  }
2269
2349
  }
2270
2350
 
2271
- @CaptureSpan()
2272
- public async addDefaultAlertNotificationRulesForUser(data: {
2273
- projectId: ObjectID;
2274
- userId: ObjectID;
2275
- userEmail: UserEmail;
2276
- }): Promise<void> {
2277
- const { projectId, userId, userEmail } = data;
2278
-
2351
+ private async createAlertOnCallRules(
2352
+ projectId: ObjectID,
2353
+ userId: ObjectID,
2354
+ notificationMethod: NotificationMethodDescriptor,
2355
+ ): Promise<void> {
2279
2356
  const alertSeverities: Array<AlertSeverity> =
2280
2357
  await AlertSeverityService.findBy({
2281
2358
  query: {
@@ -2291,37 +2368,34 @@ export class Service extends DatabaseService<Model> {
2291
2368
  },
2292
2369
  });
2293
2370
 
2294
- // create for Alert severities.
2295
2371
  for (const alertSeverity of alertSeverities) {
2296
- //check if this rule already exists.
2297
2372
  const existingRule: Model | null = await this.findOneBy({
2298
2373
  query: {
2299
2374
  projectId,
2300
2375
  userId,
2301
- userEmailId: userEmail.id!,
2376
+ ...this.getNotificationMethodQuery(notificationMethod),
2302
2377
  alertSeverityId: alertSeverity.id!,
2303
2378
  ruleType: NotificationRuleType.ON_CALL_EXECUTED_ALERT,
2304
- },
2379
+ } as any,
2305
2380
  props: {
2306
2381
  isRoot: true,
2307
2382
  },
2308
2383
  });
2309
2384
 
2310
2385
  if (existingRule) {
2311
- continue; // skip this rule.
2386
+ continue;
2312
2387
  }
2313
2388
 
2314
- const notificationRule: Model = new Model();
2315
-
2316
- notificationRule.projectId = projectId;
2317
- notificationRule.userId = userId;
2318
- notificationRule.userEmailId = userEmail.id!;
2319
- notificationRule.alertSeverityId = alertSeverity.id!;
2320
- notificationRule.notifyAfterMinutes = 0;
2321
- notificationRule.ruleType = NotificationRuleType.ON_CALL_EXECUTED_ALERT;
2389
+ const rule: Model = new Model();
2390
+ rule.projectId = projectId;
2391
+ rule.userId = userId;
2392
+ this.applyNotificationMethod(rule, notificationMethod);
2393
+ rule.alertSeverityId = alertSeverity.id!;
2394
+ rule.notifyAfterMinutes = 0;
2395
+ rule.ruleType = NotificationRuleType.ON_CALL_EXECUTED_ALERT;
2322
2396
 
2323
2397
  await this.create({
2324
- data: notificationRule,
2398
+ data: rule,
2325
2399
  props: {
2326
2400
  isRoot: true,
2327
2401
  },
@@ -2329,6 +2403,43 @@ export class Service extends DatabaseService<Model> {
2329
2403
  }
2330
2404
  }
2331
2405
 
2406
+ private async createSingleRule(
2407
+ projectId: ObjectID,
2408
+ userId: ObjectID,
2409
+ notificationMethod: NotificationMethodDescriptor,
2410
+ ruleType: NotificationRuleType,
2411
+ ): Promise<void> {
2412
+ const existingRule: Model | null = await this.findOneBy({
2413
+ query: {
2414
+ projectId,
2415
+ userId,
2416
+ ...this.getNotificationMethodQuery(notificationMethod),
2417
+ ruleType,
2418
+ } as any,
2419
+ props: {
2420
+ isRoot: true,
2421
+ },
2422
+ });
2423
+
2424
+ if (existingRule) {
2425
+ return;
2426
+ }
2427
+
2428
+ const rule: Model = new Model();
2429
+ rule.projectId = projectId;
2430
+ rule.userId = userId;
2431
+ this.applyNotificationMethod(rule, notificationMethod);
2432
+ rule.notifyAfterMinutes = 0;
2433
+ rule.ruleType = ruleType;
2434
+
2435
+ await this.create({
2436
+ data: rule,
2437
+ props: {
2438
+ isRoot: true,
2439
+ },
2440
+ });
2441
+ }
2442
+
2332
2443
  @CaptureSpan()
2333
2444
  public async addDefaultNotificationRuleForUser(
2334
2445
  projectId: ObjectID,
@@ -2361,82 +2472,13 @@ export class Service extends DatabaseService<Model> {
2361
2472
  });
2362
2473
  }
2363
2474
 
2364
- // add default incident rules for user
2365
- await this.addDefaultIncidentNotificationRuleForUser({
2475
+ await this.addDefaultNotificationRulesForVerifiedMethod({
2366
2476
  projectId,
2367
2477
  userId,
2368
- userEmail,
2369
- });
2370
-
2371
- // add default alert rules for user, just like the incident
2372
-
2373
- await this.addDefaultAlertNotificationRulesForUser({
2374
- projectId,
2375
- userId,
2376
- userEmail,
2377
- });
2378
-
2379
- //check if this rule already exists.
2380
- const existingRuleOnCall: Model | null = await this.findOneBy({
2381
- query: {
2382
- projectId,
2383
- userId,
2384
- userEmailId: userEmail.id!,
2385
- ruleType: NotificationRuleType.WHEN_USER_GOES_ON_CALL,
2386
- },
2387
- props: {
2388
- isRoot: true,
2389
- },
2390
- });
2391
-
2392
- if (!existingRuleOnCall) {
2393
- // on and off call.
2394
- const onCallRule: Model = new Model();
2395
-
2396
- onCallRule.projectId = projectId;
2397
- onCallRule.userId = userId;
2398
- onCallRule.userEmailId = userEmail.id!;
2399
- onCallRule.notifyAfterMinutes = 0;
2400
- onCallRule.ruleType = NotificationRuleType.WHEN_USER_GOES_ON_CALL;
2401
-
2402
- await this.create({
2403
- data: onCallRule,
2404
- props: {
2405
- isRoot: true,
2406
- },
2407
- });
2408
- }
2409
-
2410
- //check if this rule already exists.
2411
- const existingRuleOffCall: Model | null = await this.findOneBy({
2412
- query: {
2413
- projectId,
2414
- userId,
2478
+ notificationMethod: {
2415
2479
  userEmailId: userEmail.id!,
2416
- ruleType: NotificationRuleType.WHEN_USER_GOES_OFF_CALL,
2417
- },
2418
- props: {
2419
- isRoot: true,
2420
2480
  },
2421
2481
  });
2422
-
2423
- if (!existingRuleOffCall) {
2424
- // on and off call.
2425
- const offCallRule: Model = new Model();
2426
-
2427
- offCallRule.projectId = projectId;
2428
- offCallRule.userId = userId;
2429
- offCallRule.userEmailId = userEmail.id!;
2430
- offCallRule.notifyAfterMinutes = 0;
2431
- offCallRule.ruleType = NotificationRuleType.WHEN_USER_GOES_OFF_CALL;
2432
-
2433
- await this.create({
2434
- data: offCallRule,
2435
- props: {
2436
- isRoot: true,
2437
- },
2438
- });
2439
- }
2440
2482
  }
2441
2483
  }
2442
2484
  export default new Service();
@@ -0,0 +1,142 @@
1
+ import DataToProcess from "../DataToProcess";
2
+ import CompareCriteria from "./CompareCriteria";
3
+ import {
4
+ CheckOn,
5
+ CriteriaFilter,
6
+ FilterType,
7
+ } from "../../../../Types/Monitor/CriteriaFilter";
8
+ import DomainMonitorResponse from "../../../../Types/Monitor/DomainMonitor/DomainMonitorResponse";
9
+ import ProbeMonitorResponse from "../../../../Types/Probe/ProbeMonitorResponse";
10
+ import CaptureSpan from "../../Telemetry/CaptureSpan";
11
+
12
+ export default class DomainMonitorCriteria {
13
+ @CaptureSpan()
14
+ public static async isMonitorInstanceCriteriaFilterMet(input: {
15
+ dataToProcess: DataToProcess;
16
+ criteriaFilter: CriteriaFilter;
17
+ }): Promise<string | null> {
18
+ let threshold: number | string | undefined | null =
19
+ input.criteriaFilter.value;
20
+
21
+ const dataToProcess: ProbeMonitorResponse =
22
+ input.dataToProcess as ProbeMonitorResponse;
23
+
24
+ const domainResponse: DomainMonitorResponse | undefined =
25
+ dataToProcess.domainResponse;
26
+
27
+ // Check domain expires in days
28
+ if (input.criteriaFilter.checkOn === CheckOn.DomainExpiresDaysIn) {
29
+ threshold = CompareCriteria.convertToNumber(threshold);
30
+
31
+ if (threshold === null || threshold === undefined) {
32
+ return null;
33
+ }
34
+
35
+ if (!domainResponse?.expiresDate) {
36
+ return null;
37
+ }
38
+
39
+ const expiresDate: Date = new Date(domainResponse.expiresDate);
40
+ const now: Date = new Date();
41
+ const diffMs: number = expiresDate.getTime() - now.getTime();
42
+ const diffDays: number = Math.ceil(diffMs / (1000 * 60 * 60 * 24));
43
+
44
+ return CompareCriteria.compareCriteriaNumbers({
45
+ value: diffDays,
46
+ threshold: threshold as number,
47
+ criteriaFilter: input.criteriaFilter,
48
+ });
49
+ }
50
+
51
+ // Check domain registrar
52
+ if (input.criteriaFilter.checkOn === CheckOn.DomainRegistrar) {
53
+ if (!domainResponse?.registrar) {
54
+ return null;
55
+ }
56
+
57
+ return CompareCriteria.compareCriteriaStrings({
58
+ value: domainResponse.registrar,
59
+ threshold: String(threshold),
60
+ criteriaFilter: input.criteriaFilter,
61
+ });
62
+ }
63
+
64
+ // Check domain name server
65
+ if (input.criteriaFilter.checkOn === CheckOn.DomainNameServer) {
66
+ if (
67
+ !domainResponse?.nameServers ||
68
+ domainResponse.nameServers.length === 0
69
+ ) {
70
+ return null;
71
+ }
72
+
73
+ // Check if any name server matches the criteria
74
+ for (const nameServer of domainResponse.nameServers) {
75
+ const result: string | null = CompareCriteria.compareCriteriaStrings({
76
+ value: nameServer,
77
+ threshold: String(threshold),
78
+ criteriaFilter: input.criteriaFilter,
79
+ });
80
+
81
+ if (result) {
82
+ return `Domain name server: ${result}`;
83
+ }
84
+ }
85
+
86
+ return null;
87
+ }
88
+
89
+ // Check domain status code
90
+ if (input.criteriaFilter.checkOn === CheckOn.DomainStatusCode) {
91
+ if (
92
+ !domainResponse?.domainStatus ||
93
+ domainResponse.domainStatus.length === 0
94
+ ) {
95
+ return null;
96
+ }
97
+
98
+ // Check if any status matches the criteria
99
+ for (const status of domainResponse.domainStatus) {
100
+ const result: string | null = CompareCriteria.compareCriteriaStrings({
101
+ value: status,
102
+ threshold: String(threshold),
103
+ criteriaFilter: input.criteriaFilter,
104
+ });
105
+
106
+ if (result) {
107
+ return `Domain status: ${result}`;
108
+ }
109
+ }
110
+
111
+ return null;
112
+ }
113
+
114
+ // Check if domain is expired
115
+ if (input.criteriaFilter.checkOn === CheckOn.DomainIsExpired) {
116
+ const isTrue: boolean =
117
+ input.criteriaFilter.filterType === FilterType.True;
118
+ const isFalse: boolean =
119
+ input.criteriaFilter.filterType === FilterType.False;
120
+
121
+ if (!domainResponse?.expiresDate) {
122
+ return null;
123
+ }
124
+
125
+ const expiresDate: Date = new Date(domainResponse.expiresDate);
126
+ const now: Date = new Date();
127
+ const isExpired: boolean = expiresDate.getTime() < now.getTime();
128
+
129
+ if (isExpired && isTrue) {
130
+ return `Domain is expired (expired on ${domainResponse.expiresDate}).`;
131
+ }
132
+
133
+ if (!isExpired && isFalse) {
134
+ return `Domain is not expired (expires on ${domainResponse.expiresDate}).`;
135
+ }
136
+
137
+ return null;
138
+ }
139
+
140
+ return null;
141
+ }
142
+ }
@@ -13,6 +13,7 @@ import TraceMonitorCriteria from "./Criteria/TraceMonitorCriteria";
13
13
  import ExceptionMonitorCriteria from "./Criteria/ExceptionMonitorCriteria";
14
14
  import SnmpMonitorCriteria from "./Criteria/SnmpMonitorCriteria";
15
15
  import DnsMonitorCriteria from "./Criteria/DnsMonitorCriteria";
16
+ import DomainMonitorCriteria from "./Criteria/DomainMonitorCriteria";
16
17
  import MonitorCriteriaMessageBuilder from "./MonitorCriteriaMessageBuilder";
17
18
  import MonitorCriteriaDataExtractor from "./MonitorCriteriaDataExtractor";
18
19
  import MonitorCriteriaMessageFormatter from "./MonitorCriteriaMessageFormatter";
@@ -506,6 +507,18 @@ ${contextBlock}
506
507
  }
507
508
  }
508
509
 
510
+ if (input.monitor.monitorType === MonitorType.Domain) {
511
+ const domainMonitorResult: string | null =
512
+ await DomainMonitorCriteria.isMonitorInstanceCriteriaFilterMet({
513
+ dataToProcess: input.dataToProcess,
514
+ criteriaFilter: input.criteriaFilter,
515
+ });
516
+
517
+ if (domainMonitorResult) {
518
+ return domainMonitorResult;
519
+ }
520
+ }
521
+
509
522
  return null;
510
523
  }
511
524