@oneuptime/common 12.0.28 → 12.0.30

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 (367) hide show
  1. package/Models/DatabaseModels/DashboardDomain.ts +43 -0
  2. package/Models/DatabaseModels/Index.ts +11 -0
  3. package/Models/DatabaseModels/MonitorTemplate.ts +15 -3
  4. package/Models/DatabaseModels/OnCallDutyPolicySchedule.ts +43 -0
  5. package/Models/DatabaseModels/OnCallDutyPolicyScheduleCalendarFeed.ts +662 -0
  6. package/Models/DatabaseModels/ProjectOnCallCalendarFeed.ts +584 -0
  7. package/Models/DatabaseModels/StatusPageDomain.ts +43 -0
  8. package/Models/DatabaseModels/UserOnCallCalendarFeed.ts +546 -0
  9. package/Models/DatabaseModels/UserOnCallShiftReminder.ts +231 -0
  10. package/Models/DatabaseModels/UserOnCallShiftReminderLog.ts +331 -0
  11. package/Server/API/DashboardDomainAPI.ts +74 -0
  12. package/Server/API/MicrosoftTeamsAPI.ts +81 -180
  13. package/Server/API/OnCallCalendarAPI.ts +2194 -0
  14. package/Server/API/StatusPageDomainAPI.ts +74 -0
  15. package/Server/EnvironmentConfig.ts +138 -0
  16. package/Server/Infrastructure/OnCallCalendarFeedCache.ts +1050 -0
  17. package/Server/Infrastructure/Postgres/SchemaMigrations/1790200000000-AllowNullMonitorNameOnMonitorTemplate.ts +43 -0
  18. package/Server/Infrastructure/Postgres/SchemaMigrations/1790300000000-AddCertificateReissueRequestedAtToDomains.ts +40 -0
  19. package/Server/Infrastructure/Postgres/SchemaMigrations/1790400000000-AddOnCallCalendarFeeds.ts +253 -0
  20. package/Server/Infrastructure/Postgres/SchemaMigrations/Index.ts +6 -0
  21. package/Server/Middleware/OnCallCalendarFeedRateLimit.ts +487 -0
  22. package/Server/Middleware/ProjectAuthorization.ts +6 -1
  23. package/Server/Services/DashboardDomainService.ts +131 -0
  24. package/Server/Services/Index.ts +12 -0
  25. package/Server/Services/LogPipelineProcessorService.ts +88 -0
  26. package/Server/Services/MonitorTemplateService.ts +70 -11
  27. package/Server/Services/OnCallDutyPolicyEscalationRuleScheduleService.ts +32 -0
  28. package/Server/Services/OnCallDutyPolicyScheduleCalendarFeedService.ts +260 -0
  29. package/Server/Services/OnCallDutyPolicyScheduleLayerService.ts +70 -3
  30. package/Server/Services/OnCallDutyPolicyScheduleLayerUserService.ts +138 -3
  31. package/Server/Services/OnCallDutyPolicyScheduleService.ts +1465 -134
  32. package/Server/Services/OnCallDutyPolicyUserOverrideService.ts +167 -31
  33. package/Server/Services/ProjectOnCallCalendarFeedService.ts +192 -0
  34. package/Server/Services/StatusPageDomainService.ts +163 -3
  35. package/Server/Services/StatusPagePrivateUserSessionService.ts +197 -56
  36. package/Server/Services/TeamMemberService.ts +489 -1
  37. package/Server/Services/TeamService.ts +50 -0
  38. package/Server/Services/UserNotificationSettingService.ts +44 -1
  39. package/Server/Services/UserOnCallCalendarFeedService.ts +159 -0
  40. package/Server/Services/UserOnCallShiftReminderLogService.ts +151 -0
  41. package/Server/Services/UserOnCallShiftReminderService.ts +112 -0
  42. package/Server/Types/Domain.ts +17 -0
  43. package/Server/Utils/APIKey/AccessPermission.ts +24 -0
  44. package/Server/Utils/Greenlock/Greenlock.ts +156 -58
  45. package/Server/Utils/LogPipelineProcessorValidation.ts +112 -0
  46. package/Server/Utils/OnCall/CalendarFeedToken.ts +241 -0
  47. package/Server/Utils/OnCall/OnCallCalendarFeedRenderer.ts +1488 -0
  48. package/Server/Utils/OnCall/OnCallCalendarFeedUrls.ts +230 -0
  49. package/Server/Utils/OnCall/OnCallShiftChangeListeners.ts +227 -0
  50. package/Server/Utils/OnCall/OnCallShiftMaterializer.ts +1127 -0
  51. package/Server/Utils/OnCall/OnCallShiftReminderListener.ts +174 -0
  52. package/Server/Utils/OnCall/OnCallShiftReminderRunner.ts +2584 -0
  53. package/Server/Utils/Response.ts +134 -0
  54. package/Server/Utils/SecurityEvent/GoogleSecOps/GoogleSecOpsClient.ts +1094 -45
  55. package/Server/Utils/SecurityEvent/GoogleSecOps/GoogleSecOpsPoller.ts +153 -17
  56. package/Server/Utils/StartServer.ts +10 -0
  57. package/Server/Utils/Telemetry/AppMetrics.ts +51 -0
  58. package/Server/Utils/Workspace/MicrosoftTeams/Actions/Alert.ts +23 -11
  59. package/Server/Utils/Workspace/MicrosoftTeams/Actions/Auth.ts +0 -170
  60. package/Server/Utils/Workspace/MicrosoftTeams/Actions/Authorization.ts +158 -0
  61. package/Server/Utils/Workspace/MicrosoftTeams/Actions/Incident.ts +23 -11
  62. package/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeams.ts +35 -13
  63. package/Tests/App/Dashboard/MonitorTemplateColumnAndFacet.test.tsx +505 -0
  64. package/Tests/App/Dashboard/OnCallCalendarFeedLinks.test.tsx +229 -0
  65. package/Tests/App/Dashboard/OnCallCalendarFeedPlanGate.test.tsx +489 -0
  66. package/Tests/App/Dashboard/OnCallCalendarFeedSideMenus.test.tsx +149 -0
  67. package/Tests/App/Dashboard/OnCallCalendarFeedUtil.test.ts +855 -0
  68. package/Tests/App/Dashboard/OnCallPersonalCalendarFeed.test.tsx +1209 -0
  69. package/Tests/App/Dashboard/OnCallSharedCalendarFeedCard.test.tsx +1019 -0
  70. package/Tests/App/Dashboard/OnCallShiftRemindersAndUpcomingShifts.test.tsx +655 -0
  71. package/Tests/App/Dashboard/UserSettingsSetupChecklistModel.test.ts +73 -0
  72. package/Tests/App/StatusPage/StatusPageLoginCodeBootstrap.test.ts +106 -0
  73. package/Tests/App/StatusPage/StatusPageLoginCodeUtil.test.ts +84 -0
  74. package/Tests/App/StatusPage/StatusPageRedirectSafety.test.ts +119 -0
  75. package/Tests/Models/MonitorTemplateMonitorNameColumn.test.ts +258 -0
  76. package/Tests/Models/OnCallCalendarFeedModels.test.ts +642 -0
  77. package/Tests/Models/OnCallDutyPolicyScheduleCalendarFeed.test.ts +288 -0
  78. package/Tests/Models/OnCallDutyPolicyScheduleShiftConfigVersion.test.ts +88 -0
  79. package/Tests/Models/UserOnCallShiftReminderModels.test.ts +368 -0
  80. package/Tests/Server/API/CustomDomainReissueSslAPI.test.ts +469 -0
  81. package/Tests/Server/API/Helpers.ts +8 -0
  82. package/Tests/Server/API/MicrosoftTeamsBotMessagesEndpoint.test.ts +729 -0
  83. package/Tests/Server/API/MicrosoftTeamsBotTestEndpoint.test.ts +26 -0
  84. package/Tests/Server/API/MicrosoftTeamsLegacyWebhookRemoval.test.ts +402 -0
  85. package/Tests/Server/API/OnCallCalendarAPI.test.ts +4461 -0
  86. package/Tests/Server/EnvironmentConfigOnCallCalendarFeed.test.ts +357 -0
  87. package/Tests/Server/Infrastructure/OnCallCalendarFeedCache.test.ts +1589 -0
  88. package/Tests/Server/Infrastructure/Postgres/AddOnCallCalendarFeedsMigration.test.ts +369 -0
  89. package/Tests/Server/Middleware/OnCallCalendarFeedRateLimit.test.ts +1365 -0
  90. package/Tests/Server/Middleware/ProjectAuthorizationApiKeyMiddleware.test.ts +4 -0
  91. package/Tests/Server/Services/AddCertificateReissueRequestedAtToDomainsMigration.test.ts +287 -0
  92. package/Tests/Server/Services/CustomDomainCertificateReissue.test.ts +477 -0
  93. package/Tests/Server/Services/FeedRetentionConsistency.test.ts +70 -0
  94. package/Tests/Server/Services/LogPipelineProcessorSaveValidation.test.ts +249 -0
  95. package/Tests/Server/Services/MonitorTemplateServiceBulkSync.test.ts +549 -0
  96. package/Tests/Server/Services/MonitorTemplateServiceNetworkDeviceSync.test.ts +16 -3
  97. package/Tests/Server/Services/NetworkDeviceAutoImportRuleEngineService.test.ts +57 -0
  98. package/Tests/Server/Services/OnCallCalendarFeedServices.test.ts +1103 -0
  99. package/Tests/Server/Services/OnCallDutyPolicyScheduleResolver.test.ts +1037 -0
  100. package/Tests/Server/Services/OnCallShiftConfigPropagation.test.ts +1270 -0
  101. package/Tests/Server/Services/SecurityEventLastErrorColumnWidth.test.ts +151 -15
  102. package/Tests/Server/Services/StatusPagePrivateUserSessionService.test.ts +587 -0
  103. package/Tests/Server/Services/TeamDeleteMemberCleanup.test.ts +156 -0
  104. package/Tests/Server/Services/TeamMemberOnCallCleanup.test.ts +755 -0
  105. package/Tests/Server/Services/UserNotificationSettingShiftReminderDefaults.test.ts +267 -0
  106. package/Tests/Server/Services/UserOnCallShiftReminderServices.test.ts +583 -0
  107. package/Tests/Server/Types/Database/Permissions/ApiKeyFileAccess.test.ts +286 -0
  108. package/Tests/Server/Types/Database/Permissions/MonitorTemplateColumnAccess.test.ts +328 -0
  109. package/Tests/Server/Types/Database/Permissions/OnCallCalendarFeedPermissions.test.ts +254 -0
  110. package/Tests/Server/Types/Database/QueryHelperFindWithSameTextAndIsNull.test.ts +83 -0
  111. package/Tests/Server/Utils/AI/SRE/ConfidenceSignal.test.ts +377 -0
  112. package/Tests/Server/Utils/APIKey/AccessPermission.test.ts +16 -1
  113. package/Tests/Server/Utils/Greenlock/CertificateRenewalSchedule.test.ts +250 -0
  114. package/Tests/Server/Utils/LogPipelineProcessorValidation.test.ts +198 -0
  115. package/Tests/Server/Utils/OnCall/CalendarFeedToken.test.ts +467 -0
  116. package/Tests/Server/Utils/OnCall/OnCallCalendarFeedRenderer.test.ts +2142 -0
  117. package/Tests/Server/Utils/OnCall/OnCallCalendarFeedUrls.test.ts +278 -0
  118. package/Tests/Server/Utils/OnCall/OnCallResolverTestHarness.ts +440 -0
  119. package/Tests/Server/Utils/OnCall/OnCallShiftChangeListeners.test.ts +314 -0
  120. package/Tests/Server/Utils/OnCall/OnCallShiftMaterializer.test.ts +1502 -0
  121. package/Tests/Server/Utils/OnCall/OnCallShiftReminderListener.test.ts +491 -0
  122. package/Tests/Server/Utils/OnCall/OnCallShiftReminderRunner.test.ts +3088 -0
  123. package/Tests/Server/Utils/OnCall/OnCallShiftReminderTestHarness.ts +910 -0
  124. package/Tests/Server/Utils/ResponseSendCalendarResponse.test.ts +561 -0
  125. package/Tests/Server/Utils/SecurityEvent/ConnectorErrorMessage.test.ts +48 -9
  126. package/Tests/Server/Utils/SecurityEvent/GoogleSecOpsAuth.test.ts +726 -0
  127. package/Tests/Server/Utils/SecurityEvent/GoogleSecOpsClient.test.ts +74 -11
  128. package/Tests/Server/Utils/SecurityEvent/GoogleSecOpsErrorHandling.test.ts +1107 -0
  129. package/Tests/Server/Utils/SecurityEvent/GoogleSecOpsPoller.test.ts +20 -3
  130. package/Tests/Server/Utils/SecurityEvent/GoogleSecOpsPollerFailureTaxonomy.test.ts +51 -11
  131. package/Tests/Server/Utils/SecurityEvent/GoogleSecOpsPollerHardening.test.ts +1246 -0
  132. package/Tests/Server/Utils/SecurityEvent/GoogleSecOpsRequestContract.test.ts +961 -0
  133. package/Tests/Server/Utils/SecurityEvent/GoogleSecOpsResponseParsing.test.ts +653 -0
  134. package/Tests/Server/Utils/StartServerEncryptionSecretWarning.test.ts +64 -0
  135. package/Tests/Server/Utils/Telemetry/AppMetrics.test.ts +29 -0
  136. package/Tests/Server/Utils/Workspace/MicrosoftTeamsActionAuthorization.test.ts +880 -0
  137. package/Tests/Types/API/HTTPResponse.test.ts +175 -0
  138. package/Tests/Types/API/Route.test.ts +36 -4
  139. package/Tests/Types/Calendar/ICalendar.test.ts +488 -0
  140. package/Tests/Types/Database/DatabaseProperty.test.ts +171 -0
  141. package/Tests/Types/DateLocalShortDateTimeString.test.ts +518 -0
  142. package/Tests/Types/DateLocalTimeString.test.ts +259 -0
  143. package/Tests/Types/DateUserPrefers12HourFormat.test.ts +316 -0
  144. package/Tests/Types/NotificationSetting/NotificationSettingEventType.test.ts +53 -0
  145. package/Tests/Types/OnCallDutyPolicy/CalendarFeedTestFixtures.ts +310 -0
  146. package/Tests/Types/OnCallDutyPolicy/CalendarFeedWindow.test.ts +205 -0
  147. package/Tests/Types/OnCallDutyPolicy/LayerUtilLayerMetaAndIterationCap.test.ts +458 -0
  148. package/Tests/Types/OnCallDutyPolicy/MaterializedShift.test.ts +197 -0
  149. package/Tests/Types/OnCallDutyPolicy/OnCallCalendarFeedUtil.test.ts +1986 -0
  150. package/Tests/Types/OnCallDutyPolicy/ScheduleShiftUtilGroupKey.test.ts +678 -0
  151. package/Tests/Types/OnCallDutyPolicy/ShiftSeamUtil.test.ts +417 -0
  152. package/Tests/UI/Components/LogsHistogram.test.tsx +41 -2
  153. package/Tests/UI/Components/LogsViewerEmptyStateTimeRange.test.tsx +212 -0
  154. package/Tests/UI/Components/TelemetryChartClockFormat.test.tsx +359 -0
  155. package/Tests/UI/Components/TimeRangePickerClockFormat.test.tsx +422 -0
  156. package/Tests/UI/Components/TimeRangePickerDropdown.test.tsx +146 -8
  157. package/Tests/UI/MicrosoftTeams/MicrosoftTeamsMessagingEndpointGuidance.test.tsx +520 -0
  158. package/Tests/UI/Utils/Breadcrumb/fixtures/RealBreadcrumbTrails.ts +10 -0
  159. package/Tests/UI/Utils/Breadcrumb/fixtures/RealRoutePatterns.ts +2 -0
  160. package/Tests/UI/Utils/Navigation.test.ts +82 -0
  161. package/Tests/Utils/Array.test.ts +100 -0
  162. package/Tests/Utils/CertificateReissue.test.ts +225 -0
  163. package/Tests/Utils/Grok/Grok.test.ts +492 -0
  164. package/Tests/Utils/Monitor/NetworkDeviceMonitorTemplateUtil.test.ts +181 -13
  165. package/Types/API/Route.ts +20 -5
  166. package/Types/Calendar/ICalendar.ts +442 -0
  167. package/Types/Date.ts +143 -13
  168. package/Types/Email/EmailTemplateType.ts +2 -0
  169. package/Types/NotificationSetting/NotificationSettingEventType.ts +4 -0
  170. package/Types/OnCallDutyPolicy/CalendarFeedWindow.ts +140 -0
  171. package/Types/OnCallDutyPolicy/Layer.ts +190 -35
  172. package/Types/OnCallDutyPolicy/MaterializedShift.ts +274 -0
  173. package/Types/OnCallDutyPolicy/OnCallCalendarFeedUtil.ts +1415 -0
  174. package/Types/OnCallDutyPolicy/ScheduleShiftUtil.ts +159 -4
  175. package/Types/OnCallDutyPolicy/ShiftSeamUtil.ts +106 -0
  176. package/Types/Rum/SessionReplay.ts +62 -1
  177. package/UI/Components/Date/TimeRangePickerDropdown.tsx +9 -5
  178. package/UI/Components/LogsViewer/LogsViewer.tsx +12 -7
  179. package/UI/Components/LogsViewer/components/HistogramTooltip.tsx +18 -17
  180. package/UI/Components/LogsViewer/components/LogsAnalyticsView.tsx +21 -28
  181. package/UI/Components/LogsViewer/components/LogsHistogram.tsx +2 -4
  182. package/UI/Components/TelemetryViewer/components/TelemetryHistogram.tsx +2 -4
  183. package/UI/Components/TelemetryViewer/components/TelemetryHistogramTooltip.tsx +17 -16
  184. package/UI/Utils/Navigation.ts +42 -0
  185. package/Utils/Array.ts +37 -0
  186. package/Utils/CertificateReissue.ts +128 -0
  187. package/Utils/Grok/Grok.ts +461 -0
  188. package/Utils/Grok/GrokPatterns.ts +118 -0
  189. package/Utils/Monitor/NetworkDeviceMonitorTemplateUtil.ts +58 -6
  190. package/build/dist/Models/DatabaseModels/DashboardDomain.js +44 -0
  191. package/build/dist/Models/DatabaseModels/DashboardDomain.js.map +1 -1
  192. package/build/dist/Models/DatabaseModels/Index.js +11 -0
  193. package/build/dist/Models/DatabaseModels/Index.js.map +1 -1
  194. package/build/dist/Models/DatabaseModels/MonitorTemplate.js +17 -4
  195. package/build/dist/Models/DatabaseModels/MonitorTemplate.js.map +1 -1
  196. package/build/dist/Models/DatabaseModels/OnCallDutyPolicySchedule.js +44 -0
  197. package/build/dist/Models/DatabaseModels/OnCallDutyPolicySchedule.js.map +1 -1
  198. package/build/dist/Models/DatabaseModels/OnCallDutyPolicyScheduleCalendarFeed.js +699 -0
  199. package/build/dist/Models/DatabaseModels/OnCallDutyPolicyScheduleCalendarFeed.js.map +1 -0
  200. package/build/dist/Models/DatabaseModels/ProjectOnCallCalendarFeed.js +620 -0
  201. package/build/dist/Models/DatabaseModels/ProjectOnCallCalendarFeed.js.map +1 -0
  202. package/build/dist/Models/DatabaseModels/StatusPageDomain.js +44 -0
  203. package/build/dist/Models/DatabaseModels/StatusPageDomain.js.map +1 -1
  204. package/build/dist/Models/DatabaseModels/UserOnCallCalendarFeed.js +585 -0
  205. package/build/dist/Models/DatabaseModels/UserOnCallCalendarFeed.js.map +1 -0
  206. package/build/dist/Models/DatabaseModels/UserOnCallShiftReminder.js +247 -0
  207. package/build/dist/Models/DatabaseModels/UserOnCallShiftReminder.js.map +1 -0
  208. package/build/dist/Models/DatabaseModels/UserOnCallShiftReminderLog.js +355 -0
  209. package/build/dist/Models/DatabaseModels/UserOnCallShiftReminderLog.js.map +1 -0
  210. package/build/dist/Server/API/DashboardDomainAPI.js +43 -1
  211. package/build/dist/Server/API/DashboardDomainAPI.js.map +1 -1
  212. package/build/dist/Server/API/MicrosoftTeamsAPI.js +74 -118
  213. package/build/dist/Server/API/MicrosoftTeamsAPI.js.map +1 -1
  214. package/build/dist/Server/API/OnCallCalendarAPI.js +1371 -0
  215. package/build/dist/Server/API/OnCallCalendarAPI.js.map +1 -0
  216. package/build/dist/Server/API/StatusPageDomainAPI.js +43 -1
  217. package/build/dist/Server/API/StatusPageDomainAPI.js.map +1 -1
  218. package/build/dist/Server/EnvironmentConfig.js +102 -0
  219. package/build/dist/Server/EnvironmentConfig.js.map +1 -1
  220. package/build/dist/Server/Infrastructure/OnCallCalendarFeedCache.js +663 -0
  221. package/build/dist/Server/Infrastructure/OnCallCalendarFeedCache.js.map +1 -0
  222. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1790200000000-AllowNullMonitorNameOnMonitorTemplate.js +34 -0
  223. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1790200000000-AllowNullMonitorNameOnMonitorTemplate.js.map +1 -0
  224. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1790300000000-AddCertificateReissueRequestedAtToDomains.js +28 -0
  225. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1790300000000-AddCertificateReissueRequestedAtToDomains.js.map +1 -0
  226. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1790400000000-AddOnCallCalendarFeeds.js +103 -0
  227. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1790400000000-AddOnCallCalendarFeeds.js.map +1 -0
  228. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js +6 -0
  229. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js.map +1 -1
  230. package/build/dist/Server/Middleware/OnCallCalendarFeedRateLimit.js +342 -0
  231. package/build/dist/Server/Middleware/OnCallCalendarFeedRateLimit.js.map +1 -0
  232. package/build/dist/Server/Middleware/ProjectAuthorization.js +6 -1
  233. package/build/dist/Server/Middleware/ProjectAuthorization.js.map +1 -1
  234. package/build/dist/Server/Services/DashboardDomainService.js +104 -0
  235. package/build/dist/Server/Services/DashboardDomainService.js.map +1 -1
  236. package/build/dist/Server/Services/Index.js +12 -0
  237. package/build/dist/Server/Services/Index.js.map +1 -1
  238. package/build/dist/Server/Services/LogPipelineProcessorService.js +65 -0
  239. package/build/dist/Server/Services/LogPipelineProcessorService.js.map +1 -1
  240. package/build/dist/Server/Services/MonitorTemplateService.js +57 -11
  241. package/build/dist/Server/Services/MonitorTemplateService.js.map +1 -1
  242. package/build/dist/Server/Services/OnCallDutyPolicyEscalationRuleScheduleService.js +20 -0
  243. package/build/dist/Server/Services/OnCallDutyPolicyEscalationRuleScheduleService.js.map +1 -1
  244. package/build/dist/Server/Services/OnCallDutyPolicyScheduleCalendarFeedService.js +219 -0
  245. package/build/dist/Server/Services/OnCallDutyPolicyScheduleCalendarFeedService.js.map +1 -0
  246. package/build/dist/Server/Services/OnCallDutyPolicyScheduleLayerService.js +47 -3
  247. package/build/dist/Server/Services/OnCallDutyPolicyScheduleLayerService.js.map +1 -1
  248. package/build/dist/Server/Services/OnCallDutyPolicyScheduleLayerUserService.js +112 -3
  249. package/build/dist/Server/Services/OnCallDutyPolicyScheduleLayerUserService.js.map +1 -1
  250. package/build/dist/Server/Services/OnCallDutyPolicyScheduleService.js +955 -9
  251. package/build/dist/Server/Services/OnCallDutyPolicyScheduleService.js.map +1 -1
  252. package/build/dist/Server/Services/OnCallDutyPolicyUserOverrideService.js +93 -6
  253. package/build/dist/Server/Services/OnCallDutyPolicyUserOverrideService.js.map +1 -1
  254. package/build/dist/Server/Services/ProjectOnCallCalendarFeedService.js +157 -0
  255. package/build/dist/Server/Services/ProjectOnCallCalendarFeedService.js.map +1 -0
  256. package/build/dist/Server/Services/StatusPageDomainService.js +132 -3
  257. package/build/dist/Server/Services/StatusPageDomainService.js.map +1 -1
  258. package/build/dist/Server/Services/StatusPagePrivateUserSessionService.js +112 -32
  259. package/build/dist/Server/Services/StatusPagePrivateUserSessionService.js.map +1 -1
  260. package/build/dist/Server/Services/TeamMemberService.js +396 -1
  261. package/build/dist/Server/Services/TeamMemberService.js.map +1 -1
  262. package/build/dist/Server/Services/TeamService.js +43 -0
  263. package/build/dist/Server/Services/TeamService.js.map +1 -1
  264. package/build/dist/Server/Services/UserNotificationSettingService.js +26 -1
  265. package/build/dist/Server/Services/UserNotificationSettingService.js.map +1 -1
  266. package/build/dist/Server/Services/UserOnCallCalendarFeedService.js +140 -0
  267. package/build/dist/Server/Services/UserOnCallCalendarFeedService.js.map +1 -0
  268. package/build/dist/Server/Services/UserOnCallShiftReminderLogService.js +116 -0
  269. package/build/dist/Server/Services/UserOnCallShiftReminderLogService.js.map +1 -0
  270. package/build/dist/Server/Services/UserOnCallShiftReminderService.js +92 -0
  271. package/build/dist/Server/Services/UserOnCallShiftReminderService.js.map +1 -0
  272. package/build/dist/Server/Types/Domain.js +16 -0
  273. package/build/dist/Server/Types/Domain.js.map +1 -1
  274. package/build/dist/Server/Utils/APIKey/AccessPermission.js +24 -0
  275. package/build/dist/Server/Utils/APIKey/AccessPermission.js.map +1 -1
  276. package/build/dist/Server/Utils/Greenlock/Greenlock.js +110 -35
  277. package/build/dist/Server/Utils/Greenlock/Greenlock.js.map +1 -1
  278. package/build/dist/Server/Utils/LogPipelineProcessorValidation.js +77 -0
  279. package/build/dist/Server/Utils/LogPipelineProcessorValidation.js.map +1 -0
  280. package/build/dist/Server/Utils/OnCall/CalendarFeedToken.js +161 -0
  281. package/build/dist/Server/Utils/OnCall/CalendarFeedToken.js.map +1 -0
  282. package/build/dist/Server/Utils/OnCall/OnCallCalendarFeedRenderer.js +983 -0
  283. package/build/dist/Server/Utils/OnCall/OnCallCalendarFeedRenderer.js.map +1 -0
  284. package/build/dist/Server/Utils/OnCall/OnCallCalendarFeedUrls.js +155 -0
  285. package/build/dist/Server/Utils/OnCall/OnCallCalendarFeedUrls.js.map +1 -0
  286. package/build/dist/Server/Utils/OnCall/OnCallShiftChangeListeners.js +145 -0
  287. package/build/dist/Server/Utils/OnCall/OnCallShiftChangeListeners.js.map +1 -0
  288. package/build/dist/Server/Utils/OnCall/OnCallShiftMaterializer.js +752 -0
  289. package/build/dist/Server/Utils/OnCall/OnCallShiftMaterializer.js.map +1 -0
  290. package/build/dist/Server/Utils/OnCall/OnCallShiftReminderListener.js +92 -0
  291. package/build/dist/Server/Utils/OnCall/OnCallShiftReminderListener.js.map +1 -0
  292. package/build/dist/Server/Utils/OnCall/OnCallShiftReminderRunner.js +1786 -0
  293. package/build/dist/Server/Utils/OnCall/OnCallShiftReminderRunner.js.map +1 -0
  294. package/build/dist/Server/Utils/Response.js +97 -0
  295. package/build/dist/Server/Utils/Response.js.map +1 -1
  296. package/build/dist/Server/Utils/SecurityEvent/GoogleSecOps/GoogleSecOpsClient.js +777 -36
  297. package/build/dist/Server/Utils/SecurityEvent/GoogleSecOps/GoogleSecOpsClient.js.map +1 -1
  298. package/build/dist/Server/Utils/SecurityEvent/GoogleSecOps/GoogleSecOpsPoller.js +108 -17
  299. package/build/dist/Server/Utils/SecurityEvent/GoogleSecOps/GoogleSecOpsPoller.js.map +1 -1
  300. package/build/dist/Server/Utils/StartServer.js +9 -1
  301. package/build/dist/Server/Utils/StartServer.js.map +1 -1
  302. package/build/dist/Server/Utils/Telemetry/AppMetrics.js +41 -0
  303. package/build/dist/Server/Utils/Telemetry/AppMetrics.js.map +1 -1
  304. package/build/dist/Server/Utils/Workspace/MicrosoftTeams/Actions/Alert.js +17 -6
  305. package/build/dist/Server/Utils/Workspace/MicrosoftTeams/Actions/Alert.js.map +1 -1
  306. package/build/dist/Server/Utils/Workspace/MicrosoftTeams/Actions/Auth.js +0 -159
  307. package/build/dist/Server/Utils/Workspace/MicrosoftTeams/Actions/Auth.js.map +1 -1
  308. package/build/dist/Server/Utils/Workspace/MicrosoftTeams/Actions/Authorization.js +128 -0
  309. package/build/dist/Server/Utils/Workspace/MicrosoftTeams/Actions/Authorization.js.map +1 -0
  310. package/build/dist/Server/Utils/Workspace/MicrosoftTeams/Actions/Incident.js +17 -6
  311. package/build/dist/Server/Utils/Workspace/MicrosoftTeams/Actions/Incident.js.map +1 -1
  312. package/build/dist/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeams.js +25 -13
  313. package/build/dist/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeams.js.map +1 -1
  314. package/build/dist/Types/API/Route.js +16 -5
  315. package/build/dist/Types/API/Route.js.map +1 -1
  316. package/build/dist/Types/Calendar/ICalendar.js +290 -0
  317. package/build/dist/Types/Calendar/ICalendar.js.map +1 -0
  318. package/build/dist/Types/Date.js +115 -13
  319. package/build/dist/Types/Date.js.map +1 -1
  320. package/build/dist/Types/Email/EmailTemplateType.js +2 -0
  321. package/build/dist/Types/Email/EmailTemplateType.js.map +1 -1
  322. package/build/dist/Types/NotificationSetting/NotificationSettingEventType.js +3 -0
  323. package/build/dist/Types/NotificationSetting/NotificationSettingEventType.js.map +1 -1
  324. package/build/dist/Types/OnCallDutyPolicy/CalendarFeedWindow.js +97 -0
  325. package/build/dist/Types/OnCallDutyPolicy/CalendarFeedWindow.js.map +1 -0
  326. package/build/dist/Types/OnCallDutyPolicy/Layer.js +113 -19
  327. package/build/dist/Types/OnCallDutyPolicy/Layer.js.map +1 -1
  328. package/build/dist/Types/OnCallDutyPolicy/MaterializedShift.js +134 -0
  329. package/build/dist/Types/OnCallDutyPolicy/MaterializedShift.js.map +1 -0
  330. package/build/dist/Types/OnCallDutyPolicy/OnCallCalendarFeedUtil.js +800 -0
  331. package/build/dist/Types/OnCallDutyPolicy/OnCallCalendarFeedUtil.js.map +1 -0
  332. package/build/dist/Types/OnCallDutyPolicy/ScheduleShiftUtil.js +97 -3
  333. package/build/dist/Types/OnCallDutyPolicy/ScheduleShiftUtil.js.map +1 -1
  334. package/build/dist/Types/OnCallDutyPolicy/ShiftSeamUtil.js +77 -0
  335. package/build/dist/Types/OnCallDutyPolicy/ShiftSeamUtil.js.map +1 -0
  336. package/build/dist/Types/Rum/SessionReplay.js +38 -0
  337. package/build/dist/Types/Rum/SessionReplay.js.map +1 -1
  338. package/build/dist/UI/Components/Date/TimeRangePickerDropdown.js +9 -5
  339. package/build/dist/UI/Components/Date/TimeRangePickerDropdown.js.map +1 -1
  340. package/build/dist/UI/Components/LogsViewer/LogsViewer.js +12 -7
  341. package/build/dist/UI/Components/LogsViewer/LogsViewer.js.map +1 -1
  342. package/build/dist/UI/Components/LogsViewer/components/HistogramTooltip.js +15 -14
  343. package/build/dist/UI/Components/LogsViewer/components/HistogramTooltip.js.map +1 -1
  344. package/build/dist/UI/Components/LogsViewer/components/LogsAnalyticsView.js +17 -22
  345. package/build/dist/UI/Components/LogsViewer/components/LogsAnalyticsView.js.map +1 -1
  346. package/build/dist/UI/Components/LogsViewer/components/LogsHistogram.js +2 -4
  347. package/build/dist/UI/Components/LogsViewer/components/LogsHistogram.js.map +1 -1
  348. package/build/dist/UI/Components/TelemetryViewer/components/TelemetryHistogram.js +2 -4
  349. package/build/dist/UI/Components/TelemetryViewer/components/TelemetryHistogram.js.map +1 -1
  350. package/build/dist/UI/Components/TelemetryViewer/components/TelemetryHistogramTooltip.js +15 -14
  351. package/build/dist/UI/Components/TelemetryViewer/components/TelemetryHistogramTooltip.js.map +1 -1
  352. package/build/dist/UI/Utils/Navigation.js +30 -0
  353. package/build/dist/UI/Utils/Navigation.js.map +1 -1
  354. package/build/dist/Utils/Array.js +25 -0
  355. package/build/dist/Utils/Array.js.map +1 -1
  356. package/build/dist/Utils/CertificateReissue.js +97 -0
  357. package/build/dist/Utils/CertificateReissue.js.map +1 -0
  358. package/build/dist/Utils/Grok/Grok.js +324 -0
  359. package/build/dist/Utils/Grok/Grok.js.map +1 -0
  360. package/build/dist/Utils/Grok/GrokPatterns.js +112 -0
  361. package/build/dist/Utils/Grok/GrokPatterns.js.map +1 -0
  362. package/build/dist/Utils/Monitor/NetworkDeviceMonitorTemplateUtil.js +63 -3
  363. package/build/dist/Utils/Monitor/NetworkDeviceMonitorTemplateUtil.js.map +1 -1
  364. package/package.json +1 -1
  365. package/UI/Utils/JsonWebToken.ts +0 -14
  366. package/build/dist/UI/Utils/JsonWebToken.js +0 -10
  367. package/build/dist/UI/Utils/JsonWebToken.js.map +0 -1
@@ -0,0 +1,910 @@
1
+ import { jest } from "@jest/globals";
2
+ import DatabaseConfig from "../../../../Server/DatabaseConfig";
3
+ import GlobalCache from "../../../../Server/Infrastructure/GlobalCache";
4
+ import Semaphore, {
5
+ SemaphoreMutex,
6
+ } from "../../../../Server/Infrastructure/Semaphore";
7
+ import OnCallDutyPolicyScheduleService from "../../../../Server/Services/OnCallDutyPolicyScheduleService";
8
+ import UserNotificationSettingService from "../../../../Server/Services/UserNotificationSettingService";
9
+ import UserOnCallShiftReminderLogService from "../../../../Server/Services/UserOnCallShiftReminderLogService";
10
+ import UserOnCallShiftReminderService from "../../../../Server/Services/UserOnCallShiftReminderService";
11
+ import UserService from "../../../../Server/Services/UserService";
12
+ import logger from "../../../../Server/Utils/Logger";
13
+ import Telemetry from "../../../../Server/Utils/Telemetry";
14
+ import OnCallShiftMaterializer, {
15
+ MaterializeResult,
16
+ MaterializedScheduleInfo,
17
+ MaterializedUserInfo,
18
+ } from "../../../../Server/Utils/OnCall/OnCallShiftMaterializer";
19
+ import UserOnCallShiftReminderLog, {
20
+ UserOnCallShiftReminderLogKind,
21
+ } from "../../../../Models/DatabaseModels/UserOnCallShiftReminderLog";
22
+ import URL from "../../../../Types/API/URL";
23
+ import OneUptimeDate from "../../../../Types/Date";
24
+ import NotificationSettingEventType from "../../../../Types/NotificationSetting/NotificationSettingEventType";
25
+ import ObjectID from "../../../../Types/ObjectID";
26
+ import {
27
+ MaterializedShift,
28
+ MaterializedShiftPolicy,
29
+ } from "../../../../Types/OnCallDutyPolicy/MaterializedShift";
30
+ import { matchesQuery } from "./OnCallResolverTestHarness";
31
+
32
+ /*
33
+ * In-memory stand-ins for everything OnCallShiftReminderRunner touches, so
34
+ * the REAL runner code runs end to end (watermark, lateness cap, claim ->
35
+ * send -> stamp, change pass) against rows the tests can inspect:
36
+ *
37
+ * - UserOnCallShiftReminder rows (the configured lead times),
38
+ * - the UserOnCallShiftReminderLog ledger, with the UNIQUE index enforced
39
+ * the way Postgres would (a duplicate insert throws a unique violation),
40
+ * - the materializer (answered from fixture MaterializedShifts),
41
+ * - UserNotificationSetting rows (present / absent per event type) and
42
+ * sendUserNotification (recorded, optionally failing),
43
+ * - GlobalCache (the watermark), Semaphore (the sweep lock), the dashboard
44
+ * URL and the telemetry counter.
45
+ *
46
+ * Every collaborator is a jest.spyOn on the module singleton; restore with
47
+ * jest.restoreAllMocks().
48
+ */
49
+
50
+ export interface FakeReminder {
51
+ projectId: ObjectID;
52
+ userId: ObjectID;
53
+ minutesBeforeShift: number;
54
+ }
55
+
56
+ export interface FakeLedgerRow {
57
+ _id: ObjectID;
58
+ id: ObjectID;
59
+ projectId: ObjectID;
60
+ userId: ObjectID;
61
+ onCallDutyPolicyScheduleId: ObjectID;
62
+ shiftStartsAt: Date;
63
+ minutesBeforeShift: number;
64
+ kind: UserOnCallShiftReminderLogKind;
65
+ claimedAt: Date;
66
+ sentAt: Date | null;
67
+ }
68
+
69
+ export interface SentNotification {
70
+ userId: string;
71
+ projectId: string;
72
+ eventType: NotificationSettingEventType;
73
+ templateType: string;
74
+ subject: string;
75
+ vars: Record<string, string>;
76
+ smsText: string;
77
+ pushTitle: string;
78
+ pushBody: string;
79
+ // undefined when the runner sent no WhatsApp payload at all.
80
+ whatsAppTemplateKey: string | undefined;
81
+ whatsAppBody: string | undefined;
82
+ onCallScheduleId: string | undefined;
83
+ onCallPolicyId: string | undefined;
84
+ onCallPolicyEscalationRuleId: string | undefined;
85
+ }
86
+
87
+ export interface MaterializeCall {
88
+ scheduleIds: Array<string>;
89
+ windowStart: Date;
90
+ windowEnd: Date;
91
+ now: Date | undefined;
92
+ }
93
+
94
+ export interface MetricRecord {
95
+ value: number;
96
+ attributes: Record<string, unknown>;
97
+ }
98
+
99
+ export interface ReminderHarness {
100
+ reminders: Array<FakeReminder>;
101
+ ledger: Array<FakeLedgerRow>;
102
+ shifts: Array<MaterializedShift>;
103
+ schedules: Map<string, MaterializedScheduleInfo>;
104
+ users: Array<MaterializedUserInfo>;
105
+ // userId -> candidate schedule ids; absent = derived from `shifts`.
106
+ candidateSchedules: Map<string, Array<string>>;
107
+ // "userId|projectId|eventType" for every settings row that exists.
108
+ settings: Set<string>;
109
+ cache: Map<string, string>;
110
+ sent: Array<SentNotification>;
111
+ materializeCalls: Array<MaterializeCall>;
112
+ candidateCalls: Array<{ userIds: Array<string>; projectIds: Array<string> }>;
113
+ /*
114
+ * Users UserService can resolve that materialization does NOT return —
115
+ * somebody just removed from the layer, or from the project. The change
116
+ * pass looks them up so their notice is formatted in their own timezone.
117
+ */
118
+ directory: Array<MaterializedUserInfo>;
119
+ // One entry per UserService.findBy the runner made, with the ids it asked for.
120
+ userLookupCalls: Array<Array<string>>;
121
+ metrics: Array<MetricRecord>;
122
+ // Return an Error to make the NEXT send throw; null to deliver.
123
+ failSend: (call: SentNotification) => Error | null;
124
+ // Throw from every reminder-table read.
125
+ remindersReadError: Error | null;
126
+ // Throw from materializeForSchedules for these schedule ids.
127
+ materializeErrorForSchedules: Set<string>;
128
+ // Whether Semaphore.lock succeeds.
129
+ lockAvailable: boolean;
130
+ lockCalls: Array<Record<string, unknown>>;
131
+ releasedMutexes: Array<SemaphoreMutex>;
132
+ cacheReadError: Error | null;
133
+ cacheWriteError: Error | null;
134
+ // Schedules known to OnCallDutyPolicyScheduleService (id -> projectId).
135
+ scheduleProjects: Map<string, ObjectID>;
136
+ warnings: Array<string>;
137
+ errors: Array<unknown>;
138
+ nextLedgerId: number;
139
+ // Runs before the fake ledger insert; lets a test inject a concurrent row.
140
+ beforeLedgerInsert: (() => void) | null;
141
+ reclaimUpdateOverride: number | null;
142
+ }
143
+
144
+ export const HARNESS_DASHBOARD_URL: string =
145
+ "https://oneuptime.example.com/dashboard";
146
+
147
+ /*
148
+ * The runner caches its telemetry counter in a static field the first time
149
+ * it records anything, so the fake counter must resolve the CURRENT harness
150
+ * on every add rather than closing over the one that happened to be
151
+ * installed when the counter was created.
152
+ */
153
+ let activeHarness: ReminderHarness | null = null;
154
+
155
+ export function settingsKey(data: {
156
+ userId: ObjectID | string;
157
+ projectId: ObjectID | string;
158
+ eventType: NotificationSettingEventType;
159
+ }): string {
160
+ return `${data.userId.toString()}|${data.projectId.toString()}|${data.eventType}`;
161
+ }
162
+
163
+ export function scheduleInfo(data: {
164
+ scheduleId: string;
165
+ scheduleName?: string | undefined;
166
+ projectId: string;
167
+ scheduleTimezone?: string | undefined;
168
+ truncated?: boolean | undefined;
169
+ attachedPolicies?: Array<MaterializedShiftPolicy> | undefined;
170
+ }): MaterializedScheduleInfo {
171
+ const info: MaterializedScheduleInfo = {
172
+ scheduleId: data.scheduleId,
173
+ scheduleName: data.scheduleName ?? "Payments",
174
+ projectId: data.projectId,
175
+ shiftConfigVersion: 1,
176
+ lastModifiedAt: OneUptimeDate.fromString("2026-08-01T10:00:00Z"),
177
+ truncated: data.truncated ?? false,
178
+ attachedPolicies: data.attachedPolicies ?? [],
179
+ layerProps: [],
180
+ scheduleUserIds: [],
181
+ };
182
+
183
+ if (data.scheduleTimezone !== undefined) {
184
+ info.scheduleTimezone = data.scheduleTimezone;
185
+ }
186
+
187
+ return info;
188
+ }
189
+
190
+ export function emptyHarness(): ReminderHarness {
191
+ return {
192
+ reminders: [],
193
+ ledger: [],
194
+ shifts: [],
195
+ schedules: new Map<string, MaterializedScheduleInfo>(),
196
+ users: [],
197
+ candidateSchedules: new Map<string, Array<string>>(),
198
+ settings: new Set<string>(),
199
+ cache: new Map<string, string>(),
200
+ sent: [],
201
+ materializeCalls: [],
202
+ candidateCalls: [],
203
+ directory: [],
204
+ userLookupCalls: [],
205
+ metrics: [],
206
+ failSend: (): Error | null => {
207
+ return null;
208
+ },
209
+ remindersReadError: null,
210
+ materializeErrorForSchedules: new Set<string>(),
211
+ lockAvailable: true,
212
+ lockCalls: [],
213
+ releasedMutexes: [],
214
+ cacheReadError: null,
215
+ cacheWriteError: null,
216
+ scheduleProjects: new Map<string, ObjectID>(),
217
+ warnings: [],
218
+ errors: [],
219
+ nextLedgerId: 1,
220
+ beforeLedgerInsert: null,
221
+ reclaimUpdateOverride: null,
222
+ };
223
+ }
224
+
225
+ function toKey(value: unknown): string {
226
+ if (value === null || value === undefined) {
227
+ return "";
228
+ }
229
+
230
+ return String(value);
231
+ }
232
+
233
+ function truncateToMinute(date: Date): Date {
234
+ const copy: Date = new Date(date.getTime());
235
+ copy.setUTCSeconds(0, 0);
236
+ return copy;
237
+ }
238
+
239
+ function uniqueKeyOf(row: {
240
+ userId: ObjectID | string;
241
+ onCallDutyPolicyScheduleId: ObjectID | string;
242
+ shiftStartsAt: Date;
243
+ minutesBeforeShift: number;
244
+ kind: string;
245
+ }): string {
246
+ return [
247
+ toKey(row.userId),
248
+ toKey(row.onCallDutyPolicyScheduleId),
249
+ truncateToMinute(row.shiftStartsAt).getTime(),
250
+ row.minutesBeforeShift,
251
+ row.kind,
252
+ ].join("|");
253
+ }
254
+
255
+ export class UniqueViolationError extends Error {
256
+ public postgresErrorCode: string = "23505";
257
+
258
+ public constructor() {
259
+ super("duplicate key value violates unique constraint");
260
+ }
261
+ }
262
+
263
+ /** Inserts a ledger row directly (a pre-existing claim, another replica's work). */
264
+ export function seedLedgerRow(
265
+ harness: ReminderHarness,
266
+ data: {
267
+ projectId: ObjectID;
268
+ userId: ObjectID;
269
+ scheduleId: ObjectID | string;
270
+ shiftStartsAt: Date;
271
+ minutesBeforeShift: number;
272
+ kind: UserOnCallShiftReminderLogKind;
273
+ claimedAt: Date;
274
+ sentAt: Date | null;
275
+ },
276
+ ): FakeLedgerRow {
277
+ const id: ObjectID = new ObjectID(`ledger-${harness.nextLedgerId++}`);
278
+ const row: FakeLedgerRow = {
279
+ _id: id,
280
+ id,
281
+ projectId: data.projectId,
282
+ userId: data.userId,
283
+ onCallDutyPolicyScheduleId: new ObjectID(data.scheduleId.toString()),
284
+ shiftStartsAt: truncateToMinute(data.shiftStartsAt),
285
+ minutesBeforeShift: data.minutesBeforeShift,
286
+ kind: data.kind,
287
+ claimedAt: data.claimedAt,
288
+ sentAt: data.sentAt,
289
+ };
290
+
291
+ const key: string = uniqueKeyOf(row);
292
+
293
+ for (const existing of harness.ledger) {
294
+ if (uniqueKeyOf(existing) === key) {
295
+ throw new UniqueViolationError();
296
+ }
297
+ }
298
+
299
+ harness.ledger.push(row);
300
+
301
+ return row;
302
+ }
303
+
304
+ function fakeCreate(
305
+ harness: ReminderHarness,
306
+ ): (args: {
307
+ data: UserOnCallShiftReminderLog;
308
+ }) => Promise<UserOnCallShiftReminderLog> {
309
+ return (args: {
310
+ data: UserOnCallShiftReminderLog;
311
+ }): Promise<UserOnCallShiftReminderLog> => {
312
+ const data: UserOnCallShiftReminderLog = args.data;
313
+
314
+ if (
315
+ !data.projectId ||
316
+ !data.userId ||
317
+ !data.onCallDutyPolicyScheduleId ||
318
+ !data.shiftStartsAt ||
319
+ !data.kind
320
+ ) {
321
+ return Promise.reject(new Error("fake ledger: missing required column"));
322
+ }
323
+
324
+ if (harness.beforeLedgerInsert) {
325
+ harness.beforeLedgerInsert();
326
+ }
327
+
328
+ try {
329
+ const row: FakeLedgerRow = seedLedgerRow(harness, {
330
+ projectId: data.projectId,
331
+ userId: data.userId,
332
+ scheduleId: data.onCallDutyPolicyScheduleId,
333
+ shiftStartsAt: data.shiftStartsAt,
334
+ minutesBeforeShift: data.minutesBeforeShift ?? 0,
335
+ kind: data.kind,
336
+ claimedAt: data.claimedAt || new Date(),
337
+ sentAt: null,
338
+ });
339
+
340
+ const created: UserOnCallShiftReminderLog =
341
+ new UserOnCallShiftReminderLog();
342
+ created.id = row.id;
343
+
344
+ return Promise.resolve(created);
345
+ } catch (err) {
346
+ return Promise.reject(err);
347
+ }
348
+ };
349
+ }
350
+
351
+ function fakeLedgerFindBy(
352
+ harness: ReminderHarness,
353
+ ): (args: {
354
+ query?: Record<string, unknown>;
355
+ }) => Promise<Array<UserOnCallShiftReminderLog>> {
356
+ return (args: {
357
+ query?: Record<string, unknown>;
358
+ }): Promise<Array<UserOnCallShiftReminderLog>> => {
359
+ const rows: Array<FakeLedgerRow> = harness.ledger.filter(
360
+ (row: FakeLedgerRow) => {
361
+ return matchesQuery(
362
+ args.query,
363
+ row as unknown as Record<string, unknown>,
364
+ );
365
+ },
366
+ );
367
+
368
+ // Copies: the runner must not be able to mutate the "database" by reference.
369
+ return Promise.resolve(
370
+ rows.map((row: FakeLedgerRow) => {
371
+ return { ...row } as unknown as UserOnCallShiftReminderLog;
372
+ }),
373
+ );
374
+ };
375
+ }
376
+
377
+ function fakeUpdateOneBy(
378
+ harness: ReminderHarness,
379
+ ): (args: {
380
+ query: Record<string, unknown>;
381
+ data: Record<string, unknown>;
382
+ }) => Promise<number> {
383
+ return (args: {
384
+ query: Record<string, unknown>;
385
+ data: Record<string, unknown>;
386
+ }): Promise<number> => {
387
+ if (harness.reclaimUpdateOverride !== null) {
388
+ return Promise.resolve(harness.reclaimUpdateOverride);
389
+ }
390
+
391
+ let count: number = 0;
392
+
393
+ for (const row of harness.ledger) {
394
+ if (matchesQuery(args.query, row as unknown as Record<string, unknown>)) {
395
+ Object.assign(row, args.data);
396
+ count++;
397
+ }
398
+ }
399
+
400
+ return Promise.resolve(count);
401
+ };
402
+ }
403
+
404
+ function fakeUpdateOneById(
405
+ harness: ReminderHarness,
406
+ ): (args: { id: ObjectID; data: Record<string, unknown> }) => Promise<void> {
407
+ return (args: {
408
+ id: ObjectID;
409
+ data: Record<string, unknown>;
410
+ }): Promise<void> => {
411
+ for (const row of harness.ledger) {
412
+ if (toKey(row._id) === toKey(args.id)) {
413
+ Object.assign(row, args.data);
414
+ }
415
+ }
416
+
417
+ return Promise.resolve();
418
+ };
419
+ }
420
+
421
+ function fakeDeleteOneBy(
422
+ harness: ReminderHarness,
423
+ ): (args: { query: Record<string, unknown> }) => Promise<void> {
424
+ return (args: { query: Record<string, unknown> }): Promise<void> => {
425
+ harness.ledger = harness.ledger.filter((row: FakeLedgerRow) => {
426
+ return !matchesQuery(
427
+ args.query,
428
+ row as unknown as Record<string, unknown>,
429
+ );
430
+ });
431
+
432
+ return Promise.resolve();
433
+ };
434
+ }
435
+
436
+ function fakeDeleteBy(
437
+ harness: ReminderHarness,
438
+ ): (args: {
439
+ query: Record<string, unknown>;
440
+ limit?: number;
441
+ }) => Promise<number> {
442
+ return (args: {
443
+ query: Record<string, unknown>;
444
+ limit?: number;
445
+ }): Promise<number> => {
446
+ const limit: number = args.limit ?? Number.MAX_SAFE_INTEGER;
447
+ let deleted: number = 0;
448
+
449
+ harness.ledger = harness.ledger.filter((row: FakeLedgerRow) => {
450
+ if (
451
+ deleted < limit &&
452
+ matchesQuery(args.query, row as unknown as Record<string, unknown>)
453
+ ) {
454
+ deleted++;
455
+ return false;
456
+ }
457
+
458
+ return true;
459
+ });
460
+
461
+ return Promise.resolve(deleted);
462
+ };
463
+ }
464
+
465
+ function fakeMaterialize(
466
+ harness: ReminderHarness,
467
+ ): (args: {
468
+ scheduleIds: Array<ObjectID>;
469
+ windowStart: Date;
470
+ windowEnd: Date;
471
+ now?: Date | undefined;
472
+ }) => Promise<MaterializeResult> {
473
+ return (args: {
474
+ scheduleIds: Array<ObjectID>;
475
+ windowStart: Date;
476
+ windowEnd: Date;
477
+ now?: Date | undefined;
478
+ }): Promise<MaterializeResult> => {
479
+ const ids: Array<string> = args.scheduleIds.map((id: ObjectID) => {
480
+ return id.toString();
481
+ });
482
+
483
+ harness.materializeCalls.push({
484
+ scheduleIds: ids,
485
+ windowStart: args.windowStart,
486
+ windowEnd: args.windowEnd,
487
+ now: args.now,
488
+ });
489
+
490
+ for (const id of ids) {
491
+ if (harness.materializeErrorForSchedules.has(id)) {
492
+ return Promise.reject(new Error(`materialize failed for ${id}`));
493
+ }
494
+ }
495
+
496
+ const shifts: Array<MaterializedShift> = harness.shifts.filter(
497
+ (shift: MaterializedShift) => {
498
+ return (
499
+ ids.includes(shift.scheduleId) &&
500
+ shift.start.getTime() < args.windowEnd.getTime() &&
501
+ shift.end.getTime() > args.windowStart.getTime()
502
+ );
503
+ },
504
+ );
505
+
506
+ const schedules: Array<MaterializedScheduleInfo> = [];
507
+
508
+ for (const id of ids) {
509
+ const info: MaterializedScheduleInfo | undefined =
510
+ harness.schedules.get(id);
511
+
512
+ if (info) {
513
+ schedules.push(info);
514
+ }
515
+ }
516
+
517
+ return Promise.resolve({
518
+ shifts,
519
+ truncated: schedules.some((schedule: MaterializedScheduleInfo) => {
520
+ return schedule.truncated;
521
+ }),
522
+ schedules,
523
+ users: harness.users,
524
+ generatedAt: args.now || new Date(),
525
+ });
526
+ };
527
+ }
528
+
529
+ function candidatesOf(
530
+ harness: ReminderHarness,
531
+ userKey: string,
532
+ ): Array<ObjectID> {
533
+ const explicit: Array<string> | undefined =
534
+ harness.candidateSchedules.get(userKey);
535
+
536
+ if (explicit) {
537
+ return explicit.map((id: string) => {
538
+ return new ObjectID(id);
539
+ });
540
+ }
541
+
542
+ const derived: Set<string> = new Set<string>();
543
+
544
+ for (const shift of harness.shifts) {
545
+ if (shift.userId === userKey) {
546
+ derived.add(shift.scheduleId);
547
+ }
548
+ }
549
+
550
+ return Array.from(derived).map((id: string) => {
551
+ return new ObjectID(id);
552
+ });
553
+ }
554
+
555
+ /*
556
+ * The batched lookup the runner uses: ONE call per project, every reminded
557
+ * user in it. `candidateCalls` records what it was asked for so the tests can
558
+ * pin that the sweep never goes back to a per-user query.
559
+ */
560
+ function fakeCandidatesForUsers(
561
+ harness: ReminderHarness,
562
+ ): (args: {
563
+ userIds: Array<ObjectID>;
564
+ projectIds?: Array<ObjectID> | undefined;
565
+ }) => Promise<Map<string, Array<ObjectID>>> {
566
+ return (args: {
567
+ userIds: Array<ObjectID>;
568
+ projectIds?: Array<ObjectID> | undefined;
569
+ }): Promise<Map<string, Array<ObjectID>>> => {
570
+ const userKeys: Array<string> = args.userIds.map((id: ObjectID) => {
571
+ return id.toString();
572
+ });
573
+
574
+ harness.candidateCalls.push({
575
+ userIds: userKeys,
576
+ projectIds: (args.projectIds || []).map((id: ObjectID) => {
577
+ return id.toString();
578
+ }),
579
+ });
580
+
581
+ const result: Map<string, Array<ObjectID>> = new Map<
582
+ string,
583
+ Array<ObjectID>
584
+ >();
585
+
586
+ for (const userKey of userKeys) {
587
+ result.set(userKey, candidatesOf(harness, userKey));
588
+ }
589
+
590
+ return Promise.resolve(result);
591
+ };
592
+ }
593
+
594
+ function reminderRows(
595
+ harness: ReminderHarness,
596
+ query: Record<string, unknown> | undefined,
597
+ ): Array<Record<string, unknown>> {
598
+ return harness.reminders
599
+ .map((reminder: FakeReminder, index: number) => {
600
+ return {
601
+ _id: new ObjectID(`reminder-${index}`),
602
+ id: new ObjectID(`reminder-${index}`),
603
+ projectId: reminder.projectId,
604
+ userId: reminder.userId,
605
+ minutesBeforeShift: reminder.minutesBeforeShift,
606
+ };
607
+ })
608
+ .filter((row: Record<string, unknown>) => {
609
+ return matchesQuery(query, row);
610
+ });
611
+ }
612
+
613
+ function fakeSend(
614
+ harness: ReminderHarness,
615
+ ): (args: Record<string, unknown>) => Promise<void> {
616
+ return (args: Record<string, unknown>): Promise<void> => {
617
+ const emailEnvelope: Record<string, unknown> = (args["emailEnvelope"] ||
618
+ {}) as Record<string, unknown>;
619
+ const smsMessage: Record<string, unknown> = (args["smsMessage"] ||
620
+ {}) as Record<string, unknown>;
621
+ const push: Record<string, unknown> = (args["pushNotificationMessage"] ||
622
+ {}) as Record<string, unknown>;
623
+ const whatsApp: Record<string, unknown> | undefined = args[
624
+ "whatsAppMessage"
625
+ ] as Record<string, unknown> | undefined;
626
+
627
+ const call: SentNotification = {
628
+ userId: toKey(args["userId"]),
629
+ projectId: toKey(args["projectId"]),
630
+ eventType: args["eventType"] as NotificationSettingEventType,
631
+ templateType: toKey(emailEnvelope["templateType"]),
632
+ subject: toKey(emailEnvelope["subject"]),
633
+ vars: (emailEnvelope["vars"] || {}) as Record<string, string>,
634
+ smsText: toKey(smsMessage["message"]),
635
+ pushTitle: toKey(push["title"]),
636
+ pushBody: toKey(push["body"]),
637
+ whatsAppTemplateKey: whatsApp
638
+ ? toKey(whatsApp["templateKey"])
639
+ : undefined,
640
+ whatsAppBody: whatsApp ? toKey(whatsApp["body"]) : undefined,
641
+ onCallScheduleId: args["onCallScheduleId"]
642
+ ? toKey(args["onCallScheduleId"])
643
+ : undefined,
644
+ onCallPolicyId: args["onCallPolicyId"]
645
+ ? toKey(args["onCallPolicyId"])
646
+ : undefined,
647
+ onCallPolicyEscalationRuleId: args["onCallPolicyEscalationRuleId"]
648
+ ? toKey(args["onCallPolicyEscalationRuleId"])
649
+ : undefined,
650
+ };
651
+
652
+ const failure: Error | null = harness.failSend(call);
653
+
654
+ if (failure) {
655
+ return Promise.reject(failure);
656
+ }
657
+
658
+ harness.sent.push(call);
659
+
660
+ return Promise.resolve();
661
+ };
662
+ }
663
+
664
+ /**
665
+ * Spy every collaborator of the runner and answer from `harness`. Call in a
666
+ * test or beforeEach; restore with jest.restoreAllMocks().
667
+ */
668
+ export function installReminderHarness(harness: ReminderHarness): void {
669
+ // Logger: silent, but captured.
670
+ jest.spyOn(logger, "debug").mockImplementation((): void => {
671
+ return undefined;
672
+ });
673
+ jest.spyOn(logger, "info").mockImplementation((): void => {
674
+ return undefined;
675
+ });
676
+ jest.spyOn(logger, "warn").mockImplementation((message: unknown): void => {
677
+ harness.warnings.push(toKey(message));
678
+ return undefined;
679
+ });
680
+ jest.spyOn(logger, "error").mockImplementation((message: unknown): void => {
681
+ harness.errors.push(message);
682
+ return undefined;
683
+ });
684
+
685
+ // Reminder lead times.
686
+ jest
687
+ .spyOn(UserOnCallShiftReminderService, "findAllBy")
688
+ .mockImplementation(((args: { query?: Record<string, unknown> }) => {
689
+ if (harness.remindersReadError) {
690
+ return Promise.reject(harness.remindersReadError);
691
+ }
692
+
693
+ return Promise.resolve(reminderRows(harness, args.query));
694
+ }) as never);
695
+
696
+ jest
697
+ .spyOn(UserOnCallShiftReminderService, "findBy")
698
+ .mockImplementation(((args: { query?: Record<string, unknown> }) => {
699
+ if (harness.remindersReadError) {
700
+ return Promise.reject(harness.remindersReadError);
701
+ }
702
+
703
+ return Promise.resolve(reminderRows(harness, args.query));
704
+ }) as never);
705
+
706
+ // The ledger.
707
+ jest
708
+ .spyOn(UserOnCallShiftReminderLogService, "create")
709
+ .mockImplementation(fakeCreate(harness) as never);
710
+ jest
711
+ .spyOn(UserOnCallShiftReminderLogService, "findBy")
712
+ .mockImplementation(fakeLedgerFindBy(harness) as never);
713
+ jest
714
+ .spyOn(UserOnCallShiftReminderLogService, "updateOneBy")
715
+ .mockImplementation(fakeUpdateOneBy(harness) as never);
716
+ jest
717
+ .spyOn(UserOnCallShiftReminderLogService, "updateOneById")
718
+ .mockImplementation(fakeUpdateOneById(harness) as never);
719
+ jest
720
+ .spyOn(UserOnCallShiftReminderLogService, "deleteOneBy")
721
+ .mockImplementation(fakeDeleteOneBy(harness) as never);
722
+ jest
723
+ .spyOn(UserOnCallShiftReminderLogService, "deleteBy")
724
+ .mockImplementation(fakeDeleteBy(harness) as never);
725
+
726
+ // The materializer.
727
+ jest
728
+ .spyOn(OnCallShiftMaterializer, "materializeForSchedules")
729
+ .mockImplementation(fakeMaterialize(harness) as never);
730
+ jest
731
+ .spyOn(OnCallShiftMaterializer, "getCandidateScheduleIdsForUsers")
732
+ .mockImplementation(fakeCandidatesForUsers(harness) as never);
733
+ jest
734
+ .spyOn(OnCallShiftMaterializer, "getCandidateScheduleIdsForUser")
735
+ .mockImplementation(((args: { userId: ObjectID }) => {
736
+ return Promise.resolve(candidatesOf(harness, args.userId.toString()));
737
+ }) as never);
738
+
739
+ // The user directory, for recipients materialization does not return.
740
+ jest.spyOn(UserService, "findBy").mockImplementation(((args: {
741
+ query?: Record<string, unknown>;
742
+ }) => {
743
+ const rows: Array<Record<string, unknown>> = harness.directory.map(
744
+ (user: MaterializedUserInfo) => {
745
+ return {
746
+ _id: new ObjectID(user.userId),
747
+ id: new ObjectID(user.userId),
748
+ name: user.userName,
749
+ email: user.email,
750
+ timezone: user.timezone,
751
+ };
752
+ },
753
+ );
754
+
755
+ const matched: Array<Record<string, unknown>> = rows.filter(
756
+ (row: Record<string, unknown>) => {
757
+ return matchesQuery(args.query, row);
758
+ },
759
+ );
760
+
761
+ harness.userLookupCalls.push(
762
+ matched.map((row: Record<string, unknown>) => {
763
+ return toKey(row["_id"]);
764
+ }),
765
+ );
766
+
767
+ return Promise.resolve(matched);
768
+ }) as never);
769
+
770
+ // Notification settings + delivery.
771
+ jest
772
+ .spyOn(UserNotificationSettingService, "findOneBy")
773
+ .mockImplementation(((args: { query: Record<string, unknown> }) => {
774
+ const key: string = settingsKey({
775
+ userId: toKey(args.query["userId"]),
776
+ projectId: toKey(args.query["projectId"]),
777
+ eventType: args.query["eventType"] as NotificationSettingEventType,
778
+ });
779
+
780
+ if (harness.settings.has(key)) {
781
+ return Promise.resolve({ _id: "setting", id: new ObjectID("setting") });
782
+ }
783
+
784
+ return Promise.resolve(null);
785
+ }) as never);
786
+ jest
787
+ .spyOn(UserNotificationSettingService, "sendUserNotification")
788
+ .mockImplementation(fakeSend(harness) as never);
789
+
790
+ // Watermark.
791
+ jest.spyOn(GlobalCache, "getString").mockImplementation(((
792
+ namespace: string,
793
+ key: string,
794
+ ) => {
795
+ if (harness.cacheReadError) {
796
+ return Promise.reject(harness.cacheReadError);
797
+ }
798
+
799
+ return Promise.resolve(harness.cache.get(`${namespace}:${key}`) ?? null);
800
+ }) as never);
801
+ jest.spyOn(GlobalCache, "setString").mockImplementation(((
802
+ namespace: string,
803
+ key: string,
804
+ value: string,
805
+ ) => {
806
+ if (harness.cacheWriteError) {
807
+ return Promise.reject(harness.cacheWriteError);
808
+ }
809
+
810
+ harness.cache.set(`${namespace}:${key}`, value);
811
+ return Promise.resolve();
812
+ }) as never);
813
+
814
+ // Sweep lock.
815
+ jest.spyOn(Semaphore, "lock").mockImplementation(((
816
+ options: Record<string, unknown>,
817
+ ) => {
818
+ harness.lockCalls.push(options);
819
+
820
+ if (!harness.lockAvailable) {
821
+ return Promise.reject(new Error("lock held"));
822
+ }
823
+
824
+ return Promise.resolve({
825
+ fake: true,
826
+ key: options["key"],
827
+ } as unknown as SemaphoreMutex);
828
+ }) as never);
829
+ jest.spyOn(Semaphore, "release").mockImplementation(((
830
+ mutex: SemaphoreMutex,
831
+ ) => {
832
+ harness.releasedMutexes.push(mutex);
833
+ return Promise.resolve();
834
+ }) as never);
835
+
836
+ // Dashboard URL.
837
+ jest
838
+ .spyOn(DatabaseConfig, "getDashboardUrl")
839
+ .mockImplementation((): Promise<URL> => {
840
+ return Promise.resolve(URL.fromString(HARNESS_DASHBOARD_URL));
841
+ });
842
+
843
+ // Schedule -> project lookup (change pass without a project id).
844
+ jest
845
+ .spyOn(OnCallDutyPolicyScheduleService, "findBy")
846
+ .mockImplementation(((args: { query?: Record<string, unknown> }) => {
847
+ const rows: Array<Record<string, unknown>> = [];
848
+
849
+ for (const [scheduleId, projectId] of harness.scheduleProjects) {
850
+ rows.push({
851
+ _id: new ObjectID(scheduleId),
852
+ id: new ObjectID(scheduleId),
853
+ projectId,
854
+ });
855
+ }
856
+
857
+ return Promise.resolve(
858
+ rows.filter((row: Record<string, unknown>) => {
859
+ return matchesQuery(args.query, row);
860
+ }),
861
+ );
862
+ }) as never);
863
+
864
+ // Metrics.
865
+ activeHarness = harness;
866
+ jest.spyOn(Telemetry, "getCounter").mockImplementation(((): unknown => {
867
+ return {
868
+ add: (value: number, attributes: Record<string, unknown>): void => {
869
+ if (activeHarness) {
870
+ activeHarness.metrics.push({ value, attributes });
871
+ }
872
+ },
873
+ };
874
+ }) as never);
875
+ }
876
+
877
+ export function ledgerRowsOfKind(
878
+ harness: ReminderHarness,
879
+ kind: UserOnCallShiftReminderLogKind,
880
+ ): Array<FakeLedgerRow> {
881
+ return harness.ledger.filter((row: FakeLedgerRow) => {
882
+ return row.kind === kind;
883
+ });
884
+ }
885
+
886
+ export function watermarkOf(harness: ReminderHarness): string | undefined {
887
+ return harness.cache.get("OnCallShiftReminders:watermark");
888
+ }
889
+
890
+ export function grantSettings(
891
+ harness: ReminderHarness,
892
+ data: { userId: ObjectID; projectId: ObjectID },
893
+ ): void {
894
+ harness.settings.add(
895
+ settingsKey({
896
+ userId: data.userId,
897
+ projectId: data.projectId,
898
+ eventType:
899
+ NotificationSettingEventType.SEND_BEFORE_USER_ON_CALL_SHIFT_STARTS,
900
+ }),
901
+ );
902
+ harness.settings.add(
903
+ settingsKey({
904
+ userId: data.userId,
905
+ projectId: data.projectId,
906
+ eventType:
907
+ NotificationSettingEventType.SEND_WHEN_USER_ON_CALL_SHIFT_IS_REASSIGNED,
908
+ }),
909
+ );
910
+ }