@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,678 @@
1
+ import CalendarEvent from "../../../Types/Calendar/CalendarEvent";
2
+ import OneUptimeDate from "../../../Types/Date";
3
+ import EventInterval from "../../../Types/Events/EventInterval";
4
+ import LayerUtil, {
5
+ LayerProps,
6
+ ROTATION_PERIOD_START_KEY,
7
+ } from "../../../Types/OnCallDutyPolicy/Layer";
8
+ import ScheduleShiftUtil, {
9
+ OnCallShift,
10
+ } from "../../../Types/OnCallDutyPolicy/ScheduleShiftUtil";
11
+ import ShiftSeamUtil from "../../../Types/OnCallDutyPolicy/ShiftSeamUtil";
12
+ import UserOverrideUtil, {
13
+ OVERRIDE_META_KEY,
14
+ OverrideEventMeta,
15
+ } from "../../../Types/OnCallDutyPolicy/UserOverrideUtil";
16
+ import {
17
+ at,
18
+ dailyRestriction,
19
+ noRestriction,
20
+ rotation,
21
+ user,
22
+ } from "./CalendarFeedTestFixtures";
23
+
24
+ /*
25
+ * groupEventsIntoShifts gained a `groupKey` option for the on-call calendar
26
+ * feed. The contract that matters most is that the DEFAULT behaviour is
27
+ * unchanged: every dashboard summary (LayersPreview, FinalScheduleSummary)
28
+ * calls it without a key and must keep producing byte-identical shifts.
29
+ *
30
+ * `legacyGroupEventsIntoShifts` below is a verbatim copy of the function as
31
+ * it was before the option existed; the regression tests compare against it.
32
+ */
33
+
34
+ const CONTIGUITY_TOLERANCE_SECONDS: number = 5;
35
+
36
+ function legacyGroupEventsIntoShifts(
37
+ events: Array<CalendarEvent>,
38
+ options?: { mergeAcrossGaps?: boolean | undefined } | undefined,
39
+ ): Array<OnCallShift> {
40
+ if (!events || events.length === 0) {
41
+ return [];
42
+ }
43
+
44
+ const mergeAcrossGaps: boolean = Boolean(options?.mergeAcrossGaps);
45
+
46
+ const sorted: Array<CalendarEvent> = [...events]
47
+ .filter((event: CalendarEvent) => {
48
+ return Boolean(event.title) && Boolean(event.start) && Boolean(event.end);
49
+ })
50
+ .sort((a: CalendarEvent, b: CalendarEvent) => {
51
+ if (OneUptimeDate.isBefore(a.start, b.start)) {
52
+ return -1;
53
+ }
54
+ if (OneUptimeDate.isAfter(a.start, b.start)) {
55
+ return 1;
56
+ }
57
+ return 0;
58
+ });
59
+
60
+ const shifts: Array<OnCallShift> = [];
61
+
62
+ for (const event of sorted) {
63
+ const userId: string = event.title;
64
+ const eventSeconds: number = OneUptimeDate.getDifferenceInSeconds(
65
+ event.end,
66
+ event.start,
67
+ );
68
+ const last: OnCallShift | undefined = shifts[shifts.length - 1];
69
+
70
+ const sameUser: boolean = Boolean(last) && last!.userId === userId;
71
+
72
+ const isContiguous: boolean =
73
+ Boolean(last) &&
74
+ OneUptimeDate.getDifferenceInSeconds(last!.end, event.start) <=
75
+ CONTIGUITY_TOLERANCE_SECONDS &&
76
+ OneUptimeDate.isOnOrBefore(last!.end, event.end);
77
+
78
+ if (last && sameUser && (mergeAcrossGaps || isContiguous)) {
79
+ if (OneUptimeDate.isAfter(event.end, last.end)) {
80
+ last.end = event.end;
81
+ }
82
+ last.coverageSeconds += eventSeconds;
83
+ continue;
84
+ }
85
+
86
+ shifts.push({
87
+ userId,
88
+ start: event.start,
89
+ end: event.end,
90
+ coverageSeconds: eventSeconds,
91
+ });
92
+ }
93
+
94
+ return shifts;
95
+ }
96
+
97
+ const UTC: string = "UTC";
98
+ const WINDOW_START: Date = at("2026-03-02T00:00:00Z");
99
+ const WINDOW_END: Date = at("2026-03-09T00:00:00Z");
100
+
101
+ function multiLayerFixture(withLayerMeta: boolean): Array<LayerProps> {
102
+ const primary: LayerProps = {
103
+ users: [user("A"), user("B")],
104
+ startDateTimeOfLayer: WINDOW_START,
105
+ restrictionTimes: dailyRestriction("09:00", "17:00", UTC),
106
+ handOffTime: at("2026-03-02T09:00:00Z"),
107
+ rotation: rotation(EventInterval.Day, 1),
108
+ timezone: UTC,
109
+ };
110
+
111
+ const fallback: LayerProps = {
112
+ users: [user("C"), user("A")],
113
+ startDateTimeOfLayer: WINDOW_START,
114
+ restrictionTimes: noRestriction(),
115
+ handOffTime: WINDOW_START,
116
+ rotation: rotation(EventInterval.Day, 2),
117
+ timezone: UTC,
118
+ };
119
+
120
+ if (withLayerMeta) {
121
+ primary.layerId = "layer-1";
122
+ primary.layerName = "Primary";
123
+ fallback.layerId = "layer-2";
124
+ fallback.layerName = "Fallback";
125
+ }
126
+
127
+ return [primary, fallback];
128
+ }
129
+
130
+ function expand(layers: Array<LayerProps>): Array<CalendarEvent> {
131
+ return new LayerUtil().getMultiLayerEvents({
132
+ layers,
133
+ calendarStartDate: WINDOW_START,
134
+ calendarEndDate: WINDOW_END,
135
+ });
136
+ }
137
+
138
+ function event(
139
+ userId: string,
140
+ start: Date,
141
+ end: Date,
142
+ extra?: Record<string, unknown> | undefined,
143
+ ): CalendarEvent {
144
+ return {
145
+ id: 0,
146
+ title: userId,
147
+ allDay: false,
148
+ start,
149
+ end,
150
+ ...(extra ?? {}),
151
+ } as CalendarEvent;
152
+ }
153
+
154
+ function project(shifts: Array<OnCallShift>): Array<string> {
155
+ return shifts.map((shift: OnCallShift) => {
156
+ return `${shift.userId}|${shift.start.toISOString()}|${shift.end.toISOString()}|${shift.coverageSeconds}`;
157
+ });
158
+ }
159
+
160
+ describe("ScheduleShiftUtil.groupEventsIntoShifts default grouping is unchanged", () => {
161
+ test("byte-identical to the legacy implementation on a multi-layer fixture (no layer metadata)", () => {
162
+ const events: Array<CalendarEvent> = expand(multiLayerFixture(false));
163
+ expect(events.length).toBeGreaterThan(5);
164
+
165
+ for (const mergeAcrossGaps of [false, true]) {
166
+ const current: Array<OnCallShift> =
167
+ ScheduleShiftUtil.groupEventsIntoShifts(events, { mergeAcrossGaps });
168
+ const legacy: Array<OnCallShift> = legacyGroupEventsIntoShifts(events, {
169
+ mergeAcrossGaps,
170
+ });
171
+
172
+ expect(JSON.stringify(current)).toBe(JSON.stringify(legacy));
173
+ expect(current).toEqual(legacy);
174
+ }
175
+ });
176
+
177
+ test("identical shifts (projection) when the events DO carry layer metadata", () => {
178
+ const events: Array<CalendarEvent> = expand(multiLayerFixture(true));
179
+
180
+ for (const mergeAcrossGaps of [false, true]) {
181
+ const current: Array<OnCallShift> =
182
+ ScheduleShiftUtil.groupEventsIntoShifts(events, { mergeAcrossGaps });
183
+ const legacy: Array<OnCallShift> = legacyGroupEventsIntoShifts(events, {
184
+ mergeAcrossGaps,
185
+ });
186
+
187
+ expect(project(current)).toEqual(project(legacy));
188
+ }
189
+ });
190
+
191
+ test("identical shifts (projection) after overrides are applied", () => {
192
+ const events: Array<CalendarEvent> =
193
+ UserOverrideUtil.applyOverridesToEvents({
194
+ events: expand(multiLayerFixture(false)),
195
+ overrides: [
196
+ {
197
+ overrideUserId: "A",
198
+ routeAlertsToUserId: "B",
199
+ startsAt: at("2026-03-03T11:00:00Z"),
200
+ endsAt: at("2026-03-03T13:00:00Z"),
201
+ },
202
+ {
203
+ // C holds the fallback 03-02..03-04; this sits in its evening.
204
+ overrideUserId: "C",
205
+ routeAlertsToUserId: "D",
206
+ startsAt: at("2026-03-02T18:00:00Z"),
207
+ endsAt: at("2026-03-02T22:00:00Z"),
208
+ },
209
+ ],
210
+ });
211
+
212
+ const current: Array<OnCallShift> =
213
+ ScheduleShiftUtil.groupEventsIntoShifts(events);
214
+ const legacy: Array<OnCallShift> = legacyGroupEventsIntoShifts(events);
215
+
216
+ expect(project(current)).toEqual(project(legacy));
217
+ // The substitute really is in there, so the comparison is meaningful.
218
+ expect(
219
+ current.some((shift: OnCallShift) => {
220
+ return shift.userId === "D";
221
+ }),
222
+ ).toBe(true);
223
+ });
224
+
225
+ test("hand-built events without metadata produce shift objects with exactly the legacy keys", () => {
226
+ const events: Array<CalendarEvent> = [
227
+ event("A", at("2026-03-02T09:00:00Z"), at("2026-03-02T12:00:00Z")),
228
+ event("A", at("2026-03-02T12:00:01Z"), at("2026-03-02T17:00:00Z")),
229
+ event("B", at("2026-03-02T17:00:01Z"), at("2026-03-02T20:00:00Z")),
230
+ ];
231
+
232
+ const shifts: Array<OnCallShift> =
233
+ ScheduleShiftUtil.groupEventsIntoShifts(events);
234
+
235
+ expect(shifts).toHaveLength(2);
236
+ expect(Object.keys(shifts[0]!)).toEqual([
237
+ "userId",
238
+ "start",
239
+ "end",
240
+ "coverageSeconds",
241
+ ]);
242
+ expect("override" in shifts[0]!).toBe(false);
243
+ expect("layerId" in shifts[0]!).toBe(false);
244
+ });
245
+
246
+ test("the 5 second contiguity tolerance and empty input are unchanged", () => {
247
+ expect(ScheduleShiftUtil.groupEventsIntoShifts([])).toEqual([]);
248
+
249
+ const events: Array<CalendarEvent> = [
250
+ event("A", at("2026-03-02T09:00:00Z"), at("2026-03-02T12:00:00Z")),
251
+ event("A", at("2026-03-02T12:00:05Z"), at("2026-03-02T13:00:00Z")),
252
+ event("A", at("2026-03-02T13:00:06Z"), at("2026-03-02T14:00:00Z")),
253
+ ];
254
+
255
+ expect(ScheduleShiftUtil.groupEventsIntoShifts(events)).toHaveLength(2);
256
+ });
257
+ });
258
+
259
+ describe("ScheduleShiftUtil.groupEventsIntoShifts with a custom groupKey", () => {
260
+ const overrideMeta: OverrideEventMeta = {
261
+ isOverride: true,
262
+ originalUserId: "B",
263
+ overrideUserId: "A",
264
+ overrideStartsAt: at("2026-03-02T12:00:00Z"),
265
+ overrideEndsAt: at("2026-03-02T15:00:00Z"),
266
+ };
267
+
268
+ test("groupKeyByUserOverrideAndLayer splits a layer handover between the same user", () => {
269
+ const events: Array<CalendarEvent> = [
270
+ event("A", at("2026-03-02T09:00:00Z"), at("2026-03-02T12:00:00Z"), {
271
+ layerId: "layer-1",
272
+ layerName: "Primary",
273
+ }),
274
+ event("A", at("2026-03-02T12:00:00Z"), at("2026-03-02T15:00:00Z"), {
275
+ layerId: "layer-2",
276
+ layerName: "Fallback",
277
+ }),
278
+ ];
279
+
280
+ const merged: Array<OnCallShift> =
281
+ ScheduleShiftUtil.groupEventsIntoShifts(events);
282
+ expect(merged).toHaveLength(1);
283
+
284
+ const split: Array<OnCallShift> = ScheduleShiftUtil.groupEventsIntoShifts(
285
+ events,
286
+ { groupKey: ScheduleShiftUtil.groupKeyByUserOverrideAndLayer },
287
+ );
288
+
289
+ expect(split).toHaveLength(2);
290
+ expect(split[0]!.layerId).toBe("layer-1");
291
+ expect(split[0]!.layerName).toBe("Primary");
292
+ expect(split[1]!.layerId).toBe("layer-2");
293
+ expect(split[1]!.layerName).toBe("Fallback");
294
+ expect(split[0]!.end.getTime()).toBe(split[1]!.start.getTime());
295
+ });
296
+
297
+ test("groupKeyByUserOverrideAndLayer keeps an override segment separate from the user's own shift", () => {
298
+ const events: Array<CalendarEvent> = [
299
+ event("A", at("2026-03-02T09:00:00Z"), at("2026-03-02T12:00:00Z")),
300
+ // A covering for B, immediately after A's own shift.
301
+ event("A", at("2026-03-02T12:00:00Z"), at("2026-03-02T15:00:00Z"), {
302
+ [OVERRIDE_META_KEY]: overrideMeta,
303
+ }),
304
+ event("A", at("2026-03-02T15:00:00Z"), at("2026-03-02T17:00:00Z")),
305
+ ];
306
+
307
+ expect(ScheduleShiftUtil.groupEventsIntoShifts(events)).toHaveLength(1);
308
+
309
+ const split: Array<OnCallShift> = ScheduleShiftUtil.groupEventsIntoShifts(
310
+ events,
311
+ { groupKey: ScheduleShiftUtil.groupKeyByUserOverrideAndLayer },
312
+ );
313
+
314
+ expect(split).toHaveLength(3);
315
+ expect("override" in split[0]!).toBe(false);
316
+ expect(split[1]!.override).toEqual(overrideMeta);
317
+ expect("override" in split[2]!).toBe(false);
318
+ });
319
+
320
+ test("two adjacent segments produced by the SAME override merge into one shift", () => {
321
+ const events: Array<CalendarEvent> = [
322
+ event("A", at("2026-03-02T12:00:00Z"), at("2026-03-02T13:00:00Z"), {
323
+ [OVERRIDE_META_KEY]: overrideMeta,
324
+ }),
325
+ event("A", at("2026-03-02T13:00:01Z"), at("2026-03-02T15:00:00Z"), {
326
+ [OVERRIDE_META_KEY]: overrideMeta,
327
+ }),
328
+ ];
329
+
330
+ const shifts: Array<OnCallShift> = ScheduleShiftUtil.groupEventsIntoShifts(
331
+ events,
332
+ { groupKey: ScheduleShiftUtil.groupKeyByUserOverrideAndLayer },
333
+ );
334
+
335
+ expect(shifts).toHaveLength(1);
336
+ expect(shifts[0]!.override).toEqual(overrideMeta);
337
+ expect(shifts[0]!.coverageSeconds).toBe(3600 + 7199);
338
+ });
339
+
340
+ test("a different override window for the same pair is a different key", () => {
341
+ const later: OverrideEventMeta = {
342
+ ...overrideMeta,
343
+ overrideStartsAt: at("2026-03-02T13:00:00Z"),
344
+ overrideEndsAt: at("2026-03-02T15:00:00Z"),
345
+ };
346
+
347
+ const events: Array<CalendarEvent> = [
348
+ event("A", at("2026-03-02T12:00:00Z"), at("2026-03-02T13:00:00Z"), {
349
+ [OVERRIDE_META_KEY]: overrideMeta,
350
+ }),
351
+ event("A", at("2026-03-02T13:00:00Z"), at("2026-03-02T15:00:00Z"), {
352
+ [OVERRIDE_META_KEY]: later,
353
+ }),
354
+ ];
355
+
356
+ const shifts: Array<OnCallShift> = ScheduleShiftUtil.groupEventsIntoShifts(
357
+ events,
358
+ { groupKey: ScheduleShiftUtil.groupKeyByUserOverrideAndLayer },
359
+ );
360
+
361
+ expect(shifts).toHaveLength(2);
362
+ });
363
+
364
+ test("an arbitrary key function decides grouping; the shift keeps the first segment's user", () => {
365
+ const events: Array<CalendarEvent> = [
366
+ event("A", at("2026-03-02T09:00:00Z"), at("2026-03-02T12:00:00Z")),
367
+ event("B", at("2026-03-02T12:00:00Z"), at("2026-03-02T15:00:00Z")),
368
+ ];
369
+
370
+ const shifts: Array<OnCallShift> = ScheduleShiftUtil.groupEventsIntoShifts(
371
+ events,
372
+ {
373
+ groupKey: (): string => {
374
+ return "everyone";
375
+ },
376
+ },
377
+ );
378
+
379
+ expect(shifts).toHaveLength(1);
380
+ expect(shifts[0]!.userId).toBe("A");
381
+ expect(shifts[0]!.end.getTime()).toBe(at("2026-03-02T15:00:00Z").getTime());
382
+ expect(shifts[0]!.coverageSeconds).toBe(6 * 3600);
383
+ });
384
+
385
+ test("a custom key still respects contiguity unless mergeAcrossGaps is set", () => {
386
+ const events: Array<CalendarEvent> = [
387
+ event("A", at("2026-03-02T09:00:00Z"), at("2026-03-02T12:00:00Z"), {
388
+ layerId: "layer-1",
389
+ }),
390
+ event("A", at("2026-03-02T14:00:00Z"), at("2026-03-02T17:00:00Z"), {
391
+ layerId: "layer-1",
392
+ }),
393
+ ];
394
+
395
+ expect(
396
+ ScheduleShiftUtil.groupEventsIntoShifts(events, {
397
+ groupKey: ScheduleShiftUtil.groupKeyByUserOverrideAndLayer,
398
+ }),
399
+ ).toHaveLength(2);
400
+
401
+ expect(
402
+ ScheduleShiftUtil.groupEventsIntoShifts(events, {
403
+ groupKey: ScheduleShiftUtil.groupKeyByUserOverrideAndLayer,
404
+ mergeAcrossGaps: true,
405
+ }),
406
+ ).toHaveLength(1);
407
+ });
408
+
409
+ test("engine-derived events with layer metadata group per layer under the feed key", () => {
410
+ const events: Array<CalendarEvent> = expand(multiLayerFixture(true));
411
+
412
+ const shifts: Array<OnCallShift> = ScheduleShiftUtil.groupEventsIntoShifts(
413
+ events,
414
+ { groupKey: ScheduleShiftUtil.groupKeyByUserOverrideAndLayer },
415
+ );
416
+
417
+ expect(shifts.length).toBeGreaterThan(0);
418
+ for (const shift of shifts) {
419
+ expect(["layer-1", "layer-2"]).toContain(shift.layerId);
420
+ if (shift.layerId === "layer-1") {
421
+ expect(["A", "B"]).toContain(shift.userId);
422
+ expect(shift.layerName).toBe("Primary");
423
+ } else {
424
+ expect(["C", "A"]).toContain(shift.userId);
425
+ expect(shift.layerName).toBe("Fallback");
426
+ }
427
+ }
428
+
429
+ // Never fewer shifts than the default grouping: the key only splits.
430
+ expect(shifts.length).toBeGreaterThanOrEqual(
431
+ ScheduleShiftUtil.groupEventsIntoShifts(events).length,
432
+ );
433
+ });
434
+
435
+ test("the default key helper equals the event title", () => {
436
+ const sample: CalendarEvent = event(
437
+ "user-x",
438
+ at("2026-03-02T09:00:00Z"),
439
+ at("2026-03-02T10:00:00Z"),
440
+ );
441
+ expect(ScheduleShiftUtil.defaultGroupKey(sample)).toBe("user-x");
442
+ expect(ScheduleShiftUtil.groupKeyByUserOverrideAndLayer(sample)).toBe(
443
+ "user-x|||",
444
+ );
445
+ });
446
+
447
+ test("the feed key carries the rotation period the engine stamped, except on override segments", () => {
448
+ const period: number = at("2026-03-02T00:00:00Z").getTime();
449
+
450
+ const plain: CalendarEvent = event(
451
+ "user-x",
452
+ at("2026-03-02T09:00:00Z"),
453
+ at("2026-03-02T10:00:00Z"),
454
+ { [ROTATION_PERIOD_START_KEY]: period },
455
+ );
456
+
457
+ expect(ScheduleShiftUtil.getEventRotationPeriodStart(plain)).toBe(period);
458
+ expect(ScheduleShiftUtil.groupKeyByUserOverrideAndLayer(plain)).toBe(
459
+ `user-x|||${period}`,
460
+ );
461
+
462
+ // The override window is the identity; it may legitimately span turns.
463
+ const covering: CalendarEvent = event(
464
+ "user-x",
465
+ at("2026-03-02T09:00:00Z"),
466
+ at("2026-03-02T10:00:00Z"),
467
+ {
468
+ [ROTATION_PERIOD_START_KEY]: period,
469
+ [OVERRIDE_META_KEY]: overrideMeta,
470
+ },
471
+ );
472
+
473
+ expect(ScheduleShiftUtil.groupKeyByUserOverrideAndLayer(covering)).toBe(
474
+ `user-x|B@${overrideMeta.overrideStartsAt.getTime()}-${overrideMeta.overrideEndsAt.getTime()}||`,
475
+ );
476
+ });
477
+
478
+ test("getEventRotationPeriodStart accepts Dates and ISO strings, ignores anything else", () => {
479
+ const period: Date = at("2026-03-02T00:00:00Z");
480
+
481
+ const asDate: CalendarEvent = event(
482
+ "u",
483
+ at("2026-03-02T09:00:00Z"),
484
+ at("2026-03-02T10:00:00Z"),
485
+ { [ROTATION_PERIOD_START_KEY]: period },
486
+ );
487
+ const asString: CalendarEvent = event(
488
+ "u",
489
+ at("2026-03-02T09:00:00Z"),
490
+ at("2026-03-02T10:00:00Z"),
491
+ { [ROTATION_PERIOD_START_KEY]: period.toISOString() },
492
+ );
493
+ const nonsense: CalendarEvent = event(
494
+ "u",
495
+ at("2026-03-02T09:00:00Z"),
496
+ at("2026-03-02T10:00:00Z"),
497
+ { [ROTATION_PERIOD_START_KEY]: { not: "a date" } },
498
+ );
499
+ const missing: CalendarEvent = event(
500
+ "u",
501
+ at("2026-03-02T09:00:00Z"),
502
+ at("2026-03-02T10:00:00Z"),
503
+ );
504
+
505
+ expect(ScheduleShiftUtil.getEventRotationPeriodStart(asDate)).toBe(
506
+ period.getTime(),
507
+ );
508
+ expect(ScheduleShiftUtil.getEventRotationPeriodStart(asString)).toBe(
509
+ period.getTime(),
510
+ );
511
+ expect(
512
+ ScheduleShiftUtil.getEventRotationPeriodStart(nonsense),
513
+ ).toBeUndefined();
514
+ expect(
515
+ ScheduleShiftUtil.getEventRotationPeriodStart(missing),
516
+ ).toBeUndefined();
517
+ });
518
+
519
+ test("getEventLayerId / getEventLayerName ignore non-string values", () => {
520
+ const sample: CalendarEvent = event(
521
+ "u",
522
+ at("2026-03-02T09:00:00Z"),
523
+ at("2026-03-02T10:00:00Z"),
524
+ { layerId: 42, layerName: null },
525
+ );
526
+ expect(ScheduleShiftUtil.getEventLayerId(sample)).toBeUndefined();
527
+ expect(ScheduleShiftUtil.getEventLayerName(sample)).toBeUndefined();
528
+ });
529
+ });
530
+
531
+ /*
532
+ * Regression: the feed's shift identity must not depend on where the feed
533
+ * window happens to start. Before the rotation period entered the group key,
534
+ * a run of consecutive turns held by the same user folded into ONE block that
535
+ * began at the first period overlapping the window and ended at the last one,
536
+ * so a subscriber's event silently changed its start (and therefore its UID)
537
+ * and grew its end every time the rolling window crossed a rotation boundary.
538
+ */
539
+ describe("ScheduleShiftUtil: consecutive turns of the same user stay separate shifts", () => {
540
+ const LAYER_START: Date = at("2026-03-02T00:00:00Z");
541
+
542
+ function weeklyLayer(users: Array<string>): LayerProps {
543
+ return {
544
+ users: users.map((id: string) => {
545
+ return user(id);
546
+ }),
547
+ startDateTimeOfLayer: LAYER_START,
548
+ restrictionTimes: noRestriction(),
549
+ handOffTime: LAYER_START,
550
+ rotation: rotation(EventInterval.Week, 1),
551
+ timezone: UTC,
552
+ layerId: "layer-1",
553
+ layerName: "Primary",
554
+ };
555
+ }
556
+
557
+ function feedShifts(
558
+ users: Array<string>,
559
+ windowStart: Date,
560
+ windowEnd: Date,
561
+ ): Array<OnCallShift> {
562
+ const events: Array<CalendarEvent> = new LayerUtil().getMultiLayerEvents({
563
+ layers: [weeklyLayer(users)],
564
+ calendarStartDate: windowStart,
565
+ calendarEndDate: windowEnd,
566
+ });
567
+
568
+ // Exactly what OnCallShiftMaterializer.toShifts does.
569
+ return ShiftSeamUtil.normalizeSeams(
570
+ ScheduleShiftUtil.groupEventsIntoShifts(events, {
571
+ mergeAcrossGaps: false,
572
+ groupKey: ScheduleShiftUtil.groupKeyByUserOverrideAndLayer,
573
+ }),
574
+ );
575
+ }
576
+
577
+ function starts(shifts: Array<OnCallShift>): Array<string> {
578
+ return shifts.map((shift: OnCallShift) => {
579
+ return shift.start.toISOString();
580
+ });
581
+ }
582
+
583
+ test("a single-user weekly layer yields one shift per week, not one block", () => {
584
+ const shifts: Array<OnCallShift> = feedShifts(
585
+ ["A"],
586
+ LAYER_START,
587
+ at("2026-03-30T00:00:00Z"),
588
+ );
589
+
590
+ expect(shifts.length).toBe(4);
591
+ for (const shift of shifts) {
592
+ expect(shift.userId).toBe("A");
593
+ // A week each, give or take the engine's 1 s seam.
594
+ expect(
595
+ shift.end.getTime() - shift.start.getTime(),
596
+ ).toBeGreaterThanOrEqual(7 * 24 * 3600 * 1000 - 5000);
597
+ expect(shift.end.getTime() - shift.start.getTime()).toBeLessThanOrEqual(
598
+ 7 * 24 * 3600 * 1000,
599
+ );
600
+ }
601
+ });
602
+
603
+ test("the same rotation period keeps the same start in two different windows", () => {
604
+ const early: Array<OnCallShift> = feedShifts(
605
+ ["A"],
606
+ LAYER_START,
607
+ at("2026-04-06T00:00:00Z"),
608
+ );
609
+ const later: Array<OnCallShift> = feedShifts(
610
+ ["A"],
611
+ at("2026-03-11T00:00:00Z"),
612
+ at("2026-04-13T00:00:00Z"),
613
+ );
614
+
615
+ // Every week both windows contain is the very same shift in both.
616
+ const shared: Array<string> = starts(early).filter((start: string) => {
617
+ return starts(later).includes(start);
618
+ });
619
+
620
+ expect(shared.length).toBeGreaterThan(1);
621
+ expect(starts(early)).toContain("2026-03-16T00:00:00.000Z");
622
+ expect(starts(later)).toContain("2026-03-16T00:00:00.000Z");
623
+ });
624
+
625
+ test("[A, A, B] keeps A's two turns apart while B stays one shift", () => {
626
+ const shifts: Array<OnCallShift> = feedShifts(
627
+ ["A", "A", "B"],
628
+ LAYER_START,
629
+ at("2026-03-23T00:00:00Z"),
630
+ );
631
+
632
+ expect(
633
+ shifts.map((shift: OnCallShift) => {
634
+ return shift.userId;
635
+ }),
636
+ ).toEqual(["A", "A", "B"]);
637
+ expect(starts(shifts)).toEqual([
638
+ "2026-03-02T00:00:00.000Z",
639
+ "2026-03-09T00:00:00.000Z",
640
+ "2026-03-16T00:00:00.000Z",
641
+ ]);
642
+ });
643
+
644
+ test("an override that spans a rotation boundary is still ONE shift", () => {
645
+ const events: Array<CalendarEvent> =
646
+ UserOverrideUtil.applyOverridesToEvents({
647
+ events: new LayerUtil().getMultiLayerEvents({
648
+ layers: [weeklyLayer(["A"])],
649
+ calendarStartDate: LAYER_START,
650
+ calendarEndDate: at("2026-03-23T00:00:00Z"),
651
+ }),
652
+ overrides: [
653
+ {
654
+ overrideUserId: "A",
655
+ routeAlertsToUserId: "B",
656
+ // Wednesday to Wednesday, straight through the Monday handover.
657
+ startsAt: at("2026-03-04T00:00:00Z"),
658
+ endsAt: at("2026-03-11T00:00:00Z"),
659
+ },
660
+ ],
661
+ });
662
+
663
+ const shifts: Array<OnCallShift> = ShiftSeamUtil.normalizeSeams(
664
+ ScheduleShiftUtil.groupEventsIntoShifts(events, {
665
+ mergeAcrossGaps: false,
666
+ groupKey: ScheduleShiftUtil.groupKeyByUserOverrideAndLayer,
667
+ }),
668
+ );
669
+
670
+ const covering: Array<OnCallShift> = shifts.filter((shift: OnCallShift) => {
671
+ return shift.userId === "B";
672
+ });
673
+
674
+ expect(covering).toHaveLength(1);
675
+ expect(covering[0]!.start.toISOString()).toBe("2026-03-04T00:00:00.000Z");
676
+ expect(covering[0]!.end.toISOString()).toBe("2026-03-11T00:00:00.000Z");
677
+ });
678
+ });