@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.
- package/Models/DatabaseModels/DashboardDomain.ts +43 -0
- package/Models/DatabaseModels/Index.ts +11 -0
- package/Models/DatabaseModels/MonitorTemplate.ts +15 -3
- package/Models/DatabaseModels/OnCallDutyPolicySchedule.ts +43 -0
- package/Models/DatabaseModels/OnCallDutyPolicyScheduleCalendarFeed.ts +662 -0
- package/Models/DatabaseModels/ProjectOnCallCalendarFeed.ts +584 -0
- package/Models/DatabaseModels/StatusPageDomain.ts +43 -0
- package/Models/DatabaseModels/UserOnCallCalendarFeed.ts +546 -0
- package/Models/DatabaseModels/UserOnCallShiftReminder.ts +231 -0
- package/Models/DatabaseModels/UserOnCallShiftReminderLog.ts +331 -0
- package/Server/API/DashboardDomainAPI.ts +74 -0
- package/Server/API/MicrosoftTeamsAPI.ts +81 -180
- package/Server/API/OnCallCalendarAPI.ts +2194 -0
- package/Server/API/StatusPageDomainAPI.ts +74 -0
- package/Server/EnvironmentConfig.ts +138 -0
- package/Server/Infrastructure/OnCallCalendarFeedCache.ts +1050 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/1790200000000-AllowNullMonitorNameOnMonitorTemplate.ts +43 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/1790300000000-AddCertificateReissueRequestedAtToDomains.ts +40 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/1790400000000-AddOnCallCalendarFeeds.ts +253 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/Index.ts +6 -0
- package/Server/Middleware/OnCallCalendarFeedRateLimit.ts +487 -0
- package/Server/Middleware/ProjectAuthorization.ts +6 -1
- package/Server/Services/DashboardDomainService.ts +131 -0
- package/Server/Services/Index.ts +12 -0
- package/Server/Services/LogPipelineProcessorService.ts +88 -0
- package/Server/Services/MonitorTemplateService.ts +70 -11
- package/Server/Services/OnCallDutyPolicyEscalationRuleScheduleService.ts +32 -0
- package/Server/Services/OnCallDutyPolicyScheduleCalendarFeedService.ts +260 -0
- package/Server/Services/OnCallDutyPolicyScheduleLayerService.ts +70 -3
- package/Server/Services/OnCallDutyPolicyScheduleLayerUserService.ts +138 -3
- package/Server/Services/OnCallDutyPolicyScheduleService.ts +1465 -134
- package/Server/Services/OnCallDutyPolicyUserOverrideService.ts +167 -31
- package/Server/Services/ProjectOnCallCalendarFeedService.ts +192 -0
- package/Server/Services/StatusPageDomainService.ts +163 -3
- package/Server/Services/StatusPagePrivateUserSessionService.ts +197 -56
- package/Server/Services/TeamMemberService.ts +489 -1
- package/Server/Services/TeamService.ts +50 -0
- package/Server/Services/UserNotificationSettingService.ts +44 -1
- package/Server/Services/UserOnCallCalendarFeedService.ts +159 -0
- package/Server/Services/UserOnCallShiftReminderLogService.ts +151 -0
- package/Server/Services/UserOnCallShiftReminderService.ts +112 -0
- package/Server/Types/Domain.ts +17 -0
- package/Server/Utils/APIKey/AccessPermission.ts +24 -0
- package/Server/Utils/Greenlock/Greenlock.ts +156 -58
- package/Server/Utils/LogPipelineProcessorValidation.ts +112 -0
- package/Server/Utils/OnCall/CalendarFeedToken.ts +241 -0
- package/Server/Utils/OnCall/OnCallCalendarFeedRenderer.ts +1488 -0
- package/Server/Utils/OnCall/OnCallCalendarFeedUrls.ts +230 -0
- package/Server/Utils/OnCall/OnCallShiftChangeListeners.ts +227 -0
- package/Server/Utils/OnCall/OnCallShiftMaterializer.ts +1127 -0
- package/Server/Utils/OnCall/OnCallShiftReminderListener.ts +174 -0
- package/Server/Utils/OnCall/OnCallShiftReminderRunner.ts +2584 -0
- package/Server/Utils/Response.ts +134 -0
- package/Server/Utils/SecurityEvent/GoogleSecOps/GoogleSecOpsClient.ts +1094 -45
- package/Server/Utils/SecurityEvent/GoogleSecOps/GoogleSecOpsPoller.ts +153 -17
- package/Server/Utils/StartServer.ts +10 -0
- package/Server/Utils/Telemetry/AppMetrics.ts +51 -0
- package/Server/Utils/Workspace/MicrosoftTeams/Actions/Alert.ts +23 -11
- package/Server/Utils/Workspace/MicrosoftTeams/Actions/Auth.ts +0 -170
- package/Server/Utils/Workspace/MicrosoftTeams/Actions/Authorization.ts +158 -0
- package/Server/Utils/Workspace/MicrosoftTeams/Actions/Incident.ts +23 -11
- package/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeams.ts +35 -13
- package/Tests/App/Dashboard/MonitorTemplateColumnAndFacet.test.tsx +505 -0
- package/Tests/App/Dashboard/OnCallCalendarFeedLinks.test.tsx +229 -0
- package/Tests/App/Dashboard/OnCallCalendarFeedPlanGate.test.tsx +489 -0
- package/Tests/App/Dashboard/OnCallCalendarFeedSideMenus.test.tsx +149 -0
- package/Tests/App/Dashboard/OnCallCalendarFeedUtil.test.ts +855 -0
- package/Tests/App/Dashboard/OnCallPersonalCalendarFeed.test.tsx +1209 -0
- package/Tests/App/Dashboard/OnCallSharedCalendarFeedCard.test.tsx +1019 -0
- package/Tests/App/Dashboard/OnCallShiftRemindersAndUpcomingShifts.test.tsx +655 -0
- package/Tests/App/Dashboard/UserSettingsSetupChecklistModel.test.ts +73 -0
- package/Tests/App/StatusPage/StatusPageLoginCodeBootstrap.test.ts +106 -0
- package/Tests/App/StatusPage/StatusPageLoginCodeUtil.test.ts +84 -0
- package/Tests/App/StatusPage/StatusPageRedirectSafety.test.ts +119 -0
- package/Tests/Models/MonitorTemplateMonitorNameColumn.test.ts +258 -0
- package/Tests/Models/OnCallCalendarFeedModels.test.ts +642 -0
- package/Tests/Models/OnCallDutyPolicyScheduleCalendarFeed.test.ts +288 -0
- package/Tests/Models/OnCallDutyPolicyScheduleShiftConfigVersion.test.ts +88 -0
- package/Tests/Models/UserOnCallShiftReminderModels.test.ts +368 -0
- package/Tests/Server/API/CustomDomainReissueSslAPI.test.ts +469 -0
- package/Tests/Server/API/Helpers.ts +8 -0
- package/Tests/Server/API/MicrosoftTeamsBotMessagesEndpoint.test.ts +729 -0
- package/Tests/Server/API/MicrosoftTeamsBotTestEndpoint.test.ts +26 -0
- package/Tests/Server/API/MicrosoftTeamsLegacyWebhookRemoval.test.ts +402 -0
- package/Tests/Server/API/OnCallCalendarAPI.test.ts +4461 -0
- package/Tests/Server/EnvironmentConfigOnCallCalendarFeed.test.ts +357 -0
- package/Tests/Server/Infrastructure/OnCallCalendarFeedCache.test.ts +1589 -0
- package/Tests/Server/Infrastructure/Postgres/AddOnCallCalendarFeedsMigration.test.ts +369 -0
- package/Tests/Server/Middleware/OnCallCalendarFeedRateLimit.test.ts +1365 -0
- package/Tests/Server/Middleware/ProjectAuthorizationApiKeyMiddleware.test.ts +4 -0
- package/Tests/Server/Services/AddCertificateReissueRequestedAtToDomainsMigration.test.ts +287 -0
- package/Tests/Server/Services/CustomDomainCertificateReissue.test.ts +477 -0
- package/Tests/Server/Services/FeedRetentionConsistency.test.ts +70 -0
- package/Tests/Server/Services/LogPipelineProcessorSaveValidation.test.ts +249 -0
- package/Tests/Server/Services/MonitorTemplateServiceBulkSync.test.ts +549 -0
- package/Tests/Server/Services/MonitorTemplateServiceNetworkDeviceSync.test.ts +16 -3
- package/Tests/Server/Services/NetworkDeviceAutoImportRuleEngineService.test.ts +57 -0
- package/Tests/Server/Services/OnCallCalendarFeedServices.test.ts +1103 -0
- package/Tests/Server/Services/OnCallDutyPolicyScheduleResolver.test.ts +1037 -0
- package/Tests/Server/Services/OnCallShiftConfigPropagation.test.ts +1270 -0
- package/Tests/Server/Services/SecurityEventLastErrorColumnWidth.test.ts +151 -15
- package/Tests/Server/Services/StatusPagePrivateUserSessionService.test.ts +587 -0
- package/Tests/Server/Services/TeamDeleteMemberCleanup.test.ts +156 -0
- package/Tests/Server/Services/TeamMemberOnCallCleanup.test.ts +755 -0
- package/Tests/Server/Services/UserNotificationSettingShiftReminderDefaults.test.ts +267 -0
- package/Tests/Server/Services/UserOnCallShiftReminderServices.test.ts +583 -0
- package/Tests/Server/Types/Database/Permissions/ApiKeyFileAccess.test.ts +286 -0
- package/Tests/Server/Types/Database/Permissions/MonitorTemplateColumnAccess.test.ts +328 -0
- package/Tests/Server/Types/Database/Permissions/OnCallCalendarFeedPermissions.test.ts +254 -0
- package/Tests/Server/Types/Database/QueryHelperFindWithSameTextAndIsNull.test.ts +83 -0
- package/Tests/Server/Utils/AI/SRE/ConfidenceSignal.test.ts +377 -0
- package/Tests/Server/Utils/APIKey/AccessPermission.test.ts +16 -1
- package/Tests/Server/Utils/Greenlock/CertificateRenewalSchedule.test.ts +250 -0
- package/Tests/Server/Utils/LogPipelineProcessorValidation.test.ts +198 -0
- package/Tests/Server/Utils/OnCall/CalendarFeedToken.test.ts +467 -0
- package/Tests/Server/Utils/OnCall/OnCallCalendarFeedRenderer.test.ts +2142 -0
- package/Tests/Server/Utils/OnCall/OnCallCalendarFeedUrls.test.ts +278 -0
- package/Tests/Server/Utils/OnCall/OnCallResolverTestHarness.ts +440 -0
- package/Tests/Server/Utils/OnCall/OnCallShiftChangeListeners.test.ts +314 -0
- package/Tests/Server/Utils/OnCall/OnCallShiftMaterializer.test.ts +1502 -0
- package/Tests/Server/Utils/OnCall/OnCallShiftReminderListener.test.ts +491 -0
- package/Tests/Server/Utils/OnCall/OnCallShiftReminderRunner.test.ts +3088 -0
- package/Tests/Server/Utils/OnCall/OnCallShiftReminderTestHarness.ts +910 -0
- package/Tests/Server/Utils/ResponseSendCalendarResponse.test.ts +561 -0
- package/Tests/Server/Utils/SecurityEvent/ConnectorErrorMessage.test.ts +48 -9
- package/Tests/Server/Utils/SecurityEvent/GoogleSecOpsAuth.test.ts +726 -0
- package/Tests/Server/Utils/SecurityEvent/GoogleSecOpsClient.test.ts +74 -11
- package/Tests/Server/Utils/SecurityEvent/GoogleSecOpsErrorHandling.test.ts +1107 -0
- package/Tests/Server/Utils/SecurityEvent/GoogleSecOpsPoller.test.ts +20 -3
- package/Tests/Server/Utils/SecurityEvent/GoogleSecOpsPollerFailureTaxonomy.test.ts +51 -11
- package/Tests/Server/Utils/SecurityEvent/GoogleSecOpsPollerHardening.test.ts +1246 -0
- package/Tests/Server/Utils/SecurityEvent/GoogleSecOpsRequestContract.test.ts +961 -0
- package/Tests/Server/Utils/SecurityEvent/GoogleSecOpsResponseParsing.test.ts +653 -0
- package/Tests/Server/Utils/StartServerEncryptionSecretWarning.test.ts +64 -0
- package/Tests/Server/Utils/Telemetry/AppMetrics.test.ts +29 -0
- package/Tests/Server/Utils/Workspace/MicrosoftTeamsActionAuthorization.test.ts +880 -0
- package/Tests/Types/API/HTTPResponse.test.ts +175 -0
- package/Tests/Types/API/Route.test.ts +36 -4
- package/Tests/Types/Calendar/ICalendar.test.ts +488 -0
- package/Tests/Types/Database/DatabaseProperty.test.ts +171 -0
- package/Tests/Types/DateLocalShortDateTimeString.test.ts +518 -0
- package/Tests/Types/DateLocalTimeString.test.ts +259 -0
- package/Tests/Types/DateUserPrefers12HourFormat.test.ts +316 -0
- package/Tests/Types/NotificationSetting/NotificationSettingEventType.test.ts +53 -0
- package/Tests/Types/OnCallDutyPolicy/CalendarFeedTestFixtures.ts +310 -0
- package/Tests/Types/OnCallDutyPolicy/CalendarFeedWindow.test.ts +205 -0
- package/Tests/Types/OnCallDutyPolicy/LayerUtilLayerMetaAndIterationCap.test.ts +458 -0
- package/Tests/Types/OnCallDutyPolicy/MaterializedShift.test.ts +197 -0
- package/Tests/Types/OnCallDutyPolicy/OnCallCalendarFeedUtil.test.ts +1986 -0
- package/Tests/Types/OnCallDutyPolicy/ScheduleShiftUtilGroupKey.test.ts +678 -0
- package/Tests/Types/OnCallDutyPolicy/ShiftSeamUtil.test.ts +417 -0
- package/Tests/UI/Components/LogsHistogram.test.tsx +41 -2
- package/Tests/UI/Components/LogsViewerEmptyStateTimeRange.test.tsx +212 -0
- package/Tests/UI/Components/TelemetryChartClockFormat.test.tsx +359 -0
- package/Tests/UI/Components/TimeRangePickerClockFormat.test.tsx +422 -0
- package/Tests/UI/Components/TimeRangePickerDropdown.test.tsx +146 -8
- package/Tests/UI/MicrosoftTeams/MicrosoftTeamsMessagingEndpointGuidance.test.tsx +520 -0
- package/Tests/UI/Utils/Breadcrumb/fixtures/RealBreadcrumbTrails.ts +10 -0
- package/Tests/UI/Utils/Breadcrumb/fixtures/RealRoutePatterns.ts +2 -0
- package/Tests/UI/Utils/Navigation.test.ts +82 -0
- package/Tests/Utils/Array.test.ts +100 -0
- package/Tests/Utils/CertificateReissue.test.ts +225 -0
- package/Tests/Utils/Grok/Grok.test.ts +492 -0
- package/Tests/Utils/Monitor/NetworkDeviceMonitorTemplateUtil.test.ts +181 -13
- package/Types/API/Route.ts +20 -5
- package/Types/Calendar/ICalendar.ts +442 -0
- package/Types/Date.ts +143 -13
- package/Types/Email/EmailTemplateType.ts +2 -0
- package/Types/NotificationSetting/NotificationSettingEventType.ts +4 -0
- package/Types/OnCallDutyPolicy/CalendarFeedWindow.ts +140 -0
- package/Types/OnCallDutyPolicy/Layer.ts +190 -35
- package/Types/OnCallDutyPolicy/MaterializedShift.ts +274 -0
- package/Types/OnCallDutyPolicy/OnCallCalendarFeedUtil.ts +1415 -0
- package/Types/OnCallDutyPolicy/ScheduleShiftUtil.ts +159 -4
- package/Types/OnCallDutyPolicy/ShiftSeamUtil.ts +106 -0
- package/Types/Rum/SessionReplay.ts +62 -1
- package/UI/Components/Date/TimeRangePickerDropdown.tsx +9 -5
- package/UI/Components/LogsViewer/LogsViewer.tsx +12 -7
- package/UI/Components/LogsViewer/components/HistogramTooltip.tsx +18 -17
- package/UI/Components/LogsViewer/components/LogsAnalyticsView.tsx +21 -28
- package/UI/Components/LogsViewer/components/LogsHistogram.tsx +2 -4
- package/UI/Components/TelemetryViewer/components/TelemetryHistogram.tsx +2 -4
- package/UI/Components/TelemetryViewer/components/TelemetryHistogramTooltip.tsx +17 -16
- package/UI/Utils/Navigation.ts +42 -0
- package/Utils/Array.ts +37 -0
- package/Utils/CertificateReissue.ts +128 -0
- package/Utils/Grok/Grok.ts +461 -0
- package/Utils/Grok/GrokPatterns.ts +118 -0
- package/Utils/Monitor/NetworkDeviceMonitorTemplateUtil.ts +58 -6
- package/build/dist/Models/DatabaseModels/DashboardDomain.js +44 -0
- package/build/dist/Models/DatabaseModels/DashboardDomain.js.map +1 -1
- package/build/dist/Models/DatabaseModels/Index.js +11 -0
- package/build/dist/Models/DatabaseModels/Index.js.map +1 -1
- package/build/dist/Models/DatabaseModels/MonitorTemplate.js +17 -4
- package/build/dist/Models/DatabaseModels/MonitorTemplate.js.map +1 -1
- package/build/dist/Models/DatabaseModels/OnCallDutyPolicySchedule.js +44 -0
- package/build/dist/Models/DatabaseModels/OnCallDutyPolicySchedule.js.map +1 -1
- package/build/dist/Models/DatabaseModels/OnCallDutyPolicyScheduleCalendarFeed.js +699 -0
- package/build/dist/Models/DatabaseModels/OnCallDutyPolicyScheduleCalendarFeed.js.map +1 -0
- package/build/dist/Models/DatabaseModels/ProjectOnCallCalendarFeed.js +620 -0
- package/build/dist/Models/DatabaseModels/ProjectOnCallCalendarFeed.js.map +1 -0
- package/build/dist/Models/DatabaseModels/StatusPageDomain.js +44 -0
- package/build/dist/Models/DatabaseModels/StatusPageDomain.js.map +1 -1
- package/build/dist/Models/DatabaseModels/UserOnCallCalendarFeed.js +585 -0
- package/build/dist/Models/DatabaseModels/UserOnCallCalendarFeed.js.map +1 -0
- package/build/dist/Models/DatabaseModels/UserOnCallShiftReminder.js +247 -0
- package/build/dist/Models/DatabaseModels/UserOnCallShiftReminder.js.map +1 -0
- package/build/dist/Models/DatabaseModels/UserOnCallShiftReminderLog.js +355 -0
- package/build/dist/Models/DatabaseModels/UserOnCallShiftReminderLog.js.map +1 -0
- package/build/dist/Server/API/DashboardDomainAPI.js +43 -1
- package/build/dist/Server/API/DashboardDomainAPI.js.map +1 -1
- package/build/dist/Server/API/MicrosoftTeamsAPI.js +74 -118
- package/build/dist/Server/API/MicrosoftTeamsAPI.js.map +1 -1
- package/build/dist/Server/API/OnCallCalendarAPI.js +1371 -0
- package/build/dist/Server/API/OnCallCalendarAPI.js.map +1 -0
- package/build/dist/Server/API/StatusPageDomainAPI.js +43 -1
- package/build/dist/Server/API/StatusPageDomainAPI.js.map +1 -1
- package/build/dist/Server/EnvironmentConfig.js +102 -0
- package/build/dist/Server/EnvironmentConfig.js.map +1 -1
- package/build/dist/Server/Infrastructure/OnCallCalendarFeedCache.js +663 -0
- package/build/dist/Server/Infrastructure/OnCallCalendarFeedCache.js.map +1 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1790200000000-AllowNullMonitorNameOnMonitorTemplate.js +34 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1790200000000-AllowNullMonitorNameOnMonitorTemplate.js.map +1 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1790300000000-AddCertificateReissueRequestedAtToDomains.js +28 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1790300000000-AddCertificateReissueRequestedAtToDomains.js.map +1 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1790400000000-AddOnCallCalendarFeeds.js +103 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1790400000000-AddOnCallCalendarFeeds.js.map +1 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js +6 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js.map +1 -1
- package/build/dist/Server/Middleware/OnCallCalendarFeedRateLimit.js +342 -0
- package/build/dist/Server/Middleware/OnCallCalendarFeedRateLimit.js.map +1 -0
- package/build/dist/Server/Middleware/ProjectAuthorization.js +6 -1
- package/build/dist/Server/Middleware/ProjectAuthorization.js.map +1 -1
- package/build/dist/Server/Services/DashboardDomainService.js +104 -0
- package/build/dist/Server/Services/DashboardDomainService.js.map +1 -1
- package/build/dist/Server/Services/Index.js +12 -0
- package/build/dist/Server/Services/Index.js.map +1 -1
- package/build/dist/Server/Services/LogPipelineProcessorService.js +65 -0
- package/build/dist/Server/Services/LogPipelineProcessorService.js.map +1 -1
- package/build/dist/Server/Services/MonitorTemplateService.js +57 -11
- package/build/dist/Server/Services/MonitorTemplateService.js.map +1 -1
- package/build/dist/Server/Services/OnCallDutyPolicyEscalationRuleScheduleService.js +20 -0
- package/build/dist/Server/Services/OnCallDutyPolicyEscalationRuleScheduleService.js.map +1 -1
- package/build/dist/Server/Services/OnCallDutyPolicyScheduleCalendarFeedService.js +219 -0
- package/build/dist/Server/Services/OnCallDutyPolicyScheduleCalendarFeedService.js.map +1 -0
- package/build/dist/Server/Services/OnCallDutyPolicyScheduleLayerService.js +47 -3
- package/build/dist/Server/Services/OnCallDutyPolicyScheduleLayerService.js.map +1 -1
- package/build/dist/Server/Services/OnCallDutyPolicyScheduleLayerUserService.js +112 -3
- package/build/dist/Server/Services/OnCallDutyPolicyScheduleLayerUserService.js.map +1 -1
- package/build/dist/Server/Services/OnCallDutyPolicyScheduleService.js +955 -9
- package/build/dist/Server/Services/OnCallDutyPolicyScheduleService.js.map +1 -1
- package/build/dist/Server/Services/OnCallDutyPolicyUserOverrideService.js +93 -6
- package/build/dist/Server/Services/OnCallDutyPolicyUserOverrideService.js.map +1 -1
- package/build/dist/Server/Services/ProjectOnCallCalendarFeedService.js +157 -0
- package/build/dist/Server/Services/ProjectOnCallCalendarFeedService.js.map +1 -0
- package/build/dist/Server/Services/StatusPageDomainService.js +132 -3
- package/build/dist/Server/Services/StatusPageDomainService.js.map +1 -1
- package/build/dist/Server/Services/StatusPagePrivateUserSessionService.js +112 -32
- package/build/dist/Server/Services/StatusPagePrivateUserSessionService.js.map +1 -1
- package/build/dist/Server/Services/TeamMemberService.js +396 -1
- package/build/dist/Server/Services/TeamMemberService.js.map +1 -1
- package/build/dist/Server/Services/TeamService.js +43 -0
- package/build/dist/Server/Services/TeamService.js.map +1 -1
- package/build/dist/Server/Services/UserNotificationSettingService.js +26 -1
- package/build/dist/Server/Services/UserNotificationSettingService.js.map +1 -1
- package/build/dist/Server/Services/UserOnCallCalendarFeedService.js +140 -0
- package/build/dist/Server/Services/UserOnCallCalendarFeedService.js.map +1 -0
- package/build/dist/Server/Services/UserOnCallShiftReminderLogService.js +116 -0
- package/build/dist/Server/Services/UserOnCallShiftReminderLogService.js.map +1 -0
- package/build/dist/Server/Services/UserOnCallShiftReminderService.js +92 -0
- package/build/dist/Server/Services/UserOnCallShiftReminderService.js.map +1 -0
- package/build/dist/Server/Types/Domain.js +16 -0
- package/build/dist/Server/Types/Domain.js.map +1 -1
- package/build/dist/Server/Utils/APIKey/AccessPermission.js +24 -0
- package/build/dist/Server/Utils/APIKey/AccessPermission.js.map +1 -1
- package/build/dist/Server/Utils/Greenlock/Greenlock.js +110 -35
- package/build/dist/Server/Utils/Greenlock/Greenlock.js.map +1 -1
- package/build/dist/Server/Utils/LogPipelineProcessorValidation.js +77 -0
- package/build/dist/Server/Utils/LogPipelineProcessorValidation.js.map +1 -0
- package/build/dist/Server/Utils/OnCall/CalendarFeedToken.js +161 -0
- package/build/dist/Server/Utils/OnCall/CalendarFeedToken.js.map +1 -0
- package/build/dist/Server/Utils/OnCall/OnCallCalendarFeedRenderer.js +983 -0
- package/build/dist/Server/Utils/OnCall/OnCallCalendarFeedRenderer.js.map +1 -0
- package/build/dist/Server/Utils/OnCall/OnCallCalendarFeedUrls.js +155 -0
- package/build/dist/Server/Utils/OnCall/OnCallCalendarFeedUrls.js.map +1 -0
- package/build/dist/Server/Utils/OnCall/OnCallShiftChangeListeners.js +145 -0
- package/build/dist/Server/Utils/OnCall/OnCallShiftChangeListeners.js.map +1 -0
- package/build/dist/Server/Utils/OnCall/OnCallShiftMaterializer.js +752 -0
- package/build/dist/Server/Utils/OnCall/OnCallShiftMaterializer.js.map +1 -0
- package/build/dist/Server/Utils/OnCall/OnCallShiftReminderListener.js +92 -0
- package/build/dist/Server/Utils/OnCall/OnCallShiftReminderListener.js.map +1 -0
- package/build/dist/Server/Utils/OnCall/OnCallShiftReminderRunner.js +1786 -0
- package/build/dist/Server/Utils/OnCall/OnCallShiftReminderRunner.js.map +1 -0
- package/build/dist/Server/Utils/Response.js +97 -0
- package/build/dist/Server/Utils/Response.js.map +1 -1
- package/build/dist/Server/Utils/SecurityEvent/GoogleSecOps/GoogleSecOpsClient.js +777 -36
- package/build/dist/Server/Utils/SecurityEvent/GoogleSecOps/GoogleSecOpsClient.js.map +1 -1
- package/build/dist/Server/Utils/SecurityEvent/GoogleSecOps/GoogleSecOpsPoller.js +108 -17
- package/build/dist/Server/Utils/SecurityEvent/GoogleSecOps/GoogleSecOpsPoller.js.map +1 -1
- package/build/dist/Server/Utils/StartServer.js +9 -1
- package/build/dist/Server/Utils/StartServer.js.map +1 -1
- package/build/dist/Server/Utils/Telemetry/AppMetrics.js +41 -0
- package/build/dist/Server/Utils/Telemetry/AppMetrics.js.map +1 -1
- package/build/dist/Server/Utils/Workspace/MicrosoftTeams/Actions/Alert.js +17 -6
- package/build/dist/Server/Utils/Workspace/MicrosoftTeams/Actions/Alert.js.map +1 -1
- package/build/dist/Server/Utils/Workspace/MicrosoftTeams/Actions/Auth.js +0 -159
- package/build/dist/Server/Utils/Workspace/MicrosoftTeams/Actions/Auth.js.map +1 -1
- package/build/dist/Server/Utils/Workspace/MicrosoftTeams/Actions/Authorization.js +128 -0
- package/build/dist/Server/Utils/Workspace/MicrosoftTeams/Actions/Authorization.js.map +1 -0
- package/build/dist/Server/Utils/Workspace/MicrosoftTeams/Actions/Incident.js +17 -6
- package/build/dist/Server/Utils/Workspace/MicrosoftTeams/Actions/Incident.js.map +1 -1
- package/build/dist/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeams.js +25 -13
- package/build/dist/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeams.js.map +1 -1
- package/build/dist/Types/API/Route.js +16 -5
- package/build/dist/Types/API/Route.js.map +1 -1
- package/build/dist/Types/Calendar/ICalendar.js +290 -0
- package/build/dist/Types/Calendar/ICalendar.js.map +1 -0
- package/build/dist/Types/Date.js +115 -13
- package/build/dist/Types/Date.js.map +1 -1
- package/build/dist/Types/Email/EmailTemplateType.js +2 -0
- package/build/dist/Types/Email/EmailTemplateType.js.map +1 -1
- package/build/dist/Types/NotificationSetting/NotificationSettingEventType.js +3 -0
- package/build/dist/Types/NotificationSetting/NotificationSettingEventType.js.map +1 -1
- package/build/dist/Types/OnCallDutyPolicy/CalendarFeedWindow.js +97 -0
- package/build/dist/Types/OnCallDutyPolicy/CalendarFeedWindow.js.map +1 -0
- package/build/dist/Types/OnCallDutyPolicy/Layer.js +113 -19
- package/build/dist/Types/OnCallDutyPolicy/Layer.js.map +1 -1
- package/build/dist/Types/OnCallDutyPolicy/MaterializedShift.js +134 -0
- package/build/dist/Types/OnCallDutyPolicy/MaterializedShift.js.map +1 -0
- package/build/dist/Types/OnCallDutyPolicy/OnCallCalendarFeedUtil.js +800 -0
- package/build/dist/Types/OnCallDutyPolicy/OnCallCalendarFeedUtil.js.map +1 -0
- package/build/dist/Types/OnCallDutyPolicy/ScheduleShiftUtil.js +97 -3
- package/build/dist/Types/OnCallDutyPolicy/ScheduleShiftUtil.js.map +1 -1
- package/build/dist/Types/OnCallDutyPolicy/ShiftSeamUtil.js +77 -0
- package/build/dist/Types/OnCallDutyPolicy/ShiftSeamUtil.js.map +1 -0
- package/build/dist/Types/Rum/SessionReplay.js +38 -0
- package/build/dist/Types/Rum/SessionReplay.js.map +1 -1
- package/build/dist/UI/Components/Date/TimeRangePickerDropdown.js +9 -5
- package/build/dist/UI/Components/Date/TimeRangePickerDropdown.js.map +1 -1
- package/build/dist/UI/Components/LogsViewer/LogsViewer.js +12 -7
- package/build/dist/UI/Components/LogsViewer/LogsViewer.js.map +1 -1
- package/build/dist/UI/Components/LogsViewer/components/HistogramTooltip.js +15 -14
- package/build/dist/UI/Components/LogsViewer/components/HistogramTooltip.js.map +1 -1
- package/build/dist/UI/Components/LogsViewer/components/LogsAnalyticsView.js +17 -22
- package/build/dist/UI/Components/LogsViewer/components/LogsAnalyticsView.js.map +1 -1
- package/build/dist/UI/Components/LogsViewer/components/LogsHistogram.js +2 -4
- package/build/dist/UI/Components/LogsViewer/components/LogsHistogram.js.map +1 -1
- package/build/dist/UI/Components/TelemetryViewer/components/TelemetryHistogram.js +2 -4
- package/build/dist/UI/Components/TelemetryViewer/components/TelemetryHistogram.js.map +1 -1
- package/build/dist/UI/Components/TelemetryViewer/components/TelemetryHistogramTooltip.js +15 -14
- package/build/dist/UI/Components/TelemetryViewer/components/TelemetryHistogramTooltip.js.map +1 -1
- package/build/dist/UI/Utils/Navigation.js +30 -0
- package/build/dist/UI/Utils/Navigation.js.map +1 -1
- package/build/dist/Utils/Array.js +25 -0
- package/build/dist/Utils/Array.js.map +1 -1
- package/build/dist/Utils/CertificateReissue.js +97 -0
- package/build/dist/Utils/CertificateReissue.js.map +1 -0
- package/build/dist/Utils/Grok/Grok.js +324 -0
- package/build/dist/Utils/Grok/Grok.js.map +1 -0
- package/build/dist/Utils/Grok/GrokPatterns.js +112 -0
- package/build/dist/Utils/Grok/GrokPatterns.js.map +1 -0
- package/build/dist/Utils/Monitor/NetworkDeviceMonitorTemplateUtil.js +63 -3
- package/build/dist/Utils/Monitor/NetworkDeviceMonitorTemplateUtil.js.map +1 -1
- package/package.json +1 -1
- package/UI/Utils/JsonWebToken.ts +0 -14
- package/build/dist/UI/Utils/JsonWebToken.js +0 -10
- 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
|