@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,800 @@
1
+ import moment from "moment-timezone";
2
+ import User from "../../Models/DatabaseModels/User";
3
+ import ICalendar, { ICalendarEventStatus, ICalendarTransparency, } from "../Calendar/ICalendar";
4
+ import OneUptimeDate from "../Date";
5
+ import ObjectID from "../ObjectID";
6
+ import Timezone from "../Timezone";
7
+ import CalendarFeedWindow, { MAX_EVENTS, MAX_GAP_EVENTS, } from "./CalendarFeedWindow";
8
+ import LayerUtil from "./Layer";
9
+ import MaterializedShiftUtil from "./MaterializedShift";
10
+ import ScheduleShiftUtil from "./ScheduleShiftUtil";
11
+ import ShiftSeamUtil from "./ShiftSeamUtil";
12
+ /*
13
+ * Pure mapper from materialized on-call shifts to an iCalendar document.
14
+ *
15
+ * Everything here is deterministic on its inputs (no clock reads, no I/O), so
16
+ * the same shifts always serialize to the same bytes — the property the body
17
+ * cache and ETag depend on. The server-side renderer decides WHAT to feed in
18
+ * (which shifts, which window, which timezone) and this module decides how
19
+ * that looks in a calendar.
20
+ *
21
+ * Rules implemented (see the on-call calendar feeds design):
22
+ * - UID is (schedule, seam-normalised start), never the user, so an override
23
+ * swap updates the event in place; policy variants add the policy id, gaps
24
+ * have their own namespace.
25
+ * - DTSTAMP/LAST-MODIFIED are the schedule inputs' last-modified instant and
26
+ * SEQUENCE is the schedule's shiftConfigVersion, so an unchanged schedule
27
+ * renders byte-identically.
28
+ * - Every DTSTART/DTEND is UTC; the DESCRIPTION carries the schedule-zone,
29
+ * UTC and viewer-zone wall clock.
30
+ * - TRANSP:TRANSPARENT, STATUS:CONFIRMED, CATEGORIES:On-Call; no CLASS.
31
+ */
32
+ export var OnCallCalendarFeedKind;
33
+ (function (OnCallCalendarFeedKind) {
34
+ OnCallCalendarFeedKind["Personal"] = "personal";
35
+ OnCallCalendarFeedKind["Schedule"] = "schedule";
36
+ OnCallCalendarFeedKind["Project"] = "project";
37
+ })(OnCallCalendarFeedKind || (OnCallCalendarFeedKind = {}));
38
+ const MILLISECONDS_PER_DAY = 24 * 60 * 60 * 1000;
39
+ // Engine artefact tolerance when merging envelope segments (1 s seams).
40
+ const ENVELOPE_MERGE_TOLERANCE_MILLISECONDS = 1000;
41
+ /*
42
+ * ScheduleShiftUtil.getCoverageGaps ignores holes of five seconds or less
43
+ * (its contiguity tolerance for the engine's one-second seams). The trailing
44
+ * hole this file adds itself is held to the same threshold.
45
+ */
46
+ const TRAILING_GAP_TOLERANCE_MILLISECONDS = 5 * 1000;
47
+ const ENVELOPE_USER_ID = "00000000-0000-4000-8000-000000000000";
48
+ const EMAIL_LIKE_REGEX = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
49
+ class OnCallCalendarFeedUtil {
50
+ /*
51
+ * ---------------------------------------------------------------------
52
+ * Identity and links
53
+ * ---------------------------------------------------------------------
54
+ */
55
+ // oncall-<scheduleId>-<startEpochSeconds>@oneuptime (+ -<policyId> for variants).
56
+ static getShiftUid(shift) {
57
+ const base = `oncall-${shift.scheduleId}-${OnCallCalendarFeedUtil.epochSeconds(shift.start)}`;
58
+ const suffix = shift.policyVariantOf
59
+ ? `-${shift.policyVariantOf.policyId}`
60
+ : "";
61
+ return `${base}${suffix}@${OnCallCalendarFeedUtil.UID_DOMAIN}`;
62
+ }
63
+ static getGapUid(scheduleId, start) {
64
+ return `oncall-gap-${scheduleId}-${OnCallCalendarFeedUtil.epochSeconds(start)}@${OnCallCalendarFeedUtil.UID_DOMAIN}`;
65
+ }
66
+ static getScheduleUrl(dashboardUrl, projectId, scheduleId) {
67
+ return `${OnCallCalendarFeedUtil.trimTrailingSlash(dashboardUrl)}/${projectId}/on-call-duty/schedules/${scheduleId}`;
68
+ }
69
+ static getUserOverridesUrl(dashboardUrl, projectId) {
70
+ return `${OnCallCalendarFeedUtil.trimTrailingSlash(dashboardUrl)}/${projectId}/on-call-duty/user-overrides`;
71
+ }
72
+ static getTimeLogUrl(dashboardUrl, projectId) {
73
+ return `${OnCallCalendarFeedUtil.trimTrailingSlash(dashboardUrl)}/${projectId}/on-call-duty/user-time-logs`;
74
+ }
75
+ /*
76
+ * ---------------------------------------------------------------------
77
+ * Calendar header
78
+ * ---------------------------------------------------------------------
79
+ */
80
+ static buildCalendarName(options) {
81
+ const personal = OnCallCalendarFeedUtil.PERSONAL_CALENDAR_NAME;
82
+ if (options.kind === OnCallCalendarFeedKind.Schedule) {
83
+ const name = OnCallCalendarFeedUtil.cleanName(options.scheduleName) ||
84
+ "On-Call Schedule";
85
+ return {
86
+ name,
87
+ displayName: OnCallCalendarFeedUtil.truncateName(name, OnCallCalendarFeedUtil.MAX_SCHEDULE_CALENDAR_NAME_LENGTH),
88
+ };
89
+ }
90
+ if (options.kind === OnCallCalendarFeedKind.Project) {
91
+ const project = OnCallCalendarFeedUtil.cleanName(options.projectName) || "Project";
92
+ const name = `${personal} · ${project}`;
93
+ return { name, displayName: name };
94
+ }
95
+ const filter = OnCallCalendarFeedUtil.cleanName(options.filterScheduleName);
96
+ const name = filter ? `${personal} · ${filter}` : personal;
97
+ return { name, displayName: name };
98
+ }
99
+ static buildCalendarDescription(options) {
100
+ var _a;
101
+ let intro;
102
+ if (options.kind === OnCallCalendarFeedKind.Schedule) {
103
+ intro = `Everyone's on-call shifts on ${OnCallCalendarFeedUtil.cleanName(options.scheduleName) ||
104
+ "this schedule"} from OneUptime.`;
105
+ }
106
+ else if (options.kind === OnCallCalendarFeedKind.Project) {
107
+ intro = `Everyone's on-call shifts across ${OnCallCalendarFeedUtil.cleanName(options.projectName) || "this project"} from OneUptime.`;
108
+ }
109
+ else {
110
+ const filter = OnCallCalendarFeedUtil.cleanName(options.filterScheduleName);
111
+ intro = filter
112
+ ? `Your on-call shifts on ${filter} from OneUptime.`
113
+ : "Your on-call shifts from OneUptime.";
114
+ }
115
+ const notes = ((_a = options.notes) !== null && _a !== void 0 ? _a : [])
116
+ .map((note) => {
117
+ return note.trim();
118
+ })
119
+ .filter((note) => {
120
+ return note !== "";
121
+ });
122
+ return [intro, OnCallCalendarFeedUtil.REFRESH_CAVEAT, ...notes].join(" ");
123
+ }
124
+ static buildCalendarHeader(options) {
125
+ const names = OnCallCalendarFeedUtil.buildCalendarName(options);
126
+ const header = {
127
+ productId: OnCallCalendarFeedUtil.PRODUCT_ID,
128
+ name: names.name,
129
+ displayName: names.displayName,
130
+ description: OnCallCalendarFeedUtil.buildCalendarDescription(options),
131
+ timezone: OnCallCalendarFeedUtil.isValidTimezone(options.timezone)
132
+ ? options.timezone
133
+ : Timezone.UTC,
134
+ refreshInterval: OnCallCalendarFeedUtil.REFRESH_INTERVAL,
135
+ };
136
+ if (options.lastModifiedAt !== undefined) {
137
+ header.lastModified = options.lastModifiedAt;
138
+ }
139
+ return header;
140
+ }
141
+ /*
142
+ * ---------------------------------------------------------------------
143
+ * Shift -> VEVENT
144
+ * ---------------------------------------------------------------------
145
+ */
146
+ /*
147
+ * How a person is named in this feed. The personal feed shows whatever the
148
+ * materializer resolved (an email fallback is fine — it is the subscriber's
149
+ * own data); the shared schedule/project feeds never show an email.
150
+ */
151
+ static getDisplayName(name, kind) {
152
+ const trimmed = (name !== null && name !== void 0 ? name : "").trim();
153
+ if (trimmed === "") {
154
+ return OnCallCalendarFeedUtil.FALLBACK_USER_NAME;
155
+ }
156
+ if (kind !== OnCallCalendarFeedKind.Personal &&
157
+ EMAIL_LIKE_REGEX.test(trimmed)) {
158
+ return OnCallCalendarFeedUtil.FALLBACK_USER_NAME;
159
+ }
160
+ return trimmed;
161
+ }
162
+ static buildSummary(shift, kind) {
163
+ const scheduleName = OnCallCalendarFeedUtil.cleanName(shift.scheduleName) || "Schedule";
164
+ let summary = `On-call · ${scheduleName}`;
165
+ const distinctPolicies = OnCallCalendarFeedUtil.getDistinctPolicies(shift.policies);
166
+ if (shift.policyVariantOf) {
167
+ summary += ` · ${shift.policyVariantOf.policyName}`;
168
+ }
169
+ else if (kind === OnCallCalendarFeedKind.Personal &&
170
+ distinctPolicies.length === 1) {
171
+ summary += ` · ${distinctPolicies[0].policyName}`;
172
+ }
173
+ if (shift.override) {
174
+ summary += ` (covering for ${OnCallCalendarFeedUtil.getDisplayName(shift.override.originalUserName, kind)})`;
175
+ }
176
+ if (kind === OnCallCalendarFeedKind.Personal) {
177
+ return summary;
178
+ }
179
+ return `${OnCallCalendarFeedUtil.getDisplayName(shift.userName, kind)} · ${summary}`;
180
+ }
181
+ static buildDescription(shift, context, allShifts) {
182
+ const kind = context.kind;
183
+ const lines = [];
184
+ const userName = OnCallCalendarFeedUtil.getDisplayName(shift.userName, kind);
185
+ const originalUserName = shift.override
186
+ ? OnCallCalendarFeedUtil.getDisplayName(shift.override.originalUserName, kind)
187
+ : null;
188
+ lines.push(`Who: ${userName}${originalUserName ? `, covering for ${originalUserName}` : ""}`);
189
+ const scheduleZone = OnCallCalendarFeedUtil.getScheduleZone(shift);
190
+ const isLegacyZone = !OnCallCalendarFeedUtil.isValidTimezone(shift.scheduleTimezone);
191
+ lines.push(`Schedule: ${OnCallCalendarFeedUtil.cleanName(shift.scheduleName) || "Schedule"} (${isLegacyZone
192
+ ? OnCallCalendarFeedUtil.LEGACY_TIMEZONE_NOTE
193
+ : scheduleZone})`);
194
+ if (shift.layerName) {
195
+ lines.push(`Layer: ${shift.layerName}`);
196
+ }
197
+ lines.push(...OnCallCalendarFeedUtil.buildShiftTimeLines(shift, scheduleZone, isLegacyZone, context.viewerTimezone));
198
+ const distinctPolicies = OnCallCalendarFeedUtil.getDistinctPolicies(shift.policies);
199
+ // A variant pages through ONE policy, not the schedule's whole attachment set.
200
+ const pagingPolicies = OnCallCalendarFeedUtil.getPagingPolicies(shift);
201
+ if (pagingPolicies.length === 0) {
202
+ lines.push(OnCallCalendarFeedUtil.NO_POLICY_LINE);
203
+ }
204
+ else {
205
+ const label = kind === OnCallCalendarFeedKind.Personal
206
+ ? "Pages you via"
207
+ : "Pages via";
208
+ lines.push(`${label}: ${OnCallCalendarFeedUtil.describePolicies(pagingPolicies)}`);
209
+ }
210
+ if (shift.override) {
211
+ const scope = shift.override.onCallDutyPolicyId
212
+ ? `scoped to ${OnCallCalendarFeedUtil.findPolicyName(distinctPolicies, shift.override.onCallDutyPolicyId)}`
213
+ : "global override";
214
+ lines.push(`Override: ${originalUserName} → ${userName} from ${OnCallCalendarFeedUtil.formatInZone(shift.override.overrideStartsAt, scheduleZone)} to ${OnCallCalendarFeedUtil.formatInZone(shift.override.overrideEndsAt, scheduleZone)} (${scope})`);
215
+ }
216
+ if (shift.policyVariantOf) {
217
+ const insteadOf = originalUserName !== null && originalUserName !== void 0 ? originalUserName : "the rostered user";
218
+ lines.push(kind === OnCallCalendarFeedKind.Personal
219
+ ? `For ${shift.policyVariantOf.policyName} you are paged instead of ${insteadOf} because of a policy-specific override.`
220
+ : `For ${shift.policyVariantOf.policyName}, ${userName} is paged instead of ${insteadOf} because of a policy-specific override.`);
221
+ }
222
+ else {
223
+ /*
224
+ * A pre-built index (every render builds one) beats re-scanning the
225
+ * whole shift array for every shift, which is quadratic at feed sizes
226
+ * near MAX_EVENTS.
227
+ */
228
+ const variantIndex = context.variantIndex !== undefined
229
+ ? context.variantIndex
230
+ : allShifts
231
+ ? OnCallCalendarFeedUtil.buildVariantIndex(allShifts)
232
+ : null;
233
+ if (variantIndex) {
234
+ lines.push(...OnCallCalendarFeedUtil.buildVariantMirrorLines(shift, variantIndex, kind, scheduleZone));
235
+ }
236
+ }
237
+ if (shift.isPast) {
238
+ lines.push(`${OnCallCalendarFeedUtil.PAST_SHIFT_LINE} ${OnCallCalendarFeedUtil.getTimeLogUrl(context.dashboardUrl, shift.projectId)}`);
239
+ }
240
+ lines.push(`Need cover? ${OnCallCalendarFeedUtil.getUserOverridesUrl(context.dashboardUrl, shift.projectId)}`);
241
+ lines.push(OnCallCalendarFeedUtil.REFRESH_LINE);
242
+ return lines.join("\n");
243
+ }
244
+ static shiftToEvent(shift, context, allShifts) {
245
+ return {
246
+ uid: OnCallCalendarFeedUtil.getShiftUid(shift),
247
+ dtStamp: shift.lastModifiedAt,
248
+ lastModified: shift.lastModifiedAt,
249
+ sequence: OnCallCalendarFeedUtil.toSequence(shift.shiftConfigVersion),
250
+ start: shift.start,
251
+ end: shift.end,
252
+ summary: OnCallCalendarFeedUtil.buildSummary(shift, context.kind),
253
+ description: OnCallCalendarFeedUtil.buildDescription(shift, context, allShifts),
254
+ url: OnCallCalendarFeedUtil.getScheduleUrl(context.dashboardUrl, shift.projectId, shift.scheduleId),
255
+ status: ICalendarEventStatus.Confirmed,
256
+ transparency: ICalendarTransparency.Transparent,
257
+ categories: [OnCallCalendarFeedUtil.CATEGORY],
258
+ };
259
+ }
260
+ /*
261
+ * One VEVENT per shift, in deterministic (start, schedule, key) order.
262
+ *
263
+ * `contextShifts` are shifts that do NOT become events but may explain the
264
+ * ones that do (the personal feed's pre-filter list); it defaults to the
265
+ * rendered shifts.
266
+ */
267
+ static shiftsToEvents(shifts, context, contextShifts) {
268
+ const sorted = MaterializedShiftUtil.sortByStart(shifts);
269
+ const eventContext = Object.assign(Object.assign({}, context), { variantIndex: OnCallCalendarFeedUtil.buildVariantIndex(contextShifts !== null && contextShifts !== void 0 ? contextShifts : sorted) });
270
+ return sorted.map((shift) => {
271
+ return OnCallCalendarFeedUtil.shiftToEvent(shift, eventContext);
272
+ });
273
+ }
274
+ /*
275
+ * ---------------------------------------------------------------------
276
+ * Window
277
+ * ---------------------------------------------------------------------
278
+ */
279
+ // Shifts that overlap [feedStart, feedEnd), unclipped, sorted.
280
+ static filterShiftsToWindow(shifts, feedStart, feedEnd) {
281
+ return MaterializedShiftUtil.sortByStart(shifts).filter((shift) => {
282
+ return (shift.start.getTime() < feedEnd.getTime() &&
283
+ shift.end.getTime() > feedStart.getTime());
284
+ });
285
+ }
286
+ /*
287
+ * Keep the feed under maxEvents by cutting whole UTC days off the END of
288
+ * the window (the nearest future matters most). When even the first day
289
+ * holds more than maxEvents shifts, the cut lands on the first excluded
290
+ * shift's start instead.
291
+ */
292
+ static shrinkWindowToFit(input) {
293
+ const maxEvents = input.maxEvents !== undefined && input.maxEvents > 0
294
+ ? Math.floor(input.maxEvents)
295
+ : MAX_EVENTS;
296
+ const inWindow = OnCallCalendarFeedUtil.filterShiftsToWindow(input.shifts, input.feedStart, input.feedEnd);
297
+ if (inWindow.length <= maxEvents) {
298
+ return {
299
+ shifts: inWindow,
300
+ feedEnd: input.feedEnd,
301
+ truncated: false,
302
+ daysDropped: 0,
303
+ };
304
+ }
305
+ const firstExcluded = inWindow[maxEvents];
306
+ let newFeedEnd = new Date(CalendarFeedWindow.startOfUtcDay(firstExcluded.start));
307
+ if (newFeedEnd.getTime() <= input.feedStart.getTime()) {
308
+ newFeedEnd = new Date(firstExcluded.start.getTime());
309
+ }
310
+ let kept = inWindow.filter((shift) => {
311
+ return shift.start.getTime() < newFeedEnd.getTime();
312
+ });
313
+ /*
314
+ * Shifts that started before the window (in progress at feedStart) all
315
+ * share "start < newFeedEnd"; if there are more than maxEvents of those
316
+ * no cut point exists and a hard slice is the only option left.
317
+ */
318
+ if (kept.length > maxEvents) {
319
+ kept = kept.slice(0, maxEvents);
320
+ }
321
+ const daysDropped = Math.max(0, Math.ceil((input.feedEnd.getTime() - newFeedEnd.getTime()) / MILLISECONDS_PER_DAY));
322
+ return { shifts: kept, feedEnd: newFeedEnd, truncated: true, daysDropped };
323
+ }
324
+ /*
325
+ * ---------------------------------------------------------------------
326
+ * Coverage gaps
327
+ * ---------------------------------------------------------------------
328
+ */
329
+ /*
330
+ * Where the schedule's layers INTEND to cover, regardless of who (or
331
+ * whether anyone) is assigned and of when the layer starts: each layer is
332
+ * expanded with one synthetic user from the window start, so the result is
333
+ * exactly its restriction windows — the whole window for an unrestricted
334
+ * layer, Mon-Fri 09:00-17:00 for a business-hours layer. A coverage hole
335
+ * is only worth an event when it falls inside this envelope.
336
+ */
337
+ static computeCoverageEnvelope(input) {
338
+ const layerUtil = new LayerUtil();
339
+ const segments = [];
340
+ let truncated = false;
341
+ const syntheticUser = new User();
342
+ syntheticUser.id = new ObjectID(ENVELOPE_USER_ID);
343
+ for (const layer of input.layers) {
344
+ const result = layerUtil.getEventsWithMeta({
345
+ users: [syntheticUser],
346
+ startDateTimeOfLayer: input.windowStart,
347
+ restrictionTimes: layer.restrictionTimes,
348
+ handOffTime: layer.handOffTime,
349
+ rotation: layer.rotation,
350
+ timezone: layer.timezone,
351
+ calendarStartDate: input.windowStart,
352
+ calendarEndDate: input.windowEnd,
353
+ }, input.maxSimulationIterations !== undefined
354
+ ? { maxSimulationIterations: input.maxSimulationIterations }
355
+ : undefined);
356
+ truncated = truncated || result.truncated;
357
+ for (const event of result.events) {
358
+ segments.push({ start: event.start, end: event.end });
359
+ }
360
+ }
361
+ /*
362
+ * The engine's 1-second seams (09:00:01 starts) would otherwise leak into
363
+ * gap events; normalise them exactly as the shifts themselves are.
364
+ */
365
+ return {
366
+ segments: OnCallCalendarFeedUtil.mergeSegments(ShiftSeamUtil.normalizeSeams(segments), ENVELOPE_MERGE_TOLERANCE_MILLISECONDS),
367
+ truncated,
368
+ };
369
+ }
370
+ /*
371
+ * "No coverage" events: every hole before, between and after the shifts
372
+ * inside [feedStart, feedEnd) that intersects the envelope, clipped to the
373
+ * envelope, merged, at least minimumGapSeconds long, oldest first, capped at
374
+ * maxGapEvents. Off-hours of a business-hours schedule are outside the
375
+ * envelope and are therefore never emitted.
376
+ */
377
+ static buildCoverageGapEvents(input) {
378
+ var _a;
379
+ const maxGapEvents = input.maxGapEvents !== undefined && input.maxGapEvents >= 0
380
+ ? Math.floor(input.maxGapEvents)
381
+ : MAX_GAP_EVENTS;
382
+ const minimumGapMilliseconds = Math.max(0, (_a = input.minimumGapSeconds) !== null && _a !== void 0 ? _a : 0) * 1000;
383
+ const shifts = [...input.shifts]
384
+ .sort((a, b) => {
385
+ return a.start.getTime() - b.start.getTime();
386
+ })
387
+ .map((segment) => {
388
+ return {
389
+ userId: "",
390
+ start: segment.start,
391
+ end: segment.end,
392
+ coverageSeconds: OneUptimeDate.getDifferenceInSeconds(segment.end, segment.start),
393
+ };
394
+ });
395
+ const rawGaps = ScheduleShiftUtil.getCoverageGaps(shifts, input.feedStart, input.feedEnd);
396
+ /*
397
+ * getCoverageGaps (shared with the dashboard's coverage view) treats the
398
+ * window end as an arbitrary cut and never reports the stretch after the
399
+ * last shift. For a feed the window end IS the horizon the envelope was
400
+ * computed for, so a rotation that stops mid-window (every user removed
401
+ * after a date, a layer whose users run out) must show its tail as "No
402
+ * coverage" too. The envelope intersection below still clips it to the
403
+ * hours a layer intended to cover, so a business-hours schedule whose last
404
+ * shift ends on Friday still emits nothing for the weekend.
405
+ */
406
+ if (shifts.length > 0) {
407
+ const coveredUntil = shifts.reduce((max, shift) => {
408
+ return Math.max(max, shift.end.getTime());
409
+ }, shifts[0].end.getTime());
410
+ if (input.feedEnd.getTime() - coveredUntil >
411
+ TRAILING_GAP_TOLERANCE_MILLISECONDS) {
412
+ rawGaps.push({ start: new Date(coveredUntil), end: input.feedEnd });
413
+ }
414
+ }
415
+ const envelope = OnCallCalendarFeedUtil.mergeSegments(input.envelope
416
+ .map((segment) => {
417
+ return {
418
+ start: new Date(Math.max(segment.start.getTime(), input.feedStart.getTime())),
419
+ end: new Date(Math.min(segment.end.getTime(), input.feedEnd.getTime())),
420
+ };
421
+ })
422
+ .filter((segment) => {
423
+ return segment.end.getTime() > segment.start.getTime();
424
+ }), ENVELOPE_MERGE_TOLERANCE_MILLISECONDS);
425
+ const pieces = [];
426
+ for (const gap of rawGaps) {
427
+ for (const intended of envelope) {
428
+ const start = Math.max(gap.start.getTime(), intended.start.getTime());
429
+ const end = Math.min(gap.end.getTime(), intended.end.getTime());
430
+ if (end > start) {
431
+ pieces.push({ start: new Date(start), end: new Date(end) });
432
+ }
433
+ }
434
+ }
435
+ const gaps = OnCallCalendarFeedUtil.mergeSegments(pieces, 0).filter((gap) => {
436
+ return gap.end.getTime() - gap.start.getTime() >= minimumGapMilliseconds;
437
+ });
438
+ const truncated = gaps.length > maxGapEvents;
439
+ const kept = gaps.slice(0, maxGapEvents);
440
+ return {
441
+ events: kept.map((gap) => {
442
+ return OnCallCalendarFeedUtil.gapToEvent(gap, input);
443
+ }),
444
+ gaps: kept,
445
+ truncated,
446
+ };
447
+ }
448
+ static gapToEvent(gap, input) {
449
+ const scheduleName = OnCallCalendarFeedUtil.cleanName(input.scheduleName) || "Schedule";
450
+ const url = OnCallCalendarFeedUtil.getScheduleUrl(input.dashboardUrl, input.projectId, input.scheduleId);
451
+ return {
452
+ uid: OnCallCalendarFeedUtil.getGapUid(input.scheduleId, gap.start),
453
+ dtStamp: input.lastModifiedAt,
454
+ lastModified: input.lastModifiedAt,
455
+ sequence: OnCallCalendarFeedUtil.toSequence(input.shiftConfigVersion),
456
+ start: gap.start,
457
+ end: gap.end,
458
+ summary: `No coverage · ${scheduleName}`,
459
+ description: [
460
+ `Nobody is on call for ${scheduleName} during this time, although a layer is meant to cover it.`,
461
+ `Fix the rotation: ${url}`,
462
+ OnCallCalendarFeedUtil.REFRESH_LINE,
463
+ ].join("\n"),
464
+ url,
465
+ status: ICalendarEventStatus.Confirmed,
466
+ transparency: ICalendarTransparency.Transparent,
467
+ categories: [OnCallCalendarFeedUtil.CATEGORY],
468
+ };
469
+ }
470
+ /*
471
+ * ---------------------------------------------------------------------
472
+ * Whole documents
473
+ * ---------------------------------------------------------------------
474
+ */
475
+ static buildDocument(input) {
476
+ var _a, _b, _c;
477
+ const context = {
478
+ kind: input.kind,
479
+ dashboardUrl: input.dashboardUrl,
480
+ viewerTimezone: input.viewerTimezone,
481
+ };
482
+ const events = [
483
+ ...OnCallCalendarFeedUtil.shiftsToEvents(input.shifts, context, input.contextShifts),
484
+ ...((_a = input.gapEvents) !== null && _a !== void 0 ? _a : []),
485
+ ].sort((a, b) => {
486
+ const byStart = a.start.getTime() - b.start.getTime();
487
+ if (byStart !== 0) {
488
+ return byStart;
489
+ }
490
+ if (a.uid === b.uid) {
491
+ return 0;
492
+ }
493
+ return a.uid < b.uid ? -1 : 1;
494
+ });
495
+ const lastModifiedAt = (_b = input.lastModifiedAt) !== null && _b !== void 0 ? _b : OnCallCalendarFeedUtil.getLatestModification(input.shifts, input.gapEvents);
496
+ const calendar = OnCallCalendarFeedUtil.buildCalendarHeader({
497
+ kind: input.kind,
498
+ scheduleName: input.scheduleName,
499
+ projectName: input.projectName,
500
+ filterScheduleName: input.filterScheduleName,
501
+ timezone: (_c = input.calendarTimezone) !== null && _c !== void 0 ? _c : input.viewerTimezone,
502
+ lastModifiedAt: lastModifiedAt !== null && lastModifiedAt !== void 0 ? lastModifiedAt : undefined,
503
+ notes: input.notes,
504
+ });
505
+ return { calendar, events };
506
+ }
507
+ static render(input) {
508
+ var _a;
509
+ const document = OnCallCalendarFeedUtil.buildDocument(input);
510
+ return {
511
+ body: ICalendar.serialize(document),
512
+ eventCount: document.events.length,
513
+ lastModifiedAt: (_a = document.calendar.lastModified) !== null && _a !== void 0 ? _a : null,
514
+ };
515
+ }
516
+ /*
517
+ * The empty VCALENDAR every "200 but nothing to show" case serves (feed
518
+ * disabled, rotated token inside its grace, project below plan, no eligible
519
+ * schedule). X-WR-CALDESC carries the reason so a subscriber who looks can
520
+ * tell why their calendar went blank.
521
+ */
522
+ static renderEmpty(input) {
523
+ const calendar = OnCallCalendarFeedUtil.buildCalendarHeader({
524
+ kind: input.kind,
525
+ scheduleName: input.scheduleName,
526
+ projectName: input.projectName,
527
+ filterScheduleName: input.filterScheduleName,
528
+ timezone: input.timezone,
529
+ notes: [input.reason],
530
+ });
531
+ return ICalendar.serialize({ calendar, events: [] });
532
+ }
533
+ /*
534
+ * ---------------------------------------------------------------------
535
+ * Helpers
536
+ * ---------------------------------------------------------------------
537
+ */
538
+ static isValidTimezone(timezone) {
539
+ return (typeof timezone === "string" &&
540
+ timezone.trim() !== "" &&
541
+ moment.tz.zone(timezone) !== null);
542
+ }
543
+ // The zone a shift's wall clock is rendered in; legacy schedules use UTC.
544
+ static getScheduleZone(shift) {
545
+ return OnCallCalendarFeedUtil.isValidTimezone(shift.scheduleTimezone)
546
+ ? shift.scheduleTimezone
547
+ : Timezone.UTC;
548
+ }
549
+ static truncateName(name, maxLength) {
550
+ const characters = Array.from(name);
551
+ if (characters.length <= maxLength) {
552
+ return name;
553
+ }
554
+ return `${characters
555
+ .slice(0, Math.max(0, maxLength - 1))
556
+ .join("")
557
+ .trimEnd()}…`;
558
+ }
559
+ // Sort by start and merge segments that overlap or sit within `toleranceMs`.
560
+ static mergeSegments(segments, toleranceMilliseconds) {
561
+ const sorted = segments
562
+ .filter((segment) => {
563
+ return segment.end.getTime() > segment.start.getTime();
564
+ })
565
+ .map((segment) => {
566
+ return {
567
+ start: new Date(segment.start.getTime()),
568
+ end: new Date(segment.end.getTime()),
569
+ };
570
+ })
571
+ .sort((a, b) => {
572
+ return a.start.getTime() - b.start.getTime();
573
+ });
574
+ const merged = [];
575
+ for (const segment of sorted) {
576
+ const last = merged[merged.length - 1];
577
+ if (last &&
578
+ segment.start.getTime() <= last.end.getTime() + toleranceMilliseconds) {
579
+ if (segment.end.getTime() > last.end.getTime()) {
580
+ last.end = segment.end;
581
+ }
582
+ continue;
583
+ }
584
+ merged.push(segment);
585
+ }
586
+ return merged;
587
+ }
588
+ static getDistinctPolicies(policies) {
589
+ const seen = new Set();
590
+ const distinct = [];
591
+ for (const policy of policies) {
592
+ if (seen.has(policy.policyId)) {
593
+ continue;
594
+ }
595
+ seen.add(policy.policyId);
596
+ distinct.push(policy);
597
+ }
598
+ return distinct.sort((a, b) => {
599
+ return OnCallCalendarFeedUtil.compareStrings(a.policyName, b.policyName);
600
+ });
601
+ }
602
+ // "Payments › Primary (step 1); Billing › Backup (step 2)"
603
+ static describePolicies(policies) {
604
+ return [...policies]
605
+ .sort((a, b) => {
606
+ return (OnCallCalendarFeedUtil.compareStrings(a.policyName, b.policyName) ||
607
+ a.ruleOrder - b.ruleOrder ||
608
+ OnCallCalendarFeedUtil.compareStrings(a.ruleName, b.ruleName));
609
+ })
610
+ .map((policy) => {
611
+ return `${policy.policyName} › ${policy.ruleName} (step ${policy.ruleOrder})`;
612
+ })
613
+ .join("; ");
614
+ }
615
+ static buildShiftTimeLines(shift, scheduleZone, isLegacyZone, viewerTimezone) {
616
+ const zones = [scheduleZone];
617
+ if (scheduleZone !== Timezone.UTC) {
618
+ zones.push(Timezone.UTC);
619
+ }
620
+ if (OnCallCalendarFeedUtil.isValidTimezone(viewerTimezone) &&
621
+ !zones.includes(viewerTimezone)) {
622
+ zones.push(viewerTimezone);
623
+ }
624
+ const starts = OnCallCalendarFeedUtil.formatInZones(shift.start, zones);
625
+ const ends = OnCallCalendarFeedUtil.formatInZones(shift.end, zones);
626
+ const lines = [];
627
+ for (let i = 0; i < zones.length; i++) {
628
+ const zone = zones[i];
629
+ const range = `${starts[i]} → ${ends[i]}`;
630
+ if (i === 0) {
631
+ lines.push(`Shift: ${range} (${isLegacyZone
632
+ ? `UTC — ${OnCallCalendarFeedUtil.LEGACY_TIMEZONE_NOTE}`
633
+ : `${zone} — schedule zone`})`);
634
+ continue;
635
+ }
636
+ const label = zone === Timezone.UTC && zone !== viewerTimezone
637
+ ? "UTC"
638
+ : `${zone} (your zone)`;
639
+ lines.push(`Shift in ${label}: ${range}`);
640
+ }
641
+ return lines;
642
+ }
643
+ /*
644
+ * Mirror lines on a base shift for every policy variant that takes part of
645
+ * it over.
646
+ *
647
+ * Matching is (same schedule) + (time overlap) + (different person). The
648
+ * variant's own `policyVariantOf.globalUserId` is deliberately NOT required
649
+ * to equal this shift's user: the materializer records only the FIRST
650
+ * overlapping base user, so a variant spanning a handover would otherwise
651
+ * inform the first person and leave the second — whose shift the variant
652
+ * also takes over — reading "Pages you via <policy>" for a window in which
653
+ * somebody else is paged for it.
654
+ *
655
+ * The quoted window is clipped to the overlap so the sentence names exactly
656
+ * the part of THIS shift that is taken over.
657
+ */
658
+ static buildVariantMirrorLines(shift, variantIndex, kind, scheduleZone) {
659
+ const candidates = variantIndex.get(shift.scheduleId);
660
+ if (!candidates || candidates.length === 0) {
661
+ return [];
662
+ }
663
+ const lines = [];
664
+ for (const other of candidates) {
665
+ if (!other.policyVariantOf || other.userId === shift.userId) {
666
+ continue;
667
+ }
668
+ const overlapStart = Math.max(other.start.getTime(), shift.start.getTime());
669
+ const overlapEnd = Math.min(other.end.getTime(), shift.end.getTime());
670
+ if (overlapStart >= overlapEnd) {
671
+ continue;
672
+ }
673
+ lines.push(`For ${other.policyVariantOf.policyName}, ${OnCallCalendarFeedUtil.getDisplayName(other.userName, kind)} is paged instead from ${OnCallCalendarFeedUtil.formatInZone(new Date(overlapStart), scheduleZone)} to ${OnCallCalendarFeedUtil.formatInZone(new Date(overlapEnd), scheduleZone)}.`);
674
+ }
675
+ return lines;
676
+ }
677
+ /*
678
+ * Bucket the policy-variant shifts of a feed by schedule id, sorted inside
679
+ * each bucket so the DESCRIPTION lines are deterministic (the body cache and
680
+ * the ETag depend on it). Returns null when there is not a single variant —
681
+ * the overwhelmingly common case — so the mirror-line pass is skipped
682
+ * outright instead of scanning every shift for every shift.
683
+ */
684
+ static buildVariantIndex(shifts) {
685
+ let index = null;
686
+ for (const shift of shifts) {
687
+ if (!shift.policyVariantOf) {
688
+ continue;
689
+ }
690
+ if (!index) {
691
+ index = new Map();
692
+ }
693
+ const bucket = index.get(shift.scheduleId);
694
+ if (bucket) {
695
+ bucket.push(shift);
696
+ }
697
+ else {
698
+ index.set(shift.scheduleId, [shift]);
699
+ }
700
+ }
701
+ if (!index) {
702
+ return null;
703
+ }
704
+ for (const bucket of index.values()) {
705
+ bucket.sort((a, b) => {
706
+ return (a.start.getTime() - b.start.getTime() ||
707
+ a.end.getTime() - b.end.getTime() ||
708
+ OnCallCalendarFeedUtil.compareStrings(a.policyVariantOf.policyId, b.policyVariantOf.policyId) ||
709
+ OnCallCalendarFeedUtil.compareStrings(a.userId, b.userId));
710
+ });
711
+ }
712
+ return index;
713
+ }
714
+ /*
715
+ * The policies this shift actually pages through. A policy-variant shift
716
+ * exists for exactly one policy — the schedule's other policies still page
717
+ * the rostered user — but the materializer stamps every shift of a schedule
718
+ * with its full attachment set, so listing them all on a variant would
719
+ * contradict the sentence directly below it. Falls back to the full list if
720
+ * the variant's policy is somehow not among them, so a shift never claims to
721
+ * page nobody.
722
+ */
723
+ static getPagingPolicies(shift) {
724
+ if (!shift.policyVariantOf) {
725
+ return shift.policies;
726
+ }
727
+ const variantPolicyId = shift.policyVariantOf.policyId;
728
+ const scoped = shift.policies.filter((policy) => {
729
+ return policy.policyId === variantPolicyId;
730
+ });
731
+ return scoped.length > 0 ? scoped : shift.policies;
732
+ }
733
+ static formatInZones(date, zones) {
734
+ return OneUptimeDate.getDateAsFormattedStringInMultipleTimezones({
735
+ date,
736
+ timezones: zones,
737
+ use12HourFormat: false,
738
+ }).split("\n");
739
+ }
740
+ // Same formatter as the shift lines, so every date in a DESCRIPTION matches.
741
+ static formatInZone(date, zone) {
742
+ var _a;
743
+ return (_a = OnCallCalendarFeedUtil.formatInZones(date, [zone])[0]) !== null && _a !== void 0 ? _a : "";
744
+ }
745
+ static findPolicyName(policies, policyId) {
746
+ const match = policies.find((policy) => {
747
+ return policy.policyId === policyId;
748
+ });
749
+ return match ? match.policyName : "an escalation policy";
750
+ }
751
+ static getLatestModification(shifts, gapEvents) {
752
+ let latest = null;
753
+ for (const shift of shifts) {
754
+ const time = shift.lastModifiedAt.getTime();
755
+ if (latest === null || time > latest) {
756
+ latest = time;
757
+ }
758
+ }
759
+ for (const event of gapEvents !== null && gapEvents !== void 0 ? gapEvents : []) {
760
+ const time = event.dtStamp.getTime();
761
+ if (latest === null || time > latest) {
762
+ latest = time;
763
+ }
764
+ }
765
+ return latest === null ? null : new Date(latest);
766
+ }
767
+ static toSequence(version) {
768
+ return Number.isFinite(version) ? Math.max(0, Math.floor(version)) : 0;
769
+ }
770
+ static epochSeconds(date) {
771
+ return Math.floor(date.getTime() / 1000);
772
+ }
773
+ static cleanName(name) {
774
+ return (name !== null && name !== void 0 ? name : "").replace(/\s+/g, " ").trim();
775
+ }
776
+ static trimTrailingSlash(url) {
777
+ return url.replace(/\/+$/, "");
778
+ }
779
+ static compareStrings(a, b) {
780
+ if (a === b) {
781
+ return 0;
782
+ }
783
+ return a < b ? -1 : 1;
784
+ }
785
+ }
786
+ OnCallCalendarFeedUtil.PRODUCT_ID = "-//OneUptime//On-Call Calendar Feed//EN";
787
+ OnCallCalendarFeedUtil.REFRESH_INTERVAL = "PT1H";
788
+ OnCallCalendarFeedUtil.UID_DOMAIN = "oneuptime";
789
+ OnCallCalendarFeedUtil.CATEGORY = "On-Call";
790
+ OnCallCalendarFeedUtil.PERSONAL_CALENDAR_NAME = "OneUptime On-Call";
791
+ // Confluence Team Calendars rejects longer calendar names.
792
+ OnCallCalendarFeedUtil.MAX_SCHEDULE_CALENDAR_NAME_LENGTH = 28;
793
+ OnCallCalendarFeedUtil.FALLBACK_USER_NAME = "Unnamed user";
794
+ OnCallCalendarFeedUtil.REFRESH_CAVEAT = "Calendar apps refresh subscribed feeds on their own schedule (Google Calendar every 12-24 h, Outlook on the web about every 3 h, Apple Calendar per its fetch setting), so recent changes can lag.";
795
+ OnCallCalendarFeedUtil.REFRESH_LINE = "Changes appear after your calendar app next refreshes (Google Calendar: up to 24 h).";
796
+ OnCallCalendarFeedUtil.PAST_SHIFT_LINE = "Past shifts reflect the current rotation, not who was actually paged — see the On-Call Time Log report.";
797
+ OnCallCalendarFeedUtil.NO_POLICY_LINE = "Not attached to any escalation policy, so it will not page anyone.";
798
+ OnCallCalendarFeedUtil.LEGACY_TIMEZONE_NOTE = "schedule has no timezone set (expanded in the server's zone, as paging does)";
799
+ export default OnCallCalendarFeedUtil;
800
+ //# sourceMappingURL=OnCallCalendarFeedUtil.js.map