@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,1127 @@
|
|
|
1
|
+
import crypto from "crypto";
|
|
2
|
+
import OnCallDutyPolicyScheduleService, {
|
|
3
|
+
ResolvedShiftSegments,
|
|
4
|
+
} from "../../Services/OnCallDutyPolicyScheduleService";
|
|
5
|
+
import OnCallDutyPolicyScheduleLayerUserService from "../../Services/OnCallDutyPolicyScheduleLayerUserService";
|
|
6
|
+
import OnCallDutyPolicyUserOverrideService from "../../Services/OnCallDutyPolicyUserOverrideService";
|
|
7
|
+
import ProjectService from "../../Services/ProjectService";
|
|
8
|
+
import UserService from "../../Services/UserService";
|
|
9
|
+
import QueryHelper from "../../Types/Database/QueryHelper";
|
|
10
|
+
import CaptureSpan from "../Telemetry/CaptureSpan";
|
|
11
|
+
import OnCallDutyPolicySchedule from "../../../Models/DatabaseModels/OnCallDutyPolicySchedule";
|
|
12
|
+
import OnCallDutyPolicyScheduleLayerUser from "../../../Models/DatabaseModels/OnCallDutyPolicyScheduleLayerUser";
|
|
13
|
+
import OnCallDutyPolicyUserOverride from "../../../Models/DatabaseModels/OnCallDutyPolicyUserOverride";
|
|
14
|
+
import Project from "../../../Models/DatabaseModels/Project";
|
|
15
|
+
import User from "../../../Models/DatabaseModels/User";
|
|
16
|
+
import CalendarEvent from "../../../Types/Calendar/CalendarEvent";
|
|
17
|
+
import { LIMIT_PER_PROJECT } from "../../../Types/Database/LimitMax";
|
|
18
|
+
import OneUptimeDate from "../../../Types/Date";
|
|
19
|
+
import BadDataException from "../../../Types/Exception/BadDataException";
|
|
20
|
+
import ObjectID from "../../../Types/ObjectID";
|
|
21
|
+
import { FEED_SIMULATION_ITERATION_CAP } from "../../../Types/OnCallDutyPolicy/CalendarFeedWindow";
|
|
22
|
+
import { LayerProps } from "../../../Types/OnCallDutyPolicy/Layer";
|
|
23
|
+
import MaterializedShiftUtil, {
|
|
24
|
+
MaterializedShift,
|
|
25
|
+
MaterializedShiftOverride,
|
|
26
|
+
MaterializedShiftPolicy,
|
|
27
|
+
} from "../../../Types/OnCallDutyPolicy/MaterializedShift";
|
|
28
|
+
import OnCallCalendarFeedUtil from "../../../Types/OnCallDutyPolicy/OnCallCalendarFeedUtil";
|
|
29
|
+
import ScheduleShiftUtil, {
|
|
30
|
+
OnCallShift,
|
|
31
|
+
} from "../../../Types/OnCallDutyPolicy/ScheduleShiftUtil";
|
|
32
|
+
import ShiftSeamUtil from "../../../Types/OnCallDutyPolicy/ShiftSeamUtil";
|
|
33
|
+
import { UserOverrideRecord } from "../../../Types/OnCallDutyPolicy/UserOverrideUtil";
|
|
34
|
+
|
|
35
|
+
/*
|
|
36
|
+
* Turns the resolver's raw coverage segments into MaterializedShifts — the
|
|
37
|
+
* one shape the calendar feeds, /my-shifts and the shift reminders consume.
|
|
38
|
+
*
|
|
39
|
+
* Per schedule, in this order:
|
|
40
|
+
* 1. ScheduleShiftUtil.groupEventsIntoShifts with mergeAcrossGaps = false
|
|
41
|
+
* and a group key of user + override identity + layer, so a restricted
|
|
42
|
+
* layer yields one shift per active block, an override split yields
|
|
43
|
+
* A / B / A as three shifts, and a layer handover is a boundary.
|
|
44
|
+
* 2. ShiftSeamUtil.normalizeSeams: the engine's one-second seams become
|
|
45
|
+
* touching, minute-aligned boundaries. Every identity below (shiftKey,
|
|
46
|
+
* the calendar UID, the reminder idempotency key) is derived AFTER this
|
|
47
|
+
* step, which is what makes it stable across windows and edits.
|
|
48
|
+
* 3. Policy-variant diffing: for a schedule attached to several policies
|
|
49
|
+
* with a policy-scoped override, the per-policy resolution is compared
|
|
50
|
+
* with the base one and every interval where a DIFFERENT person is
|
|
51
|
+
* paged becomes an extra shift tagged policyVariantOf.
|
|
52
|
+
* 4. One user lookup for display names and timezones, one project lookup
|
|
53
|
+
* for project names.
|
|
54
|
+
*
|
|
55
|
+
* The public entry points differ only in how they choose schedules:
|
|
56
|
+
* - materializeForSchedule(s): everything on those schedules (shared feeds,
|
|
57
|
+
* the schedule-level cache, the reminder sweep);
|
|
58
|
+
* - materializeForUser: the schedules the user is rostered on, plus (when
|
|
59
|
+
* includeCoveringShifts) the schedules of people the user substitutes
|
|
60
|
+
* for, filtered down to the user's own shifts;
|
|
61
|
+
* - materializeForProject: every schedule in the project.
|
|
62
|
+
* Standing escalation targets (direct users, teams) have no start and end
|
|
63
|
+
* and are deliberately never emitted.
|
|
64
|
+
*/
|
|
65
|
+
|
|
66
|
+
export interface MaterializedUserInfo {
|
|
67
|
+
userId: string;
|
|
68
|
+
userName: string;
|
|
69
|
+
email?: string;
|
|
70
|
+
timezone?: string;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
export interface MaterializedScheduleInfo {
|
|
74
|
+
scheduleId: string;
|
|
75
|
+
scheduleName: string;
|
|
76
|
+
projectId: string;
|
|
77
|
+
projectName?: string;
|
|
78
|
+
scheduleTimezone?: string;
|
|
79
|
+
shiftConfigVersion: number;
|
|
80
|
+
lastModifiedAt: Date;
|
|
81
|
+
truncated: boolean;
|
|
82
|
+
attachedPolicies: Array<MaterializedShiftPolicy>;
|
|
83
|
+
// The engine input, for the coverage envelope (OnCallCalendarFeedUtil).
|
|
84
|
+
layerProps: Array<LayerProps>;
|
|
85
|
+
scheduleUserIds: Array<string>;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
export interface MaterializeResult {
|
|
89
|
+
shifts: Array<MaterializedShift>;
|
|
90
|
+
// True when any schedule's expansion hit the iteration cap.
|
|
91
|
+
truncated: boolean;
|
|
92
|
+
schedules: Array<MaterializedScheduleInfo>;
|
|
93
|
+
users: Array<MaterializedUserInfo>;
|
|
94
|
+
generatedAt: Date;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
interface MaterializeWindowOptions {
|
|
98
|
+
windowStart: Date;
|
|
99
|
+
windowEnd: Date;
|
|
100
|
+
// "now" for isPast; defaults to the clock.
|
|
101
|
+
now?: Date | undefined;
|
|
102
|
+
// Defaults to FEED_SIMULATION_ITERATION_CAP.
|
|
103
|
+
maxSimulationIterations?: number | undefined;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
export interface MaterializeForScheduleOptions
|
|
107
|
+
extends MaterializeWindowOptions {
|
|
108
|
+
scheduleId: ObjectID;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
export interface MaterializeForSchedulesOptions
|
|
112
|
+
extends MaterializeWindowOptions {
|
|
113
|
+
scheduleIds: Array<ObjectID>;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
export interface MaterializeForUserOptions extends MaterializeWindowOptions {
|
|
117
|
+
userId: ObjectID;
|
|
118
|
+
// Restrict to these projects; every project the user is rostered in otherwise.
|
|
119
|
+
projectIds?: Array<ObjectID> | undefined;
|
|
120
|
+
// Restrict to one schedule (the `?schedule=` feed filter).
|
|
121
|
+
scheduleId?: ObjectID | undefined;
|
|
122
|
+
// Include shifts the user covers as a substitute on other people's schedules.
|
|
123
|
+
includeCoveringShifts: boolean;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
export interface MaterializeForProjectOptions extends MaterializeWindowOptions {
|
|
127
|
+
projectId: ObjectID;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
export interface MaterializeResolvedOptions {
|
|
131
|
+
now: Date;
|
|
132
|
+
users: Map<string, MaterializedUserInfo>;
|
|
133
|
+
projectName?: string | undefined;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
export default class OnCallShiftMaterializer {
|
|
137
|
+
// -- Entry points -----------------------------------------------------
|
|
138
|
+
|
|
139
|
+
@CaptureSpan()
|
|
140
|
+
public static async materializeForSchedule(
|
|
141
|
+
options: MaterializeForScheduleOptions,
|
|
142
|
+
): Promise<MaterializeResult> {
|
|
143
|
+
return await OnCallShiftMaterializer.materializeForSchedules({
|
|
144
|
+
scheduleIds: [options.scheduleId],
|
|
145
|
+
windowStart: options.windowStart,
|
|
146
|
+
windowEnd: options.windowEnd,
|
|
147
|
+
now: options.now,
|
|
148
|
+
maxSimulationIterations: options.maxSimulationIterations,
|
|
149
|
+
});
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
@CaptureSpan()
|
|
153
|
+
public static async materializeForSchedules(
|
|
154
|
+
options: MaterializeForSchedulesOptions,
|
|
155
|
+
): Promise<MaterializeResult> {
|
|
156
|
+
OnCallShiftMaterializer.validateWindow(
|
|
157
|
+
options.windowStart,
|
|
158
|
+
options.windowEnd,
|
|
159
|
+
);
|
|
160
|
+
|
|
161
|
+
const now: Date = options.now || OneUptimeDate.getCurrentDate();
|
|
162
|
+
const scheduleIds: Array<ObjectID> = OnCallShiftMaterializer.dedupeIds(
|
|
163
|
+
options.scheduleIds,
|
|
164
|
+
);
|
|
165
|
+
|
|
166
|
+
if (scheduleIds.length === 0) {
|
|
167
|
+
return OnCallShiftMaterializer.emptyResult(now);
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
const resolved: Array<ResolvedShiftSegments> =
|
|
171
|
+
await OnCallDutyPolicyScheduleService.getResolvedShiftSegmentsForSchedules(
|
|
172
|
+
{
|
|
173
|
+
scheduleIds,
|
|
174
|
+
windowStart: options.windowStart,
|
|
175
|
+
windowEnd: options.windowEnd,
|
|
176
|
+
maxSimulationIterations:
|
|
177
|
+
options.maxSimulationIterations ?? FEED_SIMULATION_ITERATION_CAP,
|
|
178
|
+
},
|
|
179
|
+
);
|
|
180
|
+
|
|
181
|
+
if (resolved.length === 0) {
|
|
182
|
+
return OnCallShiftMaterializer.emptyResult(now);
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
const users: Map<string, MaterializedUserInfo> =
|
|
186
|
+
await OnCallShiftMaterializer.loadUsers(
|
|
187
|
+
OnCallShiftMaterializer.collectUserIds(resolved),
|
|
188
|
+
);
|
|
189
|
+
|
|
190
|
+
const projectNames: Map<string, string> =
|
|
191
|
+
await OnCallShiftMaterializer.loadProjectNames(
|
|
192
|
+
resolved.map((item: ResolvedShiftSegments) => {
|
|
193
|
+
return item.schedule.projectId;
|
|
194
|
+
}),
|
|
195
|
+
);
|
|
196
|
+
|
|
197
|
+
const shifts: Array<MaterializedShift> = [];
|
|
198
|
+
const schedules: Array<MaterializedScheduleInfo> = [];
|
|
199
|
+
let truncated: boolean = false;
|
|
200
|
+
|
|
201
|
+
for (const item of resolved) {
|
|
202
|
+
const projectName: string | undefined = projectNames.get(
|
|
203
|
+
item.schedule.projectId,
|
|
204
|
+
);
|
|
205
|
+
|
|
206
|
+
shifts.push(
|
|
207
|
+
...OnCallShiftMaterializer.materializeResolved(item, {
|
|
208
|
+
now,
|
|
209
|
+
users,
|
|
210
|
+
projectName,
|
|
211
|
+
}),
|
|
212
|
+
);
|
|
213
|
+
|
|
214
|
+
const info: MaterializedScheduleInfo = {
|
|
215
|
+
scheduleId: item.schedule.id,
|
|
216
|
+
scheduleName: item.schedule.name,
|
|
217
|
+
projectId: item.schedule.projectId,
|
|
218
|
+
shiftConfigVersion: item.schedule.shiftConfigVersion,
|
|
219
|
+
lastModifiedAt: item.lastModifiedAt,
|
|
220
|
+
truncated: item.truncated,
|
|
221
|
+
attachedPolicies: item.attachedPolicies,
|
|
222
|
+
layerProps: item.layerProps,
|
|
223
|
+
scheduleUserIds: item.scheduleUserIds,
|
|
224
|
+
};
|
|
225
|
+
|
|
226
|
+
if (projectName !== undefined) {
|
|
227
|
+
info.projectName = projectName;
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
if (item.schedule.timezone !== undefined) {
|
|
231
|
+
info.scheduleTimezone = item.schedule.timezone;
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
schedules.push(info);
|
|
235
|
+
truncated = truncated || item.truncated;
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
return {
|
|
239
|
+
shifts: MaterializedShiftUtil.sortByStart(shifts),
|
|
240
|
+
truncated,
|
|
241
|
+
schedules,
|
|
242
|
+
users: Array.from(users.values()),
|
|
243
|
+
generatedAt: now,
|
|
244
|
+
};
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
@CaptureSpan()
|
|
248
|
+
public static async materializeForUser(
|
|
249
|
+
options: MaterializeForUserOptions,
|
|
250
|
+
): Promise<MaterializeResult> {
|
|
251
|
+
OnCallShiftMaterializer.validateWindow(
|
|
252
|
+
options.windowStart,
|
|
253
|
+
options.windowEnd,
|
|
254
|
+
);
|
|
255
|
+
|
|
256
|
+
const now: Date = options.now || OneUptimeDate.getCurrentDate();
|
|
257
|
+
|
|
258
|
+
const candidateScheduleIds: Array<ObjectID> =
|
|
259
|
+
await OnCallShiftMaterializer.getCandidateScheduleIdsForUser({
|
|
260
|
+
userId: options.userId,
|
|
261
|
+
projectIds: options.projectIds,
|
|
262
|
+
scheduleId: options.scheduleId,
|
|
263
|
+
windowStart: options.windowStart,
|
|
264
|
+
windowEnd: options.windowEnd,
|
|
265
|
+
includeCoveringShifts: options.includeCoveringShifts,
|
|
266
|
+
});
|
|
267
|
+
|
|
268
|
+
if (candidateScheduleIds.length === 0) {
|
|
269
|
+
return OnCallShiftMaterializer.emptyResult(now);
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
const result: MaterializeResult =
|
|
273
|
+
await OnCallShiftMaterializer.materializeForSchedules({
|
|
274
|
+
scheduleIds: candidateScheduleIds,
|
|
275
|
+
windowStart: options.windowStart,
|
|
276
|
+
windowEnd: options.windowEnd,
|
|
277
|
+
now,
|
|
278
|
+
maxSimulationIterations: options.maxSimulationIterations,
|
|
279
|
+
});
|
|
280
|
+
|
|
281
|
+
return {
|
|
282
|
+
...result,
|
|
283
|
+
shifts: OnCallShiftMaterializer.filterShiftsForUser(
|
|
284
|
+
result.shifts,
|
|
285
|
+
options.userId,
|
|
286
|
+
),
|
|
287
|
+
};
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
@CaptureSpan()
|
|
291
|
+
public static async materializeForProject(
|
|
292
|
+
options: MaterializeForProjectOptions,
|
|
293
|
+
): Promise<MaterializeResult> {
|
|
294
|
+
OnCallShiftMaterializer.validateWindow(
|
|
295
|
+
options.windowStart,
|
|
296
|
+
options.windowEnd,
|
|
297
|
+
);
|
|
298
|
+
|
|
299
|
+
const now: Date = options.now || OneUptimeDate.getCurrentDate();
|
|
300
|
+
|
|
301
|
+
const schedules: Array<OnCallDutyPolicySchedule> =
|
|
302
|
+
await OnCallDutyPolicyScheduleService.findBy({
|
|
303
|
+
query: {
|
|
304
|
+
projectId: options.projectId,
|
|
305
|
+
},
|
|
306
|
+
select: {
|
|
307
|
+
_id: true,
|
|
308
|
+
},
|
|
309
|
+
limit: LIMIT_PER_PROJECT,
|
|
310
|
+
skip: 0,
|
|
311
|
+
props: {
|
|
312
|
+
isRoot: true,
|
|
313
|
+
},
|
|
314
|
+
});
|
|
315
|
+
|
|
316
|
+
const scheduleIds: Array<ObjectID> = schedules
|
|
317
|
+
.map((schedule: OnCallDutyPolicySchedule) => {
|
|
318
|
+
return schedule.id;
|
|
319
|
+
})
|
|
320
|
+
.filter((id: ObjectID | null | undefined): id is ObjectID => {
|
|
321
|
+
return Boolean(id);
|
|
322
|
+
});
|
|
323
|
+
|
|
324
|
+
if (scheduleIds.length === 0) {
|
|
325
|
+
return OnCallShiftMaterializer.emptyResult(now);
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
return await OnCallShiftMaterializer.materializeForSchedules({
|
|
329
|
+
scheduleIds,
|
|
330
|
+
windowStart: options.windowStart,
|
|
331
|
+
windowEnd: options.windowEnd,
|
|
332
|
+
now,
|
|
333
|
+
maxSimulationIterations: options.maxSimulationIterations,
|
|
334
|
+
});
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
// -- Candidate schedules ----------------------------------------------
|
|
338
|
+
|
|
339
|
+
/**
|
|
340
|
+
* The schedules whose materialization can contain the user's shifts:
|
|
341
|
+
* distinct schedule ids from the user's layer-user rows (optionally within
|
|
342
|
+
* `projectIds`), plus — when includeCoveringShifts — the schedules of every
|
|
343
|
+
* user the caller substitutes for through an override overlapping the
|
|
344
|
+
* window. Optionally narrowed to one schedule. Root reads: the caller has
|
|
345
|
+
* already decided the user may see these.
|
|
346
|
+
*
|
|
347
|
+
* One user, 2-3 queries. Anything that needs this for a GROUP of users
|
|
348
|
+
* (the reminder sweep and its change pass walk every reminded user of a
|
|
349
|
+
* project on every tick) must call getCandidateScheduleIdsForUsers, which
|
|
350
|
+
* answers the whole group in the same 2-3 queries.
|
|
351
|
+
*/
|
|
352
|
+
@CaptureSpan()
|
|
353
|
+
public static async getCandidateScheduleIdsForUser(data: {
|
|
354
|
+
userId: ObjectID;
|
|
355
|
+
projectIds?: Array<ObjectID> | undefined;
|
|
356
|
+
scheduleId?: ObjectID | undefined;
|
|
357
|
+
windowStart: Date;
|
|
358
|
+
windowEnd: Date;
|
|
359
|
+
includeCoveringShifts: boolean;
|
|
360
|
+
}): Promise<Array<ObjectID>> {
|
|
361
|
+
const byUser: Map<
|
|
362
|
+
string,
|
|
363
|
+
Array<ObjectID>
|
|
364
|
+
> = await OnCallShiftMaterializer.getCandidateScheduleIdsForUsers({
|
|
365
|
+
userIds: [data.userId],
|
|
366
|
+
projectIds: data.projectIds,
|
|
367
|
+
scheduleId: data.scheduleId,
|
|
368
|
+
windowStart: data.windowStart,
|
|
369
|
+
windowEnd: data.windowEnd,
|
|
370
|
+
includeCoveringShifts: data.includeCoveringShifts,
|
|
371
|
+
});
|
|
372
|
+
|
|
373
|
+
return byUser.get(data.userId.toString()) || [];
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
/**
|
|
377
|
+
* The batched form of getCandidateScheduleIdsForUser: the candidate
|
|
378
|
+
* schedules of MANY users in the same two or three queries it takes for
|
|
379
|
+
* one, keyed by user id (every requested user gets an entry, empty when
|
|
380
|
+
* they are on nothing). Semantics per user are identical — own layer-user
|
|
381
|
+
* rows, plus the schedules of the people that user substitutes for through
|
|
382
|
+
* an override overlapping the window, narrowed to `scheduleId` when given.
|
|
383
|
+
*
|
|
384
|
+
* The covering overrides are matched back to the SUBSTITUTE that each one
|
|
385
|
+
* routes to, so one user's coverage never leaks into another's list.
|
|
386
|
+
*/
|
|
387
|
+
@CaptureSpan()
|
|
388
|
+
public static async getCandidateScheduleIdsForUsers(data: {
|
|
389
|
+
userIds: Array<ObjectID>;
|
|
390
|
+
projectIds?: Array<ObjectID> | undefined;
|
|
391
|
+
scheduleId?: ObjectID | undefined;
|
|
392
|
+
windowStart: Date;
|
|
393
|
+
windowEnd: Date;
|
|
394
|
+
includeCoveringShifts: boolean;
|
|
395
|
+
}): Promise<Map<string, Array<ObjectID>>> {
|
|
396
|
+
const userIds: Array<ObjectID> = OnCallShiftMaterializer.dedupeIds(
|
|
397
|
+
data.userIds,
|
|
398
|
+
);
|
|
399
|
+
|
|
400
|
+
const candidatesByUser: Map<string, Array<ObjectID>> = new Map<
|
|
401
|
+
string,
|
|
402
|
+
Array<ObjectID>
|
|
403
|
+
>();
|
|
404
|
+
|
|
405
|
+
for (const userId of userIds) {
|
|
406
|
+
candidatesByUser.set(userId.toString(), []);
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
if (userIds.length === 0) {
|
|
410
|
+
return candidatesByUser;
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
const projectFilter: Array<ObjectID> | undefined =
|
|
414
|
+
data.projectIds && data.projectIds.length > 0
|
|
415
|
+
? OnCallShiftMaterializer.dedupeIds(data.projectIds)
|
|
416
|
+
: undefined;
|
|
417
|
+
|
|
418
|
+
const ownRows: Array<OnCallDutyPolicyScheduleLayerUser> =
|
|
419
|
+
await OnCallDutyPolicyScheduleLayerUserService.findBy({
|
|
420
|
+
query: projectFilter
|
|
421
|
+
? {
|
|
422
|
+
userId: QueryHelper.any(userIds),
|
|
423
|
+
projectId: QueryHelper.any(projectFilter),
|
|
424
|
+
}
|
|
425
|
+
: {
|
|
426
|
+
userId: QueryHelper.any(userIds),
|
|
427
|
+
},
|
|
428
|
+
select: {
|
|
429
|
+
onCallDutyPolicyScheduleId: true,
|
|
430
|
+
projectId: true,
|
|
431
|
+
userId: true,
|
|
432
|
+
},
|
|
433
|
+
limit: LIMIT_PER_PROJECT,
|
|
434
|
+
skip: 0,
|
|
435
|
+
props: {
|
|
436
|
+
isRoot: true,
|
|
437
|
+
},
|
|
438
|
+
});
|
|
439
|
+
|
|
440
|
+
for (const row of ownRows) {
|
|
441
|
+
const userKey: string = row.userId?.toString() || "";
|
|
442
|
+
const candidates: Array<ObjectID> | undefined =
|
|
443
|
+
candidatesByUser.get(userKey);
|
|
444
|
+
|
|
445
|
+
if (!candidates || !row.onCallDutyPolicyScheduleId) {
|
|
446
|
+
continue;
|
|
447
|
+
}
|
|
448
|
+
|
|
449
|
+
candidates.push(row.onCallDutyPolicyScheduleId);
|
|
450
|
+
}
|
|
451
|
+
|
|
452
|
+
if (data.includeCoveringShifts) {
|
|
453
|
+
const coveringOverrides: Array<OnCallDutyPolicyUserOverride> =
|
|
454
|
+
await OnCallDutyPolicyUserOverrideService.findBy({
|
|
455
|
+
query: projectFilter
|
|
456
|
+
? {
|
|
457
|
+
routeAlertsToUserId: QueryHelper.any(userIds),
|
|
458
|
+
projectId: QueryHelper.any(projectFilter),
|
|
459
|
+
startsAt: QueryHelper.lessThanEqualTo(data.windowEnd),
|
|
460
|
+
endsAt: QueryHelper.greaterThanEqualTo(data.windowStart),
|
|
461
|
+
}
|
|
462
|
+
: {
|
|
463
|
+
routeAlertsToUserId: QueryHelper.any(userIds),
|
|
464
|
+
startsAt: QueryHelper.lessThanEqualTo(data.windowEnd),
|
|
465
|
+
endsAt: QueryHelper.greaterThanEqualTo(data.windowStart),
|
|
466
|
+
},
|
|
467
|
+
select: {
|
|
468
|
+
projectId: true,
|
|
469
|
+
overrideUserId: true,
|
|
470
|
+
routeAlertsToUserId: true,
|
|
471
|
+
startsAt: true,
|
|
472
|
+
endsAt: true,
|
|
473
|
+
},
|
|
474
|
+
limit: LIMIT_PER_PROJECT,
|
|
475
|
+
skip: 0,
|
|
476
|
+
props: {
|
|
477
|
+
isRoot: true,
|
|
478
|
+
},
|
|
479
|
+
});
|
|
480
|
+
|
|
481
|
+
// substitute -> the (project, overridden user) pairs THEY cover.
|
|
482
|
+
const pairsByUser: Map<string, Set<string>> = new Map<
|
|
483
|
+
string,
|
|
484
|
+
Set<string>
|
|
485
|
+
>();
|
|
486
|
+
const overriddenUserIds: Array<ObjectID> = [];
|
|
487
|
+
const overrideProjectIds: Array<ObjectID> = [];
|
|
488
|
+
|
|
489
|
+
for (const override of coveringOverrides) {
|
|
490
|
+
const overriddenUserId: ObjectID | undefined = override.overrideUserId;
|
|
491
|
+
const projectId: ObjectID | undefined = override.projectId;
|
|
492
|
+
const substituteKey: string =
|
|
493
|
+
override.routeAlertsToUserId?.toString() || "";
|
|
494
|
+
|
|
495
|
+
if (
|
|
496
|
+
!overriddenUserId ||
|
|
497
|
+
!projectId ||
|
|
498
|
+
!candidatesByUser.has(substituteKey)
|
|
499
|
+
) {
|
|
500
|
+
continue;
|
|
501
|
+
}
|
|
502
|
+
|
|
503
|
+
if (
|
|
504
|
+
override.startsAt &&
|
|
505
|
+
override.endsAt &&
|
|
506
|
+
(OneUptimeDate.isAfter(override.startsAt, data.windowEnd) ||
|
|
507
|
+
OneUptimeDate.isBefore(override.endsAt, data.windowStart))
|
|
508
|
+
) {
|
|
509
|
+
continue;
|
|
510
|
+
}
|
|
511
|
+
|
|
512
|
+
let pairs: Set<string> | undefined = pairsByUser.get(substituteKey);
|
|
513
|
+
|
|
514
|
+
if (!pairs) {
|
|
515
|
+
pairs = new Set<string>();
|
|
516
|
+
pairsByUser.set(substituteKey, pairs);
|
|
517
|
+
}
|
|
518
|
+
|
|
519
|
+
pairs.add(`${projectId.toString()}:${overriddenUserId.toString()}`);
|
|
520
|
+
overriddenUserIds.push(overriddenUserId);
|
|
521
|
+
overrideProjectIds.push(projectId);
|
|
522
|
+
}
|
|
523
|
+
|
|
524
|
+
if (pairsByUser.size > 0) {
|
|
525
|
+
const coveredRows: Array<OnCallDutyPolicyScheduleLayerUser> =
|
|
526
|
+
await OnCallDutyPolicyScheduleLayerUserService.findBy({
|
|
527
|
+
query: {
|
|
528
|
+
userId: QueryHelper.any(
|
|
529
|
+
OnCallShiftMaterializer.dedupeIds(overriddenUserIds),
|
|
530
|
+
),
|
|
531
|
+
projectId: QueryHelper.any(
|
|
532
|
+
OnCallShiftMaterializer.dedupeIds(overrideProjectIds),
|
|
533
|
+
),
|
|
534
|
+
},
|
|
535
|
+
select: {
|
|
536
|
+
onCallDutyPolicyScheduleId: true,
|
|
537
|
+
projectId: true,
|
|
538
|
+
userId: true,
|
|
539
|
+
},
|
|
540
|
+
limit: LIMIT_PER_PROJECT,
|
|
541
|
+
skip: 0,
|
|
542
|
+
props: {
|
|
543
|
+
isRoot: true,
|
|
544
|
+
},
|
|
545
|
+
});
|
|
546
|
+
|
|
547
|
+
for (const row of coveredRows) {
|
|
548
|
+
const key: string = `${row.projectId?.toString() || ""}:${
|
|
549
|
+
row.userId?.toString() || ""
|
|
550
|
+
}`;
|
|
551
|
+
|
|
552
|
+
if (!row.onCallDutyPolicyScheduleId) {
|
|
553
|
+
continue;
|
|
554
|
+
}
|
|
555
|
+
|
|
556
|
+
for (const [substituteKey, pairs] of pairsByUser) {
|
|
557
|
+
if (pairs.has(key)) {
|
|
558
|
+
candidatesByUser
|
|
559
|
+
.get(substituteKey)
|
|
560
|
+
?.push(row.onCallDutyPolicyScheduleId);
|
|
561
|
+
}
|
|
562
|
+
}
|
|
563
|
+
}
|
|
564
|
+
}
|
|
565
|
+
}
|
|
566
|
+
|
|
567
|
+
const wanted: string | undefined = data.scheduleId?.toString();
|
|
568
|
+
|
|
569
|
+
for (const [userKey, candidates] of candidatesByUser) {
|
|
570
|
+
let deduped: Array<ObjectID> =
|
|
571
|
+
OnCallShiftMaterializer.dedupeIds(candidates);
|
|
572
|
+
|
|
573
|
+
if (wanted) {
|
|
574
|
+
deduped = deduped.filter((id: ObjectID) => {
|
|
575
|
+
return id.toString() === wanted;
|
|
576
|
+
});
|
|
577
|
+
}
|
|
578
|
+
|
|
579
|
+
candidatesByUser.set(userKey, deduped);
|
|
580
|
+
}
|
|
581
|
+
|
|
582
|
+
return candidatesByUser;
|
|
583
|
+
}
|
|
584
|
+
|
|
585
|
+
// -- Pure materialization ---------------------------------------------
|
|
586
|
+
|
|
587
|
+
/**
|
|
588
|
+
* Materialize ONE resolved schedule: base shifts plus policy-variant
|
|
589
|
+
* shifts. Pure — every lookup has already happened.
|
|
590
|
+
*/
|
|
591
|
+
public static materializeResolved(
|
|
592
|
+
resolved: ResolvedShiftSegments,
|
|
593
|
+
options: MaterializeResolvedOptions,
|
|
594
|
+
): Array<MaterializedShift> {
|
|
595
|
+
const baseShifts: Array<OnCallShift> = OnCallShiftMaterializer.toShifts(
|
|
596
|
+
resolved.segments,
|
|
597
|
+
);
|
|
598
|
+
|
|
599
|
+
const shifts: Array<MaterializedShift> = baseShifts.map(
|
|
600
|
+
(shift: OnCallShift) => {
|
|
601
|
+
return OnCallShiftMaterializer.toMaterializedShift({
|
|
602
|
+
shift,
|
|
603
|
+
resolved,
|
|
604
|
+
options,
|
|
605
|
+
policyVariant: null,
|
|
606
|
+
});
|
|
607
|
+
},
|
|
608
|
+
);
|
|
609
|
+
|
|
610
|
+
for (const variant of resolved.policyVariants) {
|
|
611
|
+
const variantShifts: Array<OnCallShift> =
|
|
612
|
+
OnCallShiftMaterializer.toShifts(variant.segments);
|
|
613
|
+
|
|
614
|
+
const differing: Array<{ shift: OnCallShift; globalUserId: string }> =
|
|
615
|
+
OnCallShiftMaterializer.diffPolicyVariant(baseShifts, variantShifts);
|
|
616
|
+
|
|
617
|
+
for (const entry of differing) {
|
|
618
|
+
shifts.push(
|
|
619
|
+
OnCallShiftMaterializer.toMaterializedShift({
|
|
620
|
+
shift: entry.shift,
|
|
621
|
+
resolved,
|
|
622
|
+
options,
|
|
623
|
+
policyVariant: {
|
|
624
|
+
policyId: variant.policyId,
|
|
625
|
+
policyName: variant.policyName,
|
|
626
|
+
globalUserId: entry.globalUserId,
|
|
627
|
+
},
|
|
628
|
+
}),
|
|
629
|
+
);
|
|
630
|
+
}
|
|
631
|
+
}
|
|
632
|
+
|
|
633
|
+
return MaterializedShiftUtil.sortByStart(shifts);
|
|
634
|
+
}
|
|
635
|
+
|
|
636
|
+
/*
|
|
637
|
+
* Group + seam-normalise raw segments into shifts. Exposed so tests and
|
|
638
|
+
* the coverage-gap logic can reproduce exactly what the feed sees.
|
|
639
|
+
*/
|
|
640
|
+
public static toShifts(segments: Array<CalendarEvent>): Array<OnCallShift> {
|
|
641
|
+
const grouped: Array<OnCallShift> = ScheduleShiftUtil.groupEventsIntoShifts(
|
|
642
|
+
segments,
|
|
643
|
+
{
|
|
644
|
+
mergeAcrossGaps: false,
|
|
645
|
+
groupKey: ScheduleShiftUtil.groupKeyByUserOverrideAndLayer,
|
|
646
|
+
},
|
|
647
|
+
);
|
|
648
|
+
|
|
649
|
+
/*
|
|
650
|
+
* Seam normalisation widens a shift by the engine's one-second seams;
|
|
651
|
+
* with mergeAcrossGaps = false every shift is one contiguous block, so
|
|
652
|
+
* its honest active time is exactly its span. Recompute rather than
|
|
653
|
+
* carry the pre-normalisation sum, which would read 28799 for an 8-hour
|
|
654
|
+
* block.
|
|
655
|
+
*/
|
|
656
|
+
return ShiftSeamUtil.normalizeSeams(grouped)
|
|
657
|
+
.filter((shift: OnCallShift) => {
|
|
658
|
+
return shift.end.getTime() > shift.start.getTime();
|
|
659
|
+
})
|
|
660
|
+
.map((shift: OnCallShift) => {
|
|
661
|
+
return {
|
|
662
|
+
...shift,
|
|
663
|
+
coverageSeconds: Math.round(
|
|
664
|
+
(shift.end.getTime() - shift.start.getTime()) / 1000,
|
|
665
|
+
),
|
|
666
|
+
};
|
|
667
|
+
});
|
|
668
|
+
}
|
|
669
|
+
|
|
670
|
+
/*
|
|
671
|
+
* The variant shifts during which a DIFFERENT person is paged than in the
|
|
672
|
+
* base resolution. A variant shift whose whole span is covered by base
|
|
673
|
+
* shifts of the same user is identical for the subscriber and dropped; one
|
|
674
|
+
* that overlaps a base shift of another user (or no base shift at all) is
|
|
675
|
+
* kept, together with who the base resolution pages at its start.
|
|
676
|
+
*/
|
|
677
|
+
public static diffPolicyVariant(
|
|
678
|
+
baseShifts: Array<OnCallShift>,
|
|
679
|
+
variantShifts: Array<OnCallShift>,
|
|
680
|
+
): Array<{ shift: OnCallShift; globalUserId: string }> {
|
|
681
|
+
const result: Array<{ shift: OnCallShift; globalUserId: string }> = [];
|
|
682
|
+
|
|
683
|
+
for (const variant of variantShifts) {
|
|
684
|
+
const overlapping: Array<OnCallShift> = baseShifts.filter(
|
|
685
|
+
(base: OnCallShift) => {
|
|
686
|
+
return (
|
|
687
|
+
base.start.getTime() < variant.end.getTime() &&
|
|
688
|
+
base.end.getTime() > variant.start.getTime()
|
|
689
|
+
);
|
|
690
|
+
},
|
|
691
|
+
);
|
|
692
|
+
|
|
693
|
+
const differingBase: OnCallShift | undefined = overlapping.find(
|
|
694
|
+
(base: OnCallShift) => {
|
|
695
|
+
return base.userId !== variant.userId;
|
|
696
|
+
},
|
|
697
|
+
);
|
|
698
|
+
|
|
699
|
+
if (overlapping.length > 0 && !differingBase) {
|
|
700
|
+
continue; // same person throughout: nothing policy-specific here.
|
|
701
|
+
}
|
|
702
|
+
|
|
703
|
+
const globalUserId: string =
|
|
704
|
+
differingBase?.userId ||
|
|
705
|
+
variant.override?.originalUserId ||
|
|
706
|
+
variant.userId;
|
|
707
|
+
|
|
708
|
+
result.push({ shift: variant, globalUserId });
|
|
709
|
+
}
|
|
710
|
+
|
|
711
|
+
return result;
|
|
712
|
+
}
|
|
713
|
+
|
|
714
|
+
/** Keep only the shifts where `userId` is the person on call. */
|
|
715
|
+
public static filterShiftsForUser(
|
|
716
|
+
shifts: Array<MaterializedShift>,
|
|
717
|
+
userId: ObjectID | string,
|
|
718
|
+
): Array<MaterializedShift> {
|
|
719
|
+
const wanted: string = userId.toString();
|
|
720
|
+
return shifts.filter((shift: MaterializedShift) => {
|
|
721
|
+
return shift.userId === wanted;
|
|
722
|
+
});
|
|
723
|
+
}
|
|
724
|
+
|
|
725
|
+
/*
|
|
726
|
+
* sha256 over every field that changes what a subscriber sees for this
|
|
727
|
+
* shift, excluding the identity (shiftKey), the version and the
|
|
728
|
+
* modification stamp, so two renders of an unchanged shift hash the same
|
|
729
|
+
* and any content change — the person, the end, the layer, the policies,
|
|
730
|
+
* the override provenance — changes the hash.
|
|
731
|
+
*/
|
|
732
|
+
public static computeContentHash(
|
|
733
|
+
shift: Omit<
|
|
734
|
+
MaterializedShift,
|
|
735
|
+
"contentHash" | "shiftKey" | "lastModifiedAt" | "shiftConfigVersion"
|
|
736
|
+
>,
|
|
737
|
+
): string {
|
|
738
|
+
const payload: Record<string, unknown> = {
|
|
739
|
+
scheduleId: shift.scheduleId,
|
|
740
|
+
scheduleName: shift.scheduleName,
|
|
741
|
+
scheduleTimezone: shift.scheduleTimezone ?? null,
|
|
742
|
+
projectName: shift.projectName ?? null,
|
|
743
|
+
userId: shift.userId,
|
|
744
|
+
userName: shift.userName,
|
|
745
|
+
start: shift.start.toISOString(),
|
|
746
|
+
end: shift.end.toISOString(),
|
|
747
|
+
coverageSeconds: shift.coverageSeconds,
|
|
748
|
+
layerId: shift.layerId ?? null,
|
|
749
|
+
layerName: shift.layerName ?? null,
|
|
750
|
+
override: shift.override
|
|
751
|
+
? {
|
|
752
|
+
originalUserId: shift.override.originalUserId,
|
|
753
|
+
originalUserName: shift.override.originalUserName,
|
|
754
|
+
overrideStartsAt: shift.override.overrideStartsAt.toISOString(),
|
|
755
|
+
overrideEndsAt: shift.override.overrideEndsAt.toISOString(),
|
|
756
|
+
onCallDutyPolicyId: shift.override.onCallDutyPolicyId ?? null,
|
|
757
|
+
}
|
|
758
|
+
: null,
|
|
759
|
+
policyVariantOf: shift.policyVariantOf
|
|
760
|
+
? {
|
|
761
|
+
policyId: shift.policyVariantOf.policyId,
|
|
762
|
+
policyName: shift.policyVariantOf.policyName,
|
|
763
|
+
globalUserId: shift.policyVariantOf.globalUserId,
|
|
764
|
+
}
|
|
765
|
+
: null,
|
|
766
|
+
policies: shift.policies.map((policy: MaterializedShiftPolicy) => {
|
|
767
|
+
return {
|
|
768
|
+
policyId: policy.policyId,
|
|
769
|
+
policyName: policy.policyName,
|
|
770
|
+
ruleId: policy.ruleId,
|
|
771
|
+
ruleName: policy.ruleName,
|
|
772
|
+
ruleOrder: policy.ruleOrder,
|
|
773
|
+
};
|
|
774
|
+
}),
|
|
775
|
+
};
|
|
776
|
+
|
|
777
|
+
return crypto
|
|
778
|
+
.createHash("sha256")
|
|
779
|
+
.update(JSON.stringify(payload), "utf8")
|
|
780
|
+
.digest("hex");
|
|
781
|
+
}
|
|
782
|
+
|
|
783
|
+
// -- Internals ---------------------------------------------------------
|
|
784
|
+
|
|
785
|
+
private static toMaterializedShift(data: {
|
|
786
|
+
shift: OnCallShift;
|
|
787
|
+
resolved: ResolvedShiftSegments;
|
|
788
|
+
options: MaterializeResolvedOptions;
|
|
789
|
+
policyVariant: {
|
|
790
|
+
policyId: string;
|
|
791
|
+
policyName: string;
|
|
792
|
+
globalUserId: string;
|
|
793
|
+
} | null;
|
|
794
|
+
}): MaterializedShift {
|
|
795
|
+
const { shift, resolved, options } = data;
|
|
796
|
+
|
|
797
|
+
const base: Omit<
|
|
798
|
+
MaterializedShift,
|
|
799
|
+
"contentHash" | "shiftKey" | "lastModifiedAt" | "shiftConfigVersion"
|
|
800
|
+
> = {
|
|
801
|
+
projectId: resolved.schedule.projectId,
|
|
802
|
+
scheduleId: resolved.schedule.id,
|
|
803
|
+
scheduleName: resolved.schedule.name,
|
|
804
|
+
userId: shift.userId,
|
|
805
|
+
userName: OnCallShiftMaterializer.displayName(
|
|
806
|
+
options.users,
|
|
807
|
+
shift.userId,
|
|
808
|
+
),
|
|
809
|
+
start: shift.start,
|
|
810
|
+
end: shift.end,
|
|
811
|
+
coverageSeconds: shift.coverageSeconds,
|
|
812
|
+
policies: resolved.attachedPolicies.map(
|
|
813
|
+
(policy: MaterializedShiftPolicy) => {
|
|
814
|
+
return { ...policy };
|
|
815
|
+
},
|
|
816
|
+
),
|
|
817
|
+
isPast: shift.start.getTime() < options.now.getTime(),
|
|
818
|
+
};
|
|
819
|
+
|
|
820
|
+
if (options.projectName !== undefined) {
|
|
821
|
+
base.projectName = options.projectName;
|
|
822
|
+
}
|
|
823
|
+
|
|
824
|
+
if (resolved.schedule.timezone !== undefined) {
|
|
825
|
+
base.scheduleTimezone = resolved.schedule.timezone;
|
|
826
|
+
}
|
|
827
|
+
|
|
828
|
+
if (shift.layerId !== undefined) {
|
|
829
|
+
base.layerId = shift.layerId;
|
|
830
|
+
}
|
|
831
|
+
|
|
832
|
+
if (shift.layerName !== undefined) {
|
|
833
|
+
base.layerName = shift.layerName;
|
|
834
|
+
}
|
|
835
|
+
|
|
836
|
+
if (shift.override) {
|
|
837
|
+
const override: MaterializedShiftOverride = {
|
|
838
|
+
originalUserId: shift.override.originalUserId,
|
|
839
|
+
originalUserName: OnCallShiftMaterializer.displayName(
|
|
840
|
+
options.users,
|
|
841
|
+
shift.override.originalUserId,
|
|
842
|
+
),
|
|
843
|
+
overrideStartsAt: new Date(shift.override.overrideStartsAt.getTime()),
|
|
844
|
+
overrideEndsAt: new Date(shift.override.overrideEndsAt.getTime()),
|
|
845
|
+
};
|
|
846
|
+
|
|
847
|
+
const policyId: string | null =
|
|
848
|
+
OnCallShiftMaterializer.findOverridePolicyId(resolved.overrides, {
|
|
849
|
+
originalUserId: shift.override.originalUserId,
|
|
850
|
+
substituteUserId: shift.userId,
|
|
851
|
+
startsAt: shift.override.overrideStartsAt,
|
|
852
|
+
endsAt: shift.override.overrideEndsAt,
|
|
853
|
+
preferredPolicyId: data.policyVariant?.policyId,
|
|
854
|
+
});
|
|
855
|
+
|
|
856
|
+
if (policyId) {
|
|
857
|
+
override.onCallDutyPolicyId = policyId;
|
|
858
|
+
}
|
|
859
|
+
|
|
860
|
+
base.override = override;
|
|
861
|
+
}
|
|
862
|
+
|
|
863
|
+
if (data.policyVariant) {
|
|
864
|
+
base.policyVariantOf = {
|
|
865
|
+
policyId: data.policyVariant.policyId,
|
|
866
|
+
policyName: data.policyVariant.policyName,
|
|
867
|
+
globalUserId: data.policyVariant.globalUserId,
|
|
868
|
+
};
|
|
869
|
+
}
|
|
870
|
+
|
|
871
|
+
return {
|
|
872
|
+
...base,
|
|
873
|
+
shiftKey: MaterializedShiftUtil.buildShiftKey({
|
|
874
|
+
scheduleId: resolved.schedule.id,
|
|
875
|
+
start: shift.start,
|
|
876
|
+
policyId: data.policyVariant?.policyId,
|
|
877
|
+
}),
|
|
878
|
+
contentHash: OnCallShiftMaterializer.computeContentHash(base),
|
|
879
|
+
lastModifiedAt: resolved.lastModifiedAt,
|
|
880
|
+
shiftConfigVersion: resolved.schedule.shiftConfigVersion,
|
|
881
|
+
};
|
|
882
|
+
}
|
|
883
|
+
|
|
884
|
+
/*
|
|
885
|
+
* Map a segment's override meta back to the override record that produced
|
|
886
|
+
* it, to learn its policy scope. Prefers a record scoped to
|
|
887
|
+
* `preferredPolicyId` (the variant being materialized); falls back to any
|
|
888
|
+
* record with the same users and window.
|
|
889
|
+
*/
|
|
890
|
+
private static findOverridePolicyId(
|
|
891
|
+
overrides: Array<UserOverrideRecord>,
|
|
892
|
+
data: {
|
|
893
|
+
originalUserId: string;
|
|
894
|
+
substituteUserId: string;
|
|
895
|
+
startsAt: Date;
|
|
896
|
+
endsAt: Date;
|
|
897
|
+
preferredPolicyId?: string | undefined;
|
|
898
|
+
},
|
|
899
|
+
): string | null {
|
|
900
|
+
const matches: Array<UserOverrideRecord> = overrides.filter(
|
|
901
|
+
(record: UserOverrideRecord) => {
|
|
902
|
+
return (
|
|
903
|
+
record.overrideUserId === data.originalUserId &&
|
|
904
|
+
record.routeAlertsToUserId === data.substituteUserId &&
|
|
905
|
+
record.startsAt.getTime() === data.startsAt.getTime() &&
|
|
906
|
+
record.endsAt.getTime() === data.endsAt.getTime()
|
|
907
|
+
);
|
|
908
|
+
},
|
|
909
|
+
);
|
|
910
|
+
|
|
911
|
+
if (matches.length === 0) {
|
|
912
|
+
return null;
|
|
913
|
+
}
|
|
914
|
+
|
|
915
|
+
if (data.preferredPolicyId) {
|
|
916
|
+
const preferred: UserOverrideRecord | undefined = matches.find(
|
|
917
|
+
(record: UserOverrideRecord) => {
|
|
918
|
+
return record.onCallDutyPolicyId === data.preferredPolicyId;
|
|
919
|
+
},
|
|
920
|
+
);
|
|
921
|
+
if (preferred) {
|
|
922
|
+
return preferred.onCallDutyPolicyId || null;
|
|
923
|
+
}
|
|
924
|
+
}
|
|
925
|
+
|
|
926
|
+
// A global match wins over a scoped one for the base resolution.
|
|
927
|
+
const global: UserOverrideRecord | undefined = matches.find(
|
|
928
|
+
(record: UserOverrideRecord) => {
|
|
929
|
+
return !record.onCallDutyPolicyId;
|
|
930
|
+
},
|
|
931
|
+
);
|
|
932
|
+
|
|
933
|
+
if (global) {
|
|
934
|
+
return null;
|
|
935
|
+
}
|
|
936
|
+
|
|
937
|
+
return matches[0]!.onCallDutyPolicyId || null;
|
|
938
|
+
}
|
|
939
|
+
|
|
940
|
+
private static displayName(
|
|
941
|
+
users: Map<string, MaterializedUserInfo>,
|
|
942
|
+
userId: string,
|
|
943
|
+
): string {
|
|
944
|
+
return (
|
|
945
|
+
users.get(userId)?.userName || OnCallCalendarFeedUtil.FALLBACK_USER_NAME
|
|
946
|
+
);
|
|
947
|
+
}
|
|
948
|
+
|
|
949
|
+
private static collectUserIds(
|
|
950
|
+
resolved: Array<ResolvedShiftSegments>,
|
|
951
|
+
): Array<string> {
|
|
952
|
+
const ids: Set<string> = new Set<string>();
|
|
953
|
+
|
|
954
|
+
for (const item of resolved) {
|
|
955
|
+
for (const segment of item.segments) {
|
|
956
|
+
if (segment.title) {
|
|
957
|
+
ids.add(segment.title);
|
|
958
|
+
}
|
|
959
|
+
}
|
|
960
|
+
for (const variant of item.policyVariants) {
|
|
961
|
+
for (const segment of variant.segments) {
|
|
962
|
+
if (segment.title) {
|
|
963
|
+
ids.add(segment.title);
|
|
964
|
+
}
|
|
965
|
+
}
|
|
966
|
+
}
|
|
967
|
+
for (const override of item.overrides) {
|
|
968
|
+
if (override.overrideUserId) {
|
|
969
|
+
ids.add(override.overrideUserId);
|
|
970
|
+
}
|
|
971
|
+
if (override.routeAlertsToUserId) {
|
|
972
|
+
ids.add(override.routeAlertsToUserId);
|
|
973
|
+
}
|
|
974
|
+
}
|
|
975
|
+
for (const userId of item.scheduleUserIds) {
|
|
976
|
+
ids.add(userId);
|
|
977
|
+
}
|
|
978
|
+
}
|
|
979
|
+
|
|
980
|
+
return Array.from(ids);
|
|
981
|
+
}
|
|
982
|
+
|
|
983
|
+
/*
|
|
984
|
+
* One root user lookup for names and timezones. Name falls back to email;
|
|
985
|
+
* the feed mapper masks emails where they must not appear (shared feeds).
|
|
986
|
+
*/
|
|
987
|
+
private static async loadUsers(
|
|
988
|
+
userIds: Array<string>,
|
|
989
|
+
): Promise<Map<string, MaterializedUserInfo>> {
|
|
990
|
+
const users: Map<string, MaterializedUserInfo> = new Map<
|
|
991
|
+
string,
|
|
992
|
+
MaterializedUserInfo
|
|
993
|
+
>();
|
|
994
|
+
|
|
995
|
+
if (userIds.length === 0) {
|
|
996
|
+
return users;
|
|
997
|
+
}
|
|
998
|
+
|
|
999
|
+
const rows: Array<User> = await UserService.findBy({
|
|
1000
|
+
query: {
|
|
1001
|
+
_id: QueryHelper.any(userIds),
|
|
1002
|
+
},
|
|
1003
|
+
select: {
|
|
1004
|
+
_id: true,
|
|
1005
|
+
name: true,
|
|
1006
|
+
email: true,
|
|
1007
|
+
timezone: true,
|
|
1008
|
+
},
|
|
1009
|
+
limit: LIMIT_PER_PROJECT,
|
|
1010
|
+
skip: 0,
|
|
1011
|
+
props: {
|
|
1012
|
+
isRoot: true,
|
|
1013
|
+
},
|
|
1014
|
+
});
|
|
1015
|
+
|
|
1016
|
+
for (const row of rows) {
|
|
1017
|
+
const userId: string | undefined = row.id?.toString();
|
|
1018
|
+
if (!userId) {
|
|
1019
|
+
continue;
|
|
1020
|
+
}
|
|
1021
|
+
|
|
1022
|
+
const name: string = row.name?.toString().trim() || "";
|
|
1023
|
+
const email: string = row.email?.toString().trim() || "";
|
|
1024
|
+
const timezone: string = row.timezone?.toString() || "";
|
|
1025
|
+
|
|
1026
|
+
const info: MaterializedUserInfo = {
|
|
1027
|
+
userId,
|
|
1028
|
+
userName: name || email || OnCallCalendarFeedUtil.FALLBACK_USER_NAME,
|
|
1029
|
+
};
|
|
1030
|
+
|
|
1031
|
+
if (email) {
|
|
1032
|
+
info.email = email;
|
|
1033
|
+
}
|
|
1034
|
+
|
|
1035
|
+
if (timezone) {
|
|
1036
|
+
info.timezone = timezone;
|
|
1037
|
+
}
|
|
1038
|
+
|
|
1039
|
+
users.set(userId, info);
|
|
1040
|
+
}
|
|
1041
|
+
|
|
1042
|
+
return users;
|
|
1043
|
+
}
|
|
1044
|
+
|
|
1045
|
+
private static async loadProjectNames(
|
|
1046
|
+
projectIds: Array<string>,
|
|
1047
|
+
): Promise<Map<string, string>> {
|
|
1048
|
+
const names: Map<string, string> = new Map<string, string>();
|
|
1049
|
+
const distinct: Array<string> = Array.from(
|
|
1050
|
+
new Set<string>(
|
|
1051
|
+
projectIds.filter((id: string) => {
|
|
1052
|
+
return Boolean(id);
|
|
1053
|
+
}),
|
|
1054
|
+
),
|
|
1055
|
+
);
|
|
1056
|
+
|
|
1057
|
+
if (distinct.length === 0) {
|
|
1058
|
+
return names;
|
|
1059
|
+
}
|
|
1060
|
+
|
|
1061
|
+
const projects: Array<Project> = await ProjectService.findBy({
|
|
1062
|
+
query: {
|
|
1063
|
+
_id: QueryHelper.any(distinct),
|
|
1064
|
+
},
|
|
1065
|
+
select: {
|
|
1066
|
+
_id: true,
|
|
1067
|
+
name: true,
|
|
1068
|
+
},
|
|
1069
|
+
limit: LIMIT_PER_PROJECT,
|
|
1070
|
+
skip: 0,
|
|
1071
|
+
props: {
|
|
1072
|
+
isRoot: true,
|
|
1073
|
+
},
|
|
1074
|
+
});
|
|
1075
|
+
|
|
1076
|
+
for (const project of projects) {
|
|
1077
|
+
const id: string | undefined = project.id?.toString();
|
|
1078
|
+
if (id && project.name) {
|
|
1079
|
+
names.set(id, project.name);
|
|
1080
|
+
}
|
|
1081
|
+
}
|
|
1082
|
+
|
|
1083
|
+
return names;
|
|
1084
|
+
}
|
|
1085
|
+
|
|
1086
|
+
private static validateWindow(windowStart: Date, windowEnd: Date): void {
|
|
1087
|
+
if (
|
|
1088
|
+
!(windowStart instanceof Date) ||
|
|
1089
|
+
!(windowEnd instanceof Date) ||
|
|
1090
|
+
Number.isNaN(windowStart.getTime()) ||
|
|
1091
|
+
Number.isNaN(windowEnd.getTime())
|
|
1092
|
+
) {
|
|
1093
|
+
throw new BadDataException("windowStart and windowEnd must be dates");
|
|
1094
|
+
}
|
|
1095
|
+
|
|
1096
|
+
if (windowStart.getTime() >= windowEnd.getTime()) {
|
|
1097
|
+
throw new BadDataException("windowStart must be before windowEnd");
|
|
1098
|
+
}
|
|
1099
|
+
}
|
|
1100
|
+
|
|
1101
|
+
private static emptyResult(now: Date): MaterializeResult {
|
|
1102
|
+
return {
|
|
1103
|
+
shifts: [],
|
|
1104
|
+
truncated: false,
|
|
1105
|
+
schedules: [],
|
|
1106
|
+
users: [],
|
|
1107
|
+
generatedAt: now,
|
|
1108
|
+
};
|
|
1109
|
+
}
|
|
1110
|
+
|
|
1111
|
+
private static dedupeIds(ids: Array<ObjectID>): Array<ObjectID> {
|
|
1112
|
+
const seen: Set<string> = new Set<string>();
|
|
1113
|
+
const result: Array<ObjectID> = [];
|
|
1114
|
+
for (const id of ids) {
|
|
1115
|
+
if (!id) {
|
|
1116
|
+
continue;
|
|
1117
|
+
}
|
|
1118
|
+
const key: string = id.toString();
|
|
1119
|
+
if (!key || seen.has(key)) {
|
|
1120
|
+
continue;
|
|
1121
|
+
}
|
|
1122
|
+
seen.add(key);
|
|
1123
|
+
result.push(id);
|
|
1124
|
+
}
|
|
1125
|
+
return result;
|
|
1126
|
+
}
|
|
1127
|
+
}
|