@oneuptime/common 8.0.5581 → 8.0.5583

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.
@@ -193,6 +193,13 @@ export class Service extends DatabaseService<Model> {
193
193
  const statusPageName: string =
194
194
  statuspage.pageTitle || statuspage.name || "Status Page";
195
195
 
196
+ const scheduledEventDetailsUrl: string =
197
+ event.id && statusPageURL
198
+ ? URL.fromString(statusPageURL)
199
+ .addRoute(`/scheduled-events/${event.id.toString()}`)
200
+ .toString()
201
+ : statusPageURL;
202
+
196
203
  // Send email to Email subscribers.
197
204
 
198
205
  const resourcesAffected: string =
@@ -291,6 +298,7 @@ ${resourcesAffected ? `**Resources Affected:** ${resourcesAffected}` : ""}
291
298
  vars: {
292
299
  statusPageName: statusPageName,
293
300
  statusPageUrl: statusPageURL,
301
+ detailsUrl: scheduledEventDetailsUrl,
294
302
  logoUrl:
295
303
  statuspage.logoFileId && statusPageIdString
296
304
  ? new URL(httpProtocol, host)
@@ -377,27 +385,60 @@ ${resourcesAffected ? `**Resources Affected:** ${resourcesAffected}` : ""}
377
385
  (updateBy.data.startsAt as Date) ||
378
386
  (scheduledMaintenance.startsAt! as Date);
379
387
 
380
- const notificationSettings: Array<Recurring> =
381
- (updateBy.data
382
- .sendSubscriberNotificationsOnBeforeTheEvent as Array<Recurring>) ||
383
- (scheduledMaintenance.sendSubscriberNotificationsOnBeforeTheEvent as Array<Recurring>);
388
+ let notificationSettings: Array<Recurring> | null = null;
389
+
390
+ const updatedNotificationSettings: Array<Recurring> | null | undefined =
391
+ updateBy.data.sendSubscriberNotificationsOnBeforeTheEvent as
392
+ | Array<Recurring>
393
+ | null
394
+ | undefined;
395
+
396
+ if (
397
+ updatedNotificationSettings !== null &&
398
+ updatedNotificationSettings !== undefined
399
+ ) {
400
+ notificationSettings = updatedNotificationSettings;
401
+ } else {
402
+ const existingNotificationSettings:
403
+ | Array<Recurring>
404
+ | null
405
+ | undefined =
406
+ scheduledMaintenance.sendSubscriberNotificationsOnBeforeTheEvent as
407
+ | Array<Recurring>
408
+ | null
409
+ | undefined;
410
+
411
+ if (
412
+ existingNotificationSettings !== null &&
413
+ existingNotificationSettings !== undefined
414
+ ) {
415
+ notificationSettings = existingNotificationSettings;
416
+ }
417
+ }
384
418
 
385
419
  logger.debug(
386
420
  `Using startsAt: ${startsAt} and notificationSettings: ${JSON.stringify(notificationSettings)}`,
387
421
  );
388
422
 
389
- const nextTimeToNotifyBeforeTheEvent: Date | null =
390
- this.getNextTimeToNotify({
391
- eventScheduledDate: startsAt,
392
- sendSubscriberNotifiationsOn: notificationSettings,
393
- });
423
+ if (!notificationSettings || notificationSettings.length === 0) {
424
+ logger.debug(
425
+ "No subscriber notification schedule configured. Clearing nextSubscriberNotificationBeforeTheEventAt.",
426
+ );
427
+ updateBy.data.nextSubscriberNotificationBeforeTheEventAt = null;
428
+ } else {
429
+ const nextTimeToNotifyBeforeTheEvent: Date | null =
430
+ this.getNextTimeToNotify({
431
+ eventScheduledDate: startsAt,
432
+ sendSubscriberNotifiationsOn: notificationSettings,
433
+ });
394
434
 
395
- updateBy.data.nextSubscriberNotificationBeforeTheEventAt =
396
- nextTimeToNotifyBeforeTheEvent;
435
+ updateBy.data.nextSubscriberNotificationBeforeTheEventAt =
436
+ nextTimeToNotifyBeforeTheEvent;
397
437
 
398
- logger.debug(
399
- `nextSubscriberNotificationBeforeTheEventAt set to: ${nextTimeToNotifyBeforeTheEvent}`,
400
- );
438
+ logger.debug(
439
+ `nextSubscriberNotificationBeforeTheEventAt set to: ${nextTimeToNotifyBeforeTheEvent}`,
440
+ );
441
+ }
401
442
  }
402
443
 
403
444
  // Set notification status based on shouldStatusPageSubscribersBeNotifiedOnEventCreated if it's being updated
@@ -475,7 +516,7 @@ ${resourcesAffected ? `**Resources Affected:** ${resourcesAffected}` : ""}
475
516
 
476
517
  public getNextTimeToNotify(data: {
477
518
  eventScheduledDate: Date;
478
- sendSubscriberNotifiationsOn: Array<Recurring>;
519
+ sendSubscriberNotifiationsOn?: Array<Recurring> | null | undefined;
479
520
  }): Date | null {
480
521
  let recurringDate: Date | null = null;
481
522
 
@@ -486,7 +527,23 @@ ${resourcesAffected ? `**Resources Affected:** ${resourcesAffected}` : ""}
486
527
  `Calculating next time to notify for event scheduled date: ${data.eventScheduledDate}`,
487
528
  );
488
529
 
489
- for (const recurringItem of data.sendSubscriberNotifiationsOn) {
530
+ const notificationSchedules: Array<Recurring> = Array.isArray(
531
+ data.sendSubscriberNotifiationsOn,
532
+ )
533
+ ? (data.sendSubscriberNotifiationsOn as Array<Recurring>)
534
+ : [];
535
+
536
+ if (notificationSchedules.length === 0) {
537
+ logger.debug(
538
+ "No sendSubscriberNotifiationsOn entries. Returning null for next notification time.",
539
+ );
540
+ return null;
541
+ }
542
+
543
+ for (const recurringItem of notificationSchedules) {
544
+ if (!recurringItem) {
545
+ continue;
546
+ }
490
547
  const notificationDate: Date = Recurring.getNextDateInterval(
491
548
  data.eventScheduledDate,
492
549
  recurringItem,
@@ -778,6 +778,7 @@ export class Service extends DatabaseService<StatusPage> {
778
778
  subscriberEmailNotificationFooterText:
779
779
  Service.getSubscriberEmailFooterText(statuspage),
780
780
  statusPageUrl: statusPageURL,
781
+ detailsUrl: statusPageURL,
781
782
  hasResources: report.totalResources > 0 ? "true" : "false",
782
783
  report: report as any,
783
784
  logoUrl:
@@ -11,6 +11,8 @@ import MetricMonitorCriteria from "./Criteria/MetricMonitorCriteria";
11
11
  import TraceMonitorCriteria from "./Criteria/TraceMonitorCriteria";
12
12
  import ExceptionMonitorCriteria from "./Criteria/ExceptionMonitorCriteria";
13
13
  import MonitorCriteriaMessageBuilder from "./MonitorCriteriaMessageBuilder";
14
+ import MonitorCriteriaDataExtractor from "./MonitorCriteriaDataExtractor";
15
+ import MonitorCriteriaMessageFormatter from "./MonitorCriteriaMessageFormatter";
14
16
  import DataToProcess from "./DataToProcess";
15
17
  import Monitor from "../../../Models/DatabaseModels/Monitor";
16
18
  import MonitorCriteria from "../../../Types/Monitor/MonitorCriteria";
@@ -98,6 +100,19 @@ export default class MonitorCriteriaEvaluator {
98
100
  **Filter Conditions Met**: ${rootCause}
99
101
  `;
100
102
 
103
+ const contextBlock: string | null =
104
+ MonitorCriteriaEvaluator.buildRootCauseContext({
105
+ dataToProcess: input.dataToProcess,
106
+ monitorStep: input.monitorStep,
107
+ monitor: input.monitor,
108
+ });
109
+
110
+ if (contextBlock) {
111
+ input.probeApiIngestResponse.rootCause += `
112
+ ${contextBlock}
113
+ `;
114
+ }
115
+
101
116
  if ((input.dataToProcess as ProbeMonitorResponse).failureCause) {
102
117
  input.probeApiIngestResponse.rootCause += `
103
118
  **Cause**: ${(input.dataToProcess as ProbeMonitorResponse).failureCause || ""}
@@ -449,4 +464,160 @@ export default class MonitorCriteriaEvaluator {
449
464
 
450
465
  return null;
451
466
  }
467
+
468
+ private static buildRootCauseContext(input: {
469
+ dataToProcess: DataToProcess;
470
+ monitorStep: MonitorStep;
471
+ monitor: Monitor;
472
+ }): string | null {
473
+ const requestDetails: Array<string> = [];
474
+ const responseDetails: Array<string> = [];
475
+
476
+ const probeResponse: ProbeMonitorResponse | null =
477
+ MonitorCriteriaDataExtractor.getProbeMonitorResponse(input.dataToProcess);
478
+
479
+ const destination: string | null =
480
+ MonitorCriteriaEvaluator.getMonitorDestinationString({
481
+ monitorStep: input.monitorStep,
482
+ probeResponse: probeResponse,
483
+ });
484
+
485
+ if (destination) {
486
+ requestDetails.push(`- Destination: ${destination}`);
487
+ }
488
+
489
+ const port: string | null = MonitorCriteriaEvaluator.getMonitorPortString({
490
+ monitorStep: input.monitorStep,
491
+ probeResponse: probeResponse,
492
+ });
493
+
494
+ if (port) {
495
+ requestDetails.push(`- Destination Port: ${port}`);
496
+ }
497
+
498
+ const requestMethod: string | null =
499
+ MonitorCriteriaEvaluator.getRequestMethodString({
500
+ monitor: input.monitor,
501
+ monitorStep: input.monitorStep,
502
+ });
503
+
504
+ if (requestMethod) {
505
+ requestDetails.push(`- Request Method: ${requestMethod}`);
506
+ }
507
+
508
+ if (probeResponse?.responseCode !== undefined) {
509
+ responseDetails.push(
510
+ `- Response Status Code: ${probeResponse.responseCode}`,
511
+ );
512
+ }
513
+
514
+ const responseTime: string | null =
515
+ MonitorCriteriaEvaluator.formatMilliseconds(
516
+ probeResponse?.responseTimeInMs,
517
+ );
518
+
519
+ if (responseTime) {
520
+ responseDetails.push(`- Response Time: ${responseTime}`);
521
+ }
522
+
523
+ if (probeResponse?.isTimeout !== undefined) {
524
+ responseDetails.push(
525
+ `- Timed Out: ${probeResponse.isTimeout ? "Yes" : "No"}`,
526
+ );
527
+ }
528
+
529
+ const sections: Array<string> = [];
530
+
531
+ if (requestDetails.length > 0) {
532
+ sections.push(`**Request Details**\n${requestDetails.join("\n")}`);
533
+ }
534
+
535
+ if (responseDetails.length > 0) {
536
+ sections.push(`\n\n**Response Snapshot**\n${responseDetails.join("\n")}`);
537
+ }
538
+
539
+ if (!sections.length) {
540
+ return null;
541
+ }
542
+
543
+ return sections.join("\n");
544
+ }
545
+
546
+ private static getMonitorDestinationString(input: {
547
+ monitorStep: MonitorStep;
548
+ probeResponse: ProbeMonitorResponse | null;
549
+ }): string | null {
550
+ if (input.probeResponse?.monitorDestination) {
551
+ return MonitorCriteriaEvaluator.stringifyValue(
552
+ input.probeResponse.monitorDestination,
553
+ );
554
+ }
555
+
556
+ if (input.monitorStep.data?.monitorDestination) {
557
+ return MonitorCriteriaEvaluator.stringifyValue(
558
+ input.monitorStep.data.monitorDestination,
559
+ );
560
+ }
561
+
562
+ return null;
563
+ }
564
+
565
+ private static getMonitorPortString(input: {
566
+ monitorStep: MonitorStep;
567
+ probeResponse: ProbeMonitorResponse | null;
568
+ }): string | null {
569
+ if (input.probeResponse?.monitorDestinationPort) {
570
+ return MonitorCriteriaEvaluator.stringifyValue(
571
+ input.probeResponse.monitorDestinationPort,
572
+ );
573
+ }
574
+
575
+ if (input.monitorStep.data?.monitorDestinationPort) {
576
+ return MonitorCriteriaEvaluator.stringifyValue(
577
+ input.monitorStep.data.monitorDestinationPort,
578
+ );
579
+ }
580
+
581
+ return null;
582
+ }
583
+
584
+ private static getRequestMethodString(input: {
585
+ monitor: Monitor;
586
+ monitorStep: MonitorStep;
587
+ }): string | null {
588
+ if (
589
+ input.monitor.monitorType === MonitorType.API &&
590
+ input.monitorStep.data
591
+ ) {
592
+ return `${input.monitorStep.data.requestType}`;
593
+ }
594
+
595
+ return null;
596
+ }
597
+
598
+ private static formatMilliseconds(value?: number): string | null {
599
+ if (value === undefined || value === null || isNaN(value)) {
600
+ return null;
601
+ }
602
+
603
+ const formatted: string | null =
604
+ MonitorCriteriaMessageFormatter.formatNumber(value, {
605
+ maximumFractionDigits: value < 100 ? 2 : value < 1000 ? 1 : 0,
606
+ });
607
+
608
+ return `${formatted ?? value} ms`;
609
+ }
610
+
611
+ private static stringifyValue(value: unknown): string | null {
612
+ if (value === null || value === undefined) {
613
+ return null;
614
+ }
615
+
616
+ try {
617
+ return String(value).trim();
618
+ } catch (err) {
619
+ logger.error(err);
620
+ return null;
621
+ }
622
+ }
452
623
  }
@@ -669,32 +669,42 @@ export default class OpenAPIUtil {
669
669
  modelType: new () => DatabaseBaseModel,
670
670
  model: DatabaseBaseModel,
671
671
  ): void {
672
- // Check if model has create permissions and should not exclude API generation
673
- if (!this.shouldExcludeApiForPermissions(model.createRecordPermissions)) {
672
+ const canCreate: boolean = !this.shouldExcludeApiForPermissions(
673
+ model.createRecordPermissions,
674
+ );
675
+ const canRead: boolean = !this.shouldExcludeApiForPermissions(
676
+ model.readRecordPermissions,
677
+ );
678
+ const canUpdate: boolean = !this.shouldExcludeApiForPermissions(
679
+ model.updateRecordPermissions,
680
+ );
681
+ const canDelete: boolean = !this.shouldExcludeApiForPermissions(
682
+ model.deleteRecordPermissions,
683
+ );
684
+
685
+ if (canCreate) {
674
686
  const createSchema: ModelSchemaType = ModelSchema.getCreateModelSchema({
675
687
  modelType,
676
688
  });
677
689
  registry.register(`${tableName}CreateSchema`, createSchema);
678
690
  }
679
691
 
680
- // Check if model has read permissions and should not exclude API generation
681
- if (!this.shouldExcludeApiForPermissions(model.readRecordPermissions)) {
692
+ // Register read schema whenever any operation might return it (create/update/read)
693
+ if (canRead || canCreate || canUpdate) {
682
694
  const readSchema: ModelSchemaType = ModelSchema.getReadModelSchema({
683
695
  modelType,
684
696
  });
685
697
  registry.register(`${tableName}ReadSchema`, readSchema);
686
698
  }
687
699
 
688
- // Check if model has update permissions and should not exclude API generation
689
- if (!this.shouldExcludeApiForPermissions(model.updateRecordPermissions)) {
700
+ if (canUpdate) {
690
701
  const updateSchema: ModelSchemaType = ModelSchema.getUpdateModelSchema({
691
702
  modelType,
692
703
  });
693
704
  registry.register(`${tableName}UpdateSchema`, updateSchema);
694
705
  }
695
706
 
696
- // Check if model has delete permissions and should not exclude API generation
697
- if (!this.shouldExcludeApiForPermissions(model.deleteRecordPermissions)) {
707
+ if (canDelete) {
698
708
  const deleteSchema: ModelSchemaType = ModelSchema.getDeleteModelSchema({
699
709
  modelType,
700
710
  });
@@ -766,8 +776,20 @@ export default class OpenAPIUtil {
766
776
  modelType: new () => AnalyticsBaseModel,
767
777
  model: AnalyticsBaseModel,
768
778
  ): void {
769
- // Check if model has create permissions and should not exclude API generation
770
- if (!this.shouldExcludeApiForPermissions(model.getCreatePermissions())) {
779
+ const canCreate: boolean = !this.shouldExcludeApiForPermissions(
780
+ model.getCreatePermissions(),
781
+ );
782
+ const canRead: boolean = !this.shouldExcludeApiForPermissions(
783
+ model.getReadPermissions(),
784
+ );
785
+ const canUpdate: boolean = !this.shouldExcludeApiForPermissions(
786
+ model.getUpdatePermissions(),
787
+ );
788
+ const canDelete: boolean = !this.shouldExcludeApiForPermissions(
789
+ model.getDeletePermissions(),
790
+ );
791
+
792
+ if (canCreate) {
771
793
  const createSchema: AnalyticsModelSchemaType =
772
794
  AnalyticsModelSchema.getCreateModelSchema({
773
795
  modelType,
@@ -775,8 +797,7 @@ export default class OpenAPIUtil {
775
797
  registry.register(`${tableName}CreateSchema`, createSchema);
776
798
  }
777
799
 
778
- // Check if model has read permissions and should not exclude API generation
779
- if (!this.shouldExcludeApiForPermissions(model.getReadPermissions())) {
800
+ if (canRead || canCreate || canUpdate) {
780
801
  const readSchema: AnalyticsModelSchemaType =
781
802
  AnalyticsModelSchema.getModelSchema({
782
803
  modelType,
@@ -784,8 +805,7 @@ export default class OpenAPIUtil {
784
805
  registry.register(`${tableName}ReadSchema`, readSchema);
785
806
  }
786
807
 
787
- // Check if model has update permissions and should not exclude API generation
788
- if (!this.shouldExcludeApiForPermissions(model.getUpdatePermissions())) {
808
+ if (canUpdate) {
789
809
  const updateSchema: AnalyticsModelSchemaType =
790
810
  AnalyticsModelSchema.getCreateModelSchema({
791
811
  modelType,
@@ -793,8 +813,7 @@ export default class OpenAPIUtil {
793
813
  registry.register(`${tableName}UpdateSchema`, updateSchema);
794
814
  }
795
815
 
796
- // Check if model has delete permissions and should not exclude API generation
797
- if (!this.shouldExcludeApiForPermissions(model.getDeletePermissions())) {
816
+ if (canDelete) {
798
817
  const deleteSchema: AnalyticsModelSchemaType =
799
818
  AnalyticsModelSchema.getModelSchema({
800
819
  modelType,
@@ -922,28 +922,28 @@ export class PermissionHelper {
922
922
  title: "Create Dashboard",
923
923
  description: "This permission can create Dashboards of this project",
924
924
  isAssignableToTenant: true,
925
- isAccessControlPermission: false,
925
+ isAccessControlPermission: true,
926
926
  },
927
927
  {
928
928
  permission: Permission.DeleteDashboard,
929
929
  title: "Delete Dashboard",
930
930
  description: "This permission can delete Dashboard of this project.",
931
931
  isAssignableToTenant: true,
932
- isAccessControlPermission: false,
932
+ isAccessControlPermission: true,
933
933
  },
934
934
  {
935
935
  permission: Permission.EditDashboard,
936
936
  title: "Edit Dashboard",
937
937
  description: "This permission can edit Dashboards of this project.",
938
938
  isAssignableToTenant: true,
939
- isAccessControlPermission: false,
939
+ isAccessControlPermission: true,
940
940
  },
941
941
  {
942
942
  permission: Permission.ReadDashboard,
943
943
  title: "Read Dashboard",
944
944
  description: "This permission can read Dashboards of this project.",
945
945
  isAssignableToTenant: true,
946
- isAccessControlPermission: false,
946
+ isAccessControlPermission: true,
947
947
  },
948
948
 
949
949
  // Table view permissions
@@ -139,6 +139,11 @@ export class Service extends DatabaseService {
139
139
  });
140
140
  const statusPageURL = await StatusPageService.getStatusPageURL(statuspage.id);
141
141
  const statusPageName = statuspage.pageTitle || statuspage.name || "Status Page";
142
+ const scheduledEventDetailsUrl = event.id && statusPageURL
143
+ ? URL.fromString(statusPageURL)
144
+ .addRoute(`/scheduled-events/${event.id.toString()}`)
145
+ .toString()
146
+ : statusPageURL;
142
147
  // Send email to Email subscribers.
143
148
  const resourcesAffected = ((_f = statusPageToResources[statuspage._id]) === null || _f === void 0 ? void 0 : _f.map((r) => {
144
149
  return r.displayName;
@@ -211,6 +216,7 @@ ${resourcesAffected ? `**Resources Affected:** ${resourcesAffected}` : ""}
211
216
  vars: {
212
217
  statusPageName: statusPageName,
213
218
  statusPageUrl: statusPageURL,
219
+ detailsUrl: scheduledEventDetailsUrl,
214
220
  logoUrl: statuspage.logoFileId && statusPageIdString
215
221
  ? new URL(httpProtocol, host)
216
222
  .addRoute(StatusPageApiRoute)
@@ -267,17 +273,33 @@ ${resourcesAffected ? `**Resources Affected:** ${resourcesAffected}` : ""}
267
273
  }
268
274
  const startsAt = updateBy.data.startsAt ||
269
275
  scheduledMaintenance.startsAt;
270
- const notificationSettings = updateBy.data
271
- .sendSubscriberNotificationsOnBeforeTheEvent ||
272
- scheduledMaintenance.sendSubscriberNotificationsOnBeforeTheEvent;
276
+ let notificationSettings = null;
277
+ const updatedNotificationSettings = updateBy.data.sendSubscriberNotificationsOnBeforeTheEvent;
278
+ if (updatedNotificationSettings !== null &&
279
+ updatedNotificationSettings !== undefined) {
280
+ notificationSettings = updatedNotificationSettings;
281
+ }
282
+ else {
283
+ const existingNotificationSettings = scheduledMaintenance.sendSubscriberNotificationsOnBeforeTheEvent;
284
+ if (existingNotificationSettings !== null &&
285
+ existingNotificationSettings !== undefined) {
286
+ notificationSettings = existingNotificationSettings;
287
+ }
288
+ }
273
289
  logger.debug(`Using startsAt: ${startsAt} and notificationSettings: ${JSON.stringify(notificationSettings)}`);
274
- const nextTimeToNotifyBeforeTheEvent = this.getNextTimeToNotify({
275
- eventScheduledDate: startsAt,
276
- sendSubscriberNotifiationsOn: notificationSettings,
277
- });
278
- updateBy.data.nextSubscriberNotificationBeforeTheEventAt =
279
- nextTimeToNotifyBeforeTheEvent;
280
- logger.debug(`nextSubscriberNotificationBeforeTheEventAt set to: ${nextTimeToNotifyBeforeTheEvent}`);
290
+ if (!notificationSettings || notificationSettings.length === 0) {
291
+ logger.debug("No subscriber notification schedule configured. Clearing nextSubscriberNotificationBeforeTheEventAt.");
292
+ updateBy.data.nextSubscriberNotificationBeforeTheEventAt = null;
293
+ }
294
+ else {
295
+ const nextTimeToNotifyBeforeTheEvent = this.getNextTimeToNotify({
296
+ eventScheduledDate: startsAt,
297
+ sendSubscriberNotifiationsOn: notificationSettings,
298
+ });
299
+ updateBy.data.nextSubscriberNotificationBeforeTheEventAt =
300
+ nextTimeToNotifyBeforeTheEvent;
301
+ logger.debug(`nextSubscriberNotificationBeforeTheEventAt set to: ${nextTimeToNotifyBeforeTheEvent}`);
302
+ }
281
303
  }
282
304
  // Set notification status based on shouldStatusPageSubscribersBeNotifiedOnEventCreated if it's being updated
283
305
  if (updateBy.data.shouldStatusPageSubscribersBeNotifiedOnEventCreated !==
@@ -337,7 +359,17 @@ ${resourcesAffected ? `**Resources Affected:** ${resourcesAffected}` : ""}
337
359
  logger.debug(`getNextTimeToNotify: `);
338
360
  logger.debug(data);
339
361
  logger.debug(`Calculating next time to notify for event scheduled date: ${data.eventScheduledDate}`);
340
- for (const recurringItem of data.sendSubscriberNotifiationsOn) {
362
+ const notificationSchedules = Array.isArray(data.sendSubscriberNotifiationsOn)
363
+ ? data.sendSubscriberNotifiationsOn
364
+ : [];
365
+ if (notificationSchedules.length === 0) {
366
+ logger.debug("No sendSubscriberNotifiationsOn entries. Returning null for next notification time.");
367
+ return null;
368
+ }
369
+ for (const recurringItem of notificationSchedules) {
370
+ if (!recurringItem) {
371
+ continue;
372
+ }
341
373
  const notificationDate = Recurring.getNextDateInterval(data.eventScheduledDate, recurringItem, true);
342
374
  logger.debug(`Notification date calculated: ${notificationDate} for recurring item: ${recurringItem}`);
343
375
  // if this date is in the future. set it to recurring date.