@oneuptime/common 11.3.20 → 11.3.22

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 (212) hide show
  1. package/Models/AnalyticsModels/AnalyticsBaseModel/CommonModel.ts +2 -0
  2. package/Models/AnalyticsModels/ExceptionInstance.ts +1 -0
  3. package/Models/AnalyticsModels/Log.ts +1 -0
  4. package/Models/AnalyticsModels/Metric.ts +1 -0
  5. package/Models/AnalyticsModels/MonitorLog.ts +1 -0
  6. package/Models/AnalyticsModels/Span.ts +1 -0
  7. package/Models/DatabaseModels/Alert.ts +100 -0
  8. package/Models/DatabaseModels/AlertInternalNote.ts +2 -0
  9. package/Models/DatabaseModels/AlertReminderRule.ts +597 -0
  10. package/Models/DatabaseModels/AlertSeverity.ts +2 -0
  11. package/Models/DatabaseModels/AlertState.ts +2 -0
  12. package/Models/DatabaseModels/AlertStateTimeline.ts +2 -0
  13. package/Models/DatabaseModels/EnterpriseLicenseInstance.ts +173 -0
  14. package/Models/DatabaseModels/GlobalConfig.ts +19 -0
  15. package/Models/DatabaseModels/Incident.ts +100 -0
  16. package/Models/DatabaseModels/IncidentInternalNote.ts +2 -0
  17. package/Models/DatabaseModels/IncidentPublicNote.ts +2 -0
  18. package/Models/DatabaseModels/IncidentReminderRule.ts +597 -0
  19. package/Models/DatabaseModels/IncidentSeverity.ts +2 -0
  20. package/Models/DatabaseModels/IncidentStateTimeline.ts +2 -0
  21. package/Models/DatabaseModels/Index.ts +8 -0
  22. package/Models/DatabaseModels/Label.ts +2 -0
  23. package/Models/DatabaseModels/MonitorStatusTimeline.ts +2 -0
  24. package/Models/DatabaseModels/ScheduledMaintenance.ts +100 -0
  25. package/Models/DatabaseModels/ScheduledMaintenanceReminderRule.ts +585 -0
  26. package/Models/DatabaseModels/ScheduledMaintenanceState.ts +2 -0
  27. package/Models/DatabaseModels/ScheduledMaintenanceStateTimeline.ts +2 -0
  28. package/Models/DatabaseModels/StatusPageAnnouncement.ts +2 -0
  29. package/Server/API/DashboardAPI.ts +221 -0
  30. package/Server/API/EnterpriseLicenseAPI.ts +279 -12
  31. package/Server/API/GlobalConfigAPI.ts +81 -16
  32. package/Server/API/StatusPageAPI.ts +894 -791
  33. package/Server/EnvironmentConfig.ts +9 -0
  34. package/Server/Infrastructure/Postgres/SchemaMigrations/1783226868418-AddEnterpriseLicenseInstance.ts +47 -0
  35. package/Server/Infrastructure/Postgres/SchemaMigrations/1783234451106-AddIncidentAlertReminderRules.ts +211 -0
  36. package/Server/Infrastructure/Postgres/SchemaMigrations/1783320000000-AddScheduledMaintenanceReminderRules.ts +113 -0
  37. package/Server/Infrastructure/Postgres/SchemaMigrations/1783330000000-AddRemindWhileScheduledToScheduledMaintenanceReminderRule.ts +20 -0
  38. package/Server/Infrastructure/Postgres/SchemaMigrations/1783340000000-AddLabelsToIncidentAndAlertReminderRules.ts +69 -0
  39. package/Server/Infrastructure/Postgres/SchemaMigrations/Index.ts +10 -0
  40. package/Server/Services/AlertReminderRuleService.ts +253 -0
  41. package/Server/Services/AlertService.ts +106 -0
  42. package/Server/Services/EnterpriseLicenseInstanceService.ts +10 -0
  43. package/Server/Services/IncidentReminderRuleService.ts +256 -0
  44. package/Server/Services/IncidentService.ts +143 -0
  45. package/Server/Services/Index.ts +8 -0
  46. package/Server/Services/IoTDeviceService.ts +99 -4
  47. package/Server/Services/ScheduledMaintenanceReminderRuleService.ts +236 -0
  48. package/Server/Services/ScheduledMaintenanceService.ts +127 -0
  49. package/Server/Services/StatusPageService.ts +104 -45
  50. package/Server/Services/TelemetryExceptionService.ts +123 -0
  51. package/Server/Services/UserNotificationSettingService.ts +24 -0
  52. package/Server/Utils/AnalyticsDatabase/Statement.ts +7 -3
  53. package/Server/Utils/AnalyticsDatabase/StatementGenerator.ts +35 -0
  54. package/Server/Utils/Monitor/Criteria/MetricMonitorCriteria.ts +6 -4
  55. package/Server/Utils/Monitor/MonitorCriteriaDataExtractor.ts +7 -6
  56. package/Server/Utils/Monitor/MonitorCriteriaObservationBuilder.ts +6 -4
  57. package/Server/Utils/Telemetry/IoTSnapshotScan.ts +45 -7
  58. package/Server/Utils/WhatsAppTemplateUtil.ts +10 -0
  59. package/ServiceRoute.ts +4 -0
  60. package/Tests/Server/Services/IoTDeviceService.test.ts +280 -0
  61. package/Tests/Server/Utils/AnalyticsDatabase/StatementGenerator.test.ts +49 -0
  62. package/Tests/Server/Utils/Monitor/Criteria/TelemetryStepMetricAlias.test.ts +390 -0
  63. package/Tests/Server/Utils/Telemetry/IoTSnapshotScan.test.ts +202 -0
  64. package/Tests/Utils/EnterpriseLicenseUsage.test.ts +165 -0
  65. package/Types/Email/EmailTemplateType.ts +3 -0
  66. package/Types/EnterpriseLicense/EnterpriseLicenseInstanceSummary.ts +18 -0
  67. package/Types/Monitor/MonitorStep.ts +32 -0
  68. package/Types/NotificationSetting/NotificationSettingEventType.ts +3 -0
  69. package/Types/Permission.ts +144 -0
  70. package/Types/Reminder/ReminderStopState.ts +6 -0
  71. package/Types/Reminder/ScheduledMaintenanceReminderStopState.ts +6 -0
  72. package/Types/WhatsApp/WhatsAppTemplates.ts +14 -0
  73. package/UI/Components/EditionLabel/EditionLabel.tsx +172 -0
  74. package/Utils/EnterpriseLicense/EnterpriseLicenseUsage.ts +125 -0
  75. package/build/dist/Models/AnalyticsModels/AnalyticsBaseModel/CommonModel.js.map +1 -1
  76. package/build/dist/Models/AnalyticsModels/ExceptionInstance.js +1 -0
  77. package/build/dist/Models/AnalyticsModels/ExceptionInstance.js.map +1 -1
  78. package/build/dist/Models/AnalyticsModels/Log.js +1 -0
  79. package/build/dist/Models/AnalyticsModels/Log.js.map +1 -1
  80. package/build/dist/Models/AnalyticsModels/Metric.js +1 -0
  81. package/build/dist/Models/AnalyticsModels/Metric.js.map +1 -1
  82. package/build/dist/Models/AnalyticsModels/MonitorLog.js +1 -0
  83. package/build/dist/Models/AnalyticsModels/MonitorLog.js.map +1 -1
  84. package/build/dist/Models/AnalyticsModels/Span.js +1 -0
  85. package/build/dist/Models/AnalyticsModels/Span.js.map +1 -1
  86. package/build/dist/Models/DatabaseModels/Alert.js +103 -0
  87. package/build/dist/Models/DatabaseModels/Alert.js.map +1 -1
  88. package/build/dist/Models/DatabaseModels/AlertInternalNote.js +2 -0
  89. package/build/dist/Models/DatabaseModels/AlertInternalNote.js.map +1 -1
  90. package/build/dist/Models/DatabaseModels/AlertReminderRule.js +605 -0
  91. package/build/dist/Models/DatabaseModels/AlertReminderRule.js.map +1 -0
  92. package/build/dist/Models/DatabaseModels/AlertSeverity.js +2 -0
  93. package/build/dist/Models/DatabaseModels/AlertSeverity.js.map +1 -1
  94. package/build/dist/Models/DatabaseModels/AlertState.js +2 -0
  95. package/build/dist/Models/DatabaseModels/AlertState.js.map +1 -1
  96. package/build/dist/Models/DatabaseModels/AlertStateTimeline.js +2 -0
  97. package/build/dist/Models/DatabaseModels/AlertStateTimeline.js.map +1 -1
  98. package/build/dist/Models/DatabaseModels/EnterpriseLicenseInstance.js +197 -0
  99. package/build/dist/Models/DatabaseModels/EnterpriseLicenseInstance.js.map +1 -0
  100. package/build/dist/Models/DatabaseModels/GlobalConfig.js +18 -0
  101. package/build/dist/Models/DatabaseModels/GlobalConfig.js.map +1 -1
  102. package/build/dist/Models/DatabaseModels/Incident.js +103 -0
  103. package/build/dist/Models/DatabaseModels/Incident.js.map +1 -1
  104. package/build/dist/Models/DatabaseModels/IncidentInternalNote.js +2 -0
  105. package/build/dist/Models/DatabaseModels/IncidentInternalNote.js.map +1 -1
  106. package/build/dist/Models/DatabaseModels/IncidentPublicNote.js +2 -0
  107. package/build/dist/Models/DatabaseModels/IncidentPublicNote.js.map +1 -1
  108. package/build/dist/Models/DatabaseModels/IncidentReminderRule.js +605 -0
  109. package/build/dist/Models/DatabaseModels/IncidentReminderRule.js.map +1 -0
  110. package/build/dist/Models/DatabaseModels/IncidentSeverity.js +2 -0
  111. package/build/dist/Models/DatabaseModels/IncidentSeverity.js.map +1 -1
  112. package/build/dist/Models/DatabaseModels/IncidentStateTimeline.js +2 -0
  113. package/build/dist/Models/DatabaseModels/IncidentStateTimeline.js.map +1 -1
  114. package/build/dist/Models/DatabaseModels/Index.js +8 -0
  115. package/build/dist/Models/DatabaseModels/Index.js.map +1 -1
  116. package/build/dist/Models/DatabaseModels/Label.js +2 -0
  117. package/build/dist/Models/DatabaseModels/Label.js.map +1 -1
  118. package/build/dist/Models/DatabaseModels/MonitorStatusTimeline.js +2 -0
  119. package/build/dist/Models/DatabaseModels/MonitorStatusTimeline.js.map +1 -1
  120. package/build/dist/Models/DatabaseModels/ScheduledMaintenance.js +103 -0
  121. package/build/dist/Models/DatabaseModels/ScheduledMaintenance.js.map +1 -1
  122. package/build/dist/Models/DatabaseModels/ScheduledMaintenanceReminderRule.js +596 -0
  123. package/build/dist/Models/DatabaseModels/ScheduledMaintenanceReminderRule.js.map +1 -0
  124. package/build/dist/Models/DatabaseModels/ScheduledMaintenanceState.js +2 -0
  125. package/build/dist/Models/DatabaseModels/ScheduledMaintenanceState.js.map +1 -1
  126. package/build/dist/Models/DatabaseModels/ScheduledMaintenanceStateTimeline.js +2 -0
  127. package/build/dist/Models/DatabaseModels/ScheduledMaintenanceStateTimeline.js.map +1 -1
  128. package/build/dist/Models/DatabaseModels/StatusPageAnnouncement.js +2 -0
  129. package/build/dist/Models/DatabaseModels/StatusPageAnnouncement.js.map +1 -1
  130. package/build/dist/Server/API/DashboardAPI.js +166 -16
  131. package/build/dist/Server/API/DashboardAPI.js.map +1 -1
  132. package/build/dist/Server/API/EnterpriseLicenseAPI.js +203 -13
  133. package/build/dist/Server/API/EnterpriseLicenseAPI.js.map +1 -1
  134. package/build/dist/Server/API/GlobalConfigAPI.js +62 -15
  135. package/build/dist/Server/API/GlobalConfigAPI.js.map +1 -1
  136. package/build/dist/Server/API/StatusPageAPI.js +74 -31
  137. package/build/dist/Server/API/StatusPageAPI.js.map +1 -1
  138. package/build/dist/Server/EnvironmentConfig.js +6 -0
  139. package/build/dist/Server/EnvironmentConfig.js.map +1 -1
  140. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1783226868418-AddEnterpriseLicenseInstance.js +22 -0
  141. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1783226868418-AddEnterpriseLicenseInstance.js.map +1 -0
  142. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1783234451106-AddIncidentAlertReminderRules.js +78 -0
  143. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1783234451106-AddIncidentAlertReminderRules.js.map +1 -0
  144. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1783320000000-AddScheduledMaintenanceReminderRules.js +44 -0
  145. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1783320000000-AddScheduledMaintenanceReminderRules.js.map +1 -0
  146. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1783330000000-AddRemindWhileScheduledToScheduledMaintenanceReminderRule.js +12 -0
  147. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1783330000000-AddRemindWhileScheduledToScheduledMaintenanceReminderRule.js.map +1 -0
  148. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1783340000000-AddLabelsToIncidentAndAlertReminderRules.js +30 -0
  149. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1783340000000-AddLabelsToIncidentAndAlertReminderRules.js.map +1 -0
  150. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js +10 -0
  151. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js.map +1 -1
  152. package/build/dist/Server/Services/AlertReminderRuleService.js +228 -0
  153. package/build/dist/Server/Services/AlertReminderRuleService.js.map +1 -0
  154. package/build/dist/Server/Services/AlertService.js +87 -0
  155. package/build/dist/Server/Services/AlertService.js.map +1 -1
  156. package/build/dist/Server/Services/EnterpriseLicenseInstanceService.js +9 -0
  157. package/build/dist/Server/Services/EnterpriseLicenseInstanceService.js.map +1 -0
  158. package/build/dist/Server/Services/IncidentReminderRuleService.js +231 -0
  159. package/build/dist/Server/Services/IncidentReminderRuleService.js.map +1 -0
  160. package/build/dist/Server/Services/IncidentService.js +116 -0
  161. package/build/dist/Server/Services/IncidentService.js.map +1 -1
  162. package/build/dist/Server/Services/Index.js +8 -0
  163. package/build/dist/Server/Services/Index.js.map +1 -1
  164. package/build/dist/Server/Services/IoTDeviceService.js +66 -4
  165. package/build/dist/Server/Services/IoTDeviceService.js.map +1 -1
  166. package/build/dist/Server/Services/ScheduledMaintenanceReminderRuleService.js +214 -0
  167. package/build/dist/Server/Services/ScheduledMaintenanceReminderRuleService.js.map +1 -0
  168. package/build/dist/Server/Services/ScheduledMaintenanceService.js +102 -0
  169. package/build/dist/Server/Services/ScheduledMaintenanceService.js.map +1 -1
  170. package/build/dist/Server/Services/StatusPageService.js +91 -43
  171. package/build/dist/Server/Services/StatusPageService.js.map +1 -1
  172. package/build/dist/Server/Services/TelemetryExceptionService.js +101 -0
  173. package/build/dist/Server/Services/TelemetryExceptionService.js.map +1 -1
  174. package/build/dist/Server/Services/UserNotificationSettingService.js +6 -0
  175. package/build/dist/Server/Services/UserNotificationSettingService.js.map +1 -1
  176. package/build/dist/Server/Utils/AnalyticsDatabase/Statement.js +4 -2
  177. package/build/dist/Server/Utils/AnalyticsDatabase/Statement.js.map +1 -1
  178. package/build/dist/Server/Utils/AnalyticsDatabase/StatementGenerator.js +29 -0
  179. package/build/dist/Server/Utils/AnalyticsDatabase/StatementGenerator.js.map +1 -1
  180. package/build/dist/Server/Utils/Monitor/Criteria/MetricMonitorCriteria.js +4 -5
  181. package/build/dist/Server/Utils/Monitor/Criteria/MetricMonitorCriteria.js.map +1 -1
  182. package/build/dist/Server/Utils/Monitor/MonitorCriteriaDataExtractor.js +6 -5
  183. package/build/dist/Server/Utils/Monitor/MonitorCriteriaDataExtractor.js.map +1 -1
  184. package/build/dist/Server/Utils/Monitor/MonitorCriteriaObservationBuilder.js +9 -9
  185. package/build/dist/Server/Utils/Monitor/MonitorCriteriaObservationBuilder.js.map +1 -1
  186. package/build/dist/Server/Utils/Telemetry/IoTSnapshotScan.js +33 -2
  187. package/build/dist/Server/Utils/Telemetry/IoTSnapshotScan.js.map +1 -1
  188. package/build/dist/Server/Utils/WhatsAppTemplateUtil.js +6 -0
  189. package/build/dist/Server/Utils/WhatsAppTemplateUtil.js.map +1 -1
  190. package/build/dist/ServiceRoute.js +1 -0
  191. package/build/dist/ServiceRoute.js.map +1 -1
  192. package/build/dist/Types/Email/EmailTemplateType.js +3 -0
  193. package/build/dist/Types/Email/EmailTemplateType.js.map +1 -1
  194. package/build/dist/Types/EnterpriseLicense/EnterpriseLicenseInstanceSummary.js +2 -0
  195. package/build/dist/Types/EnterpriseLicense/EnterpriseLicenseInstanceSummary.js.map +1 -0
  196. package/build/dist/Types/Monitor/MonitorStep.js +25 -0
  197. package/build/dist/Types/Monitor/MonitorStep.js.map +1 -1
  198. package/build/dist/Types/NotificationSetting/NotificationSettingEventType.js +3 -0
  199. package/build/dist/Types/NotificationSetting/NotificationSettingEventType.js.map +1 -1
  200. package/build/dist/Types/Permission.js +126 -0
  201. package/build/dist/Types/Permission.js.map +1 -1
  202. package/build/dist/Types/Reminder/ReminderStopState.js +7 -0
  203. package/build/dist/Types/Reminder/ReminderStopState.js.map +1 -0
  204. package/build/dist/Types/Reminder/ScheduledMaintenanceReminderStopState.js +7 -0
  205. package/build/dist/Types/Reminder/ScheduledMaintenanceReminderStopState.js.map +1 -0
  206. package/build/dist/Types/WhatsApp/WhatsAppTemplates.js +9 -0
  207. package/build/dist/Types/WhatsApp/WhatsAppTemplates.js.map +1 -1
  208. package/build/dist/UI/Components/EditionLabel/EditionLabel.js +92 -0
  209. package/build/dist/UI/Components/EditionLabel/EditionLabel.js.map +1 -1
  210. package/build/dist/Utils/EnterpriseLicense/EnterpriseLicenseUsage.js +83 -0
  211. package/build/dist/Utils/EnterpriseLicense/EnterpriseLicenseUsage.js.map +1 -0
  212. package/package.json +1 -1
@@ -8,6 +8,8 @@ import ScheduledMaintenanceOwnerTeamService from "./ScheduledMaintenanceOwnerTea
8
8
  import ScheduledMaintenanceOwnerUserService from "./ScheduledMaintenanceOwnerUserService";
9
9
  import ScheduledMaintenanceStateService from "./ScheduledMaintenanceStateService";
10
10
  import ScheduledMaintenanceStateTimelineService from "./ScheduledMaintenanceStateTimelineService";
11
+ import ScheduledMaintenanceReminderRuleService from "./ScheduledMaintenanceReminderRuleService";
12
+ import ScheduledMaintenanceReminderRule from "../../Models/DatabaseModels/ScheduledMaintenanceReminderRule";
11
13
  import TeamMemberService from "./TeamMemberService";
12
14
  import URL from "../../Types/API/URL";
13
15
  import DatabaseCommonInteractionProps from "../../Types/BaseDatabase/DatabaseCommonInteractionProps";
@@ -809,6 +811,80 @@ ${resourcesAffected ? `**Resources Affected:** ${resourcesAffected}` : ""}
809
811
  return { createBy, carryForward: null };
810
812
  }
811
813
 
814
+ @CaptureSpan()
815
+ public async refreshReminderSchedule(data: {
816
+ scheduledMaintenanceId: ObjectID;
817
+ projectId: ObjectID;
818
+ }): Promise<void> {
819
+ const scheduledMaintenance: Model | null = await this.findOneById({
820
+ id: data.scheduledMaintenanceId,
821
+ select: {
822
+ enableReminders: true,
823
+ startsAt: true,
824
+ labels: {
825
+ _id: true,
826
+ },
827
+ },
828
+ props: {
829
+ isRoot: true,
830
+ },
831
+ });
832
+
833
+ if (!scheduledMaintenance) {
834
+ return;
835
+ }
836
+
837
+ let nextReminderNotificationAt: Date | null = null;
838
+
839
+ if (scheduledMaintenance.enableReminders !== false) {
840
+ const matchingRule: ScheduledMaintenanceReminderRule | null =
841
+ await ScheduledMaintenanceReminderRuleService.findMatchingRule({
842
+ projectId: data.projectId,
843
+ labelIds: scheduledMaintenance.labels?.map((label: Label) => {
844
+ return label.id!;
845
+ }),
846
+ });
847
+
848
+ if (
849
+ matchingRule &&
850
+ matchingRule.reminderIntervalInMinutes &&
851
+ !(await this.isScheduledMaintenanceCompleted({
852
+ scheduledMaintenanceId: data.scheduledMaintenanceId,
853
+ }))
854
+ ) {
855
+ /*
856
+ * When the rule does not remind while the event is still scheduled,
857
+ * defer the first reminder until after the event has started so that
858
+ * owners are not notified about an event that has not begun yet.
859
+ */
860
+ let referenceDate: Date = OneUptimeDate.getCurrentDate();
861
+
862
+ if (
863
+ !matchingRule.remindWhileScheduled &&
864
+ scheduledMaintenance.startsAt &&
865
+ OneUptimeDate.isInTheFuture(scheduledMaintenance.startsAt)
866
+ ) {
867
+ referenceDate = scheduledMaintenance.startsAt;
868
+ }
869
+
870
+ nextReminderNotificationAt = OneUptimeDate.addRemoveMinutes(
871
+ referenceDate,
872
+ matchingRule.reminderIntervalInMinutes,
873
+ );
874
+ }
875
+ }
876
+
877
+ await this.updateOneById({
878
+ id: data.scheduledMaintenanceId,
879
+ data: {
880
+ nextReminderNotificationAt: nextReminderNotificationAt,
881
+ },
882
+ props: {
883
+ isRoot: true,
884
+ },
885
+ });
886
+ }
887
+
812
888
  @CaptureSpan()
813
889
  protected override async onCreateSuccess(
814
890
  onCreate: OnCreate<Model>,
@@ -985,6 +1061,26 @@ ${resourcesAffected ? `**Resources Affected:** ${resourcesAffected}` : ""}
985
1061
  );
986
1062
  }
987
1063
  })
1064
+ .then(async () => {
1065
+ // Schedule reminder notifications for scheduled maintenance if a matching reminder rule exists
1066
+ try {
1067
+ if (createdItem.projectId && createdItem.id) {
1068
+ await this.refreshReminderSchedule({
1069
+ scheduledMaintenanceId: createdItem.id,
1070
+ projectId: createdItem.projectId,
1071
+ });
1072
+ }
1073
+ } catch (error) {
1074
+ logger.error(
1075
+ `Reminder scheduling failed in ScheduledMaintenanceService.onCreateSuccess: ${error}`,
1076
+ {
1077
+ projectId: createdItem.projectId?.toString(),
1078
+ scheduledMaintenanceId: createdItem.id?.toString(),
1079
+ userId: createdItem.createdByUserId?.toString(),
1080
+ } as LogAttributes,
1081
+ );
1082
+ }
1083
+ })
988
1084
  .catch((error: Error) => {
989
1085
  logger.error(
990
1086
  `Critical error in ScheduledMaintenanceService sequential operations: ${error}`,
@@ -1577,6 +1673,37 @@ ${labels
1577
1673
  },
1578
1674
  );
1579
1675
  }
1676
+
1677
+ /*
1678
+ * Re-evaluate reminder schedule when reminders are enabled or disabled,
1679
+ * or when labels change (which may change the matching reminder rule).
1680
+ */
1681
+ if (
1682
+ onUpdate.updateBy.props.tenantId &&
1683
+ (Object.prototype.hasOwnProperty.call(
1684
+ onUpdate.updateBy.data,
1685
+ "enableReminders",
1686
+ ) ||
1687
+ Object.prototype.hasOwnProperty.call(
1688
+ onUpdate.updateBy.data,
1689
+ "labels",
1690
+ ))
1691
+ ) {
1692
+ try {
1693
+ await this.refreshReminderSchedule({
1694
+ scheduledMaintenanceId: scheduledMaintenanceId,
1695
+ projectId: onUpdate.updateBy.props.tenantId as ObjectID,
1696
+ });
1697
+ } catch (reminderError) {
1698
+ logger.error(
1699
+ `Reminder rescheduling failed in ScheduledMaintenanceService.onUpdateSuccess: ${reminderError}`,
1700
+ {
1701
+ projectId: onUpdate.updateBy.props.tenantId?.toString(),
1702
+ scheduledMaintenanceId: scheduledMaintenanceId?.toString(),
1703
+ } as LogAttributes,
1704
+ );
1705
+ }
1706
+ }
1580
1707
  }
1581
1708
  }
1582
1709
 
@@ -37,6 +37,7 @@ import User from "../../Models/DatabaseModels/User";
37
37
  import {
38
38
  AllowedStatusPageCountInFreePlan,
39
39
  IsBillingEnabled,
40
+ LetsEncryptAccountKey,
40
41
  } from "../EnvironmentConfig";
41
42
  import { PlanType } from "../../Types/Billing/SubscriptionPlan";
42
43
  import Recurring from "../../Types/Events/Recurring";
@@ -96,9 +97,9 @@ export class Service extends DatabaseService<StatusPage> {
96
97
  * Caches the resolved status page URL per statusPageId. `getStatusPageURL`
97
98
  * is called inside per-subscriber notification loops (see
98
99
  * `StatusPageSubscriberService`), where a single batch can fire N×
99
- * Postgres lookups against `StatusPageDomain`. SSL-provisioned custom
100
- * domains change rarely (provisioning takes minutes), so a 60s staleness
101
- * window is acceptable. Callers that need stronger consistency can call
100
+ * Postgres lookups against `StatusPageDomain`. Usable custom domains
101
+ * change rarely (provisioning/verification takes minutes), so a 60s
102
+ * staleness window is acceptable. Callers that need stronger consistency can call
102
103
  * `clearStatusPageUrlCache` after writes to the underlying domain.
103
104
  */
104
105
  private statusPageUrlCache: InMemoryTTLCache<string> = new InMemoryTTLCache(
@@ -679,32 +680,107 @@ export class Service extends DatabaseService<StatusPage> {
679
680
  return monitorStatusTimelines;
680
681
  }
681
682
 
683
+ /*
684
+ * Picks the custom domain to use when linking to this status page from
685
+ * subscriber notifications (email/SMS/Slack/Teams/webhooks) and SSO
686
+ * redirects. A domain is usable when any of these holds, in order of
687
+ * preference:
688
+ * 1. `isSslProvisioned` — OneUptime ordered the certificate and confirmed
689
+ * the domain serves HTTPS.
690
+ * 2. `isCustomCertificate` + `isCnameVerified` — the user uploaded their
691
+ * own certificate (the SSL provisioning jobs skip these domains, so
692
+ * `isSslProvisioned` never becomes true for them) and the CNAME check
693
+ * confirmed the domain actually routes to this instance. CNAME alone
694
+ * guards against certs uploaded before DNS is pointed.
695
+ * 3. `isCnameVerified`, but only when this instance has no Let's Encrypt
696
+ * account key — self-hosted installs that terminate TLS at their own
697
+ * proxy can never provision SSL, so CNAME verification is the
698
+ * strongest available signal that the domain routes to this instance.
699
+ * (With Let's Encrypt configured, a verified-but-unprovisioned domain
700
+ * is just mid-provisioning and will be picked up by rule 1 shortly.)
701
+ * Without rules 2 and 3, such status pages fall back to the installation
702
+ * URL in every subscriber notification
703
+ * (https://github.com/OneUptime/oneuptime/issues/1951).
704
+ */
682
705
  @CaptureSpan()
683
- public async getStatusPageURL(statusPageId: ObjectID): Promise<string> {
684
- const cacheKey: string = statusPageId.toString();
685
- const cached: string | undefined = this.statusPageUrlCache.get(cacheKey);
686
- if (cached !== undefined) {
687
- return cached;
688
- }
689
-
690
- const domain: StatusPageDomain | null =
691
- await StatusPageDomainService.findOneBy({
706
+ public async getUsableCustomDomain(
707
+ statusPageId: ObjectID,
708
+ ): Promise<StatusPageDomain | null> {
709
+ const domains: Array<StatusPageDomain> =
710
+ await StatusPageDomainService.findBy({
692
711
  query: {
693
712
  statusPageId: statusPageId,
694
- isSslProvisioned: true,
695
713
  },
696
714
  select: {
697
715
  fullDomain: true,
716
+ isSslProvisioned: true,
717
+ isCustomCertificate: true,
718
+ isCnameVerified: true,
698
719
  },
720
+ skip: 0,
721
+ limit: LIMIT_PER_PROJECT,
699
722
  props: {
700
723
  isRoot: true,
701
724
  ignoreHooks: true,
702
725
  },
703
726
  });
704
727
 
705
- let statusPageURL: string = domain?.fullDomain
706
- ? `https://${domain.fullDomain}`
707
- : "";
728
+ const domainsWithFullDomain: Array<StatusPageDomain> = domains.filter(
729
+ (domain: StatusPageDomain) => {
730
+ return Boolean(domain.fullDomain);
731
+ },
732
+ );
733
+
734
+ return (
735
+ domainsWithFullDomain.find((domain: StatusPageDomain) => {
736
+ return domain.isSslProvisioned;
737
+ }) ||
738
+ domainsWithFullDomain.find((domain: StatusPageDomain) => {
739
+ return domain.isCustomCertificate && domain.isCnameVerified;
740
+ }) ||
741
+ (!LetsEncryptAccountKey
742
+ ? domainsWithFullDomain.find((domain: StatusPageDomain) => {
743
+ return domain.isCnameVerified;
744
+ })
745
+ : undefined) ||
746
+ null
747
+ );
748
+ }
749
+
750
+ /*
751
+ * SSL-provisioned and custom-certificate domains are known to serve
752
+ * HTTPS. A CNAME-verified-only domain (self-hosted instance behind the
753
+ * user's own proxy) follows the instance protocol instead — CNAME
754
+ * verification can succeed over plain HTTP, so an HTTP-only install
755
+ * gets http:// links that actually work.
756
+ */
757
+ private async getProtocolForCustomDomain(
758
+ domain: StatusPageDomain,
759
+ ): Promise<Protocol> {
760
+ if (domain.isSslProvisioned || domain.isCustomCertificate) {
761
+ return Protocol.HTTPS;
762
+ }
763
+
764
+ return DatabaseConfig.getHttpProtocol();
765
+ }
766
+
767
+ @CaptureSpan()
768
+ public async getStatusPageURL(statusPageId: ObjectID): Promise<string> {
769
+ const cacheKey: string = statusPageId.toString();
770
+ const cached: string | undefined = this.statusPageUrlCache.get(cacheKey);
771
+ if (cached !== undefined) {
772
+ return cached;
773
+ }
774
+
775
+ const domain: StatusPageDomain | null =
776
+ await this.getUsableCustomDomain(statusPageId);
777
+
778
+ let statusPageURL: string = "";
779
+
780
+ if (domain?.fullDomain) {
781
+ const protocol: Protocol = await this.getProtocolForCustomDomain(domain);
782
+ statusPageURL = `${protocol}${domain.fullDomain}`;
783
+ }
708
784
 
709
785
  if (!statusPageURL) {
710
786
  const host: Hostname = await DatabaseConfig.getHost();
@@ -724,39 +800,22 @@ export class Service extends DatabaseService<StatusPage> {
724
800
 
725
801
  @CaptureSpan()
726
802
  public async getStatusPageFirstURL(statusPageId: ObjectID): Promise<string> {
727
- const domains: Array<StatusPageDomain> =
728
- await StatusPageDomainService.findBy({
729
- query: {
730
- statusPageId: statusPageId,
731
- isSslProvisioned: true,
732
- },
733
- select: {
734
- fullDomain: true,
735
- },
736
- skip: 0,
737
- limit: LIMIT_PER_PROJECT,
738
- props: {
739
- isRoot: true,
740
- ignoreHooks: true,
741
- },
742
- });
743
-
744
- let statusPageURL: string = "";
803
+ const domain: StatusPageDomain | null =
804
+ await this.getUsableCustomDomain(statusPageId);
745
805
 
746
- if (domains.length === 0) {
747
- const host: Hostname = await DatabaseConfig.getHost();
806
+ if (domain?.fullDomain) {
807
+ const protocol: Protocol = await this.getProtocolForCustomDomain(domain);
808
+ return `${protocol}${domain.fullDomain}`;
809
+ }
748
810
 
749
- const httpProtocol: Protocol = await DatabaseConfig.getHttpProtocol();
811
+ const host: Hostname = await DatabaseConfig.getHost();
750
812
 
751
- // 'https://local.oneuptime.com/status-page/40092fb5-cc33-4995-b532-b4e49c441c98'
752
- statusPageURL = new URL(httpProtocol, host)
753
- .addRoute("/status-page/" + statusPageId.toString())
754
- .toString();
755
- } else {
756
- statusPageURL = domains[0]?.fullDomain || "";
757
- }
813
+ const httpProtocol: Protocol = await DatabaseConfig.getHttpProtocol();
758
814
 
759
- return statusPageURL;
815
+ // 'https://local.oneuptime.com/status-page/40092fb5-cc33-4995-b532-b4e49c441c98'
816
+ return new URL(httpProtocol, host)
817
+ .addRoute("/status-page/" + statusPageId.toString())
818
+ .toString();
760
819
  }
761
820
 
762
821
  @CaptureSpan()
@@ -15,6 +15,17 @@ import AIAgentTaskTelemetryExceptionService from "./AIAgentTaskTelemetryExceptio
15
15
  import QueryHelper from "../Types/Database/QueryHelper";
16
16
  import ModelPermission from "../Types/Database/Permissions/Index";
17
17
  import CaptureSpan from "../Utils/Telemetry/CaptureSpan";
18
+ import Query from "../Types/Database/Query";
19
+ import Includes from "../../Types/BaseDatabase/Includes";
20
+ import logger from "../Utils/Logger";
21
+
22
+ /*
23
+ * Hard cap on the fingerprint NOT IN list handed to the ClickHouse count
24
+ * query. ClickHouse receives query params via the request URI (default
25
+ * http_max_uri_size 1 MiB ≈ 14k sha256 fingerprints); 5k keeps a wide
26
+ * safety margin while covering all realistic projects.
27
+ */
28
+ export const EXCLUDED_FINGERPRINT_LIMIT: number = 5000;
18
29
 
19
30
  export interface CreateAIAgentTaskForExceptionParams {
20
31
  telemetryExceptionId: ObjectID;
@@ -263,6 +274,118 @@ export class Service extends DatabaseService<Model> {
263
274
  };
264
275
  }
265
276
 
277
+ /**
278
+ * Fingerprints of exception groups marked resolved and/or archived —
279
+ * used by the exception monitor to exclude occurrences of those groups
280
+ * from the exception count. Resolution state lives on this Postgres
281
+ * model per (projectId, primaryEntityId, fingerprint) group, while
282
+ * occurrences live in ClickHouse; fingerprints are the join key.
283
+ * Fingerprints hash projectId + primaryEntityId into the digest, so a
284
+ * fingerprint never collides across services and excluding by
285
+ * fingerprint alone is exact.
286
+ *
287
+ * The result is capped at EXCLUDED_FINGERPRINT_LIMIT, keeping the most
288
+ * recently seen groups: the ClickHouse client ships query params in the
289
+ * request URI, so an unbounded NOT IN list would blow past ClickHouse's
290
+ * http_max_uri_size (~1 MiB, roughly 14k fingerprints) and hard-fail the
291
+ * monitor's count query. Overflow groups simply stay counted (fail-open:
292
+ * better a stale incident than a silently swallowed one), and the
293
+ * lastSeenAt-descending order means what gets dropped is the groups
294
+ * least likely to have occurrences in the monitor window.
295
+ *
296
+ * Deliberately NO lastSeenAt lower bound: ExceptionInstance.time is the
297
+ * client-reported occurrence timestamp while lastSeenAt is server-side
298
+ * ingest time, so any window-derived cutoff breaks under client clock
299
+ * skew (and under monitor configs with no time window at all).
300
+ */
301
+ @CaptureSpan()
302
+ public async getResolvedOrArchivedFingerprints(data: {
303
+ projectId: ObjectID;
304
+ telemetryServiceIds?: Array<ObjectID> | undefined;
305
+ resolved: boolean;
306
+ archived: boolean;
307
+ }): Promise<Array<string>> {
308
+ const statusFilters: Array<Query<Model>> = [];
309
+
310
+ if (data.resolved) {
311
+ statusFilters.push({ isResolved: true });
312
+ }
313
+
314
+ if (data.archived) {
315
+ statusFilters.push({ isArchived: true });
316
+ }
317
+
318
+ if (statusFilters.length === 0) {
319
+ return [];
320
+ }
321
+
322
+ // resolved-OR-archived needs two indexed lookups — findBy has no OR.
323
+ const resultsPerStatus: Array<Array<Model>> = await Promise.all(
324
+ statusFilters.map(
325
+ async (statusFilter: Query<Model>): Promise<Array<Model>> => {
326
+ const query: Query<Model> = {
327
+ projectId: data.projectId,
328
+ ...statusFilter,
329
+ };
330
+
331
+ if (data.telemetryServiceIds && data.telemetryServiceIds.length > 0) {
332
+ query.primaryEntityId = new Includes(data.telemetryServiceIds);
333
+ }
334
+
335
+ /*
336
+ * Per-status limit equals the final cap: the merged most-recent
337
+ * EXCLUDED_FINGERPRINT_LIMIT groups are necessarily within the
338
+ * top EXCLUDED_FINGERPRINT_LIMIT of their own status query.
339
+ */
340
+ return this.findBy({
341
+ query: query,
342
+ select: {
343
+ fingerprint: true,
344
+ lastSeenAt: true,
345
+ },
346
+ sort: {
347
+ lastSeenAt: SortOrder.Descending,
348
+ },
349
+ limit: EXCLUDED_FINGERPRINT_LIMIT,
350
+ skip: 0,
351
+ props: {
352
+ isRoot: true,
353
+ },
354
+ });
355
+ },
356
+ ),
357
+ );
358
+
359
+ // Merge the status lists most-recently-seen first, dedupe, then cap.
360
+ const merged: Array<Model> = resultsPerStatus
361
+ .flat()
362
+ .sort((a: Model, b: Model): number => {
363
+ return (b.lastSeenAt?.getTime() || 0) - (a.lastSeenAt?.getTime() || 0);
364
+ });
365
+
366
+ const fingerprints: Set<string> = new Set<string>();
367
+ let truncated: boolean = false;
368
+
369
+ for (const exception of merged) {
370
+ if (!exception.fingerprint) {
371
+ continue;
372
+ }
373
+ if (fingerprints.size >= EXCLUDED_FINGERPRINT_LIMIT) {
374
+ truncated = true;
375
+ break;
376
+ }
377
+ fingerprints.add(exception.fingerprint);
378
+ }
379
+
380
+ if (truncated) {
381
+ logger.warn(
382
+ `TelemetryException fingerprint exclusion list for project ${data.projectId.toString()} exceeds ${EXCLUDED_FINGERPRINT_LIMIT}; least recently seen resolved/archived exception groups will still be counted by exception monitors.`,
383
+ );
384
+ }
385
+
386
+ return Array.from(fingerprints);
387
+ }
388
+
266
389
  @CaptureSpan()
267
390
  private async aggregateUnresolvedByService(
268
391
  projectId: ObjectID,
@@ -580,6 +580,18 @@ export class Service extends DatabaseService<UserNotificationSetting> {
580
580
  await this.addAlertNotificationSettings(userId, projectId);
581
581
  await this.addAlertEpisodeNotificationSettings(userId, projectId);
582
582
  await this.addIncidentEpisodeNotificationSettings(userId, projectId);
583
+ await this.addScheduledMaintenanceNotificationSettings(userId, projectId);
584
+ }
585
+
586
+ private async addScheduledMaintenanceNotificationSettings(
587
+ userId: ObjectID,
588
+ projectId: ObjectID,
589
+ ): Promise<void> {
590
+ await this.addNotificationSettingIfNotExists(
591
+ userId,
592
+ projectId,
593
+ NotificationSettingEventType.SEND_SCHEDULED_MAINTENANCE_REMINDER_OWNER_NOTIFICATION,
594
+ );
583
595
  }
584
596
 
585
597
  private async addProbeOwnerNotificationSettings(
@@ -614,6 +626,12 @@ export class Service extends DatabaseService<UserNotificationSetting> {
614
626
  projectId,
615
627
  NotificationSettingEventType.SEND_INCIDENT_STATE_CHANGED_OWNER_NOTIFICATION,
616
628
  );
629
+
630
+ await this.addNotificationSettingIfNotExists(
631
+ userId,
632
+ projectId,
633
+ NotificationSettingEventType.SEND_INCIDENT_REMINDER_OWNER_NOTIFICATION,
634
+ );
617
635
  }
618
636
 
619
637
  private async addMonitorNotificationSettings(
@@ -689,6 +707,12 @@ export class Service extends DatabaseService<UserNotificationSetting> {
689
707
  projectId,
690
708
  NotificationSettingEventType.SEND_ALERT_STATE_CHANGED_OWNER_NOTIFICATION,
691
709
  );
710
+
711
+ await this.addNotificationSettingIfNotExists(
712
+ userId,
713
+ projectId,
714
+ NotificationSettingEventType.SEND_ALERT_REMINDER_OWNER_NOTIFICATION,
715
+ );
692
716
  }
693
717
 
694
718
  private async addAlertEpisodeNotificationSettings(
@@ -5,6 +5,7 @@ import TableColumnType from "../../../Types/AnalyticsDatabase/TableColumnType";
5
5
  import GreaterThan from "../../../Types/BaseDatabase/GreaterThan";
6
6
  import GreaterThanOrEqual from "../../../Types/BaseDatabase/GreaterThanOrEqual";
7
7
  import Includes from "../../../Types/BaseDatabase/Includes";
8
+ import IncludesNone from "../../../Types/BaseDatabase/IncludesNone";
8
9
  import LessThan from "../../../Types/BaseDatabase/LessThan";
9
10
  import LessThanOrEqual from "../../../Types/BaseDatabase/LessThanOrEqual";
10
11
  import LessThanOrNull from "../../../Types/BaseDatabase/LessThanOrNull";
@@ -108,7 +109,7 @@ export class Statement implements BaseQueryParams {
108
109
  v.value instanceof NotEqual
109
110
  ) {
110
111
  finalValue = v.value.value;
111
- } else if (v.value instanceof Includes) {
112
+ } else if (v.value instanceof Includes || v.value instanceof IncludesNone) {
112
113
  if (
113
114
  v.type === TableColumnType.Text ||
114
115
  v.type === TableColumnType.ObjectID
@@ -198,9 +199,12 @@ export class Statement implements BaseQueryParams {
198
199
  [TableColumnType.UInt64]: "UInt64",
199
200
  };
200
201
 
201
- if ((statementParam as StatementParameter).value instanceof Includes) {
202
+ if (
203
+ (statementParam as StatementParameter).value instanceof Includes ||
204
+ (statementParam as StatementParameter).value instanceof IncludesNone
205
+ ) {
202
206
  const includesValues: Array<string | number | ObjectID> = (
203
- (statementParam as StatementParameter).value as Includes
207
+ (statementParam as StatementParameter).value as Includes | IncludesNone
204
208
  ).values as Array<string | number | ObjectID>;
205
209
 
206
210
  const isNumberArray: boolean = includesValues.every(
@@ -675,6 +675,41 @@ export default class StatementGenerator<TBaseModel extends AnalyticsBaseModel> {
675
675
  type: tableColumn.type,
676
676
  }}`,
677
677
  );
678
+ } else if (
679
+ value instanceof IncludesNone &&
680
+ tableColumn.type === TableColumnType.ArrayText
681
+ ) {
682
+ /*
683
+ * Array(String) exclusion — the negation of the Includes/hasAny
684
+ * branch above. An empty IncludesNone excludes nothing, so it
685
+ * drops to no predicate (mirrors the empty-Includes behavior).
686
+ */
687
+ const arrayExcludeValues: Array<string> =
688
+ ((value as IncludesNone).values as Array<string>) || [];
689
+ if (arrayExcludeValues.length > 0) {
690
+ whereStatement.append(
691
+ SQL`AND NOT hasAny(${key}, ${{
692
+ value: arrayExcludeValues,
693
+ type: TableColumnType.ArrayText,
694
+ }})`,
695
+ );
696
+ }
697
+ } else if (value instanceof IncludesNone) {
698
+ /*
699
+ * Scalar exclusion ("is none of"): `col NOT IN (v1, v2, ...)`.
700
+ * An empty IncludesNone means "exclude nothing" — skip the
701
+ * predicate entirely rather than emitting `NOT IN ()`.
702
+ */
703
+ const excludeValues: Array<string | ObjectID | number> =
704
+ ((value as IncludesNone).values as Array<string | ObjectID>) || [];
705
+ if (excludeValues.length > 0) {
706
+ whereStatement.append(
707
+ SQL`AND ${key} NOT IN ${{
708
+ value: value,
709
+ type: tableColumn.type,
710
+ }}`,
711
+ );
712
+ }
678
713
  } else if (value instanceof IsNull) {
679
714
  if (tableColumn.type === TableColumnType.Text) {
680
715
  whereStatement.append(SQL`AND (${key} IS NULL OR ${key} = '')`);
@@ -3,6 +3,7 @@ import AggregatedResult from "../../../../Types/BaseDatabase/AggregatedResult";
3
3
  import MetricFormulaConfigData from "../../../../Types/Metrics/MetricFormulaConfigData";
4
4
  import MetricQueryConfigData from "../../../../Types/Metrics/MetricQueryConfigData";
5
5
  import MetricsAggregationType from "../../../../Types/Metrics/MetricsAggregationType";
6
+ import MetricsViewConfig from "../../../../Types/Metrics/MetricsViewConfig";
6
7
  import MetricMonitorResponse from "../../../../Types/Monitor/MetricMonitor/MetricMonitorResponse";
7
8
  import MetricCriteriaContext, {
8
9
  MetricAnomalyBaseline,
@@ -114,12 +115,13 @@ export default class MetricMonitorCriteria {
114
115
  const seriesBreakdown: Array<MetricSeriesResult> | undefined =
115
116
  metricResponse.seriesBreakdown;
116
117
 
118
+ const metricViewConfig: MetricsViewConfig | undefined =
119
+ MonitorStep.getMetricsViewConfig(input.monitorStep);
120
+
117
121
  const queryConfigs: Array<MetricQueryConfigData> =
118
- input.monitorStep.data?.metricMonitor?.metricViewConfig?.queryConfigs ||
119
- [];
122
+ metricViewConfig?.queryConfigs || [];
120
123
  const formulaConfigs: Array<MetricFormulaConfigData> =
121
- input.monitorStep.data?.metricMonitor?.metricViewConfig?.formulaConfigs ||
122
- [];
124
+ metricViewConfig?.formulaConfigs || [];
123
125
 
124
126
  const nativeUnitsByMetricName: { [key: string]: string } | undefined =
125
127
  metricResponse.nativeUnitsByMetricName;
@@ -14,6 +14,7 @@ import AggregatedResult from "../../../Types/BaseDatabase/AggregatedResult";
14
14
  import AggregateModel from "../../../Types/BaseDatabase/AggregatedModel";
15
15
  import MetricQueryConfigData from "../../../Types/Metrics/MetricQueryConfigData";
16
16
  import MetricFormulaConfigData from "../../../Types/Metrics/MetricFormulaConfigData";
17
+ import MetricsViewConfig from "../../../Types/Metrics/MetricsViewConfig";
17
18
  import ExceptionMonitorResponse from "../../../Types/Monitor/ExceptionMonitor/ExceptionMonitorResponse";
18
19
 
19
20
  export default class MonitorCriteriaDataExtractor {
@@ -156,10 +157,12 @@ export default class MonitorCriteriaDataExtractor {
156
157
 
157
158
  let result: AggregatedResult | undefined;
158
159
 
160
+ const metricViewConfig: MetricsViewConfig | undefined =
161
+ MonitorStep.getMetricsViewConfig(input.monitorStep);
162
+
159
163
  if (alias) {
160
164
  const queryConfigs: Array<MetricQueryConfigData> =
161
- input.monitorStep.data?.metricMonitor?.metricViewConfig?.queryConfigs ||
162
- [];
165
+ metricViewConfig?.queryConfigs || [];
163
166
 
164
167
  let aliasIndex: number = queryConfigs.findIndex(
165
168
  (queryConfig: MetricQueryConfigData) => {
@@ -169,8 +172,7 @@ export default class MonitorCriteriaDataExtractor {
169
172
 
170
173
  if (aliasIndex < 0) {
171
174
  const formulaConfigs: Array<MetricFormulaConfigData> =
172
- input.monitorStep.data?.metricMonitor?.metricViewConfig
173
- ?.formulaConfigs || [];
175
+ metricViewConfig?.formulaConfigs || [];
174
176
 
175
177
  const formulaIndex: number = formulaConfigs.findIndex(
176
178
  (formulaConfig: MetricFormulaConfigData) => {
@@ -192,8 +194,7 @@ export default class MonitorCriteriaDataExtractor {
192
194
  result = aggregatedResults[0];
193
195
  if (!alias) {
194
196
  const defaultAlias: string | undefined =
195
- input.monitorStep.data?.metricMonitor?.metricViewConfig
196
- ?.queryConfigs?.[0]?.metricAliasData?.metricVariable;
197
+ metricViewConfig?.queryConfigs?.[0]?.metricAliasData?.metricVariable;
197
198
  alias = defaultAlias || null;
198
199
  }
199
200
  }