@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,655 @@
1
+ import "@testing-library/jest-dom";
2
+ import {
3
+ afterEach,
4
+ beforeEach,
5
+ describe,
6
+ expect,
7
+ jest,
8
+ test,
9
+ } from "@jest/globals";
10
+ import {
11
+ cleanup,
12
+ fireEvent,
13
+ render,
14
+ screen,
15
+ waitFor,
16
+ within,
17
+ } from "@testing-library/react";
18
+ import React from "react";
19
+ import getJestMockFunction, { MockFunction } from "../../MockType";
20
+
21
+ /*
22
+ * The two cards on the Calendar Feed page that are NOT about the link: the
23
+ * reminder chips (one UserOnCallShiftReminder row per chip) and the live
24
+ * "Upcoming shifts" list from /my-shifts.
25
+ *
26
+ * Reminders: every chip click is one create or one delete of exactly the row
27
+ * it represents, the custom modal refuses what the service would refuse (with
28
+ * a reason, before the round trip), and a duplicate lead time is caught
29
+ * locally. Upcoming shifts: the window sent is the 30-day one, rows are
30
+ * grouped by day, the "covering for" and policy-variant facts survive to the
31
+ * DOM, and every row can reach "Get cover".
32
+ */
33
+
34
+ const getMock: MockFunction = getJestMockFunction();
35
+ const getListMock: MockFunction = getJestMockFunction();
36
+ const createMock: MockFunction = getJestMockFunction();
37
+ const deleteItemMock: MockFunction = getJestMockFunction();
38
+
39
+ jest.mock("../../../UI/Utils/API/API", () => {
40
+ return {
41
+ __esModule: true,
42
+ default: {
43
+ get: (...args: Array<any>) => {
44
+ return getMock(...args);
45
+ },
46
+ getFriendlyMessage: (error: unknown): string => {
47
+ if (error instanceof Error) {
48
+ return error.message;
49
+ }
50
+
51
+ if (
52
+ error &&
53
+ typeof error === "object" &&
54
+ "message" in (error as Record<string, unknown>)
55
+ ) {
56
+ return String((error as Record<string, unknown>)["message"]);
57
+ }
58
+
59
+ return "Something went wrong";
60
+ },
61
+ },
62
+ };
63
+ });
64
+
65
+ jest.mock("../../../UI/Utils/ModelAPI/ModelAPI", () => {
66
+ return {
67
+ __esModule: true,
68
+ default: {
69
+ getCommonHeaders: (): Record<string, string> => {
70
+ return {};
71
+ },
72
+ getList: (...args: Array<any>) => {
73
+ return getListMock(...args);
74
+ },
75
+ create: (...args: Array<any>) => {
76
+ return createMock(...args);
77
+ },
78
+ deleteItem: (...args: Array<any>) => {
79
+ return deleteItemMock(...args);
80
+ },
81
+ },
82
+ };
83
+ });
84
+
85
+ jest.mock("react-i18next", () => {
86
+ return {
87
+ useTranslation: () => {
88
+ return {
89
+ t: (key: string, options?: { defaultValue?: string }): string => {
90
+ return options?.defaultValue ?? key;
91
+ },
92
+ };
93
+ },
94
+ };
95
+ });
96
+
97
+ import ObjectID from "../../../Types/ObjectID";
98
+ import HTTPErrorResponse from "../../../Types/API/HTTPErrorResponse";
99
+ import HTTPResponse from "../../../Types/API/HTTPResponse";
100
+ import Route from "../../../Types/API/Route";
101
+ import { JSONArray, JSONObject } from "../../../Types/JSON";
102
+ import { MaterializedShiftJson } from "../../../Types/OnCallDutyPolicy/MaterializedShift";
103
+ import UserOnCallShiftReminder from "../../../Models/DatabaseModels/UserOnCallShiftReminder";
104
+ import ShiftRemindersCard from "../../../../App/FeatureSet/Dashboard/src/Components/OnCallPolicy/CalendarFeed/ShiftRemindersCard";
105
+ import UpcomingShiftsCard from "../../../../App/FeatureSet/Dashboard/src/Components/OnCallPolicy/CalendarFeed/UpcomingShiftsCard";
106
+ import {
107
+ MY_SHIFTS_PATH,
108
+ STANDING_ASSIGNMENTS_COPY,
109
+ } from "../../../../App/FeatureSet/Dashboard/src/Components/OnCallPolicy/CalendarFeed/CalendarFeedUtil";
110
+ import PageMap from "../../../../App/FeatureSet/Dashboard/src/Utils/PageMap";
111
+ import RouteMap, {
112
+ RouteUtil,
113
+ } from "../../../../App/FeatureSet/Dashboard/src/Utils/RouteMap";
114
+
115
+ const PROJECT_ID: ObjectID = new ObjectID(
116
+ "8f2a1b3c-4d5e-4f60-9a7b-1c2d3e4f5a6b",
117
+ );
118
+ const USER_ID: ObjectID = new ObjectID("44444444-4444-4444-8444-444444444444");
119
+ const NOW: Date = new Date("2026-08-31T12:00:00.000Z");
120
+
121
+ type ReminderRowFunction = (
122
+ id: string,
123
+ minutes: number,
124
+ ) => UserOnCallShiftReminder;
125
+
126
+ const reminderRow: ReminderRowFunction = (
127
+ id: string,
128
+ minutes: number,
129
+ ): UserOnCallShiftReminder => {
130
+ const row: UserOnCallShiftReminder = new UserOnCallShiftReminder();
131
+ row.id = new ObjectID(id);
132
+ row.minutesBeforeShift = minutes;
133
+ return row;
134
+ };
135
+
136
+ type ListFunction = (rows: Array<UserOnCallShiftReminder>) => {
137
+ data: Array<UserOnCallShiftReminder>;
138
+ count: number;
139
+ skip: number;
140
+ limit: number;
141
+ };
142
+
143
+ const list: ListFunction = (
144
+ rows: Array<UserOnCallShiftReminder>,
145
+ ): {
146
+ data: Array<UserOnCallShiftReminder>;
147
+ count: number;
148
+ skip: number;
149
+ limit: number;
150
+ } => {
151
+ return { data: rows, count: rows.length, skip: 0, limit: 10 };
152
+ };
153
+
154
+ type ShiftFunction = (
155
+ overrides: Partial<MaterializedShiftJson> & { shiftKey: string },
156
+ ) => MaterializedShiftJson;
157
+
158
+ const shift: ShiftFunction = (
159
+ overrides: Partial<MaterializedShiftJson> & { shiftKey: string },
160
+ ): MaterializedShiftJson => {
161
+ return {
162
+ contentHash: "hash",
163
+ projectId: PROJECT_ID.toString(),
164
+ scheduleId: "schedule-1",
165
+ scheduleName: "Primary",
166
+ scheduleTimezone: "Europe/Stockholm",
167
+ userId: USER_ID.toString(),
168
+ userName: "Jane",
169
+ start: "2026-09-01T07:00:00.000Z",
170
+ end: "2026-09-01T15:00:00.000Z",
171
+ coverageSeconds: 8 * 3600,
172
+ policies: [],
173
+ isPast: false,
174
+ lastModifiedAt: "2026-08-30T00:00:00.000Z",
175
+ shiftConfigVersion: 3,
176
+ ...overrides,
177
+ };
178
+ };
179
+
180
+ type MyShiftsJsonFunction = (
181
+ shifts: Array<MaterializedShiftJson>,
182
+ truncated: boolean,
183
+ ) => JSONObject;
184
+
185
+ const myShiftsJson: MyShiftsJsonFunction = (
186
+ shifts: Array<MaterializedShiftJson>,
187
+ truncated: boolean,
188
+ ): JSONObject => {
189
+ return {
190
+ shifts: shifts as unknown as JSONArray,
191
+ truncated: truncated,
192
+ generatedAt: NOW.toISOString(),
193
+ };
194
+ };
195
+
196
+ type OkFunction = (json: JSONObject) => HTTPResponse<JSONObject>;
197
+
198
+ const ok: OkFunction = (json: JSONObject): HTTPResponse<JSONObject> => {
199
+ return new HTTPResponse<JSONObject>(200, json, {});
200
+ };
201
+
202
+ function goToCalendarFeedPage(): void {
203
+ window.history.pushState(
204
+ {},
205
+ "",
206
+ `/dashboard/${PROJECT_ID.toString()}/user-settings/calendar-feed`,
207
+ );
208
+ }
209
+
210
+ function resetMocks(): void {
211
+ getMock.mockReset();
212
+ getListMock.mockReset();
213
+ createMock.mockReset();
214
+ deleteItemMock.mockReset();
215
+ createMock.mockResolvedValue(new UserOnCallShiftReminder());
216
+ deleteItemMock.mockResolvedValue(undefined);
217
+ }
218
+
219
+ describe("ShiftRemindersCard", () => {
220
+ beforeEach(() => {
221
+ resetMocks();
222
+ goToCalendarFeedPage();
223
+ });
224
+
225
+ afterEach(() => {
226
+ cleanup();
227
+ });
228
+
229
+ test("lists the caller's reminder rows in this project and lights the matching chips", async () => {
230
+ getListMock.mockResolvedValue(
231
+ list([reminderRow("aaaaaaaa-0000-4000-8000-000000000001", 1440)]),
232
+ );
233
+
234
+ render(<ShiftRemindersCard projectId={PROJECT_ID} userId={USER_ID} />);
235
+
236
+ await waitFor(() => {
237
+ expect(screen.getByTestId("shift-reminder-chip-1440")).toHaveAttribute(
238
+ "aria-pressed",
239
+ "true",
240
+ );
241
+ });
242
+
243
+ expect(screen.getByTestId("shift-reminder-chip-10080")).toHaveAttribute(
244
+ "aria-pressed",
245
+ "false",
246
+ );
247
+ expect(screen.getByTestId("shift-reminder-chip-60")).toHaveAttribute(
248
+ "aria-pressed",
249
+ "false",
250
+ );
251
+ expect(screen.getByTestId("shift-reminder-chip-15")).toHaveAttribute(
252
+ "aria-pressed",
253
+ "false",
254
+ );
255
+
256
+ const call: Record<string, unknown> = getListMock.mock
257
+ .calls[0]![0] as Record<string, unknown>;
258
+ expect(call["modelType"]).toBe(UserOnCallShiftReminder);
259
+ const query: Record<string, unknown> = call["query"] as Record<
260
+ string,
261
+ unknown
262
+ >;
263
+ expect(String(query["projectId"])).toBe(PROJECT_ID.toString());
264
+ expect(String(query["userId"])).toBe(USER_ID.toString());
265
+ expect(call["select"]).toEqual({ _id: true, minutesBeforeShift: true });
266
+
267
+ // The reminders card points at Notification Settings for the channels.
268
+ const expectedRoute: Route = RouteUtil.populateRouteParams(
269
+ RouteMap[PageMap.USER_SETTINGS_NOTIFICATION_SETTINGS] as Route,
270
+ );
271
+ expect(
272
+ screen.getByText("Choose how reminders reach you").closest("a"),
273
+ ).toHaveAttribute("href", expectedRoute.toString());
274
+ });
275
+
276
+ test("clicking an unlit chip creates exactly that row, letting the service default userId", async () => {
277
+ getListMock
278
+ .mockResolvedValueOnce(list([]))
279
+ .mockResolvedValueOnce(
280
+ list([reminderRow("aaaaaaaa-0000-4000-8000-000000000002", 60)]),
281
+ );
282
+
283
+ render(<ShiftRemindersCard projectId={PROJECT_ID} userId={USER_ID} />);
284
+
285
+ const chip: HTMLElement = await screen.findByTestId(
286
+ "shift-reminder-chip-60",
287
+ );
288
+ fireEvent.click(chip);
289
+
290
+ await waitFor(() => {
291
+ expect(createMock).toHaveBeenCalledTimes(1);
292
+ });
293
+
294
+ const call: Record<string, unknown> = createMock.mock
295
+ .calls[0]![0] as Record<string, unknown>;
296
+ expect(call["modelType"]).toBe(UserOnCallShiftReminder);
297
+ const model: UserOnCallShiftReminder = call[
298
+ "model"
299
+ ] as UserOnCallShiftReminder;
300
+ expect(model.minutesBeforeShift).toBe(60);
301
+ expect(String(model.projectId)).toBe(PROJECT_ID.toString());
302
+ expect(model.userId).toBeUndefined();
303
+
304
+ await waitFor(() => {
305
+ expect(screen.getByTestId("shift-reminder-chip-60")).toHaveAttribute(
306
+ "aria-pressed",
307
+ "true",
308
+ );
309
+ });
310
+ expect(getListMock).toHaveBeenCalledTimes(2);
311
+ });
312
+
313
+ test("clicking a lit chip deletes exactly that row", async () => {
314
+ getListMock
315
+ .mockResolvedValueOnce(
316
+ list([reminderRow("aaaaaaaa-0000-4000-8000-000000000003", 15)]),
317
+ )
318
+ .mockResolvedValueOnce(list([]));
319
+
320
+ render(<ShiftRemindersCard projectId={PROJECT_ID} userId={USER_ID} />);
321
+
322
+ await waitFor(() => {
323
+ expect(screen.getByTestId("shift-reminder-chip-15")).toHaveAttribute(
324
+ "aria-pressed",
325
+ "true",
326
+ );
327
+ });
328
+
329
+ fireEvent.click(screen.getByTestId("shift-reminder-chip-15"));
330
+
331
+ await waitFor(() => {
332
+ expect(deleteItemMock).toHaveBeenCalledTimes(1);
333
+ });
334
+ const call: Record<string, unknown> = deleteItemMock.mock
335
+ .calls[0]![0] as Record<string, unknown>;
336
+ expect(call["modelType"]).toBe(UserOnCallShiftReminder);
337
+ expect(String(call["id"])).toBe("aaaaaaaa-0000-4000-8000-000000000003");
338
+ expect(createMock).not.toHaveBeenCalled();
339
+
340
+ await waitFor(() => {
341
+ expect(screen.getByTestId("shift-reminder-chip-15")).toHaveAttribute(
342
+ "aria-pressed",
343
+ "false",
344
+ );
345
+ });
346
+ });
347
+
348
+ test("a non-preset row appears as its own lit chip with a human label", async () => {
349
+ getListMock.mockResolvedValue(
350
+ list([reminderRow("aaaaaaaa-0000-4000-8000-000000000004", 90)]),
351
+ );
352
+
353
+ render(<ShiftRemindersCard projectId={PROJECT_ID} userId={USER_ID} />);
354
+
355
+ const chip: HTMLElement = await screen.findByTestId(
356
+ "shift-reminder-chip-90",
357
+ );
358
+ expect(chip).toHaveAttribute("aria-pressed", "true");
359
+ expect(chip).toHaveTextContent("90 minutes");
360
+ });
361
+
362
+ test("the custom modal validates locally before any round trip", async () => {
363
+ getListMock.mockResolvedValue(
364
+ list([reminderRow("aaaaaaaa-0000-4000-8000-000000000005", 1440)]),
365
+ );
366
+
367
+ render(<ShiftRemindersCard projectId={PROJECT_ID} userId={USER_ID} />);
368
+
369
+ fireEvent.click(await screen.findByTestId("shift-reminder-chip-custom"));
370
+
371
+ const modal: HTMLElement = await screen.findByTestId("modal");
372
+ const input: HTMLElement = within(modal).getByTestId(
373
+ "shift-reminder-custom-minutes",
374
+ );
375
+ const submit: HTMLElement = within(modal).getByRole("button", {
376
+ name: "Add reminder",
377
+ });
378
+
379
+ fireEvent.click(submit);
380
+ await waitFor(() => {
381
+ expect(modal).toHaveTextContent(
382
+ "Enter how many minutes before the shift.",
383
+ );
384
+ });
385
+
386
+ fireEvent.change(input, { target: { value: "12.5" } });
387
+ fireEvent.click(submit);
388
+ await waitFor(() => {
389
+ expect(modal).toHaveTextContent("Enter a whole number of minutes.");
390
+ });
391
+
392
+ fireEvent.change(input, { target: { value: "5" } });
393
+ fireEvent.click(submit);
394
+ await waitFor(() => {
395
+ expect(modal).toHaveTextContent(
396
+ "Reminders can be sent between 15 minutes and 14 days before a shift.",
397
+ );
398
+ });
399
+
400
+ // 1440 already exists as a row: caught locally, not by a 400.
401
+ fireEvent.change(input, { target: { value: "1440" } });
402
+ fireEvent.click(submit);
403
+ await waitFor(() => {
404
+ expect(modal).toHaveTextContent(
405
+ "You already have a reminder at that lead time.",
406
+ );
407
+ });
408
+
409
+ expect(createMock).not.toHaveBeenCalled();
410
+ });
411
+
412
+ test("a valid custom lead time creates the row and closes the modal", async () => {
413
+ getListMock
414
+ .mockResolvedValueOnce(list([]))
415
+ .mockResolvedValueOnce(
416
+ list([reminderRow("aaaaaaaa-0000-4000-8000-000000000006", 120)]),
417
+ );
418
+
419
+ render(<ShiftRemindersCard projectId={PROJECT_ID} userId={USER_ID} />);
420
+
421
+ fireEvent.click(await screen.findByTestId("shift-reminder-chip-custom"));
422
+
423
+ const modal: HTMLElement = await screen.findByTestId("modal");
424
+ fireEvent.change(
425
+ within(modal).getByTestId("shift-reminder-custom-minutes"),
426
+ {
427
+ target: { value: "120" },
428
+ },
429
+ );
430
+ fireEvent.click(
431
+ within(modal).getByRole("button", { name: "Add reminder" }),
432
+ );
433
+
434
+ await waitFor(() => {
435
+ expect(createMock).toHaveBeenCalledTimes(1);
436
+ });
437
+ const model: UserOnCallShiftReminder = (
438
+ createMock.mock.calls[0]![0] as Record<string, unknown>
439
+ )["model"] as UserOnCallShiftReminder;
440
+ expect(model.minutesBeforeShift).toBe(120);
441
+
442
+ await waitFor(() => {
443
+ expect(screen.queryByTestId("modal")).not.toBeInTheDocument();
444
+ });
445
+ await waitFor(() => {
446
+ expect(screen.getByTestId("shift-reminder-chip-120")).toHaveTextContent(
447
+ "2 hours",
448
+ );
449
+ });
450
+ });
451
+
452
+ test("a failed write surfaces the server's message on the card", async () => {
453
+ getListMock.mockResolvedValue(list([]));
454
+ createMock.mockRejectedValue(
455
+ new HTTPErrorResponse(
456
+ 400,
457
+ {
458
+ message: "You already have a reminder 60 minutes before your shifts.",
459
+ },
460
+ {},
461
+ ),
462
+ );
463
+
464
+ render(<ShiftRemindersCard projectId={PROJECT_ID} userId={USER_ID} />);
465
+
466
+ fireEvent.click(await screen.findByTestId("shift-reminder-chip-60"));
467
+
468
+ await waitFor(() => {
469
+ expect(
470
+ screen.getByText(
471
+ "You already have a reminder 60 minutes before your shifts.",
472
+ ),
473
+ ).toBeInTheDocument();
474
+ });
475
+ expect(screen.getByTestId("shift-reminder-chip-60")).toHaveAttribute(
476
+ "aria-pressed",
477
+ "false",
478
+ );
479
+ });
480
+
481
+ test("without a project or user the chips are inert and nothing is fetched", async () => {
482
+ render(<ShiftRemindersCard projectId={null} userId={USER_ID} />);
483
+
484
+ await waitFor(() => {
485
+ expect(screen.getByTestId("shift-reminder-chip-custom")).toBeDisabled();
486
+ });
487
+ expect(screen.getByTestId("shift-reminder-chip-60")).toBeDisabled();
488
+ expect(getListMock).not.toHaveBeenCalled();
489
+ });
490
+ });
491
+
492
+ describe("UpcomingShiftsCard", () => {
493
+ beforeEach(() => {
494
+ resetMocks();
495
+ goToCalendarFeedPage();
496
+ });
497
+
498
+ afterEach(() => {
499
+ cleanup();
500
+ });
501
+
502
+ test("asks /my-shifts for the next 30 days and groups the answer by day", async () => {
503
+ getMock.mockResolvedValue(
504
+ ok(
505
+ myShiftsJson(
506
+ [
507
+ shift({
508
+ shiftKey: "later",
509
+ start: "2026-09-03T07:00:00.000Z",
510
+ end: "2026-09-03T15:00:00.000Z",
511
+ scheduleName: "Secondary",
512
+ layerName: "Weekdays",
513
+ }),
514
+ shift({ shiftKey: "first" }),
515
+ shift({
516
+ shiftKey: "cover",
517
+ start: "2026-09-01T15:00:00.000Z",
518
+ end: "2026-09-01T23:00:00.000Z",
519
+ projectName: "Payments",
520
+ override: {
521
+ originalUserId: "user-2",
522
+ originalUserName: "Bob",
523
+ overrideStartsAt: "2026-09-01T15:00:00.000Z",
524
+ overrideEndsAt: "2026-09-01T23:00:00.000Z",
525
+ },
526
+ policyVariantOf: {
527
+ policyId: "policy-1",
528
+ policyName: "Checkout",
529
+ globalUserId: "user-3",
530
+ },
531
+ }),
532
+ ],
533
+ false,
534
+ ),
535
+ ),
536
+ );
537
+
538
+ render(<UpcomingShiftsCard now={NOW} />);
539
+
540
+ await waitFor(() => {
541
+ expect(screen.getAllByTestId("upcoming-shift-row").length).toBe(3);
542
+ });
543
+
544
+ const url: string = String(
545
+ (getMock.mock.calls[0]![0] as Record<string, unknown>)["url"],
546
+ );
547
+ expect(url).toContain(MY_SHIFTS_PATH);
548
+ expect(url).toContain("from=");
549
+ expect(url).toContain("to=");
550
+
551
+ const days: Array<HTMLElement> =
552
+ screen.getAllByTestId("upcoming-shift-day");
553
+ expect(days.length).toBe(2);
554
+ expect(within(days[0]!).getAllByTestId("upcoming-shift-row").length).toBe(
555
+ 2,
556
+ );
557
+ expect(within(days[1]!).getAllByTestId("upcoming-shift-row").length).toBe(
558
+ 1,
559
+ );
560
+
561
+ expect(screen.getByText("Covering for Bob")).toBeInTheDocument();
562
+ expect(screen.getByText("Only on policy Checkout")).toBeInTheDocument();
563
+ expect(screen.getByText("Weekdays")).toBeInTheDocument();
564
+ expect(screen.getByText("Secondary")).toBeInTheDocument();
565
+ expect(screen.getByText(/Payments/)).toBeInTheDocument();
566
+
567
+ const expectedOverridesRoute: Route = RouteUtil.populateRouteParams(
568
+ RouteMap[PageMap.ON_CALL_DUTY_POLICY_USER_OVERRIDES] as Route,
569
+ );
570
+ const coverLinks: Array<HTMLElement> = screen.getAllByText("Get cover");
571
+ expect(coverLinks.length).toBe(3);
572
+ expect(coverLinks[0]!.closest("a")).toHaveAttribute(
573
+ "href",
574
+ expectedOverridesRoute.toString(),
575
+ );
576
+
577
+ expect(screen.getByText(STANDING_ASSIGNMENTS_COPY)).toBeInTheDocument();
578
+ });
579
+
580
+ test("an empty answer says so rather than rendering a blank card", async () => {
581
+ getMock.mockResolvedValue(ok(myShiftsJson([], false)));
582
+
583
+ render(<UpcomingShiftsCard now={NOW} />);
584
+
585
+ await waitFor(() => {
586
+ expect(screen.getByTestId("upcoming-shifts-empty")).toHaveTextContent(
587
+ "no shifts on any schedule in the next 30 days",
588
+ );
589
+ });
590
+ expect(
591
+ screen.queryByTestId("upcoming-shifts-truncated"),
592
+ ).not.toBeInTheDocument();
593
+ });
594
+
595
+ /*
596
+ * A schedule that hits the simulation cap emits NOTHING for the capped layer
597
+ * (Layer.ts would otherwise attribute shifts to the wrong person), so
598
+ * {shifts: [], truncated: true} is a real answer from /my-shifts - and the
599
+ * only honest reading of it is "we could not compute this", never "you have
600
+ * no shifts".
601
+ */
602
+ test("an empty BUT truncated answer keeps the warning and does not claim there are no shifts", async () => {
603
+ getMock.mockResolvedValue(ok(myShiftsJson([], true)));
604
+
605
+ render(<UpcomingShiftsCard now={NOW} />);
606
+
607
+ await waitFor(() => {
608
+ expect(screen.getByTestId("upcoming-shifts-truncated")).toHaveTextContent(
609
+ "too complex to compute fully",
610
+ );
611
+ });
612
+
613
+ expect(screen.getByTestId("upcoming-shifts-empty")).toHaveTextContent(
614
+ "No shifts could be listed for the next 30 days.",
615
+ );
616
+ expect(screen.getByTestId("upcoming-shifts-empty")).not.toHaveTextContent(
617
+ "You have no shifts",
618
+ );
619
+ });
620
+
621
+ test("a truncated answer is flagged above the list", async () => {
622
+ getMock.mockResolvedValue(
623
+ ok(myShiftsJson([shift({ shiftKey: "only" })], true)),
624
+ );
625
+
626
+ render(<UpcomingShiftsCard now={NOW} />);
627
+
628
+ await waitFor(() => {
629
+ expect(screen.getByTestId("upcoming-shifts-truncated")).toHaveTextContent(
630
+ "some shifts may be missing",
631
+ );
632
+ });
633
+ });
634
+
635
+ test("a failed fetch shows the reason, and Refresh asks again", async () => {
636
+ getMock
637
+ .mockResolvedValueOnce(
638
+ new HTTPErrorResponse(503, { message: "Render cap reached" }, {}),
639
+ )
640
+ .mockResolvedValueOnce(ok(myShiftsJson([], false)));
641
+
642
+ render(<UpcomingShiftsCard now={NOW} />);
643
+
644
+ await waitFor(() => {
645
+ expect(screen.getByText("Render cap reached")).toBeInTheDocument();
646
+ });
647
+
648
+ fireEvent.click(screen.getByRole("button", { name: "Refresh" }));
649
+
650
+ await waitFor(() => {
651
+ expect(screen.getByTestId("upcoming-shifts-empty")).toBeInTheDocument();
652
+ });
653
+ expect(getMock).toHaveBeenCalledTimes(2);
654
+ });
655
+ });