@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,453 @@
1
+ /*
2
+ * MonitorStatusTimelineUtil.updateMonitorStatusTimeline runs on EVERY probe /
3
+ * ingest result whose criteria matched — for a healthy fleet that is almost
4
+ * every check, and each call used to issue a sorted findOneBy against
5
+ * MonitorStatusTimeline (ORDER BY startsAt DESC on one of the largest tables)
6
+ * just to conclude "status unchanged, nothing to do".
7
+ *
8
+ * The optimization under test adds a steady-state fast-path BEFORE that
9
+ * SELECT: when the criteria wants to change the status
10
+ * (changeMonitorStatus + monitorStatusId set) and the target status already
11
+ * equals Monitor.currentMonitorStatusId, return null with NO query at all.
12
+ * Monitor.currentMonitorStatusId is kept in lockstep with the latest timeline
13
+ * row by MonitorStatusTimelineService, and onBeforeCreate still dedupes as
14
+ * the concurrency backstop — so skipping the SELECT is behavior-preserving.
15
+ *
16
+ * The same change also fixed a long-standing comparison bug: the second
17
+ * shouldUpdateStatus condition used to compare the criteria's monitorStatusId
18
+ * to the timeline row's PK id (which never matches a status id), and now
19
+ * compares it to lastMonitorStatusTimeline.monitorStatusId.
20
+ *
21
+ * This suite pins:
22
+ * - the fast-path returns null with zero service calls,
23
+ * - the fast-path requires changeMonitorStatus (legacy read path preserved),
24
+ * - the same-as-last-status inner check still prevents duplicate rows when
25
+ * currentMonitorStatusId is out of sync,
26
+ * - a real status change still creates a correctly-populated timeline row,
27
+ * - first-row creation (no timeline history) still works,
28
+ * - the two exact-message swallow paths (dedupe race + lock refusal)
29
+ * return null while every other create error propagates,
30
+ * - the fixed monitorStatusId-vs-monitorStatusId comparison.
31
+ *
32
+ * MonitorStatusTimelineService is mocked at the module boundary — the real
33
+ * module pulls in the whole database stack. No Postgres, no Redis.
34
+ */
35
+
36
+ const mockFindOneBy: jest.Mock = jest.fn();
37
+ const mockCreate: jest.Mock = jest.fn();
38
+
39
+ jest.mock("../../../Server/Services/MonitorStatusTimelineService", () => {
40
+ return {
41
+ __esModule: true,
42
+ default: {
43
+ findOneBy: (...args: Array<unknown>) => {
44
+ return mockFindOneBy(...args);
45
+ },
46
+ create: (...args: Array<unknown>) => {
47
+ return mockCreate(...args);
48
+ },
49
+ },
50
+ /*
51
+ * Same literal values as the real service module. The test imports these
52
+ * from the mocked module below, so the swallow-path assertions use the
53
+ * exact strings the util itself sees at runtime.
54
+ */
55
+ MONITOR_STATUS_SAME_AS_PREVIOUS_ERROR_MESSAGE:
56
+ "Monitor Status cannot be same as previous status.",
57
+ MONITOR_STATUS_TIMELINE_LOCK_ERROR_MESSAGE:
58
+ "Could not acquire the monitor status timeline lock for this monitor.",
59
+ };
60
+ });
61
+
62
+ jest.mock("../../../Server/Utils/Logger", () => {
63
+ return {
64
+ __esModule: true,
65
+ default: {
66
+ debug: jest.fn(),
67
+ info: jest.fn(),
68
+ warn: jest.fn(),
69
+ error: jest.fn(),
70
+ },
71
+ };
72
+ });
73
+
74
+ import MonitorStatusTimelineUtil from "../../../Server/Utils/Monitor/MonitorStatusTimeline";
75
+ import {
76
+ MONITOR_STATUS_SAME_AS_PREVIOUS_ERROR_MESSAGE,
77
+ MONITOR_STATUS_TIMELINE_LOCK_ERROR_MESSAGE,
78
+ } from "../../../Server/Services/MonitorStatusTimelineService";
79
+ import DataToProcess from "../../../Server/Utils/Monitor/DataToProcess";
80
+ import Monitor from "../../../Models/DatabaseModels/Monitor";
81
+ import MonitorStatusTimeline from "../../../Models/DatabaseModels/MonitorStatusTimeline";
82
+ import MonitorCriteriaInstance from "../../../Types/Monitor/MonitorCriteriaInstance";
83
+ import SortOrder from "../../../Types/BaseDatabase/SortOrder";
84
+ import BadDataException from "../../../Types/Exception/BadDataException";
85
+ import ServerException from "../../../Types/Exception/ServerException";
86
+ import ObjectID from "../../../Types/ObjectID";
87
+ import { describe, expect, test, beforeEach } from "@jest/globals";
88
+
89
+ const MONITOR_ID: ObjectID = new ObjectID(
90
+ "11111111-1111-4111-8111-111111111111",
91
+ );
92
+ const PROJECT_ID: ObjectID = new ObjectID(
93
+ "22222222-2222-4222-8222-222222222222",
94
+ );
95
+ const ONLINE_STATUS_ID: ObjectID = new ObjectID(
96
+ "33333333-3333-4333-8333-333333333333",
97
+ );
98
+ const OFFLINE_STATUS_ID: ObjectID = new ObjectID(
99
+ "44444444-4444-4444-8444-444444444444",
100
+ );
101
+ const TIMELINE_ROW_ID: ObjectID = new ObjectID(
102
+ "55555555-5555-4555-8555-555555555555",
103
+ );
104
+
105
+ type MakeMonitorFunction = (
106
+ currentMonitorStatusId: ObjectID | undefined,
107
+ ) => Monitor;
108
+
109
+ const makeMonitor: MakeMonitorFunction = (
110
+ currentMonitorStatusId: ObjectID | undefined,
111
+ ): Monitor => {
112
+ const monitor: Monitor = new Monitor();
113
+ monitor.id = MONITOR_ID;
114
+ monitor.projectId = PROJECT_ID;
115
+ if (currentMonitorStatusId) {
116
+ monitor.currentMonitorStatusId = currentMonitorStatusId;
117
+ }
118
+ return monitor;
119
+ };
120
+
121
+ type MakeCriteriaFunction = (input: {
122
+ changeMonitorStatus: boolean;
123
+ monitorStatusId: ObjectID | undefined;
124
+ }) => MonitorCriteriaInstance;
125
+
126
+ const makeCriteria: MakeCriteriaFunction = (input: {
127
+ changeMonitorStatus: boolean;
128
+ monitorStatusId: ObjectID | undefined;
129
+ }): MonitorCriteriaInstance => {
130
+ const criteria: MonitorCriteriaInstance = new MonitorCriteriaInstance();
131
+ criteria.data!.changeMonitorStatus = input.changeMonitorStatus;
132
+ criteria.data!.monitorStatusId = input.monitorStatusId;
133
+ return criteria;
134
+ };
135
+
136
+ type MakeTimelineRowFunction = (input: {
137
+ id: ObjectID;
138
+ monitorStatusId: ObjectID;
139
+ }) => MonitorStatusTimeline;
140
+
141
+ const makeTimelineRow: MakeTimelineRowFunction = (input: {
142
+ id: ObjectID;
143
+ monitorStatusId: ObjectID;
144
+ }): MonitorStatusTimeline => {
145
+ const row: MonitorStatusTimeline = new MonitorStatusTimeline();
146
+ row.id = input.id;
147
+ row.monitorStatusId = input.monitorStatusId;
148
+ return row;
149
+ };
150
+
151
+ const dataToProcess: DataToProcess = {
152
+ monitorId: MONITOR_ID,
153
+ projectId: PROJECT_ID,
154
+ isOnline: true,
155
+ } as unknown as DataToProcess;
156
+
157
+ const ROOT_CAUSE: string = "Monitor is online because criteria matched.";
158
+
159
+ type RunFunction = (input: {
160
+ criteriaInstance: MonitorCriteriaInstance;
161
+ monitor: Monitor;
162
+ }) => Promise<MonitorStatusTimeline | null>;
163
+
164
+ const run: RunFunction = (input: {
165
+ criteriaInstance: MonitorCriteriaInstance;
166
+ monitor: Monitor;
167
+ }): Promise<MonitorStatusTimeline | null> => {
168
+ return MonitorStatusTimelineUtil.updateMonitorStatusTimeline({
169
+ criteriaInstance: input.criteriaInstance,
170
+ monitor: input.monitor,
171
+ dataToProcess: dataToProcess,
172
+ rootCause: ROOT_CAUSE,
173
+ props: {},
174
+ });
175
+ };
176
+
177
+ describe("MonitorStatusTimelineUtil.updateMonitorStatusTimeline steady-state fast-path", () => {
178
+ beforeEach(() => {
179
+ mockFindOneBy.mockReset();
180
+ mockCreate.mockReset();
181
+ });
182
+
183
+ test("returns null with ZERO queries when target status already equals currentMonitorStatusId", async () => {
184
+ /*
185
+ * The hot path this whole optimization exists for: healthy check, online
186
+ * criteria matched, monitor already online. Before the change this issued
187
+ * a sorted SELECT per check result; now it must touch the service not at
188
+ * all.
189
+ */
190
+ const result: MonitorStatusTimeline | null = await run({
191
+ criteriaInstance: makeCriteria({
192
+ changeMonitorStatus: true,
193
+ monitorStatusId: ONLINE_STATUS_ID,
194
+ }),
195
+ monitor: makeMonitor(ONLINE_STATUS_ID),
196
+ });
197
+
198
+ expect(result).toBeNull();
199
+ expect(mockFindOneBy).not.toHaveBeenCalled();
200
+ expect(mockCreate).not.toHaveBeenCalled();
201
+ });
202
+
203
+ test("does NOT fast-path when changeMonitorStatus is falsy — legacy read path preserved", async () => {
204
+ /*
205
+ * A criteria that does not change the status must keep its pre-existing
206
+ * behavior (the timeline SELECT still runs, nothing is created). The
207
+ * fast-path condition deliberately requires changeMonitorStatus so it
208
+ * only skips work the function would provably not do.
209
+ */
210
+ mockFindOneBy.mockResolvedValue(
211
+ makeTimelineRow({
212
+ id: TIMELINE_ROW_ID,
213
+ monitorStatusId: ONLINE_STATUS_ID,
214
+ }),
215
+ );
216
+
217
+ const result: MonitorStatusTimeline | null = await run({
218
+ criteriaInstance: makeCriteria({
219
+ changeMonitorStatus: false,
220
+ monitorStatusId: ONLINE_STATUS_ID,
221
+ }),
222
+ monitor: makeMonitor(ONLINE_STATUS_ID),
223
+ });
224
+
225
+ expect(result).toBeNull();
226
+ expect(mockCreate).not.toHaveBeenCalled();
227
+
228
+ // The legacy path issues exactly the sorted latest-row lookup.
229
+ expect(mockFindOneBy).toHaveBeenCalledTimes(1);
230
+ expect(mockFindOneBy).toHaveBeenCalledWith({
231
+ query: {
232
+ monitorId: MONITOR_ID,
233
+ projectId: PROJECT_ID,
234
+ },
235
+ select: {
236
+ _id: true,
237
+ monitorStatusId: true,
238
+ },
239
+ sort: {
240
+ startsAt: SortOrder.Descending,
241
+ },
242
+ props: {
243
+ isRoot: true,
244
+ },
245
+ });
246
+ });
247
+
248
+ test("returns null without create when the LAST TIMELINE ROW already has the target status (currentMonitorStatusId out of sync)", async () => {
249
+ /*
250
+ * The timeline is the source of truth. If currentMonitorStatusId lags
251
+ * behind (so the fast-path does not trigger), the same-as-last-status
252
+ * check on the fetched row must still prevent a duplicate row.
253
+ */
254
+ mockFindOneBy.mockResolvedValue(
255
+ makeTimelineRow({
256
+ id: TIMELINE_ROW_ID,
257
+ monitorStatusId: ONLINE_STATUS_ID,
258
+ }),
259
+ );
260
+
261
+ const result: MonitorStatusTimeline | null = await run({
262
+ criteriaInstance: makeCriteria({
263
+ changeMonitorStatus: true,
264
+ monitorStatusId: ONLINE_STATUS_ID,
265
+ }),
266
+ // out of sync: monitor still points at the old status.
267
+ monitor: makeMonitor(OFFLINE_STATUS_ID),
268
+ });
269
+
270
+ expect(result).toBeNull();
271
+ expect(mockFindOneBy).toHaveBeenCalledTimes(1);
272
+ expect(mockCreate).not.toHaveBeenCalled();
273
+ });
274
+
275
+ test("creates a fully-populated timeline row on a real status change", async () => {
276
+ mockFindOneBy.mockResolvedValue(
277
+ makeTimelineRow({
278
+ id: TIMELINE_ROW_ID,
279
+ monitorStatusId: OFFLINE_STATUS_ID,
280
+ }),
281
+ );
282
+
283
+ const createdRow: MonitorStatusTimeline = makeTimelineRow({
284
+ id: ObjectID.generate(),
285
+ monitorStatusId: ONLINE_STATUS_ID,
286
+ });
287
+ mockCreate.mockResolvedValue(createdRow);
288
+
289
+ const result: MonitorStatusTimeline | null = await run({
290
+ criteriaInstance: makeCriteria({
291
+ changeMonitorStatus: true,
292
+ monitorStatusId: ONLINE_STATUS_ID,
293
+ }),
294
+ monitor: makeMonitor(OFFLINE_STATUS_ID),
295
+ });
296
+
297
+ expect(result).toBe(createdRow);
298
+ expect(mockFindOneBy).toHaveBeenCalledTimes(1);
299
+ expect(mockCreate).toHaveBeenCalledTimes(1);
300
+
301
+ const createArg: {
302
+ data: MonitorStatusTimeline;
303
+ props: { isRoot: boolean };
304
+ } = mockCreate.mock.calls[0]![0] as {
305
+ data: MonitorStatusTimeline;
306
+ props: { isRoot: boolean };
307
+ };
308
+
309
+ expect(createArg.props).toEqual({ isRoot: true });
310
+ expect(createArg.data.monitorId?.toString()).toBe(MONITOR_ID.toString());
311
+ expect(createArg.data.projectId?.toString()).toBe(PROJECT_ID.toString());
312
+ expect(createArg.data.monitorStatusId?.toString()).toBe(
313
+ ONLINE_STATUS_ID.toString(),
314
+ );
315
+ expect(createArg.data.rootCause).toBe(ROOT_CAUSE);
316
+ expect(createArg.data.statusChangeLog).toEqual(
317
+ JSON.parse(JSON.stringify(dataToProcess)),
318
+ );
319
+ });
320
+
321
+ test("creates the first timeline row when the monitor has no history at all", async () => {
322
+ /*
323
+ * Brand-new monitor: no timeline rows and no currentMonitorStatusId, so
324
+ * the fast-path must not trigger and the first row must still be written.
325
+ */
326
+ mockFindOneBy.mockResolvedValue(null);
327
+
328
+ const createdRow: MonitorStatusTimeline = makeTimelineRow({
329
+ id: ObjectID.generate(),
330
+ monitorStatusId: ONLINE_STATUS_ID,
331
+ });
332
+ mockCreate.mockResolvedValue(createdRow);
333
+
334
+ const result: MonitorStatusTimeline | null = await run({
335
+ criteriaInstance: makeCriteria({
336
+ changeMonitorStatus: true,
337
+ monitorStatusId: ONLINE_STATUS_ID,
338
+ }),
339
+ monitor: makeMonitor(undefined),
340
+ });
341
+
342
+ expect(result).toBe(createdRow);
343
+ expect(mockFindOneBy).toHaveBeenCalledTimes(1);
344
+ expect(mockCreate).toHaveBeenCalledTimes(1);
345
+ });
346
+
347
+ describe("create error handling", () => {
348
+ beforeEach(() => {
349
+ // A genuine status change so the create path is reached every time.
350
+ mockFindOneBy.mockResolvedValue(
351
+ makeTimelineRow({
352
+ id: TIMELINE_ROW_ID,
353
+ monitorStatusId: OFFLINE_STATUS_ID,
354
+ }),
355
+ );
356
+ });
357
+
358
+ const runStatusChange: () => Promise<MonitorStatusTimeline | null> =
359
+ (): Promise<MonitorStatusTimeline | null> => {
360
+ return run({
361
+ criteriaInstance: makeCriteria({
362
+ changeMonitorStatus: true,
363
+ monitorStatusId: ONLINE_STATUS_ID,
364
+ }),
365
+ monitor: makeMonitor(OFFLINE_STATUS_ID),
366
+ });
367
+ };
368
+
369
+ test("swallows the exact same-as-previous dedupe race and returns null", async () => {
370
+ /*
371
+ * Loser of a concurrent-probe race: onBeforeCreate's dedupe throws
372
+ * this exact BadDataException. Idempotent no-op — must not fail the
373
+ * ingest run.
374
+ */
375
+ mockCreate.mockRejectedValue(
376
+ new BadDataException(MONITOR_STATUS_SAME_AS_PREVIOUS_ERROR_MESSAGE),
377
+ );
378
+
379
+ await expect(runStatusChange()).resolves.toBeNull();
380
+ expect(mockCreate).toHaveBeenCalledTimes(1);
381
+ });
382
+
383
+ test("swallows the exact lock-refusal ServerException and returns null", async () => {
384
+ /*
385
+ * Fail-closed mutex: the create was refused rather than performed
386
+ * unlocked. Skipping is recoverable (next probe result retries), so
387
+ * the util logs and returns null.
388
+ */
389
+ mockCreate.mockRejectedValue(
390
+ new ServerException(MONITOR_STATUS_TIMELINE_LOCK_ERROR_MESSAGE),
391
+ );
392
+
393
+ await expect(runStatusChange()).resolves.toBeNull();
394
+ expect(mockCreate).toHaveBeenCalledTimes(1);
395
+ });
396
+
397
+ test("propagates a BadDataException with any OTHER message", async () => {
398
+ const otherError: BadDataException = new BadDataException(
399
+ "Some unrelated validation failure.",
400
+ );
401
+ mockCreate.mockRejectedValue(otherError);
402
+
403
+ await expect(runStatusChange()).rejects.toBe(otherError);
404
+ });
405
+
406
+ test("propagates non-matching error types", async () => {
407
+ const dbError: Error = new Error("connection reset by peer");
408
+ mockCreate.mockRejectedValue(dbError);
409
+
410
+ await expect(runStatusChange()).rejects.toBe(dbError);
411
+ });
412
+ });
413
+
414
+ test("comparison-bug pin: last row's PK id differs from its monitorStatusId — still no duplicate create", async () => {
415
+ /*
416
+ * Before this branch, the second shouldUpdateStatus condition compared
417
+ * the criteria's target monitorStatusId to lastMonitorStatusTimeline.id
418
+ * (the row's PK) — two different UUID namespaces that never match — so
419
+ * that condition was ALWAYS true whenever changeMonitorStatus was set,
420
+ * and only the inner same-as-last-status check (line ~121) saved the day
421
+ * by returning null before the INSERT.
422
+ *
423
+ * The fixed code compares against lastMonitorStatusTimeline
424
+ * .monitorStatusId, so with target === last.monitorStatusId (and id
425
+ * deliberately different, and currentMonitorStatusId out of sync so the
426
+ * fast-path stays out of the way) shouldUpdateStatus is reached only via
427
+ * the third condition and the outcome must be: exactly one SELECT, zero
428
+ * creates, null. If someone reverts the comparison to `.id`, the outcome
429
+ * is unchanged only because of the inner backstop — this test plus the
430
+ * out-of-sync test above pin that the backstop and the fixed condition
431
+ * agree on the row's monitorStatusId, never its PK.
432
+ */
433
+ mockFindOneBy.mockResolvedValue(
434
+ makeTimelineRow({
435
+ // PK deliberately NOT equal to the status id it points at.
436
+ id: TIMELINE_ROW_ID,
437
+ monitorStatusId: ONLINE_STATUS_ID,
438
+ }),
439
+ );
440
+
441
+ const result: MonitorStatusTimeline | null = await run({
442
+ criteriaInstance: makeCriteria({
443
+ changeMonitorStatus: true,
444
+ monitorStatusId: ONLINE_STATUS_ID,
445
+ }),
446
+ monitor: makeMonitor(OFFLINE_STATUS_ID),
447
+ });
448
+
449
+ expect(result).toBeNull();
450
+ expect(mockFindOneBy).toHaveBeenCalledTimes(1);
451
+ expect(mockCreate).not.toHaveBeenCalled();
452
+ });
453
+ });