@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,755 @@
1
+ import TeamMemberService, {
2
+ OnCallLeaveCleanupResult,
3
+ } from "../../../Server/Services/TeamMemberService";
4
+ import OnCallDutyPolicyScheduleService from "../../../Server/Services/OnCallDutyPolicyScheduleService";
5
+ import OnCallDutyPolicyScheduleLayerUserService from "../../../Server/Services/OnCallDutyPolicyScheduleLayerUserService";
6
+ import OnCallDutyPolicyEscalationRuleUserService from "../../../Server/Services/OnCallDutyPolicyEscalationRuleUserService";
7
+ import OnCallDutyPolicyUserOverrideService from "../../../Server/Services/OnCallDutyPolicyUserOverrideService";
8
+ import UserOnCallCalendarFeedService from "../../../Server/Services/UserOnCallCalendarFeedService";
9
+ import UserOnCallShiftReminderService from "../../../Server/Services/UserOnCallShiftReminderService";
10
+ import OnCallDutyPolicyScheduleCalendarFeedService from "../../../Server/Services/OnCallDutyPolicyScheduleCalendarFeedService";
11
+ import ProjectOnCallCalendarFeedService from "../../../Server/Services/ProjectOnCallCalendarFeedService";
12
+ import UserNotificationSettingService from "../../../Server/Services/UserNotificationSettingService";
13
+ import OnCallCalendarFeedCache from "../../../Server/Infrastructure/OnCallCalendarFeedCache";
14
+ import { OnCallShiftChangeReason } from "../../../Server/Utils/OnCall/OnCallShiftChangeListeners";
15
+ import logger from "../../../Server/Utils/Logger";
16
+ import ObjectID from "../../../Types/ObjectID";
17
+ import PositiveNumber from "../../../Types/PositiveNumber";
18
+ import {
19
+ afterEach,
20
+ beforeEach,
21
+ describe,
22
+ expect,
23
+ jest,
24
+ test,
25
+ } from "@jest/globals";
26
+
27
+ /*
28
+ * Decision 5 of the calendar-feeds spec: when a user's LAST accepted team
29
+ * membership in a project goes away, their on-call assignments in that
30
+ * project go with it. Nothing did this before — the layer-user and
31
+ * escalation-rule-user rows survived, so schedules kept rotating onto an
32
+ * ex-member.
33
+ *
34
+ * The rule is the one removeDefaultNotificationSettingsForUser already uses:
35
+ * countBy({projectId, userId, hasAcceptedInvitation: true}) === 0. Everything
36
+ * the cleanup touches is spied; no Postgres or Redis involved.
37
+ */
38
+
39
+ const PROJECT_ID: ObjectID = new ObjectID("project-1");
40
+ const USER_ID: ObjectID = new ObjectID("user-1");
41
+ const SCHEDULE_1: ObjectID = new ObjectID("schedule-1");
42
+ const SCHEDULE_2: ObjectID = new ObjectID("schedule-2");
43
+ const LAYER_1: ObjectID = new ObjectID("layer-1");
44
+ const LAYER_2: ObjectID = new ObjectID("layer-2");
45
+ const FEED_1: ObjectID = new ObjectID("feed-1");
46
+ const PROJECT_FEED: ObjectID = new ObjectID("project-feed-1");
47
+
48
+ function ids(list: Array<ObjectID>): Array<string> {
49
+ return list.map((id: ObjectID) => {
50
+ return id.toString();
51
+ });
52
+ }
53
+
54
+ interface Spies {
55
+ countMembers: any;
56
+ layerUsersFindBy: any;
57
+ layerUsersDeleteBy: any;
58
+ resequence: any;
59
+ ruleUsersCountBy: any;
60
+ ruleUsersDeleteBy: any;
61
+ overridesDeleteBy: any;
62
+ refreshRoster: any;
63
+ propagate: any;
64
+ feedCountBy: any;
65
+ feedUpdateOneBy: any;
66
+ purgeForUser: any;
67
+ purgeForProject: any;
68
+ remindersDeleteBy: any;
69
+ rotateScheduleFeeds: any;
70
+ rotateProjectFeeds: any;
71
+ }
72
+
73
+ /*
74
+ * The "everything is there" world: two layer memberships on two schedules,
75
+ * one escalation-rule membership, a personal feed, two reminders, one
76
+ * flagged schedule feed and no project feed.
77
+ */
78
+ function stubWorld(options?: {
79
+ remainingMemberships?: number;
80
+ layerUserRows?: Array<Record<string, unknown>>;
81
+ ruleUserCount?: number;
82
+ userOverrideCountPerSide?: number;
83
+ feedCount?: number;
84
+ rotatedScheduleFeeds?: Array<ObjectID>;
85
+ rotatedProjectFeeds?: Array<ObjectID>;
86
+ }): Spies {
87
+ const layerUserRows: Array<Record<string, unknown>> =
88
+ options?.layerUserRows ?? [
89
+ {
90
+ _id: new ObjectID("lu-1"),
91
+ onCallDutyPolicyScheduleId: SCHEDULE_1,
92
+ onCallDutyPolicyScheduleLayerId: LAYER_1,
93
+ },
94
+ {
95
+ _id: new ObjectID("lu-2"),
96
+ onCallDutyPolicyScheduleId: SCHEDULE_2,
97
+ onCallDutyPolicyScheduleLayerId: LAYER_2,
98
+ },
99
+ // Same schedule and layer twice (a user can sit in one layer twice).
100
+ {
101
+ _id: new ObjectID("lu-3"),
102
+ onCallDutyPolicyScheduleId: SCHEDULE_1,
103
+ onCallDutyPolicyScheduleLayerId: LAYER_1,
104
+ },
105
+ ];
106
+
107
+ return {
108
+ countMembers: jest
109
+ .spyOn(TeamMemberService, "countBy")
110
+ .mockResolvedValue(
111
+ new PositiveNumber(options?.remainingMemberships ?? 0) as never,
112
+ ),
113
+ layerUsersFindBy: jest
114
+ .spyOn(OnCallDutyPolicyScheduleLayerUserService, "findBy")
115
+ .mockResolvedValue(layerUserRows as never),
116
+ layerUsersDeleteBy: jest
117
+ .spyOn(OnCallDutyPolicyScheduleLayerUserService, "deleteBy")
118
+ .mockResolvedValue(layerUserRows.length as never),
119
+ resequence: jest
120
+ .spyOn(OnCallDutyPolicyScheduleLayerUserService, "resequenceOrderInLayer")
121
+ .mockResolvedValue(undefined),
122
+ ruleUsersCountBy: jest
123
+ .spyOn(OnCallDutyPolicyEscalationRuleUserService, "countBy")
124
+ .mockResolvedValue(
125
+ new PositiveNumber(options?.ruleUserCount ?? 1) as never,
126
+ ),
127
+ ruleUsersDeleteBy: jest
128
+ .spyOn(OnCallDutyPolicyEscalationRuleUserService, "deleteBy")
129
+ .mockResolvedValue((options?.ruleUserCount ?? 1) as never),
130
+ overridesDeleteBy: jest
131
+ .spyOn(OnCallDutyPolicyUserOverrideService, "deleteBy")
132
+ .mockResolvedValue((options?.userOverrideCountPerSide ?? 1) as never),
133
+ refreshRoster: jest
134
+ .spyOn(
135
+ OnCallDutyPolicyScheduleService,
136
+ "refreshCurrentUserIdAndHandoffTimeInSchedule",
137
+ )
138
+ .mockResolvedValue({
139
+ currentUserId: null,
140
+ handOffTimeAt: null,
141
+ nextUserId: null,
142
+ nextHandOffTimeAt: null,
143
+ rosterStartAt: null,
144
+ nextRosterStartAt: null,
145
+ }),
146
+ propagate: jest
147
+ .spyOn(OnCallDutyPolicyScheduleService, "propagateShiftConfigChange")
148
+ .mockResolvedValue(undefined),
149
+ feedCountBy: jest
150
+ .spyOn(UserOnCallCalendarFeedService, "countBy")
151
+ .mockResolvedValue(new PositiveNumber(options?.feedCount ?? 1) as never),
152
+ feedUpdateOneBy: jest
153
+ .spyOn(UserOnCallCalendarFeedService, "updateOneBy")
154
+ .mockResolvedValue(undefined as never),
155
+ purgeForUser: jest
156
+ .spyOn(OnCallCalendarFeedCache, "purgeForUser")
157
+ .mockResolvedValue(undefined),
158
+ purgeForProject: jest
159
+ .spyOn(OnCallCalendarFeedCache, "purgeForProject")
160
+ .mockResolvedValue(undefined),
161
+ remindersDeleteBy: jest
162
+ .spyOn(UserOnCallShiftReminderService, "deleteBy")
163
+ .mockResolvedValue(2 as never),
164
+ rotateScheduleFeeds: jest
165
+ .spyOn(
166
+ OnCallDutyPolicyScheduleCalendarFeedService,
167
+ "rotateFeedsForMemberLeave",
168
+ )
169
+ .mockResolvedValue(options?.rotatedScheduleFeeds ?? [FEED_1]),
170
+ rotateProjectFeeds: jest
171
+ .spyOn(ProjectOnCallCalendarFeedService, "rotateFeedsForMemberLeave")
172
+ .mockResolvedValue(options?.rotatedProjectFeeds ?? []),
173
+ };
174
+ }
175
+
176
+ describe("TeamMemberService on-call cleanup when a user leaves the project", () => {
177
+ beforeEach(() => {
178
+ jest.spyOn(logger, "error").mockImplementation((): void => {
179
+ return undefined;
180
+ });
181
+ jest.spyOn(logger, "debug").mockImplementation((): void => {
182
+ return undefined;
183
+ });
184
+ });
185
+
186
+ afterEach(() => {
187
+ jest.restoreAllMocks();
188
+ });
189
+
190
+ describe("still a member of another team", () => {
191
+ test("nothing is removed, refreshed, disabled or rotated", async () => {
192
+ const spies: Spies = stubWorld({ remainingMemberships: 1 });
193
+
194
+ const result: OnCallLeaveCleanupResult | null =
195
+ await TeamMemberService.cleanupOnCallAssignmentsIfUserLeftProject({
196
+ projectId: PROJECT_ID,
197
+ userId: USER_ID,
198
+ });
199
+
200
+ expect(result).toBeNull();
201
+
202
+ expect(spies.countMembers).toHaveBeenCalledWith({
203
+ query: {
204
+ projectId: PROJECT_ID,
205
+ userId: USER_ID,
206
+ hasAcceptedInvitation: true,
207
+ },
208
+ props: { isRoot: true },
209
+ });
210
+
211
+ expect(spies.layerUsersFindBy).not.toHaveBeenCalled();
212
+ expect(spies.layerUsersDeleteBy).not.toHaveBeenCalled();
213
+ expect(spies.ruleUsersDeleteBy).not.toHaveBeenCalled();
214
+ expect(spies.refreshRoster).not.toHaveBeenCalled();
215
+ expect(spies.propagate).not.toHaveBeenCalled();
216
+ expect(spies.feedUpdateOneBy).not.toHaveBeenCalled();
217
+ expect(spies.remindersDeleteBy).not.toHaveBeenCalled();
218
+ expect(spies.rotateScheduleFeeds).not.toHaveBeenCalled();
219
+ expect(spies.rotateProjectFeeds).not.toHaveBeenCalled();
220
+ expect(spies.purgeForUser).not.toHaveBeenCalled();
221
+ expect(spies.purgeForProject).not.toHaveBeenCalled();
222
+ });
223
+ });
224
+
225
+ describe("last team in the project", () => {
226
+ test("removes layer users and rule users, refreshes rosters, bumps, disables the feed, deletes reminders, rotates flagged feeds", async () => {
227
+ const spies: Spies = stubWorld();
228
+
229
+ const result: OnCallLeaveCleanupResult | null =
230
+ await TeamMemberService.cleanupOnCallAssignmentsIfUserLeftProject({
231
+ projectId: PROJECT_ID,
232
+ userId: USER_ID,
233
+ });
234
+
235
+ expect(result).toEqual({
236
+ removedLayerUserCount: 3,
237
+ removedEscalationRuleUserCount: 1,
238
+ // one delete per side of the override (overridden user, substitute).
239
+ removedUserOverrideCount: 2,
240
+ refreshedScheduleIds: ["schedule-1", "schedule-2"],
241
+ personalFeedDisabled: true,
242
+ removedReminderCount: 2,
243
+ rotatedScheduleFeedIds: ["feed-1"],
244
+ rotatedProjectFeedIds: [],
245
+ });
246
+
247
+ // 1. Layer-user rows: one root delete over exactly {projectId, userId}.
248
+ expect(spies.layerUsersDeleteBy).toHaveBeenCalledTimes(1);
249
+ expect(spies.layerUsersDeleteBy.mock.calls[0]![0]).toMatchObject({
250
+ query: { projectId: PROJECT_ID, userId: USER_ID },
251
+ props: { isRoot: true },
252
+ });
253
+ // ...and each affected layer re-sequenced once.
254
+ expect(
255
+ ids(
256
+ spies.resequence.mock.calls.map((c: Array<any>) => {
257
+ return c[0];
258
+ }),
259
+ ),
260
+ ).toEqual(["layer-1", "layer-2"]);
261
+
262
+ // 2. Rule-user rows through the service (hooks close time logs, notify).
263
+ expect(spies.ruleUsersDeleteBy).toHaveBeenCalledTimes(1);
264
+ expect(spies.ruleUsersDeleteBy.mock.calls[0]![0]).toMatchObject({
265
+ query: { projectId: PROJECT_ID, userId: USER_ID },
266
+ props: { isRoot: true },
267
+ });
268
+
269
+ // 3. Every affected schedule's roster re-resolved exactly once.
270
+ expect(
271
+ ids(
272
+ spies.refreshRoster.mock.calls.map((c: Array<any>) => {
273
+ return c[0];
274
+ }),
275
+ ),
276
+ ).toEqual(["schedule-1", "schedule-2"]);
277
+
278
+ // 4. Version bump / purge / listeners with the leaving user named.
279
+ expect(spies.propagate).toHaveBeenCalledTimes(1);
280
+ const change: any = spies.propagate.mock.calls[0]![0];
281
+ expect(ids(change.scheduleIds)).toEqual(["schedule-1", "schedule-2"]);
282
+ expect(change.projectId).toBe(PROJECT_ID);
283
+ expect(ids(change.userIds)).toEqual(["user-1"]);
284
+ expect(change.reason).toBe(OnCallShiftChangeReason.MemberLeftProject);
285
+
286
+ // 5. Personal feed disabled — not deleted — and its bodies purged.
287
+ expect(spies.feedUpdateOneBy).toHaveBeenCalledTimes(1);
288
+ expect(spies.feedUpdateOneBy.mock.calls[0]![0]).toEqual({
289
+ query: { projectId: PROJECT_ID, userId: USER_ID },
290
+ data: { isEnabled: false },
291
+ props: { isRoot: true },
292
+ });
293
+ expect(spies.purgeForUser).toHaveBeenCalledWith("project-1", "user-1");
294
+
295
+ // 6. Reminders gone.
296
+ expect(spies.remindersDeleteBy.mock.calls[0]![0]).toMatchObject({
297
+ query: { projectId: PROJECT_ID, userId: USER_ID },
298
+ props: { isRoot: true },
299
+ });
300
+
301
+ // 7. Flagged feeds rotated through the owning services, project purged.
302
+ expect(spies.rotateScheduleFeeds).toHaveBeenCalledWith({
303
+ projectId: PROJECT_ID,
304
+ });
305
+ expect(spies.rotateProjectFeeds).toHaveBeenCalledWith({
306
+ projectId: PROJECT_ID,
307
+ });
308
+ expect(spies.purgeForProject).toHaveBeenCalledWith("project-1");
309
+ });
310
+
311
+ test("deletes the project's still-running overrides on BOTH sides through the service, and leaves ended ones alone", async () => {
312
+ const spies: Spies = stubWorld();
313
+ const before: Date = new Date();
314
+
315
+ await TeamMemberService.cleanupOnCallAssignmentsForUserLeavingProject({
316
+ projectId: PROJECT_ID,
317
+ userId: USER_ID,
318
+ });
319
+
320
+ /*
321
+ * An override "route Alice's alerts to Bob" keeps paging Bob and keeps
322
+ * Bob's covering shifts in the feeds and in /my-shifts; "route Bob's
323
+ * alerts to Carol" is Bob's own, and Bob is gone. Both sides go, and
324
+ * only through the service — its hooks refresh the overridden
325
+ * colleague's roster and propagate.
326
+ */
327
+ expect(spies.overridesDeleteBy).toHaveBeenCalledTimes(2);
328
+
329
+ const overrideSide: any = spies.overridesDeleteBy.mock.calls[0]![0];
330
+ const substituteSide: any = spies.overridesDeleteBy.mock.calls[1]![0];
331
+
332
+ expect(overrideSide.query.projectId).toBe(PROJECT_ID);
333
+ expect(overrideSide.query.overrideUserId).toBe(USER_ID);
334
+ expect(overrideSide.query.routeAlertsToUserId).toBeUndefined();
335
+ expect(overrideSide.props).toEqual({ isRoot: true });
336
+
337
+ expect(substituteSide.query.projectId).toBe(PROJECT_ID);
338
+ expect(substituteSide.query.routeAlertsToUserId).toBe(USER_ID);
339
+ expect(substituteSide.query.overrideUserId).toBeUndefined();
340
+ expect(substituteSide.props).toEqual({ isRoot: true });
341
+
342
+ // Ended overrides are history: both deletes are scoped to endsAt > now.
343
+ for (const call of [overrideSide, substituteSide]) {
344
+ const endsAt: any = call.query.endsAt;
345
+ expect(endsAt).toBeDefined();
346
+ expect(String(endsAt.getSql('"endsAt"'))).toContain('"endsAt" > :');
347
+
348
+ const boundValues: Array<unknown> = Object.values(
349
+ endsAt.objectLiteralParameters as Record<string, unknown>,
350
+ );
351
+ expect(boundValues).toHaveLength(1);
352
+ const bound: Date = boundValues[0] as Date;
353
+ expect(bound instanceof Date).toBe(true);
354
+ expect(bound.getTime()).toBeGreaterThanOrEqual(before.getTime() - 1000);
355
+ expect(bound.getTime()).toBeLessThanOrEqual(Date.now() + 1000);
356
+ }
357
+ });
358
+
359
+ test("the overrides go before the rosters are refreshed and the change propagated", async () => {
360
+ const spies: Spies = stubWorld();
361
+
362
+ await TeamMemberService.cleanupOnCallAssignmentsForUserLeavingProject({
363
+ projectId: PROJECT_ID,
364
+ userId: USER_ID,
365
+ });
366
+
367
+ expect(spies.overridesDeleteBy.mock.invocationCallOrder[0]!).toBeLessThan(
368
+ spies.refreshRoster.mock.invocationCallOrder[0]!,
369
+ );
370
+ expect(spies.overridesDeleteBy.mock.invocationCallOrder[0]!).toBeLessThan(
371
+ spies.propagate.mock.invocationCallOrder[0]!,
372
+ );
373
+ });
374
+
375
+ test("the order is: rows removed, rosters refreshed, then propagated, then feeds", async () => {
376
+ const spies: Spies = stubWorld();
377
+
378
+ await TeamMemberService.cleanupOnCallAssignmentsForUserLeavingProject({
379
+ projectId: PROJECT_ID,
380
+ userId: USER_ID,
381
+ });
382
+
383
+ const order: (spy: any) => number = (spy: any): number => {
384
+ return spy.mock.invocationCallOrder[0]!;
385
+ };
386
+
387
+ expect(order(spies.layerUsersDeleteBy)).toBeLessThan(
388
+ order(spies.refreshRoster),
389
+ );
390
+ expect(order(spies.ruleUsersDeleteBy)).toBeLessThan(
391
+ order(spies.refreshRoster),
392
+ );
393
+ expect(order(spies.refreshRoster)).toBeLessThan(order(spies.propagate));
394
+ expect(order(spies.propagate)).toBeLessThan(order(spies.feedUpdateOneBy));
395
+ expect(order(spies.feedUpdateOneBy)).toBeLessThan(
396
+ order(spies.rotateScheduleFeeds),
397
+ );
398
+ });
399
+
400
+ test("nothing rotated => no project-wide purge; unflagged feeds are the services' business", async () => {
401
+ const spies: Spies = stubWorld({
402
+ rotatedScheduleFeeds: [],
403
+ rotatedProjectFeeds: [],
404
+ });
405
+
406
+ const result: OnCallLeaveCleanupResult =
407
+ await TeamMemberService.cleanupOnCallAssignmentsForUserLeavingProject({
408
+ projectId: PROJECT_ID,
409
+ userId: USER_ID,
410
+ });
411
+
412
+ expect(result.rotatedScheduleFeedIds).toEqual([]);
413
+ expect(result.rotatedProjectFeedIds).toEqual([]);
414
+ expect(spies.purgeForProject).not.toHaveBeenCalled();
415
+ // The personal-feed purge still happens.
416
+ expect(spies.purgeForUser).toHaveBeenCalledTimes(1);
417
+ });
418
+
419
+ test("a rotated project feed also purges the project", async () => {
420
+ const spies: Spies = stubWorld({
421
+ rotatedScheduleFeeds: [],
422
+ rotatedProjectFeeds: [PROJECT_FEED],
423
+ });
424
+
425
+ const result: OnCallLeaveCleanupResult =
426
+ await TeamMemberService.cleanupOnCallAssignmentsForUserLeavingProject({
427
+ projectId: PROJECT_ID,
428
+ userId: USER_ID,
429
+ });
430
+
431
+ expect(result.rotatedProjectFeedIds).toEqual(["project-feed-1"]);
432
+ expect(spies.purgeForProject).toHaveBeenCalledTimes(1);
433
+ });
434
+
435
+ test("no on-call rows at all: no deletes, no refresh, but the feed/reminder/rotation steps still run", async () => {
436
+ const spies: Spies = stubWorld({
437
+ layerUserRows: [],
438
+ ruleUserCount: 0,
439
+ userOverrideCountPerSide: 0,
440
+ feedCount: 0,
441
+ });
442
+
443
+ const result: OnCallLeaveCleanupResult =
444
+ await TeamMemberService.cleanupOnCallAssignmentsForUserLeavingProject({
445
+ projectId: PROJECT_ID,
446
+ userId: USER_ID,
447
+ });
448
+
449
+ expect(spies.layerUsersDeleteBy).not.toHaveBeenCalled();
450
+ expect(spies.resequence).not.toHaveBeenCalled();
451
+ expect(spies.ruleUsersDeleteBy).not.toHaveBeenCalled();
452
+ expect(spies.refreshRoster).not.toHaveBeenCalled();
453
+ expect(spies.feedUpdateOneBy).not.toHaveBeenCalled();
454
+
455
+ // The listener event still names the user (their feed just changed).
456
+ expect(spies.propagate).toHaveBeenCalledTimes(1);
457
+ expect(spies.propagate.mock.calls[0]![0].scheduleIds).toEqual([]);
458
+ expect(ids(spies.propagate.mock.calls[0]![0].userIds)).toEqual([
459
+ "user-1",
460
+ ]);
461
+
462
+ expect(spies.purgeForUser).toHaveBeenCalledTimes(1);
463
+ expect(spies.remindersDeleteBy).toHaveBeenCalledTimes(1);
464
+ expect(spies.rotateScheduleFeeds).toHaveBeenCalledTimes(1);
465
+
466
+ expect(result).toEqual({
467
+ removedLayerUserCount: 0,
468
+ removedEscalationRuleUserCount: 0,
469
+ removedUserOverrideCount: 0,
470
+ refreshedScheduleIds: [],
471
+ personalFeedDisabled: false,
472
+ removedReminderCount: 2,
473
+ rotatedScheduleFeedIds: ["feed-1"],
474
+ rotatedProjectFeedIds: [],
475
+ });
476
+ });
477
+ });
478
+
479
+ describe("failure isolation", () => {
480
+ test("a failing layer-user lookup does not stop the other steps", async () => {
481
+ const spies: Spies = stubWorld();
482
+ spies.layerUsersFindBy.mockRejectedValue(new Error("db down"));
483
+
484
+ const result: OnCallLeaveCleanupResult =
485
+ await TeamMemberService.cleanupOnCallAssignmentsForUserLeavingProject({
486
+ projectId: PROJECT_ID,
487
+ userId: USER_ID,
488
+ });
489
+
490
+ expect(result.removedLayerUserCount).toBe(0);
491
+ expect(result.refreshedScheduleIds).toEqual([]);
492
+ expect(result.removedEscalationRuleUserCount).toBe(1);
493
+ expect(result.personalFeedDisabled).toBe(true);
494
+ expect(result.removedReminderCount).toBe(2);
495
+ expect(result.rotatedScheduleFeedIds).toEqual(["feed-1"]);
496
+ expect(logger.error).toHaveBeenCalled();
497
+ });
498
+
499
+ test("a failing roster refresh on one schedule still refreshes the other and continues", async () => {
500
+ const spies: Spies = stubWorld();
501
+ spies.refreshRoster.mockImplementation((id: ObjectID): Promise<any> => {
502
+ if (id.toString() === "schedule-1") {
503
+ return Promise.reject(new Error("redis down"));
504
+ }
505
+ return Promise.resolve({});
506
+ });
507
+
508
+ const result: OnCallLeaveCleanupResult =
509
+ await TeamMemberService.cleanupOnCallAssignmentsForUserLeavingProject({
510
+ projectId: PROJECT_ID,
511
+ userId: USER_ID,
512
+ });
513
+
514
+ expect(result.refreshedScheduleIds).toEqual(["schedule-2"]);
515
+ expect(spies.propagate).toHaveBeenCalledTimes(1);
516
+ expect(result.personalFeedDisabled).toBe(true);
517
+ });
518
+
519
+ test("a failing feed disable, reminder delete and rotation are each logged and the result reflects it", async () => {
520
+ const spies: Spies = stubWorld();
521
+ spies.feedUpdateOneBy.mockRejectedValue(new Error("db down"));
522
+ spies.remindersDeleteBy.mockRejectedValue(new Error("db down"));
523
+ spies.rotateScheduleFeeds.mockRejectedValue(new Error("db down"));
524
+
525
+ const result: OnCallLeaveCleanupResult =
526
+ await TeamMemberService.cleanupOnCallAssignmentsForUserLeavingProject({
527
+ projectId: PROJECT_ID,
528
+ userId: USER_ID,
529
+ });
530
+
531
+ expect(result.personalFeedDisabled).toBe(false);
532
+ expect(result.removedReminderCount).toBe(0);
533
+ expect(result.rotatedScheduleFeedIds).toEqual([]);
534
+ // The project feed rotation ran regardless of the schedule-feed failure.
535
+ expect(spies.rotateProjectFeeds).toHaveBeenCalledTimes(1);
536
+ expect(result.removedLayerUserCount).toBe(3);
537
+ });
538
+
539
+ test("a failing override delete is logged and the rest of the cleanup still runs", async () => {
540
+ const spies: Spies = stubWorld();
541
+ spies.overridesDeleteBy.mockRejectedValue(new Error("db down"));
542
+
543
+ const result: OnCallLeaveCleanupResult =
544
+ await TeamMemberService.cleanupOnCallAssignmentsForUserLeavingProject({
545
+ projectId: PROJECT_ID,
546
+ userId: USER_ID,
547
+ });
548
+
549
+ expect(result.removedUserOverrideCount).toBe(0);
550
+ expect(result.removedLayerUserCount).toBe(3);
551
+ expect(result.refreshedScheduleIds).toEqual(["schedule-1", "schedule-2"]);
552
+ expect(result.personalFeedDisabled).toBe(true);
553
+ expect(logger.error).toHaveBeenCalled();
554
+ });
555
+
556
+ test("the membership count failing makes the guard a no-op rather than an exception", async () => {
557
+ const spies: Spies = stubWorld();
558
+ spies.countMembers.mockRejectedValue(new Error("db down"));
559
+
560
+ await expect(
561
+ TeamMemberService.cleanupOnCallAssignmentsIfUserLeftProject({
562
+ projectId: PROJECT_ID,
563
+ userId: USER_ID,
564
+ }),
565
+ ).resolves.toBeNull();
566
+
567
+ expect(spies.layerUsersDeleteBy).not.toHaveBeenCalled();
568
+ });
569
+ });
570
+
571
+ describe("a revoked invitation that was never accepted", () => {
572
+ /*
573
+ * Revoking a pending invite also brings the accepted-membership count to
574
+ * zero, so the guard fires. The row cleanup is harmless (there is nothing
575
+ * to remove), but rotating every opted-in shared feed would silently clear
576
+ * each subscribed teammate's calendar for someone who never had the link.
577
+ */
578
+ test("cleans up rows but rotates NO shared feed", async () => {
579
+ const spies: Spies = stubWorld();
580
+
581
+ const result: OnCallLeaveCleanupResult | null =
582
+ await TeamMemberService.cleanupOnCallAssignmentsIfUserLeftProject({
583
+ projectId: PROJECT_ID,
584
+ userId: USER_ID,
585
+ hadAcceptedMembership: false,
586
+ });
587
+
588
+ expect(result).not.toBeNull();
589
+ expect(spies.layerUsersDeleteBy).toHaveBeenCalledTimes(1);
590
+ expect(spies.remindersDeleteBy).toHaveBeenCalledTimes(1);
591
+ expect(spies.feedUpdateOneBy).toHaveBeenCalledTimes(1);
592
+
593
+ expect(spies.rotateScheduleFeeds).not.toHaveBeenCalled();
594
+ expect(spies.rotateProjectFeeds).not.toHaveBeenCalled();
595
+ expect(spies.purgeForProject).not.toHaveBeenCalled();
596
+ expect(result!.rotatedScheduleFeedIds).toEqual([]);
597
+ expect(result!.rotatedProjectFeedIds).toEqual([]);
598
+ });
599
+
600
+ test("an accepted membership (the default) still rotates", async () => {
601
+ const spies: Spies = stubWorld();
602
+
603
+ await TeamMemberService.cleanupOnCallAssignmentsIfUserLeftProject({
604
+ projectId: PROJECT_ID,
605
+ userId: USER_ID,
606
+ hadAcceptedMembership: true,
607
+ });
608
+
609
+ expect(spies.rotateScheduleFeeds).toHaveBeenCalledTimes(1);
610
+ expect(spies.rotateProjectFeeds).toHaveBeenCalledTimes(1);
611
+ });
612
+ });
613
+
614
+ describe("onDeleteSuccess wiring", () => {
615
+ test("runs the cleanup once per (user, project) BEFORE the notification settings are removed", async () => {
616
+ jest
617
+ .spyOn(TeamMemberService, "refreshTokens")
618
+ .mockResolvedValue(undefined);
619
+ jest
620
+ .spyOn(
621
+ TeamMemberService,
622
+ "updateSubscriptionSeatsByUniqueTeamMembersInProject",
623
+ )
624
+ .mockResolvedValue(undefined);
625
+ const removeSettings: any = jest
626
+ .spyOn(
627
+ UserNotificationSettingService,
628
+ "removeDefaultNotificationSettingsForUser",
629
+ )
630
+ .mockResolvedValue(undefined);
631
+ const cleanup: any = jest
632
+ .spyOn(TeamMemberService, "cleanupOnCallAssignmentsIfUserLeftProject")
633
+ .mockResolvedValue(null);
634
+
635
+ const onDelete: any = {
636
+ deleteBy: { query: {}, props: { isRoot: true } },
637
+ carryForward: [
638
+ {
639
+ userId: USER_ID,
640
+ projectId: PROJECT_ID,
641
+ teamId: new ObjectID("t1"),
642
+ },
643
+ {
644
+ userId: USER_ID,
645
+ projectId: PROJECT_ID,
646
+ teamId: new ObjectID("t2"),
647
+ },
648
+ {
649
+ userId: new ObjectID("user-2"),
650
+ projectId: PROJECT_ID,
651
+ teamId: new ObjectID("t1"),
652
+ },
653
+ ],
654
+ };
655
+
656
+ await (TeamMemberService as any).onDeleteSuccess(onDelete);
657
+
658
+ expect(cleanup).toHaveBeenCalledTimes(2);
659
+ expect(
660
+ cleanup.mock.calls.map((c: Array<any>) => {
661
+ return c[0].userId.toString();
662
+ }),
663
+ ).toEqual(["user-1", "user-2"]);
664
+ expect(cleanup.mock.calls[0]![0].projectId).toBe(PROJECT_ID);
665
+
666
+ // The settings are removed for every membership row, after the cleanup.
667
+ expect(removeSettings).toHaveBeenCalledTimes(3);
668
+ expect(cleanup.mock.invocationCallOrder[0]!).toBeLessThan(
669
+ removeSettings.mock.invocationCallOrder[0]!,
670
+ );
671
+ });
672
+
673
+ test("hadAcceptedMembership is true only when one of the deleted rows was accepted", async () => {
674
+ jest
675
+ .spyOn(TeamMemberService, "refreshTokens")
676
+ .mockResolvedValue(undefined);
677
+ jest
678
+ .spyOn(
679
+ TeamMemberService,
680
+ "updateSubscriptionSeatsByUniqueTeamMembersInProject",
681
+ )
682
+ .mockResolvedValue(undefined);
683
+ jest
684
+ .spyOn(
685
+ UserNotificationSettingService,
686
+ "removeDefaultNotificationSettingsForUser",
687
+ )
688
+ .mockResolvedValue(undefined);
689
+ const cleanup: any = jest
690
+ .spyOn(TeamMemberService, "cleanupOnCallAssignmentsIfUserLeftProject")
691
+ .mockResolvedValue(null);
692
+
693
+ await (TeamMemberService as any).onDeleteSuccess({
694
+ deleteBy: { query: {}, props: { isRoot: true } },
695
+ carryForward: [
696
+ // user-1: left two teams, one of them a real (accepted) membership.
697
+ {
698
+ userId: USER_ID,
699
+ projectId: PROJECT_ID,
700
+ teamId: new ObjectID("t1"),
701
+ hasAcceptedInvitation: false,
702
+ },
703
+ {
704
+ userId: USER_ID,
705
+ projectId: PROJECT_ID,
706
+ teamId: new ObjectID("t2"),
707
+ hasAcceptedInvitation: true,
708
+ },
709
+ // user-2: a revoked invitation, never accepted.
710
+ {
711
+ userId: new ObjectID("user-2"),
712
+ projectId: PROJECT_ID,
713
+ teamId: new ObjectID("t1"),
714
+ hasAcceptedInvitation: false,
715
+ },
716
+ ],
717
+ });
718
+
719
+ expect(cleanup).toHaveBeenCalledTimes(2);
720
+ expect(cleanup.mock.calls[0]![0]).toMatchObject({
721
+ userId: USER_ID,
722
+ hadAcceptedMembership: true,
723
+ });
724
+ expect(cleanup.mock.calls[1]![0].userId.toString()).toBe("user-2");
725
+ expect(cleanup.mock.calls[1]![0].hadAcceptedMembership).toBe(false);
726
+ });
727
+
728
+ test("the guard runs the real countBy rule when invoked through onDeleteSuccess", async () => {
729
+ jest
730
+ .spyOn(TeamMemberService, "refreshTokens")
731
+ .mockResolvedValue(undefined);
732
+ jest
733
+ .spyOn(
734
+ TeamMemberService,
735
+ "updateSubscriptionSeatsByUniqueTeamMembersInProject",
736
+ )
737
+ .mockResolvedValue(undefined);
738
+ jest
739
+ .spyOn(
740
+ UserNotificationSettingService,
741
+ "removeDefaultNotificationSettingsForUser",
742
+ )
743
+ .mockResolvedValue(undefined);
744
+ const spies: Spies = stubWorld({ remainingMemberships: 2 });
745
+
746
+ await (TeamMemberService as any).onDeleteSuccess({
747
+ deleteBy: { query: {}, props: {} },
748
+ carryForward: [{ userId: USER_ID, projectId: PROJECT_ID }],
749
+ });
750
+
751
+ expect(spies.countMembers).toHaveBeenCalledTimes(1);
752
+ expect(spies.layerUsersDeleteBy).not.toHaveBeenCalled();
753
+ });
754
+ });
755
+ });