@oneuptime/common 11.7.0 → 11.7.2

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 (264) hide show
  1. package/Models/DatabaseModels/DashboardDomain.ts +1 -0
  2. package/Models/DatabaseModels/Incident.ts +2 -0
  3. package/Models/DatabaseModels/IncidentEpisode.ts +1 -0
  4. package/Models/DatabaseModels/IncidentEpisodePublicNote.ts +1 -0
  5. package/Models/DatabaseModels/IncidentEpisodeStateTimeline.ts +1 -0
  6. package/Models/DatabaseModels/IncidentPublicNote.ts +1 -0
  7. package/Models/DatabaseModels/IncidentStateTimeline.ts +1 -0
  8. package/Models/DatabaseModels/Monitor.ts +1 -0
  9. package/Models/DatabaseModels/MonitorStatusTimeline.ts +1 -0
  10. package/Models/DatabaseModels/NetworkSite.ts +1 -0
  11. package/Models/DatabaseModels/NetworkSiteStatusTimeline.ts +1 -0
  12. package/Models/DatabaseModels/OnCallDutyPolicyExecutionLog.ts +1 -0
  13. package/Models/DatabaseModels/OnCallDutyPolicySchedule.ts +2 -0
  14. package/Models/DatabaseModels/Probe.ts +13 -13
  15. package/Models/DatabaseModels/ScheduledMaintenance.ts +1 -0
  16. package/Models/DatabaseModels/ScheduledMaintenancePublicNote.ts +1 -0
  17. package/Models/DatabaseModels/ScheduledMaintenanceStateTimeline.ts +1 -0
  18. package/Models/DatabaseModels/ScheduledMaintenanceTemplate.ts +1 -0
  19. package/Models/DatabaseModels/StatusPage.ts +1 -0
  20. package/Models/DatabaseModels/StatusPageAnnouncement.ts +1 -0
  21. package/Models/DatabaseModels/StatusPageDomain.ts +1 -0
  22. package/Models/DatabaseModels/WorkspaceNotificationSummary.ts +1 -0
  23. package/Models/DatabaseModels/WorkspaceProjectAuthToken.ts +11 -0
  24. package/Server/API/BaseAPI.ts +17 -1
  25. package/Server/API/CommonAPI.ts +32 -0
  26. package/Server/API/MicrosoftTeamsAPI.ts +99 -2
  27. package/Server/API/ProbeAPI.ts +2 -0
  28. package/Server/API/SlackAPI.ts +62 -0
  29. package/Server/API/StatusPageAPI.ts +157 -192
  30. package/Server/Infrastructure/Postgres/SchemaMigrations/1785140242697-AddHotQueryIndexes.ts +89 -0
  31. package/Server/Infrastructure/Postgres/SchemaMigrations/1785148065137-AddHotQueryIndexesSecondPass.ts +61 -0
  32. package/Server/Infrastructure/Postgres/SchemaMigrations/1785240000000-RepairCrossProjectMonitorStatusReferences.ts +280 -0
  33. package/Server/Infrastructure/Postgres/SchemaMigrations/Index.ts +6 -0
  34. package/Server/Services/DatabaseService.ts +11 -3
  35. package/Server/Services/MonitorGroupService.ts +145 -0
  36. package/Server/Services/MonitorService.ts +231 -30
  37. package/Server/Services/ProbeService.ts +319 -16
  38. package/Server/Services/StatusPageService.ts +72 -42
  39. package/Server/Services/WorkspaceNotificationRuleService.ts +232 -2
  40. package/Server/Utils/Database/PostgresErrorTranslator.ts +131 -0
  41. package/Server/Utils/Monitor/MonitorResource.ts +88 -35
  42. package/Server/Utils/Monitor/MonitorStatusTimeline.ts +21 -1
  43. package/Server/Utils/Monitor/MonitorStepsProjectValidator.ts +158 -0
  44. package/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeams.ts +706 -6
  45. package/Tests/Models/DatabaseModels/ProbeColumnAccessControl.test.ts +103 -0
  46. package/Tests/Server/API/BaseAPIUpdateNoOp.test.ts +135 -0
  47. package/Tests/Server/API/CommonAPIAuthGuard.test.ts +317 -0
  48. package/Tests/Server/API/MicrosoftTeamsManifest.test.ts +228 -0
  49. package/Tests/Server/API/ProbeAPI.test.ts +6 -0
  50. package/Tests/Server/API/ZZVerifyChatsAuth.test.ts +167 -0
  51. package/Tests/Server/Services/AddHotQueryIndexesMigration.test.ts +255 -0
  52. package/Tests/Server/Services/AddHotQueryIndexesSecondPassMigration.test.ts +258 -0
  53. package/Tests/Server/Services/DatabaseServiceSanitizeUpdateData.test.ts +1 -1
  54. package/Tests/Server/Services/DiscoveryScanClaimHookFreeSafety.test.ts +121 -0
  55. package/Tests/Server/Services/HeartbeatWriteFastPathSafety.test.ts +200 -0
  56. package/Tests/Server/Services/HookFreeWriteSafetySecondPass.test.ts +181 -0
  57. package/Tests/Server/Services/MonitorGroupServiceBatchedStatuses.test.ts +513 -0
  58. package/Tests/Server/Services/MonitorResourceProbeAgreementReuse.test.ts +492 -0
  59. package/Tests/Server/Services/MonitorServiceProbeSelection.test.ts +401 -0
  60. package/Tests/Server/Services/MonitorStatusTimelineFastPath.test.ts +453 -0
  61. package/Tests/Server/Services/ProbeAuthKeyCacheAndHeartbeat.test.ts +679 -0
  62. package/Tests/Server/Services/ServiceLevelObjectiveService.test.ts +3 -3
  63. package/Tests/Server/Services/StatusPageDomainResolutionCache.test.ts +245 -0
  64. package/Tests/Server/Services/StatusPageOverviewTimelineWindow.test.ts +283 -0
  65. package/Tests/Server/Services/StatusPageServiceMcp.test.ts +17 -1
  66. package/Tests/Server/Services/StatusPageServiceMonitorGroupBatching.test.ts +331 -0
  67. package/Tests/Server/Services/UserOnCallLogClaimNotificationRule.test.ts +12 -12
  68. package/Tests/Server/Services/WorkspaceNotificationRuleChats.test.ts +1390 -0
  69. package/Tests/Server/Utils/AI/Insights/InsightFixRouting.test.ts +4 -4
  70. package/Tests/Server/Utils/AI/Insights/InsightScanner.test.ts +2 -2
  71. package/Tests/Server/Utils/AI/Insights/InsightStore.test.ts +1 -3
  72. package/Tests/Server/Utils/AI/Insights/InsightStoreHardening.test.ts +1 -3
  73. package/Tests/Server/Utils/AI/Insights/InsightTriage.test.ts +1 -1
  74. package/Tests/Server/Utils/AI/Insights/InsightTriageRunner.test.ts +5 -5
  75. package/Tests/Server/Utils/Alert/AlertPrivacyFilter.test.ts +187 -0
  76. package/Tests/Server/Utils/AlertEpisode/AlertEpisodePrivacyFilter.test.ts +170 -0
  77. package/Tests/Server/Utils/Database/PostgresErrorTranslator.test.ts +125 -0
  78. package/Tests/Server/Utils/Incident/IncidentPrivacyFilter.test.ts +264 -0
  79. package/Tests/Server/Utils/IncidentEpisode/IncidentEpisodePrivacyFilter.test.ts +175 -0
  80. package/Tests/Server/Utils/Monitor/MonitorStepsProjectValidator.test.ts +181 -0
  81. package/Tests/Server/Utils/Monitor/NetworkDeviceWalkUtil.test.ts +1 -1
  82. package/Tests/Server/Utils/Monitor/NetworkInventoryUtil.test.ts +2 -2
  83. package/Tests/Server/Utils/Workspace/MicrosoftTeamsChannelsList.test.ts +341 -0
  84. package/Tests/Server/Utils/Workspace/MicrosoftTeamsChats.test.ts +2175 -0
  85. package/Tests/Server/Utils/Workspace/SlackChannelsList.test.ts +585 -0
  86. package/Tests/Types/Billing/MeteredPlan.test.ts +46 -0
  87. package/Tests/Types/DateUserTimezone.test.ts +205 -0
  88. package/Tests/Types/DateUserTimezoneOverridesBrowser.test.ts +73 -0
  89. package/Tests/Types/Workspace/NotificationRules/NotificationRuleCondition.test.ts +219 -1
  90. package/Tests/UI/Components/BulkUpdateForm.test.tsx +174 -0
  91. package/Tests/UI/Components/CardModelDetailEdit.test.tsx +315 -0
  92. package/Tests/UI/Components/ModelDetailSelect.test.tsx +229 -0
  93. package/Tests/UI/Components/ModelTable/BaseModelTableBulkSelectAll.test.tsx +1258 -0
  94. package/Tests/UI/Components/ModelTableExportFromColumns.test.ts +153 -0
  95. package/Tests/UI/Components/MonacoLoader.test.ts +67 -0
  96. package/Tests/UI/Components/MonacoRuntime.test.ts +121 -0
  97. package/Tests/UI/Components/TableBulkCsvExport.test.tsx +212 -3
  98. package/Tests/UI/Components/TimePicker/TimePicker.test.tsx +11 -0
  99. package/Tests/UI/Utils/Project.test.ts +809 -0
  100. package/Tests/UI/Utils/TableColumnsToCsv.test.ts +360 -0
  101. package/Tests/Utils/Alerts/AlertMetricType.test.ts +143 -0
  102. package/Tests/Utils/Dashboard/VariableUrlState.test.ts +219 -0
  103. package/Tests/Utils/Incident/IncidentMetricType.test.ts +174 -0
  104. package/Tests/Utils/Monitor/MonitorProbeSelectionUtil.test.ts +145 -0
  105. package/Tests/Utils/StatusPage/ResourceUptime.test.ts +263 -0
  106. package/Types/Date.ts +119 -50
  107. package/Types/Workspace/NotificationRules/BaseNotificationRule.ts +4 -0
  108. package/Types/Workspace/NotificationRules/NotificationRuleCondition.ts +25 -10
  109. package/Types/Workspace/WorkspaceMessagePayload.ts +1 -0
  110. package/UI/Components/BulkUpdate/BulkUpdateForm.tsx +63 -8
  111. package/UI/Components/CodeEditor/CodeEditor.tsx +3 -0
  112. package/UI/Components/CodeEditor/MonacoLoader.ts +36 -0
  113. package/UI/Components/Forms/Fields/FormField.tsx +13 -3
  114. package/UI/Components/Forms/ModelForm.tsx +41 -18
  115. package/UI/Components/Input/Input.tsx +6 -1
  116. package/UI/Components/ModelDetail/CardModelDetail.tsx +23 -1
  117. package/UI/Components/ModelDetail/ModelDetail.tsx +46 -1
  118. package/UI/Components/ModelTable/BaseModelTable.tsx +300 -30
  119. package/UI/Components/ModelTable/Column.ts +12 -0
  120. package/UI/Components/ModelTable/ExportFromColumns.ts +57 -0
  121. package/UI/Components/Navbar/NavBar.tsx +66 -1
  122. package/UI/Components/Table/Table.tsx +52 -13
  123. package/UI/Components/Table/Types/Column.ts +18 -0
  124. package/UI/Components/TimePicker/TimePicker.tsx +8 -4
  125. package/UI/Utils/Project.ts +214 -18
  126. package/UI/Utils/TableColumnsToCsv.ts +86 -6
  127. package/UI/Utils/User.ts +19 -0
  128. package/UI/esbuild-config.js +34 -0
  129. package/Utils/Monitor/MonitorProbeSelectionUtil.ts +72 -0
  130. package/build/dist/Models/DatabaseModels/DashboardDomain.js +1 -0
  131. package/build/dist/Models/DatabaseModels/DashboardDomain.js.map +1 -1
  132. package/build/dist/Models/DatabaseModels/Incident.js +2 -0
  133. package/build/dist/Models/DatabaseModels/Incident.js.map +1 -1
  134. package/build/dist/Models/DatabaseModels/IncidentEpisode.js +1 -0
  135. package/build/dist/Models/DatabaseModels/IncidentEpisode.js.map +1 -1
  136. package/build/dist/Models/DatabaseModels/IncidentEpisodePublicNote.js +1 -0
  137. package/build/dist/Models/DatabaseModels/IncidentEpisodePublicNote.js.map +1 -1
  138. package/build/dist/Models/DatabaseModels/IncidentEpisodeStateTimeline.js +1 -0
  139. package/build/dist/Models/DatabaseModels/IncidentEpisodeStateTimeline.js.map +1 -1
  140. package/build/dist/Models/DatabaseModels/IncidentPublicNote.js +1 -0
  141. package/build/dist/Models/DatabaseModels/IncidentPublicNote.js.map +1 -1
  142. package/build/dist/Models/DatabaseModels/IncidentStateTimeline.js +1 -0
  143. package/build/dist/Models/DatabaseModels/IncidentStateTimeline.js.map +1 -1
  144. package/build/dist/Models/DatabaseModels/Monitor.js +1 -0
  145. package/build/dist/Models/DatabaseModels/Monitor.js.map +1 -1
  146. package/build/dist/Models/DatabaseModels/MonitorStatusTimeline.js +1 -0
  147. package/build/dist/Models/DatabaseModels/MonitorStatusTimeline.js.map +1 -1
  148. package/build/dist/Models/DatabaseModels/NetworkSite.js +1 -0
  149. package/build/dist/Models/DatabaseModels/NetworkSite.js.map +1 -1
  150. package/build/dist/Models/DatabaseModels/NetworkSiteStatusTimeline.js +1 -0
  151. package/build/dist/Models/DatabaseModels/NetworkSiteStatusTimeline.js.map +1 -1
  152. package/build/dist/Models/DatabaseModels/OnCallDutyPolicyExecutionLog.js +1 -0
  153. package/build/dist/Models/DatabaseModels/OnCallDutyPolicyExecutionLog.js.map +1 -1
  154. package/build/dist/Models/DatabaseModels/OnCallDutyPolicySchedule.js +2 -0
  155. package/build/dist/Models/DatabaseModels/OnCallDutyPolicySchedule.js.map +1 -1
  156. package/build/dist/Models/DatabaseModels/Probe.js +13 -13
  157. package/build/dist/Models/DatabaseModels/Probe.js.map +1 -1
  158. package/build/dist/Models/DatabaseModels/ScheduledMaintenance.js +1 -0
  159. package/build/dist/Models/DatabaseModels/ScheduledMaintenance.js.map +1 -1
  160. package/build/dist/Models/DatabaseModels/ScheduledMaintenancePublicNote.js +1 -0
  161. package/build/dist/Models/DatabaseModels/ScheduledMaintenancePublicNote.js.map +1 -1
  162. package/build/dist/Models/DatabaseModels/ScheduledMaintenanceStateTimeline.js +1 -0
  163. package/build/dist/Models/DatabaseModels/ScheduledMaintenanceStateTimeline.js.map +1 -1
  164. package/build/dist/Models/DatabaseModels/ScheduledMaintenanceTemplate.js +1 -0
  165. package/build/dist/Models/DatabaseModels/ScheduledMaintenanceTemplate.js.map +1 -1
  166. package/build/dist/Models/DatabaseModels/StatusPage.js +1 -0
  167. package/build/dist/Models/DatabaseModels/StatusPage.js.map +1 -1
  168. package/build/dist/Models/DatabaseModels/StatusPageAnnouncement.js +1 -0
  169. package/build/dist/Models/DatabaseModels/StatusPageAnnouncement.js.map +1 -1
  170. package/build/dist/Models/DatabaseModels/StatusPageDomain.js +1 -0
  171. package/build/dist/Models/DatabaseModels/StatusPageDomain.js.map +1 -1
  172. package/build/dist/Models/DatabaseModels/WorkspaceNotificationSummary.js +1 -0
  173. package/build/dist/Models/DatabaseModels/WorkspaceNotificationSummary.js.map +1 -1
  174. package/build/dist/Models/DatabaseModels/WorkspaceProjectAuthToken.js.map +1 -1
  175. package/build/dist/Server/API/BaseAPI.js +12 -1
  176. package/build/dist/Server/API/BaseAPI.js.map +1 -1
  177. package/build/dist/Server/API/CommonAPI.js +21 -0
  178. package/build/dist/Server/API/CommonAPI.js.map +1 -1
  179. package/build/dist/Server/API/MicrosoftTeamsAPI.js +72 -2
  180. package/build/dist/Server/API/MicrosoftTeamsAPI.js.map +1 -1
  181. package/build/dist/Server/API/ProbeAPI.js +2 -0
  182. package/build/dist/Server/API/ProbeAPI.js.map +1 -1
  183. package/build/dist/Server/API/SlackAPI.js +44 -0
  184. package/build/dist/Server/API/SlackAPI.js.map +1 -1
  185. package/build/dist/Server/API/StatusPageAPI.js +98 -152
  186. package/build/dist/Server/API/StatusPageAPI.js.map +1 -1
  187. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1785140242697-AddHotQueryIndexes.js +36 -0
  188. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1785140242697-AddHotQueryIndexes.js.map +1 -0
  189. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1785148065137-AddHotQueryIndexesSecondPass.js +26 -0
  190. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1785148065137-AddHotQueryIndexesSecondPass.js.map +1 -0
  191. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1785240000000-RepairCrossProjectMonitorStatusReferences.js +254 -0
  192. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1785240000000-RepairCrossProjectMonitorStatusReferences.js.map +1 -0
  193. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js +6 -0
  194. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js.map +1 -1
  195. package/build/dist/Server/Services/DatabaseService.js +10 -2
  196. package/build/dist/Server/Services/DatabaseService.js.map +1 -1
  197. package/build/dist/Server/Services/MonitorGroupService.js +118 -0
  198. package/build/dist/Server/Services/MonitorGroupService.js.map +1 -1
  199. package/build/dist/Server/Services/MonitorService.js +198 -24
  200. package/build/dist/Server/Services/MonitorService.js.map +1 -1
  201. package/build/dist/Server/Services/ProbeService.js +238 -9
  202. package/build/dist/Server/Services/ProbeService.js.map +1 -1
  203. package/build/dist/Server/Services/StatusPageService.js +46 -39
  204. package/build/dist/Server/Services/StatusPageService.js.map +1 -1
  205. package/build/dist/Server/Services/WorkspaceNotificationRuleService.js +178 -3
  206. package/build/dist/Server/Services/WorkspaceNotificationRuleService.js.map +1 -1
  207. package/build/dist/Server/Utils/Database/PostgresErrorTranslator.js +80 -0
  208. package/build/dist/Server/Utils/Database/PostgresErrorTranslator.js.map +1 -0
  209. package/build/dist/Server/Utils/Monitor/MonitorResource.js +67 -29
  210. package/build/dist/Server/Utils/Monitor/MonitorResource.js.map +1 -1
  211. package/build/dist/Server/Utils/Monitor/MonitorStatusTimeline.js +31 -14
  212. package/build/dist/Server/Utils/Monitor/MonitorStatusTimeline.js.map +1 -1
  213. package/build/dist/Server/Utils/Monitor/MonitorStepsProjectValidator.js +117 -0
  214. package/build/dist/Server/Utils/Monitor/MonitorStepsProjectValidator.js.map +1 -0
  215. package/build/dist/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeams.js +549 -7
  216. package/build/dist/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeams.js.map +1 -1
  217. package/build/dist/Types/Date.js +106 -35
  218. package/build/dist/Types/Date.js.map +1 -1
  219. package/build/dist/Types/Workspace/NotificationRules/NotificationRuleCondition.js +17 -7
  220. package/build/dist/Types/Workspace/NotificationRules/NotificationRuleCondition.js.map +1 -1
  221. package/build/dist/UI/Components/BulkUpdate/BulkUpdateForm.js +37 -11
  222. package/build/dist/UI/Components/BulkUpdate/BulkUpdateForm.js.map +1 -1
  223. package/build/dist/UI/Components/CodeEditor/CodeEditor.js +2 -0
  224. package/build/dist/UI/Components/CodeEditor/CodeEditor.js.map +1 -1
  225. package/build/dist/UI/Components/CodeEditor/MonacoLoader.js +34 -0
  226. package/build/dist/UI/Components/CodeEditor/MonacoLoader.js.map +1 -0
  227. package/build/dist/UI/Components/Forms/Fields/FormField.js +11 -3
  228. package/build/dist/UI/Components/Forms/Fields/FormField.js.map +1 -1
  229. package/build/dist/UI/Components/Forms/ModelForm.js +37 -17
  230. package/build/dist/UI/Components/Forms/ModelForm.js.map +1 -1
  231. package/build/dist/UI/Components/Input/Input.js +6 -1
  232. package/build/dist/UI/Components/Input/Input.js.map +1 -1
  233. package/build/dist/UI/Components/ModelDetail/CardModelDetail.js +22 -1
  234. package/build/dist/UI/Components/ModelDetail/CardModelDetail.js.map +1 -1
  235. package/build/dist/UI/Components/ModelDetail/ModelDetail.js +31 -1
  236. package/build/dist/UI/Components/ModelDetail/ModelDetail.js.map +1 -1
  237. package/build/dist/UI/Components/ModelTable/BaseModelTable.js +234 -21
  238. package/build/dist/UI/Components/ModelTable/BaseModelTable.js.map +1 -1
  239. package/build/dist/UI/Components/ModelTable/ExportFromColumns.js +30 -0
  240. package/build/dist/UI/Components/ModelTable/ExportFromColumns.js.map +1 -0
  241. package/build/dist/UI/Components/Navbar/NavBar.js +39 -1
  242. package/build/dist/UI/Components/Navbar/NavBar.js.map +1 -1
  243. package/build/dist/UI/Components/Table/Table.js +31 -11
  244. package/build/dist/UI/Components/Table/Table.js.map +1 -1
  245. package/build/dist/UI/Components/TimePicker/TimePicker.js +4 -4
  246. package/build/dist/UI/Components/TimePicker/TimePicker.js.map +1 -1
  247. package/build/dist/UI/Utils/Project.js +161 -15
  248. package/build/dist/UI/Utils/Project.js.map +1 -1
  249. package/build/dist/UI/Utils/TableColumnsToCsv.js +67 -6
  250. package/build/dist/UI/Utils/TableColumnsToCsv.js.map +1 -1
  251. package/build/dist/UI/Utils/User.js +19 -0
  252. package/build/dist/UI/Utils/User.js.map +1 -1
  253. package/build/dist/Utils/Monitor/MonitorProbeSelectionUtil.js +52 -0
  254. package/build/dist/Utils/Monitor/MonitorProbeSelectionUtil.js.map +1 -0
  255. package/package.json +4 -3
  256. package/Server/Infrastructure/Postgres/SchemaMigrations/1773414578773-MigrationName.ts +0 -79
  257. package/Server/Infrastructure/Postgres/SchemaMigrations/1773500000000-MigrationName.ts +0 -41
  258. package/Server/Infrastructure/Postgres/SchemaMigrations/1776886248361-MigrationName.ts +0 -17
  259. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1773414578773-MigrationName.js +0 -34
  260. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1773414578773-MigrationName.js.map +0 -1
  261. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1773500000000-MigrationName.js +0 -22
  262. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1773500000000-MigrationName.js.map +0 -1
  263. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1776886248361-MigrationName.js +0 -12
  264. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1776886248361-MigrationName.js.map +0 -1
@@ -0,0 +1,263 @@
1
+ import StatusPageResourceUptimeUtil from "../../../Utils/StatusPage/ResourceUptime";
2
+ import { Green, Red, Yellow } from "../../../Types/BrandColors";
3
+ import ObjectID from "../../../Types/ObjectID";
4
+ import Dictionary from "../../../Types/Dictionary";
5
+ import MonitorStatus from "../../../Models/DatabaseModels/MonitorStatus";
6
+ import MonitorStatusTimeline from "../../../Models/DatabaseModels/MonitorStatusTimeline";
7
+ import StatusPageResource from "../../../Models/DatabaseModels/StatusPageResource";
8
+ import StatusPageGroup from "../../../Models/DatabaseModels/StatusPageGroup";
9
+
10
+ const MONITOR_A: ObjectID = new ObjectID(
11
+ "11111111-1111-4111-8111-111111111111",
12
+ );
13
+ const MONITOR_B: ObjectID = new ObjectID(
14
+ "22222222-2222-4222-8222-222222222222",
15
+ );
16
+ const MONITOR_C: ObjectID = new ObjectID(
17
+ "33333333-3333-4333-8333-333333333333",
18
+ );
19
+ const GROUP_ONE: ObjectID = new ObjectID(
20
+ "aaaaaaaa-aaaa-4aaa-8aaa-aaaaaaaaaaaa",
21
+ );
22
+ const GROUP_TWO: ObjectID = new ObjectID(
23
+ "bbbbbbbb-bbbb-4bbb-8bbb-bbbbbbbbbbbb",
24
+ );
25
+
26
+ function makeStatus(data: {
27
+ name: string;
28
+ priority: number;
29
+ color: typeof Green;
30
+ }): MonitorStatus {
31
+ const status: MonitorStatus = new MonitorStatus();
32
+ status.name = data.name;
33
+ status.priority = data.priority;
34
+ status.color = data.color;
35
+ return status;
36
+ }
37
+
38
+ function makeResourceForMonitor(monitorId: ObjectID): StatusPageResource {
39
+ const resource: StatusPageResource = new StatusPageResource();
40
+ resource.monitorId = monitorId;
41
+ return resource;
42
+ }
43
+
44
+ function makeTimelineForMonitor(monitorId: ObjectID): MonitorStatusTimeline {
45
+ const timeline: MonitorStatusTimeline = new MonitorStatusTimeline();
46
+ timeline.monitorId = monitorId;
47
+ return timeline;
48
+ }
49
+
50
+ describe("StatusPageResourceUptimeUtil", () => {
51
+ describe("getWorstMonitorStatus", () => {
52
+ test("defaults to Operational when there are no statuses", () => {
53
+ const worst: MonitorStatus =
54
+ StatusPageResourceUptimeUtil.getWorstMonitorStatus({
55
+ monitorStatuses: [],
56
+ });
57
+
58
+ expect(worst.name).toBe("Operational");
59
+ expect(worst.color).toEqual(Green);
60
+ });
61
+
62
+ test("returns the highest priority (worst) status", () => {
63
+ const operational: MonitorStatus = makeStatus({
64
+ name: "Operational",
65
+ priority: 1,
66
+ color: Green,
67
+ });
68
+ const degraded: MonitorStatus = makeStatus({
69
+ name: "Degraded",
70
+ priority: 2,
71
+ color: Yellow,
72
+ });
73
+ const offline: MonitorStatus = makeStatus({
74
+ name: "Offline",
75
+ priority: 3,
76
+ color: Red,
77
+ });
78
+
79
+ const worst: MonitorStatus =
80
+ StatusPageResourceUptimeUtil.getWorstMonitorStatus({
81
+ monitorStatuses: [operational, offline, degraded],
82
+ });
83
+
84
+ expect(worst.name).toBe("Offline");
85
+ expect(worst.priority).toBe(3);
86
+ });
87
+
88
+ test("a single status wins over the operational default", () => {
89
+ const degraded: MonitorStatus = makeStatus({
90
+ name: "Degraded",
91
+ priority: 5,
92
+ color: Yellow,
93
+ });
94
+
95
+ const worst: MonitorStatus =
96
+ StatusPageResourceUptimeUtil.getWorstMonitorStatus({
97
+ monitorStatuses: [degraded],
98
+ });
99
+
100
+ expect(worst.name).toBe("Degraded");
101
+ });
102
+ });
103
+
104
+ describe("getResourcesInStatusPageGroup", () => {
105
+ test("returns only resources whose group id matches", () => {
106
+ const group: StatusPageGroup = new StatusPageGroup();
107
+ group._id = GROUP_ONE.toString();
108
+
109
+ const inGroup: StatusPageResource = new StatusPageResource();
110
+ inGroup.statusPageGroupId = GROUP_ONE;
111
+
112
+ const otherGroup: StatusPageResource = new StatusPageResource();
113
+ otherGroup.statusPageGroupId = GROUP_TWO;
114
+
115
+ const noGroup: StatusPageResource = new StatusPageResource();
116
+
117
+ const result: Array<StatusPageResource> =
118
+ StatusPageResourceUptimeUtil.getResourcesInStatusPageGroup({
119
+ statusPageGroup: group,
120
+ statusPageResources: [inGroup, otherGroup, noGroup],
121
+ });
122
+
123
+ expect(result).toHaveLength(1);
124
+ expect(result[0]).toBe(inGroup);
125
+ });
126
+
127
+ test("returns an empty array when nothing matches", () => {
128
+ const group: StatusPageGroup = new StatusPageGroup();
129
+ group._id = GROUP_ONE.toString();
130
+
131
+ const otherGroup: StatusPageResource = new StatusPageResource();
132
+ otherGroup.statusPageGroupId = GROUP_TWO;
133
+
134
+ expect(
135
+ StatusPageResourceUptimeUtil.getResourcesInStatusPageGroup({
136
+ statusPageGroup: group,
137
+ statusPageResources: [otherGroup],
138
+ }),
139
+ ).toHaveLength(0);
140
+ });
141
+ });
142
+
143
+ describe("getResourcesWithoutStatusPageGroup", () => {
144
+ test("returns only ungrouped resources", () => {
145
+ const grouped: StatusPageResource = new StatusPageResource();
146
+ grouped.statusPageGroupId = GROUP_ONE;
147
+
148
+ const ungroupedA: StatusPageResource = new StatusPageResource();
149
+ const ungroupedB: StatusPageResource = new StatusPageResource();
150
+
151
+ const result: Array<StatusPageResource> =
152
+ StatusPageResourceUptimeUtil.getResourcesWithoutStatusPageGroup({
153
+ statusPageResources: [grouped, ungroupedA, ungroupedB],
154
+ });
155
+
156
+ expect(result).toHaveLength(2);
157
+ expect(result).toContain(ungroupedA);
158
+ expect(result).toContain(ungroupedB);
159
+ expect(result).not.toContain(grouped);
160
+ });
161
+
162
+ test("grouped and ungrouped partitions are complementary", () => {
163
+ const group: StatusPageGroup = new StatusPageGroup();
164
+ group._id = GROUP_ONE.toString();
165
+
166
+ const grouped: StatusPageResource = new StatusPageResource();
167
+ grouped.statusPageGroupId = GROUP_ONE;
168
+ const ungrouped: StatusPageResource = new StatusPageResource();
169
+
170
+ const all: Array<StatusPageResource> = [grouped, ungrouped];
171
+
172
+ const withoutGroup: Array<StatusPageResource> =
173
+ StatusPageResourceUptimeUtil.getResourcesWithoutStatusPageGroup({
174
+ statusPageResources: all,
175
+ });
176
+ const inGroup: Array<StatusPageResource> =
177
+ StatusPageResourceUptimeUtil.getResourcesInStatusPageGroup({
178
+ statusPageGroup: group,
179
+ statusPageResources: all,
180
+ });
181
+
182
+ expect(withoutGroup.length + inGroup.length).toBe(all.length);
183
+ });
184
+ });
185
+
186
+ describe("getMonitorStatusTimelineForResource", () => {
187
+ test("filters timelines to the resource's own monitor", () => {
188
+ const resource: StatusPageResource = makeResourceForMonitor(MONITOR_A);
189
+
190
+ const result: Array<MonitorStatusTimeline> =
191
+ StatusPageResourceUptimeUtil.getMonitorStatusTimelineForResource({
192
+ statusPageResource: resource,
193
+ monitorStatusTimelines: [
194
+ makeTimelineForMonitor(MONITOR_A),
195
+ makeTimelineForMonitor(MONITOR_B),
196
+ makeTimelineForMonitor(MONITOR_A),
197
+ ],
198
+ monitorsInGroup: {},
199
+ });
200
+
201
+ expect(result).toHaveLength(2);
202
+ for (const timeline of result) {
203
+ expect(timeline.monitorId?.toString()).toBe(MONITOR_A.toString());
204
+ }
205
+ });
206
+
207
+ test("resolves a monitor-group resource to all monitors in the group", () => {
208
+ const resource: StatusPageResource = new StatusPageResource();
209
+ resource.monitorGroupId = GROUP_ONE;
210
+
211
+ const monitorsInGroup: Dictionary<Array<ObjectID>> = {
212
+ [GROUP_ONE.toString()]: [MONITOR_A, MONITOR_B],
213
+ };
214
+
215
+ const result: Array<MonitorStatusTimeline> =
216
+ StatusPageResourceUptimeUtil.getMonitorStatusTimelineForResource({
217
+ statusPageResource: resource,
218
+ monitorStatusTimelines: [
219
+ makeTimelineForMonitor(MONITOR_A),
220
+ makeTimelineForMonitor(MONITOR_B),
221
+ makeTimelineForMonitor(MONITOR_C),
222
+ ],
223
+ monitorsInGroup,
224
+ });
225
+
226
+ // MONITOR_C is not part of the group, so it is excluded.
227
+ expect(result).toHaveLength(2);
228
+ const ids: Array<string> = result.map((t: MonitorStatusTimeline) => {
229
+ return t.monitorId!.toString();
230
+ });
231
+ expect(ids).toContain(MONITOR_A.toString());
232
+ expect(ids).toContain(MONITOR_B.toString());
233
+ expect(ids).not.toContain(MONITOR_C.toString());
234
+ });
235
+
236
+ test("returns nothing when the group has no known monitors", () => {
237
+ const resource: StatusPageResource = new StatusPageResource();
238
+ resource.monitorGroupId = GROUP_ONE;
239
+
240
+ const result: Array<MonitorStatusTimeline> =
241
+ StatusPageResourceUptimeUtil.getMonitorStatusTimelineForResource({
242
+ statusPageResource: resource,
243
+ monitorStatusTimelines: [makeTimelineForMonitor(MONITOR_A)],
244
+ monitorsInGroup: {},
245
+ });
246
+
247
+ expect(result).toHaveLength(0);
248
+ });
249
+
250
+ test("returns nothing when the resource has neither monitor nor group", () => {
251
+ const resource: StatusPageResource = new StatusPageResource();
252
+
253
+ const result: Array<MonitorStatusTimeline> =
254
+ StatusPageResourceUptimeUtil.getMonitorStatusTimelineForResource({
255
+ statusPageResource: resource,
256
+ monitorStatusTimelines: [makeTimelineForMonitor(MONITOR_A)],
257
+ monitorsInGroup: {},
258
+ });
259
+
260
+ expect(result).toHaveLength(0);
261
+ });
262
+ });
263
+ });
package/Types/Date.ts CHANGED
@@ -12,6 +12,13 @@ export const Moment: typeof moment = moment;
12
12
  export default class OneUptimeDate {
13
13
  // get date time from unix timestamp
14
14
 
15
+ /*
16
+ * The timezone the signed-in user configured in User Settings, pushed in by
17
+ * the UI (see UserUtil.initializeUserTimezone). Null on the server and on
18
+ * signed-out pages, where the process / browser zone is used instead.
19
+ */
20
+ private static userTimezone: Timezone | null = null;
21
+
15
22
  private static padDatePart(value: number): string {
16
23
  return value.toString().padStart(2, "0");
17
24
  }
@@ -44,6 +51,7 @@ export default class OneUptimeDate {
44
51
  private static getLocalShortMonthName(date: Date): string {
45
52
  return date.toLocaleString("default", {
46
53
  month: "short",
54
+ timeZone: this.getCurrentTimezone().toString(),
47
55
  });
48
56
  }
49
57
 
@@ -156,6 +164,19 @@ export default class OneUptimeDate {
156
164
  return this.getLocalTimeString(date);
157
165
  }
158
166
 
167
+ /**
168
+ * The hour / minute `date` reads at in the current timezone. Time pickers use
169
+ * these instead of Date.getHours()/getMinutes() so the digits they show are
170
+ * the ones the user expects in the zone they configured.
171
+ */
172
+ public static getLocalHours(date: Date | string): number {
173
+ return this.inCurrentTimezone(date).hours();
174
+ }
175
+
176
+ public static getLocalMinutes(date: Date | string): number {
177
+ return this.inCurrentTimezone(date).minutes();
178
+ }
179
+
159
180
  public static getLocalShortMonthNameFromDate(date: Date | string): string {
160
181
  date = this.fromString(date);
161
182
  return this.getLocalShortMonthName(date);
@@ -172,26 +193,27 @@ export default class OneUptimeDate {
172
193
 
173
194
  const includeMinutes: boolean = options?.includeMinutes ?? true;
174
195
  const includeSeconds: boolean = options?.includeSeconds ?? false;
175
- const hours: string = this.padDatePart(date.getHours());
196
+ const localDate: moment.Moment = this.inCurrentTimezone(date);
197
+ const hours: string = this.padDatePart(localDate.hours());
176
198
 
177
199
  if (!includeMinutes) {
178
200
  return hours;
179
201
  }
180
202
 
181
- const minutes: string = this.padDatePart(date.getMinutes());
203
+ const minutes: string = this.padDatePart(localDate.minutes());
182
204
 
183
205
  if (!includeSeconds) {
184
206
  return `${hours}:${minutes}`;
185
207
  }
186
208
 
187
- const seconds: string = this.padDatePart(date.getSeconds());
209
+ const seconds: string = this.padDatePart(localDate.seconds());
188
210
  return `${hours}:${minutes}:${seconds}`;
189
211
  }
190
212
 
191
213
  public static getDateAsLocalDayMonthString(date: Date | string): string {
192
214
  date = this.fromString(date);
193
215
 
194
- const day: string = this.padDatePart(date.getDate());
216
+ const day: string = this.padDatePart(this.inCurrentTimezone(date).date());
195
217
  const month: string = this.getLocalShortMonthName(date);
196
218
 
197
219
  return `${day} ${month}`;
@@ -207,7 +229,7 @@ export default class OneUptimeDate {
207
229
  date = this.fromString(date);
208
230
 
209
231
  const month: string = this.getLocalShortMonthName(date);
210
- const year: string = date.getFullYear().toString();
232
+ const year: string = this.inCurrentTimezone(date).year().toString();
211
233
 
212
234
  return `${month} ${year}`;
213
235
  }
@@ -316,7 +338,7 @@ export default class OneUptimeDate {
316
338
  : `${datePart}, ${timePart}`;
317
339
 
318
340
  if (!data.timezone) {
319
- const local: moment.Moment = moment(date).local();
341
+ const local: moment.Moment = this.inCurrentTimezone(date);
320
342
  return (
321
343
  local.format(formatString) +
322
344
  (data.onlyShowDate ? "" : " " + this.getCurrentTimezoneString())
@@ -333,18 +355,18 @@ export default class OneUptimeDate {
333
355
  }
334
356
 
335
357
  /**
336
- * Reinterpret the wall-clock components of `date` — read in the process /
337
- * browser local zone — as the SAME wall-clock in `timezone`, and return that
338
- * instant. Used to store a time the user typed for the schedule's timezone
339
- * even though the time picker captured it in the browser's local zone (audit
340
- * F1). Inverse of getLocalDateFromWallClockInTimezone.
358
+ * Reinterpret the wall-clock components of `date` — read in the CURRENT
359
+ * timezone, i.e. the one the pickers render in — as the SAME wall-clock in
360
+ * `timezone`, and return that instant. Used to store a time the user typed
361
+ * for the schedule's timezone even though the time picker captured it in the
362
+ * viewer's own zone (audit F1). Inverse of
363
+ * getLocalDateFromWallClockInTimezone.
341
364
  */
342
365
  public static getInstantFromLocalWallClockInTimezone(
343
366
  date: Date | string,
344
367
  timezone: string,
345
368
  ): Date {
346
- date = this.fromString(date);
347
- const local: moment.Moment = moment(date);
369
+ const local: moment.Moment = this.inCurrentTimezone(date);
348
370
  return moment
349
371
  .tz(
350
372
  {
@@ -361,10 +383,10 @@ export default class OneUptimeDate {
361
383
  }
362
384
 
363
385
  /**
364
- * Inverse of getInstantFromLocalWallClockInTimezone: return a process /
365
- * browser local Date whose LOCAL wall-clock equals `date`'s wall-clock as seen
366
- * in `timezone`. Used to display a stored schedule-timezone time inside a
367
- * browser-local time picker (audit F1).
386
+ * Inverse of getInstantFromLocalWallClockInTimezone: return a Date whose
387
+ * wall-clock IN THE CURRENT TIMEZONE equals `date`'s wall-clock as seen in
388
+ * `timezone`. Used to display a stored schedule-timezone time inside a picker
389
+ * that renders in the viewer's own zone (audit F1).
368
390
  */
369
391
  public static getLocalDateFromWallClockInTimezone(
370
392
  date: Date | string,
@@ -372,14 +394,19 @@ export default class OneUptimeDate {
372
394
  ): Date {
373
395
  date = this.fromString(date);
374
396
  const zoned: moment.Moment = moment.tz(date, timezone);
375
- return moment({
376
- year: zoned.year(),
377
- month: zoned.month(),
378
- day: zoned.date(),
379
- hour: zoned.hour(),
380
- minute: zoned.minute(),
381
- second: zoned.second(),
382
- }).toDate();
397
+ return moment
398
+ .tz(
399
+ {
400
+ year: zoned.year(),
401
+ month: zoned.month(),
402
+ day: zoned.date(),
403
+ hour: zoned.hour(),
404
+ minute: zoned.minute(),
405
+ second: zoned.second(),
406
+ },
407
+ this.getCurrentTimezone().toString(),
408
+ )
409
+ .toDate();
383
410
  }
384
411
 
385
412
  public static isOverlapping(
@@ -709,22 +736,26 @@ export default class OneUptimeDate {
709
736
  return this.addRemoveMinutes(date, date.getTimezoneOffset());
710
737
  }
711
738
 
739
+ /**
740
+ * Render an instant as the value of an `<input type="datetime-local">` — a
741
+ * bare wall-clock with no offset — read in the current timezone.
742
+ */
712
743
  public static toDateTimeLocalString(date: Date): string {
713
- date = this.fromString(date);
714
-
715
- type TenFunction = (i: number) => string;
716
-
717
- const ten: TenFunction = (i: number): string => {
718
- return (i < 10 ? "0" : "") + i;
719
- },
720
- YYYY: number = date.getFullYear(),
721
- MM: string = ten(date.getMonth() + 1),
722
- DD: string = ten(date.getDate()),
723
- HH: string = ten(date.getHours()),
724
- II: string = ten(date.getMinutes()),
725
- SS: string = ten(date.getSeconds());
744
+ return this.inCurrentTimezone(date).format("YYYY-MM-DDTHH:mm:ss");
745
+ }
726
746
 
727
- return YYYY + "-" + MM + "-" + DD + "T" + HH + ":" + II + ":" + SS;
747
+ /**
748
+ * Inverse of toDateTimeLocalString: turn the offset-less wall-clock a user
749
+ * typed into a `<input type="date">` / `<input type="datetime-local">` into
750
+ * the instant it names in the current timezone. Without this the browser
751
+ * zone is used to resolve the wall-clock, so a user whose machine reports a
752
+ * different zone than the one they configured would have their times stored
753
+ * hours off.
754
+ */
755
+ public static fromDateTimeLocalString(value: string): Date {
756
+ return moment
757
+ .tz(value.trim(), this.getCurrentTimezone().toString())
758
+ .toDate();
728
759
  }
729
760
 
730
761
  public static fromJSON(json: JSONObject): Date {
@@ -1713,7 +1744,7 @@ export default class OneUptimeDate {
1713
1744
  formatstring = "MMM DD, YYYY";
1714
1745
  }
1715
1746
 
1716
- const momentDate: moment.Moment = moment(date).local();
1747
+ const momentDate: moment.Moment = this.inCurrentTimezone(date);
1717
1748
 
1718
1749
  return (
1719
1750
  momentDate.format(formatstring) +
@@ -1752,8 +1783,41 @@ export default class OneUptimeDate {
1752
1783
  return zoneAbbr;
1753
1784
  }
1754
1785
 
1786
+ /**
1787
+ * Set the timezone the signed-in user picked in User Settings. Once set,
1788
+ * every "local" helper below resolves wall-clock times in THIS zone instead
1789
+ * of the zone the browser reports, so what the user reads and types matches
1790
+ * the timezone they configured — even when the machine, OS or VPN they are
1791
+ * on reports a different one. Pass null to fall back to the browser zone
1792
+ * (signed-out surfaces, or a user who has not picked a timezone).
1793
+ */
1794
+ public static setUserTimezone(timezone: Timezone | null | undefined): void {
1795
+ /*
1796
+ * Ignore anything moment does not recognise as an IANA zone. A stale or
1797
+ * corrupt saved value must not take every date in the UI down with it.
1798
+ */
1799
+ if (!timezone || !moment.tz.zone(timezone.toString())) {
1800
+ this.userTimezone = null;
1801
+ return;
1802
+ }
1803
+
1804
+ this.userTimezone = timezone;
1805
+ }
1806
+
1807
+ public static getUserTimezone(): Timezone | null {
1808
+ return this.userTimezone;
1809
+ }
1810
+
1755
1811
  public static getCurrentTimezone(): Timezone {
1756
- return moment.tz.guess() as Timezone;
1812
+ return this.userTimezone || (moment.tz.guess() as Timezone);
1813
+ }
1814
+
1815
+ /**
1816
+ * The instant `date` viewed through the current timezone — the single place
1817
+ * that decides which wall clock the "local" helpers below read from.
1818
+ */
1819
+ private static inCurrentTimezone(date: Date | string): moment.Moment {
1820
+ return moment(this.fromString(date)).tz(this.getCurrentTimezone());
1757
1821
  }
1758
1822
 
1759
1823
  public static getDateString(date: Date): string {
@@ -1810,9 +1874,8 @@ export default class OneUptimeDate {
1810
1874
  }
1811
1875
 
1812
1876
  public static asDateForDatabaseQuery(date: string | Date): string {
1813
- date = this.fromString(date);
1814
1877
  const formatstring: string = "YYYY-MM-DD";
1815
- return moment(date).local().format(formatstring);
1878
+ return this.inCurrentTimezone(date).format(formatstring);
1816
1879
  }
1817
1880
 
1818
1881
  public static asFilterDateForDatabaseQuery(
@@ -1850,13 +1913,19 @@ export default class OneUptimeDate {
1850
1913
  throw new BadDataException("Invalid seconds");
1851
1914
  }
1852
1915
 
1853
- const date: Date = OneUptimeDate.getCurrentDate();
1854
-
1855
- date.setHours(hour);
1856
- date.setMinutes(minutes);
1857
- date.setSeconds(seconds);
1858
-
1859
- return date;
1916
+ /*
1917
+ * The hour/minute the user picked is a wall-clock reading in THEIR
1918
+ * timezone, so anchor it to today's date in that zone rather than setting
1919
+ * it on a browser-local Date.
1920
+ */
1921
+ return this.inCurrentTimezone(OneUptimeDate.getCurrentDate())
1922
+ .set({
1923
+ hour: hour,
1924
+ minute: minutes,
1925
+ second: seconds,
1926
+ millisecond: 0,
1927
+ })
1928
+ .toDate();
1860
1929
  }
1861
1930
 
1862
1931
  public static toDatabaseDate(date: Date): string {
@@ -10,4 +10,8 @@ export default interface BaseNotificationRule {
10
10
  shouldPostToExistingChannel: boolean;
11
11
  existingChannelNames: string; // separate by comma
12
12
  existingTeam?: string; // team to post to (for MS Teams only)
13
+
14
+ // Post to group / personal chats (for MS Teams only).
15
+ shouldPostToExistingChat?: boolean;
16
+ existingChatIds?: Array<string>; // Teams chat ids the OneUptime app has been added to.
13
17
  }
@@ -9,7 +9,7 @@ import ScheduledMaintenanceState from "../../../Models/DatabaseModels/ScheduledM
9
9
  import { DropdownOption } from "../../../UI/Components/Dropdown/Dropdown";
10
10
  import DropdownUtil from "../../../UI/Utils/Dropdown";
11
11
  import MonitorType from "../../Monitor/MonitorType";
12
- import WorkspaceType from "../WorkspaceType";
12
+ import WorkspaceType, { getWorkspaceTypeDisplayName } from "../WorkspaceType";
13
13
  import NotificationRuleEventType from "./EventType";
14
14
  import IncidentNotificationRule from "./NotificationRuleTypes/IncidentNotificationRule";
15
15
 
@@ -108,32 +108,47 @@ export class NotificationRuleConditionUtil {
108
108
  eventType === NotificationRuleEventType.IncidentEpisode ||
109
109
  eventType === NotificationRuleEventType.ScheduledMaintenance
110
110
  ) {
111
- // either create slack channel or select existing one should be active.
111
+ // either create a channel, post to an existing channel, or post to a chat.
112
+
113
+ const workspaceDisplayName: string =
114
+ getWorkspaceTypeDisplayName(workspaceType);
112
115
 
113
116
  if (
114
117
  !notificationRule.shouldCreateNewChannel &&
115
- !notificationRule.shouldPostToExistingChannel
118
+ !notificationRule.shouldPostToExistingChannel &&
119
+ !notificationRule.shouldPostToExistingChat
116
120
  ) {
117
- return (
118
- "Please select either create slack channel or post to existing " +
119
- workspaceType +
120
- " channel"
121
- );
121
+ return workspaceType === WorkspaceType.MicrosoftTeams
122
+ ? `Please select a destination: create a ${workspaceDisplayName} channel, post to an existing ${workspaceDisplayName} channel, or post to an existing ${workspaceDisplayName} chat`
123
+ : `Please select a destination: create a ${workspaceDisplayName} channel or post to an existing ${workspaceDisplayName} channel`;
122
124
  }
123
125
 
124
126
  if (notificationRule.shouldPostToExistingChannel) {
125
127
  if (!notificationRule.existingChannelNames?.trim()) {
126
- return "Existing " + workspaceType + " channel name is required";
128
+ return (
129
+ "Existing " + workspaceDisplayName + " channel name is required"
130
+ );
127
131
  }
128
132
  }
129
133
 
130
134
  if (notificationRule.shouldCreateNewChannel) {
131
135
  if (!notificationRule.newChannelTemplateName?.trim()) {
132
- return "New " + workspaceType + " channel name is required";
136
+ return "New " + workspaceDisplayName + " channel name is required";
133
137
  }
134
138
  }
135
139
  }
136
140
 
141
+ if (notificationRule.shouldPostToExistingChat) {
142
+ if (
143
+ !notificationRule.existingChatIds ||
144
+ notificationRule.existingChatIds.length === 0
145
+ ) {
146
+ const workspaceDisplayName: string =
147
+ getWorkspaceTypeDisplayName(workspaceType);
148
+ return `Please select at least one ${workspaceDisplayName} chat to post to. If no chats are listed, add the OneUptime app to a chat in ${workspaceDisplayName} first.`;
149
+ }
150
+ }
151
+
137
152
  return null;
138
153
  }
139
154
 
@@ -106,4 +106,5 @@ export default interface WorkspaceMessagePayload {
106
106
  messageBlocks: Array<WorkspaceMessageBlock>; // Message to add to blocks.
107
107
  workspaceType: WorkspaceType;
108
108
  teamId?: string | undefined; // Team ID for Microsoft Teams
109
+ chatIds?: Array<string> | undefined; // Microsoft Teams chat ids (group / personal chats) to send message to.
109
110
  }