@oneuptime/common 11.3.26 → 11.4.0
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/AIRun.ts +50 -0
- package/Models/DatabaseModels/IncidentEpisodeRoleMember.ts +1 -1
- package/Models/DatabaseModels/IncidentMember.ts +1 -1
- package/Models/DatabaseModels/IncomingCallPolicy.ts +13 -0
- package/Models/DatabaseModels/Index.ts +3 -0
- package/Models/DatabaseModels/MigrationFailure.ts +170 -0
- package/Models/DatabaseModels/OnCallDutyPolicyExecutionLog.ts +51 -2
- package/Models/DatabaseModels/OnCallDutyPolicySchedule.ts +44 -0
- package/Models/DatabaseModels/OnCallDutyPolicyScheduleLayer.ts +6 -0
- package/Models/DatabaseModels/OnCallDutyPolicyScheduleLayerUser.ts +6 -0
- package/Models/DatabaseModels/OnCallDutyPolicyTimeLog.ts +1 -1
- package/Models/DatabaseModels/OnCallDutyPolicyUserOverride.ts +2 -2
- package/Models/DatabaseModels/Project.ts +60 -0
- package/Server/API/AIInvestigationAPI.ts +148 -0
- package/Server/API/AlertAPI.ts +1 -1
- package/Server/API/IncidentAPI.ts +2 -2
- package/Server/API/IncidentEpisodeAPI.ts +1 -1
- package/Server/API/LlmProviderAPI.ts +1 -0
- package/Server/API/ScheduledMaintenanceAPI.ts +1 -1
- package/Server/Infrastructure/Postgres/SchemaMigrations/1783470000000-AddIncomingCallPolicyRoutingPhoneNumberUnique.ts +25 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/1783510935686-FixNotNullForeignKeysOnDelete.ts +81 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/1783515836148-MigrationName.ts +25 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/1783523076215-AddMigrationFailureTable.ts +35 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/1783598145111-AddEnableAutomaticIncidentInvestigationToProject.ts +19 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/1783615884106-AddInvestigationSubjectToAIRun.ts +33 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/1783632447975-AddEnableAutomaticAlertInvestigationToProject.ts +19 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/Index.ts +14 -0
- package/Server/Infrastructure/PostgresDatabase.ts +11 -0
- package/Server/Services/AIService.ts +74 -2
- package/Server/Services/AlertService.ts +25 -0
- package/Server/Services/AnalyticsDatabaseService.ts +10 -1
- package/Server/Services/DatabaseService.ts +10 -1
- package/Server/Services/IncidentService.ts +26 -1
- package/Server/Services/IncidentStateTimelineService.ts +19 -0
- package/Server/Services/IncomingCallPolicyEscalationRuleService.ts +75 -2
- package/Server/Services/IncomingCallPolicyService.ts +42 -1
- package/Server/Services/Index.ts +2 -0
- package/Server/Services/MigrationFailureService.ts +9 -0
- package/Server/Services/MonitorProbeService.ts +33 -7
- package/Server/Services/MonitorService.ts +19 -7
- package/Server/Services/OnCallDutyPolicyEscalationRuleScheduleService.ts +65 -0
- package/Server/Services/OnCallDutyPolicyEscalationRuleService.ts +185 -41
- package/Server/Services/OnCallDutyPolicyEscalationRuleUserService.ts +23 -15
- package/Server/Services/OnCallDutyPolicyExecutionLogService.ts +39 -0
- package/Server/Services/OnCallDutyPolicyScheduleLayerService.ts +148 -0
- package/Server/Services/OnCallDutyPolicyScheduleLayerUserService.ts +11 -2
- package/Server/Services/OnCallDutyPolicyScheduleService.ts +502 -26
- package/Server/Services/OnCallDutyPolicyTimeLogService.ts +49 -7
- package/Server/Services/OnCallDutyPolicyUserOverrideService.ts +218 -4
- package/Server/Services/ProjectCallSMSConfigService.ts +82 -1
- package/Server/Services/TeamMemberService.ts +6 -0
- package/Server/Services/UserNotificationRuleService.ts +13 -42
- package/Server/Services/UserOnCallLogService.ts +90 -0
- package/Server/Services/UserOnCallLogTimelineService.ts +43 -1
- package/Server/Types/Database/QueryUtil.ts +57 -0
- package/Server/Utils/AI/AlertAIContextBuilder.ts +6 -1
- package/Server/Utils/AI/Chat/ChatAgentRunner.ts +14 -2
- package/Server/Utils/AI/Chat/ObservabilityAssistant.ts +119 -11
- package/Server/Utils/AI/IncidentAIContextBuilder.ts +18 -3
- package/Server/Utils/AI/IncidentEpisodeAIContextBuilder.ts +6 -1
- package/Server/Utils/AI/ScheduledMaintenanceAIContextBuilder.ts +12 -2
- package/Server/Utils/AI/Sentinel/AlertInvestigationRunner.ts +141 -0
- package/Server/Utils/AI/Sentinel/IncidentInvestigationRunner.ts +178 -0
- package/Server/Utils/AI/Sentinel/IncidentPostmortemRunner.ts +105 -0
- package/Server/Utils/AI/Sentinel/SentinelInvestigationEngine.ts +405 -0
- package/Server/Utils/AI/Sentinel/SentinelMemory.ts +167 -0
- package/Server/Utils/AI/Toolbox/IncidentTools.ts +183 -1
- package/Server/Utils/AI/Toolbox/Index.ts +15 -1
- package/Server/Utils/AI/Toolbox/RecentChangesTools.ts +208 -0
- package/Server/Utils/AI/Toolbox/SentinelActionTools.ts +577 -0
- package/Server/Utils/AI/Toolbox/Serializer.ts +42 -0
- package/Server/Utils/Database/MigrationFailureLog.ts +240 -0
- package/Server/Utils/IncomingCallPhoneNumber.ts +41 -0
- package/Server/Utils/LLM/LLMService.ts +110 -11
- package/Server/Utils/Telemetry/IoTSnapshotScan.ts +12 -9
- package/Server/Utils/Telemetry/ProxmoxCephSnapshotScan.ts +12 -9
- package/Tests/Server/API/BaseAPI.test.ts +1 -0
- package/Tests/Server/Services/AnalyticsDatabaseService.test.ts +0 -8
- package/Tests/Server/Services/OnCallDutyPolicyScheduleLayerUserReorder.test.ts +233 -0
- package/Tests/Server/Services/OnCallDutyPolicyTimeLogServiceScoping.test.ts +242 -0
- package/Tests/Server/Services/OnCallDutyPolicyUserOverrideEdit.test.ts +198 -0
- package/Tests/Server/Services/UserOnCallLogClaimNotificationRule.test.ts +495 -0
- package/Tests/Server/Types/Database/QueryUtil.test.ts +74 -0
- package/Tests/Server/Utils/AI/LLMServiceToolCalling.test.ts +78 -0
- package/Tests/Server/Utils/AI/ToolResultSerializer.test.ts +68 -0
- package/Tests/Types/DateExhaustiveTimezone.test.ts +700 -0
- package/Tests/Types/DateTimezoneWallClock.test.ts +99 -0
- package/Tests/Types/Events/Recurring.test.ts +22 -9
- package/Tests/Types/OnCallDutyPolicy/LayerUtilAuditFixes.test.ts +291 -0
- package/Tests/Types/OnCallDutyPolicy/LayerUtilAuditFixesRound2.test.ts +398 -0
- package/Tests/Types/OnCallDutyPolicy/LayerUtilDSTWeekendGap.test.ts +114 -0
- package/Tests/Types/OnCallDutyPolicy/LayerUtilDSTWeeklyDayShift.test.ts +76 -0
- package/Tests/Types/OnCallDutyPolicy/LayerUtilDailyMutation.test.ts +128 -0
- package/Tests/Types/OnCallDutyPolicy/LayerUtilDailyProbe.test.ts +203 -0
- package/Tests/Types/OnCallDutyPolicy/LayerUtilDiffFuzz.test.ts +322 -0
- package/Tests/Types/OnCallDutyPolicy/LayerUtilEdgeCases.test.ts +280 -0
- package/Tests/Types/OnCallDutyPolicy/LayerUtilExhaustiveCurrentUser.test.ts +863 -0
- package/Tests/Types/OnCallDutyPolicy/LayerUtilExhaustiveDaily.test.ts +900 -0
- package/Tests/Types/OnCallDutyPolicy/LayerUtilExhaustiveMultiLayer.test.ts +1104 -0
- package/Tests/Types/OnCallDutyPolicy/LayerUtilExhaustiveRotation.test.ts +957 -0
- package/Tests/Types/OnCallDutyPolicy/LayerUtilExhaustiveWeekly.test.ts +1095 -0
- package/Tests/Types/OnCallDutyPolicy/LayerUtilInvariants.test.ts +232 -0
- package/Tests/Types/OnCallDutyPolicy/LayerUtilMergeAudit.test.ts +227 -0
- package/Tests/Types/OnCallDutyPolicy/LayerUtilMonthYearAudit.test.ts +255 -0
- package/Tests/Types/OnCallDutyPolicy/LayerUtilMultiLayerAudit.test.ts +388 -0
- package/Tests/Types/OnCallDutyPolicy/LayerUtilMultiLayerEdge.test.ts +300 -0
- package/Tests/Types/OnCallDutyPolicy/LayerUtilOvernightDSTAudit.test.ts +158 -0
- package/Tests/Types/OnCallDutyPolicy/LayerUtilRestrictedGapFix.test.ts +253 -0
- package/Tests/Types/OnCallDutyPolicy/LayerUtilRestrictedGapRepro.test.ts +157 -0
- package/Tests/Types/OnCallDutyPolicy/LayerUtilRotationFixes.test.ts +414 -0
- package/Tests/Types/OnCallDutyPolicy/LayerUtilTimezone.test.ts +243 -0
- package/Tests/Types/OnCallDutyPolicy/LayerUtilWeekendGapRepro.test.ts +112 -0
- package/Tests/Types/OnCallDutyPolicy/OverrideLensAudit.test.ts +117 -0
- package/Tests/Types/OnCallDutyPolicy/RestrictionTimes.test.ts +8 -6
- package/Tests/Types/OnCallDutyPolicy/RestrictionTimesDefaultDayAudit.test.ts +112 -0
- package/Tests/Types/OnCallDutyPolicy/RestrictionTimesExhaustive.test.ts +821 -0
- package/Tests/Types/OnCallDutyPolicy/RestrictionTimesMutationInvariant.test.ts +886 -0
- package/Tests/Types/OnCallDutyPolicy/ScheduleShiftUtil.test.ts +260 -0
- package/Tests/Types/OnCallDutyPolicy/UserOverrideUtil.test.ts +255 -0
- package/Tests/Types/OnCallDutyPolicy/UserOverrideUtilExhaustive.test.ts +1126 -0
- package/Types/Date.ts +246 -15
- package/Types/OnCallDutyPolicy/Layer.ts +790 -260
- package/Types/OnCallDutyPolicy/RestrictionTimes.ts +39 -13
- package/Types/OnCallDutyPolicy/ScheduleShiftUtil.ts +257 -0
- package/Types/OnCallDutyPolicy/UserOverrideUtil.ts +21 -5
- package/UI/Components/Calendar/Calendar.tsx +8 -2
- package/UI/Components/Charts/ChartGroup/ChartGroup.tsx +5 -5
- package/UI/Components/Events/RecurringFieldElement.tsx +16 -0
- package/UI/Components/ModelFormModal/ModelFormModal.tsx +8 -0
- package/UI/Components/RadioButtons/BasicRadioButtons.tsx +1 -1
- package/build/dist/Models/DatabaseModels/AIRun.js +52 -0
- package/build/dist/Models/DatabaseModels/AIRun.js.map +1 -1
- package/build/dist/Models/DatabaseModels/IncidentEpisodeRoleMember.js +1 -1
- package/build/dist/Models/DatabaseModels/IncidentEpisodeRoleMember.js.map +1 -1
- package/build/dist/Models/DatabaseModels/IncidentMember.js +1 -1
- package/build/dist/Models/DatabaseModels/IncidentMember.js.map +1 -1
- package/build/dist/Models/DatabaseModels/IncomingCallPolicy.js +10 -0
- package/build/dist/Models/DatabaseModels/IncomingCallPolicy.js.map +1 -1
- package/build/dist/Models/DatabaseModels/Index.js +2 -0
- package/build/dist/Models/DatabaseModels/Index.js.map +1 -1
- package/build/dist/Models/DatabaseModels/MigrationFailure.js +196 -0
- package/build/dist/Models/DatabaseModels/MigrationFailure.js.map +1 -0
- package/build/dist/Models/DatabaseModels/OnCallDutyPolicyExecutionLog.js +52 -2
- package/build/dist/Models/DatabaseModels/OnCallDutyPolicyExecutionLog.js.map +1 -1
- package/build/dist/Models/DatabaseModels/OnCallDutyPolicySchedule.js +45 -0
- package/build/dist/Models/DatabaseModels/OnCallDutyPolicySchedule.js.map +1 -1
- package/build/dist/Models/DatabaseModels/OnCallDutyPolicyScheduleLayer.js +6 -0
- package/build/dist/Models/DatabaseModels/OnCallDutyPolicyScheduleLayer.js.map +1 -1
- package/build/dist/Models/DatabaseModels/OnCallDutyPolicyScheduleLayerUser.js +6 -0
- package/build/dist/Models/DatabaseModels/OnCallDutyPolicyScheduleLayerUser.js.map +1 -1
- package/build/dist/Models/DatabaseModels/OnCallDutyPolicyTimeLog.js +1 -1
- package/build/dist/Models/DatabaseModels/OnCallDutyPolicyTimeLog.js.map +1 -1
- package/build/dist/Models/DatabaseModels/OnCallDutyPolicyUserOverride.js +2 -2
- package/build/dist/Models/DatabaseModels/OnCallDutyPolicyUserOverride.js.map +1 -1
- package/build/dist/Models/DatabaseModels/Project.js +62 -0
- package/build/dist/Models/DatabaseModels/Project.js.map +1 -1
- package/build/dist/Server/API/AIInvestigationAPI.js +107 -0
- package/build/dist/Server/API/AIInvestigationAPI.js.map +1 -0
- package/build/dist/Server/API/AlertAPI.js +1 -1
- package/build/dist/Server/API/IncidentAPI.js +2 -2
- package/build/dist/Server/API/IncidentEpisodeAPI.js +1 -1
- package/build/dist/Server/API/LlmProviderAPI.js +1 -0
- package/build/dist/Server/API/LlmProviderAPI.js.map +1 -1
- package/build/dist/Server/API/ScheduledMaintenanceAPI.js +1 -1
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1783470000000-AddIncomingCallPolicyRoutingPhoneNumberUnique.js +18 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1783470000000-AddIncomingCallPolicyRoutingPhoneNumberUnique.js.map +1 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1783510935686-FixNotNullForeignKeysOnDelete.js +38 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1783510935686-FixNotNullForeignKeysOnDelete.js.map +1 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1783515836148-MigrationName.js +16 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1783515836148-MigrationName.js.map +1 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1783523076215-AddMigrationFailureTable.js +18 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1783523076215-AddMigrationFailureTable.js.map +1 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1783598145111-AddEnableAutomaticIncidentInvestigationToProject.js +12 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1783598145111-AddEnableAutomaticIncidentInvestigationToProject.js.map +1 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1783615884106-AddInvestigationSubjectToAIRun.js +18 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1783615884106-AddInvestigationSubjectToAIRun.js.map +1 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1783632447975-AddEnableAutomaticAlertInvestigationToProject.js +12 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1783632447975-AddEnableAutomaticAlertInvestigationToProject.js.map +1 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js +14 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js.map +1 -1
- package/build/dist/Server/Infrastructure/PostgresDatabase.js +9 -0
- package/build/dist/Server/Infrastructure/PostgresDatabase.js.map +1 -1
- package/build/dist/Server/Services/AIService.js +53 -2
- package/build/dist/Server/Services/AIService.js.map +1 -1
- package/build/dist/Server/Services/AlertService.js +24 -0
- package/build/dist/Server/Services/AlertService.js.map +1 -1
- package/build/dist/Server/Services/AnalyticsDatabaseService.js +10 -1
- package/build/dist/Server/Services/AnalyticsDatabaseService.js.map +1 -1
- package/build/dist/Server/Services/DatabaseService.js +10 -1
- package/build/dist/Server/Services/DatabaseService.js.map +1 -1
- package/build/dist/Server/Services/IncidentService.js +25 -1
- package/build/dist/Server/Services/IncidentService.js.map +1 -1
- package/build/dist/Server/Services/IncidentStateTimelineService.js +19 -0
- package/build/dist/Server/Services/IncidentStateTimelineService.js.map +1 -1
- package/build/dist/Server/Services/IncomingCallPolicyEscalationRuleService.js +55 -2
- package/build/dist/Server/Services/IncomingCallPolicyEscalationRuleService.js.map +1 -1
- package/build/dist/Server/Services/IncomingCallPolicyService.js +40 -0
- package/build/dist/Server/Services/IncomingCallPolicyService.js.map +1 -1
- package/build/dist/Server/Services/Index.js +2 -0
- package/build/dist/Server/Services/Index.js.map +1 -1
- package/build/dist/Server/Services/MigrationFailureService.js +9 -0
- package/build/dist/Server/Services/MigrationFailureService.js.map +1 -0
- package/build/dist/Server/Services/MonitorProbeService.js +33 -7
- package/build/dist/Server/Services/MonitorProbeService.js.map +1 -1
- package/build/dist/Server/Services/MonitorService.js +12 -5
- package/build/dist/Server/Services/MonitorService.js.map +1 -1
- package/build/dist/Server/Services/OnCallDutyPolicyEscalationRuleScheduleService.js +81 -22
- package/build/dist/Server/Services/OnCallDutyPolicyEscalationRuleScheduleService.js.map +1 -1
- package/build/dist/Server/Services/OnCallDutyPolicyEscalationRuleService.js +134 -31
- package/build/dist/Server/Services/OnCallDutyPolicyEscalationRuleService.js.map +1 -1
- package/build/dist/Server/Services/OnCallDutyPolicyEscalationRuleUserService.js +21 -13
- package/build/dist/Server/Services/OnCallDutyPolicyEscalationRuleUserService.js.map +1 -1
- package/build/dist/Server/Services/OnCallDutyPolicyExecutionLogService.js +36 -0
- package/build/dist/Server/Services/OnCallDutyPolicyExecutionLogService.js.map +1 -1
- package/build/dist/Server/Services/OnCallDutyPolicyScheduleLayerService.js +121 -0
- package/build/dist/Server/Services/OnCallDutyPolicyScheduleLayerService.js.map +1 -1
- package/build/dist/Server/Services/OnCallDutyPolicyScheduleLayerUserService.js +11 -2
- package/build/dist/Server/Services/OnCallDutyPolicyScheduleLayerUserService.js.map +1 -1
- package/build/dist/Server/Services/OnCallDutyPolicyScheduleService.js +409 -36
- package/build/dist/Server/Services/OnCallDutyPolicyScheduleService.js.map +1 -1
- package/build/dist/Server/Services/OnCallDutyPolicyTimeLogService.js +52 -8
- package/build/dist/Server/Services/OnCallDutyPolicyTimeLogService.js.map +1 -1
- package/build/dist/Server/Services/OnCallDutyPolicyUserOverrideService.js +170 -3
- package/build/dist/Server/Services/OnCallDutyPolicyUserOverrideService.js.map +1 -1
- package/build/dist/Server/Services/ProjectCallSMSConfigService.js +72 -0
- package/build/dist/Server/Services/ProjectCallSMSConfigService.js.map +1 -1
- package/build/dist/Server/Services/TeamMemberService.js +6 -0
- package/build/dist/Server/Services/TeamMemberService.js.map +1 -1
- package/build/dist/Server/Services/UserNotificationRuleService.js +12 -30
- package/build/dist/Server/Services/UserNotificationRuleService.js.map +1 -1
- package/build/dist/Server/Services/UserOnCallLogService.js +75 -0
- package/build/dist/Server/Services/UserOnCallLogService.js.map +1 -1
- package/build/dist/Server/Services/UserOnCallLogTimelineService.js +31 -1
- package/build/dist/Server/Services/UserOnCallLogTimelineService.js.map +1 -1
- package/build/dist/Server/Types/Database/QueryUtil.js +45 -0
- package/build/dist/Server/Types/Database/QueryUtil.js.map +1 -1
- package/build/dist/Server/Utils/AI/AlertAIContextBuilder.js +6 -1
- package/build/dist/Server/Utils/AI/AlertAIContextBuilder.js.map +1 -1
- package/build/dist/Server/Utils/AI/Chat/ChatAgentRunner.js +13 -2
- package/build/dist/Server/Utils/AI/Chat/ChatAgentRunner.js.map +1 -1
- package/build/dist/Server/Utils/AI/Chat/ObservabilityAssistant.js +59 -11
- package/build/dist/Server/Utils/AI/Chat/ObservabilityAssistant.js.map +1 -1
- package/build/dist/Server/Utils/AI/IncidentAIContextBuilder.js +18 -3
- package/build/dist/Server/Utils/AI/IncidentAIContextBuilder.js.map +1 -1
- package/build/dist/Server/Utils/AI/IncidentEpisodeAIContextBuilder.js +6 -1
- package/build/dist/Server/Utils/AI/IncidentEpisodeAIContextBuilder.js.map +1 -1
- package/build/dist/Server/Utils/AI/ScheduledMaintenanceAIContextBuilder.js +12 -2
- package/build/dist/Server/Utils/AI/ScheduledMaintenanceAIContextBuilder.js.map +1 -1
- package/build/dist/Server/Utils/AI/Sentinel/AlertInvestigationRunner.js +119 -0
- package/build/dist/Server/Utils/AI/Sentinel/AlertInvestigationRunner.js.map +1 -0
- package/build/dist/Server/Utils/AI/Sentinel/IncidentInvestigationRunner.js +147 -0
- package/build/dist/Server/Utils/AI/Sentinel/IncidentInvestigationRunner.js.map +1 -0
- package/build/dist/Server/Utils/AI/Sentinel/IncidentPostmortemRunner.js +93 -0
- package/build/dist/Server/Utils/AI/Sentinel/IncidentPostmortemRunner.js.map +1 -0
- package/build/dist/Server/Utils/AI/Sentinel/SentinelInvestigationEngine.js +311 -0
- package/build/dist/Server/Utils/AI/Sentinel/SentinelInvestigationEngine.js.map +1 -0
- package/build/dist/Server/Utils/AI/Sentinel/SentinelMemory.js +143 -0
- package/build/dist/Server/Utils/AI/Sentinel/SentinelMemory.js.map +1 -0
- package/build/dist/Server/Utils/AI/Toolbox/IncidentTools.js +152 -1
- package/build/dist/Server/Utils/AI/Toolbox/IncidentTools.js.map +1 -1
- package/build/dist/Server/Utils/AI/Toolbox/Index.js +10 -1
- package/build/dist/Server/Utils/AI/Toolbox/Index.js.map +1 -1
- package/build/dist/Server/Utils/AI/Toolbox/RecentChangesTools.js +165 -0
- package/build/dist/Server/Utils/AI/Toolbox/RecentChangesTools.js.map +1 -0
- package/build/dist/Server/Utils/AI/Toolbox/SentinelActionTools.js +451 -0
- package/build/dist/Server/Utils/AI/Toolbox/SentinelActionTools.js.map +1 -0
- package/build/dist/Server/Utils/AI/Toolbox/Serializer.js +40 -0
- package/build/dist/Server/Utils/AI/Toolbox/Serializer.js.map +1 -1
- package/build/dist/Server/Utils/Database/MigrationFailureLog.js +174 -0
- package/build/dist/Server/Utils/Database/MigrationFailureLog.js.map +1 -0
- package/build/dist/Server/Utils/IncomingCallPhoneNumber.js +30 -0
- package/build/dist/Server/Utils/IncomingCallPhoneNumber.js.map +1 -0
- package/build/dist/Server/Utils/LLM/LLMService.js +81 -5
- package/build/dist/Server/Utils/LLM/LLMService.js.map +1 -1
- package/build/dist/Server/Utils/Telemetry/IoTSnapshotScan.js +11 -8
- package/build/dist/Server/Utils/Telemetry/IoTSnapshotScan.js.map +1 -1
- package/build/dist/Server/Utils/Telemetry/ProxmoxCephSnapshotScan.js +11 -8
- package/build/dist/Server/Utils/Telemetry/ProxmoxCephSnapshotScan.js.map +1 -1
- package/build/dist/Types/Date.js +185 -15
- package/build/dist/Types/Date.js.map +1 -1
- package/build/dist/Types/OnCallDutyPolicy/Layer.js +602 -171
- package/build/dist/Types/OnCallDutyPolicy/Layer.js.map +1 -1
- package/build/dist/Types/OnCallDutyPolicy/RestrictionTimes.js +27 -13
- package/build/dist/Types/OnCallDutyPolicy/RestrictionTimes.js.map +1 -1
- package/build/dist/Types/OnCallDutyPolicy/ScheduleShiftUtil.js +159 -0
- package/build/dist/Types/OnCallDutyPolicy/ScheduleShiftUtil.js.map +1 -0
- package/build/dist/Types/OnCallDutyPolicy/UserOverrideUtil.js +20 -5
- package/build/dist/Types/OnCallDutyPolicy/UserOverrideUtil.js.map +1 -1
- package/build/dist/UI/Components/Calendar/Calendar.js +2 -2
- package/build/dist/UI/Components/Calendar/Calendar.js.map +1 -1
- package/build/dist/UI/Components/Charts/ChartGroup/ChartGroup.js +2 -2
- package/build/dist/UI/Components/Charts/ChartGroup/ChartGroup.js.map +1 -1
- package/build/dist/UI/Components/Events/RecurringFieldElement.js +13 -0
- package/build/dist/UI/Components/Events/RecurringFieldElement.js.map +1 -1
- package/build/dist/UI/Components/ModelFormModal/ModelFormModal.js.map +1 -1
- package/build/dist/UI/Components/RadioButtons/BasicRadioButtons.js +1 -1
- package/build/dist/UI/Components/RadioButtons/BasicRadioButtons.js.map +1 -1
- package/package.json +1 -1
|
@@ -16,6 +16,8 @@ import { LIMIT_PER_PROJECT } from "../../Types/Database/LimitMax";
|
|
|
16
16
|
import OneUptimeDate from "../../Types/Date";
|
|
17
17
|
import ObjectID from "../../Types/ObjectID";
|
|
18
18
|
import LayerUtil from "../../Types/OnCallDutyPolicy/Layer";
|
|
19
|
+
import { RestrictionType } from "../../Types/OnCallDutyPolicy/RestrictionTimes";
|
|
20
|
+
import Recurring from "../../Types/Events/Recurring";
|
|
19
21
|
import UserOverrideUtil from "../../Types/OnCallDutyPolicy/UserOverrideUtil";
|
|
20
22
|
import QueryHelper from "../Types/Database/QueryHelper";
|
|
21
23
|
import CaptureSpan from "../Utils/Telemetry/CaptureSpan";
|
|
@@ -29,6 +31,7 @@ import NotificationSettingEventType from "../../Types/NotificationSetting/Notifi
|
|
|
29
31
|
import BadDataException from "../../Types/Exception/BadDataException";
|
|
30
32
|
import Timezone from "../../Types/Timezone";
|
|
31
33
|
import logger from "../Utils/Logger";
|
|
34
|
+
import Semaphore from "../Infrastructure/Semaphore";
|
|
32
35
|
import OnCallDutyPolicyFeedService from "./OnCallDutyPolicyFeedService";
|
|
33
36
|
import { OnCallDutyPolicyFeedEventType } from "../../Models/DatabaseModels/OnCallDutyPolicyFeed";
|
|
34
37
|
import { Green500 } from "../../Types/BrandColors";
|
|
@@ -111,7 +114,7 @@ export class Service extends DatabaseService {
|
|
|
111
114
|
}
|
|
112
115
|
async sendNotificationToUserOnScheduleHandoff(data) {
|
|
113
116
|
// Before we send any notification, we need to check if this schedule is attached to any on-call policy.
|
|
114
|
-
var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o, _p, _q, _r, _s, _t, _u, _v, _w, _x, _y, _z, _0, _1, _2;
|
|
117
|
+
var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o, _p, _q, _r, _s, _t, _u, _v, _w, _x, _y, _z, _0, _1, _2, _3, _4, _5, _6;
|
|
115
118
|
const escalationRulesAttachedToSchedule = await OnCallDutyPolicyEscalationRuleScheduleService.findBy({
|
|
116
119
|
query: {
|
|
117
120
|
onCallDutyPolicyScheduleId: data.scheduleId,
|
|
@@ -275,14 +278,24 @@ export class Service extends DatabaseService {
|
|
|
275
278
|
},
|
|
276
279
|
});
|
|
277
280
|
}
|
|
278
|
-
|
|
281
|
+
/*
|
|
282
|
+
* Only notify "you are now on-call" when the current user ACTUALLY
|
|
283
|
+
* changed — not merely when the handoff time changed. The enclosing
|
|
284
|
+
* guard also fires on a rosterHandoffAt change, which happens every
|
|
285
|
+
* rotation period; without this user-changed check a continuing (e.g.
|
|
286
|
+
* single-user) roster was re-sent the full email + SMS + phone call +
|
|
287
|
+
* push + WhatsApp bundle every period. Mirrors the removal branch above.
|
|
288
|
+
*/
|
|
289
|
+
if (((_m = previousInformation.currentUserIdOnRoster) === null || _m === void 0 ? void 0 : _m.toString()) !==
|
|
290
|
+
((_o = newInformation.currentUserIdOnRoster) === null || _o === void 0 ? void 0 : _o.toString()) &&
|
|
291
|
+
((_p = newInformation.currentUserIdOnRoster) === null || _p === void 0 ? void 0 : _p.toString())) {
|
|
279
292
|
// send email to the new current user.
|
|
280
293
|
const sendEmailToUserId = newInformation.currentUserIdOnRoster;
|
|
281
294
|
const userTimezone = await UserService.getTimezoneForUser(sendEmailToUserId);
|
|
282
295
|
const vars = {
|
|
283
296
|
onCallPolicyName: onCallPolicy.name || "No name provided",
|
|
284
297
|
escalationRuleName: onCallDutyPolicyEscalationRule.name || "No name provided",
|
|
285
|
-
escalationRuleOrder: ((
|
|
298
|
+
escalationRuleOrder: ((_q = onCallDutyPolicyEscalationRule.order) === null || _q === void 0 ? void 0 : _q.toString()) || "-",
|
|
286
299
|
reason: "You are now on-call for the policy " +
|
|
287
300
|
onCallPolicy.name +
|
|
288
301
|
" because your on-call roster on schedule " +
|
|
@@ -371,7 +384,7 @@ export class Service extends DatabaseService {
|
|
|
371
384
|
feedInfoInMarkdown: `📞 **${await UserService.getUserMarkdownString({
|
|
372
385
|
userId: sendEmailToUserId,
|
|
373
386
|
projectId: projectId,
|
|
374
|
-
})}** is currently on call for [On-Call Policy ${(
|
|
387
|
+
})}** is currently on call for [On-Call Policy ${(_r = escalationRule.onCallDutyPolicy) === null || _r === void 0 ? void 0 : _r.name}](${(await OnCallDutyPolicyService.getOnCallDutyPolicyLinkInDashboard(projectId, onCallDutyPolicyId)).toString()}) escalation rule **${(_s = escalationRule.onCallDutyPolicyEscalationRule) === null || _s === void 0 ? void 0 : _s.name}** with order **${(_t = escalationRule.onCallDutyPolicyEscalationRule) === null || _t === void 0 ? void 0 : _t.order}** because of schedule **${onCallSchedule.name}** and your on-call roster starts at **${OneUptimeDate.getDateAsFormattedStringInMultipleTimezones({
|
|
375
388
|
date: newInformation.rosterStartAt,
|
|
376
389
|
timezones: userTimezone ? [userTimezone] : [Timezone.GMT],
|
|
377
390
|
})}** and ends at **${OneUptimeDate.getDateAsFormattedStringInMultipleTimezones({
|
|
@@ -387,20 +400,31 @@ export class Service extends DatabaseService {
|
|
|
387
400
|
}
|
|
388
401
|
}
|
|
389
402
|
// send an email to the next user.
|
|
390
|
-
if (((
|
|
391
|
-
((
|
|
392
|
-
((
|
|
393
|
-
((
|
|
394
|
-
((
|
|
395
|
-
((
|
|
396
|
-
|
|
403
|
+
if (((_u = previousInformation.nextUserIdOnRoster) === null || _u === void 0 ? void 0 : _u.toString()) !==
|
|
404
|
+
((_v = newInformation.nextUserIdOnRoster) === null || _v === void 0 ? void 0 : _v.toString()) ||
|
|
405
|
+
((_w = previousInformation.nextHandOffTimeAt) === null || _w === void 0 ? void 0 : _w.toString()) !==
|
|
406
|
+
((_x = newInformation.nextHandOffTimeAt) === null || _x === void 0 ? void 0 : _x.toString()) ||
|
|
407
|
+
((_y = previousInformation.nextRosterStartAt) === null || _y === void 0 ? void 0 : _y.toString()) !==
|
|
408
|
+
((_z = newInformation.nextRosterStartAt) === null || _z === void 0 ? void 0 : _z.toString())) {
|
|
409
|
+
/*
|
|
410
|
+
* Only notify "you are next on-call" when the next user ACTUALLY
|
|
411
|
+
* changed — not merely when the next handoff/start time advanced. The
|
|
412
|
+
* enclosing guard also fires when nextHandOffTimeAt / nextRosterStartAt
|
|
413
|
+
* change, which happens every rotation period; without this
|
|
414
|
+
* user-changed check a continuing (e.g. single-user) roster re-sent the
|
|
415
|
+
* full email + SMS + phone call + push + WhatsApp bundle every period.
|
|
416
|
+
* Mirrors the current-user "now on-call" branch above.
|
|
417
|
+
*/
|
|
418
|
+
if (((_0 = previousInformation.nextUserIdOnRoster) === null || _0 === void 0 ? void 0 : _0.toString()) !==
|
|
419
|
+
((_1 = newInformation.nextUserIdOnRoster) === null || _1 === void 0 ? void 0 : _1.toString()) &&
|
|
420
|
+
((_2 = newInformation.nextUserIdOnRoster) === null || _2 === void 0 ? void 0 : _2.toString())) {
|
|
397
421
|
// send email to the next user.
|
|
398
422
|
const sendEmailToUserId = newInformation.nextUserIdOnRoster;
|
|
399
423
|
const userTimezone = await UserService.getTimezoneForUser(sendEmailToUserId);
|
|
400
424
|
const vars = {
|
|
401
425
|
onCallPolicyName: onCallPolicy.name || "No name provided",
|
|
402
426
|
escalationRuleName: onCallDutyPolicyEscalationRule.name || "No name provided",
|
|
403
|
-
escalationRuleOrder: ((
|
|
427
|
+
escalationRuleOrder: ((_3 = onCallDutyPolicyEscalationRule.order) === null || _3 === void 0 ? void 0 : _3.toString()) || "-",
|
|
404
428
|
reason: "You are next on-call for the policy " +
|
|
405
429
|
onCallPolicy.name +
|
|
406
430
|
" because your on-call roster on schedule " +
|
|
@@ -469,7 +493,7 @@ export class Service extends DatabaseService {
|
|
|
469
493
|
feedInfoInMarkdown: `➡️ **${await UserService.getUserMarkdownString({
|
|
470
494
|
userId: sendEmailToUserId,
|
|
471
495
|
projectId: projectId,
|
|
472
|
-
})}** is next on call for [On-Call Policy ${(
|
|
496
|
+
})}** is next on call for [On-Call Policy ${(_4 = escalationRule.onCallDutyPolicy) === null || _4 === void 0 ? void 0 : _4.name}](${(await OnCallDutyPolicyService.getOnCallDutyPolicyLinkInDashboard(projectId, onCallDutyPolicyId)).toString()}) escalation rule **${(_5 = escalationRule.onCallDutyPolicyEscalationRule) === null || _5 === void 0 ? void 0 : _5.name}** with order **${(_6 = escalationRule.onCallDutyPolicyEscalationRule) === null || _6 === void 0 ? void 0 : _6.order}**. The on-call roster on schedule **${onCallSchedule.name}** will start when the next handoff happens which is at **${OneUptimeDate.getDateAsFormattedStringInMultipleTimezones({
|
|
473
497
|
date: newInformation.nextRosterStartAt,
|
|
474
498
|
timezones: userTimezone ? [userTimezone] : [Timezone.GMT],
|
|
475
499
|
})}** and will end at **${OneUptimeDate.getDateAsFormattedStringInMultipleTimezones({
|
|
@@ -487,6 +511,50 @@ export class Service extends DatabaseService {
|
|
|
487
511
|
}
|
|
488
512
|
}
|
|
489
513
|
async refreshCurrentUserIdAndHandoffTimeInSchedule(scheduleId) {
|
|
514
|
+
/*
|
|
515
|
+
* Serialize per-schedule refreshes across processes (audit L2). This method
|
|
516
|
+
* reads the persisted roster, diffs it, SENDS the handoff notification
|
|
517
|
+
* bundle + feed items, and only THEN persists — notifying before persisting
|
|
518
|
+
* is deliberate (audit F11). Without a lock the override-triggered refresh
|
|
519
|
+
* (API process) and the EVERY_MINUTE RefreshHandoffTime cron (Workers
|
|
520
|
+
* process) can both read the same stale roster and both send the full page
|
|
521
|
+
* before either persists, double-paging the incoming user. A blocking
|
|
522
|
+
* per-schedule mutex makes the second caller wait, then read the
|
|
523
|
+
* already-persisted new roster and diff to "no change" -> no duplicate page.
|
|
524
|
+
* Best-effort: if the lock cannot be acquired (Redis unavailable, or the
|
|
525
|
+
* acquire window is exceeded) we proceed unlocked — no worse than before.
|
|
526
|
+
*/
|
|
527
|
+
let mutex = null;
|
|
528
|
+
try {
|
|
529
|
+
mutex = await Semaphore.lock({
|
|
530
|
+
key: scheduleId.toString(),
|
|
531
|
+
namespace: "OnCallDutyPolicyScheduleService.refreshRoster",
|
|
532
|
+
lockTimeout: 60000,
|
|
533
|
+
acquireTimeout: 15000,
|
|
534
|
+
retryInterval: 200,
|
|
535
|
+
acquireAttemptsLimit: 100,
|
|
536
|
+
});
|
|
537
|
+
}
|
|
538
|
+
catch (err) {
|
|
539
|
+
logger.debug("Proceeding with roster refresh without a per-schedule lock (could not acquire) for scheduleId: " +
|
|
540
|
+
scheduleId.toString());
|
|
541
|
+
logger.debug(err);
|
|
542
|
+
}
|
|
543
|
+
try {
|
|
544
|
+
return await this.refreshCurrentUserIdAndHandoffTimeInScheduleInternal(scheduleId);
|
|
545
|
+
}
|
|
546
|
+
finally {
|
|
547
|
+
if (mutex) {
|
|
548
|
+
try {
|
|
549
|
+
await Semaphore.release(mutex);
|
|
550
|
+
}
|
|
551
|
+
catch (err) {
|
|
552
|
+
logger.error(err);
|
|
553
|
+
}
|
|
554
|
+
}
|
|
555
|
+
}
|
|
556
|
+
}
|
|
557
|
+
async refreshCurrentUserIdAndHandoffTimeInScheduleInternal(scheduleId) {
|
|
490
558
|
logger.debug("refreshCurrentUserIdAndHandoffTimeInSchedule called with scheduleId: " +
|
|
491
559
|
scheduleId.toString(), { onCallDutyPolicyScheduleId: scheduleId.toString() });
|
|
492
560
|
// get previoius result.
|
|
@@ -525,10 +593,51 @@ export class Service extends DatabaseService {
|
|
|
525
593
|
});
|
|
526
594
|
logger.debug("Fetching new schedule information for scheduleId: " +
|
|
527
595
|
scheduleId.toString(), { onCallDutyPolicyScheduleId: scheduleId.toString() });
|
|
528
|
-
|
|
596
|
+
/*
|
|
597
|
+
* When this schedule is attached to exactly one on-call policy, resolve the
|
|
598
|
+
* roster in that policy's context so its policy-scoped overrides are
|
|
599
|
+
* reflected in the persisted roster, handoff notifications and time logs —
|
|
600
|
+
* matching the live paging path, which already passes the policy id (audit
|
|
601
|
+
* F10). Attached to zero or multiple policies, the roster stays
|
|
602
|
+
* policy-agnostic (global overrides only), because a single schedule-level
|
|
603
|
+
* roster cannot represent divergent per-policy overrides.
|
|
604
|
+
*/
|
|
605
|
+
const singleAttachedPolicyId = await this.getSingleAttachedPolicyId(scheduleId);
|
|
606
|
+
const newInformation = await this.getCurrrentUserIdAndHandoffTimeInSchedule(scheduleId, {
|
|
607
|
+
onCallDutyPolicyId: singleAttachedPolicyId,
|
|
608
|
+
});
|
|
529
609
|
logger.debug(newInformation, {
|
|
530
610
|
onCallDutyPolicyScheduleId: scheduleId.toString(),
|
|
531
611
|
});
|
|
612
|
+
logger.debug("Updating schedule with new information for scheduleId: " +
|
|
613
|
+
scheduleId.toString(), { onCallDutyPolicyScheduleId: scheduleId.toString() });
|
|
614
|
+
logger.debug("Sending notifications for schedule handoff for scheduleId: " +
|
|
615
|
+
scheduleId.toString(), { onCallDutyPolicyScheduleId: scheduleId.toString() });
|
|
616
|
+
/*
|
|
617
|
+
* Send the handoff notification BEFORE persisting the new roster. The
|
|
618
|
+
* decision to notify is a diff of the persisted-previous vs new roster; if
|
|
619
|
+
* we persisted first and the notification then threw (a transient DB error
|
|
620
|
+
* in the notification-settings / timezone lookup, or a partial send), the
|
|
621
|
+
* row would already show the new user, so the next tick's diff would show
|
|
622
|
+
* "no change" and the newly-on-call user would NEVER be told they are on
|
|
623
|
+
* call — the handoff page would be silently and permanently lost (audit
|
|
624
|
+
* F11). By notifying first, a failed send leaves the roster un-advanced, so
|
|
625
|
+
* the schedule is re-selected next tick and the notification is retried
|
|
626
|
+
* (at worst a duplicate send, which is far better than a missed page for an
|
|
627
|
+
* on-call system).
|
|
628
|
+
*/
|
|
629
|
+
await this.sendNotificationToUserOnScheduleHandoff({
|
|
630
|
+
scheduleId: scheduleId,
|
|
631
|
+
previousInformation: previousInformation,
|
|
632
|
+
newInformation: {
|
|
633
|
+
currentUserIdOnRoster: newInformation.currentUserId,
|
|
634
|
+
rosterHandoffAt: newInformation.handOffTimeAt,
|
|
635
|
+
nextUserIdOnRoster: newInformation.nextUserId,
|
|
636
|
+
nextHandOffTimeAt: newInformation.nextHandOffTimeAt,
|
|
637
|
+
rosterStartAt: newInformation.rosterStartAt,
|
|
638
|
+
nextRosterStartAt: newInformation.nextRosterStartAt,
|
|
639
|
+
},
|
|
640
|
+
});
|
|
532
641
|
logger.debug("Updating schedule with new information for scheduleId: " +
|
|
533
642
|
scheduleId.toString(), { onCallDutyPolicyScheduleId: scheduleId.toString() });
|
|
534
643
|
await this.updateOneById({
|
|
@@ -546,26 +655,11 @@ export class Service extends DatabaseService {
|
|
|
546
655
|
ignoreHooks: true,
|
|
547
656
|
},
|
|
548
657
|
});
|
|
549
|
-
logger.debug("Sending notifications for schedule handoff for scheduleId: " +
|
|
550
|
-
scheduleId.toString(), { onCallDutyPolicyScheduleId: scheduleId.toString() });
|
|
551
|
-
// send notification to the users.
|
|
552
|
-
await this.sendNotificationToUserOnScheduleHandoff({
|
|
553
|
-
scheduleId: scheduleId,
|
|
554
|
-
previousInformation: previousInformation,
|
|
555
|
-
newInformation: {
|
|
556
|
-
currentUserIdOnRoster: newInformation.currentUserId,
|
|
557
|
-
rosterHandoffAt: newInformation.handOffTimeAt,
|
|
558
|
-
nextUserIdOnRoster: newInformation.nextUserId,
|
|
559
|
-
nextHandOffTimeAt: newInformation.nextHandOffTimeAt,
|
|
560
|
-
rosterStartAt: newInformation.rosterStartAt,
|
|
561
|
-
nextRosterStartAt: newInformation.nextRosterStartAt,
|
|
562
|
-
},
|
|
563
|
-
});
|
|
564
658
|
logger.debug("Returning new schedule information for scheduleId: " +
|
|
565
659
|
scheduleId.toString(), { onCallDutyPolicyScheduleId: scheduleId.toString() });
|
|
566
660
|
return newInformation;
|
|
567
661
|
}
|
|
568
|
-
async getCurrrentUserIdAndHandoffTimeInSchedule(scheduleId) {
|
|
662
|
+
async getCurrrentUserIdAndHandoffTimeInSchedule(scheduleId, options) {
|
|
569
663
|
logger.debug("getCurrrentUserIdAndHandoffTimeInSchedule called with scheduleId: " +
|
|
570
664
|
scheduleId.toString(), { onCallDutyPolicyScheduleId: scheduleId.toString() });
|
|
571
665
|
const resultReturn = {
|
|
@@ -582,6 +676,7 @@ export class Service extends DatabaseService {
|
|
|
582
676
|
const events = await this.getEventByIndexInSchedule({
|
|
583
677
|
scheduleId: scheduleId,
|
|
584
678
|
getNumberOfEvents: 2,
|
|
679
|
+
onCallDutyPolicyId: options === null || options === void 0 ? void 0 : options.onCallDutyPolicyId,
|
|
585
680
|
});
|
|
586
681
|
logger.debug("Events fetched: " + JSON.stringify(events), {
|
|
587
682
|
onCallDutyPolicyScheduleId: scheduleId.toString(),
|
|
@@ -620,10 +715,28 @@ export class Service extends DatabaseService {
|
|
|
620
715
|
// get start time
|
|
621
716
|
const startTime = currentEvent === null || currentEvent === void 0 ? void 0 : currentEvent.start; // this is user id in string.
|
|
622
717
|
if (startTime) {
|
|
623
|
-
logger.debug("Current rosterStartAt: " + startTime.toISOString(), {
|
|
718
|
+
logger.debug("Current rosterStartAt (clamped): " + startTime.toISOString(), {
|
|
624
719
|
onCallDutyPolicyScheduleId: scheduleId.toString(),
|
|
625
720
|
});
|
|
626
|
-
|
|
721
|
+
/*
|
|
722
|
+
* currentEvent.start is clamped to the resolution window start (now).
|
|
723
|
+
* Recover the true start of the current coverage window so the
|
|
724
|
+
* persisted/displayed "on call since" reflects when the shift actually
|
|
725
|
+
* began, not ~now (audit F12). Best-effort with a fallback to the
|
|
726
|
+
* clamped value.
|
|
727
|
+
*/
|
|
728
|
+
if (resultReturn.currentUserId) {
|
|
729
|
+
resultReturn.rosterStartAt = await this.getTrueRosterStartAt({
|
|
730
|
+
scheduleId: scheduleId,
|
|
731
|
+
currentUserId: resultReturn.currentUserId,
|
|
732
|
+
now: OneUptimeDate.getCurrentDate(),
|
|
733
|
+
onCallDutyPolicyId: options === null || options === void 0 ? void 0 : options.onCallDutyPolicyId,
|
|
734
|
+
fallbackStart: startTime,
|
|
735
|
+
});
|
|
736
|
+
}
|
|
737
|
+
else {
|
|
738
|
+
resultReturn.rosterStartAt = startTime;
|
|
739
|
+
}
|
|
627
740
|
}
|
|
628
741
|
}
|
|
629
742
|
// do the same for next event.
|
|
@@ -660,9 +773,85 @@ export class Service extends DatabaseService {
|
|
|
660
773
|
});
|
|
661
774
|
return resultReturn;
|
|
662
775
|
}
|
|
776
|
+
/*
|
|
777
|
+
* Returns the on-call policy this schedule is attached to IFF it is attached
|
|
778
|
+
* to exactly one; otherwise undefined (audit F10). A lazy require avoids a
|
|
779
|
+
* circular import — OnCallDutyPolicyEscalationRuleScheduleService imports this
|
|
780
|
+
* service.
|
|
781
|
+
*/
|
|
782
|
+
async getSingleAttachedPolicyId(scheduleId) {
|
|
783
|
+
var _a;
|
|
784
|
+
try {
|
|
785
|
+
const escalationRuleScheduleService = require("./OnCallDutyPolicyEscalationRuleScheduleService").default;
|
|
786
|
+
const links = await escalationRuleScheduleService.findBy({
|
|
787
|
+
query: { onCallDutyPolicyScheduleId: scheduleId },
|
|
788
|
+
select: { onCallDutyPolicyId: true },
|
|
789
|
+
limit: LIMIT_PER_PROJECT,
|
|
790
|
+
skip: 0,
|
|
791
|
+
props: { isRoot: true },
|
|
792
|
+
});
|
|
793
|
+
const distinctPolicyIds = new Set();
|
|
794
|
+
for (const link of links) {
|
|
795
|
+
const policyId = (_a = link.onCallDutyPolicyId) === null || _a === void 0 ? void 0 : _a.toString();
|
|
796
|
+
if (policyId) {
|
|
797
|
+
distinctPolicyIds.add(policyId);
|
|
798
|
+
}
|
|
799
|
+
}
|
|
800
|
+
if (distinctPolicyIds.size === 1) {
|
|
801
|
+
return new ObjectID(Array.from(distinctPolicyIds)[0]);
|
|
802
|
+
}
|
|
803
|
+
}
|
|
804
|
+
catch (err) {
|
|
805
|
+
logger.error(err);
|
|
806
|
+
}
|
|
807
|
+
return undefined;
|
|
808
|
+
}
|
|
809
|
+
/**
|
|
810
|
+
* Re-resolve and persist the roster for every schedule in the project whose
|
|
811
|
+
* layers include `userId`. Called when a user override is created / updated /
|
|
812
|
+
* deleted so the persisted roster, handoff notifications and on-call time logs
|
|
813
|
+
* reflect the override mid-period instead of waiting for the next natural
|
|
814
|
+
* handoff or the per-minute cron (which does not re-select an established
|
|
815
|
+
* roster) — audit F4. Best-effort and idempotent: refreshing an unaffected
|
|
816
|
+
* schedule simply recomputes the same roster; per-schedule errors are logged
|
|
817
|
+
* and never abort the rest.
|
|
818
|
+
*/
|
|
819
|
+
async refreshRostersForUserInProject(data) {
|
|
820
|
+
var _a;
|
|
821
|
+
const layerUsers = await OnCallDutyPolicyScheduleLayerUserService.findBy({
|
|
822
|
+
query: {
|
|
823
|
+
projectId: data.projectId,
|
|
824
|
+
userId: data.userId,
|
|
825
|
+
},
|
|
826
|
+
select: {
|
|
827
|
+
onCallDutyPolicyScheduleId: true,
|
|
828
|
+
},
|
|
829
|
+
limit: LIMIT_PER_PROJECT,
|
|
830
|
+
skip: 0,
|
|
831
|
+
props: {
|
|
832
|
+
isRoot: true,
|
|
833
|
+
},
|
|
834
|
+
});
|
|
835
|
+
const scheduleIds = new Set();
|
|
836
|
+
for (const layerUser of layerUsers) {
|
|
837
|
+
const scheduleId = (_a = layerUser.onCallDutyPolicyScheduleId) === null || _a === void 0 ? void 0 : _a.toString();
|
|
838
|
+
if (scheduleId) {
|
|
839
|
+
scheduleIds.add(scheduleId);
|
|
840
|
+
}
|
|
841
|
+
}
|
|
842
|
+
for (const scheduleId of scheduleIds) {
|
|
843
|
+
try {
|
|
844
|
+
await this.refreshCurrentUserIdAndHandoffTimeInSchedule(new ObjectID(scheduleId));
|
|
845
|
+
}
|
|
846
|
+
catch (err) {
|
|
847
|
+
logger.error(`Error refreshing roster for schedule ${scheduleId} after a user override change.`);
|
|
848
|
+
logger.error(err);
|
|
849
|
+
}
|
|
850
|
+
}
|
|
851
|
+
}
|
|
663
852
|
async getScheduleLayerProps(data) {
|
|
664
853
|
// get schedule layers.
|
|
665
|
-
var _a, _b;
|
|
854
|
+
var _a, _b, _c;
|
|
666
855
|
const scheduleId = data.scheduleId;
|
|
667
856
|
const layers = await OnCallDutyPolicyScheduleLayerService.findBy({
|
|
668
857
|
query: {
|
|
@@ -706,6 +895,21 @@ export class Service extends DatabaseService {
|
|
|
706
895
|
isRoot: true,
|
|
707
896
|
},
|
|
708
897
|
});
|
|
898
|
+
/*
|
|
899
|
+
* The schedule's timezone (if set) is applied to every layer so restriction
|
|
900
|
+
* wall-clock times resolve in that zone. When null (existing schedules), the
|
|
901
|
+
* layer engine falls back to server-local time — unchanged legacy behavior.
|
|
902
|
+
*/
|
|
903
|
+
const schedule = await this.findOneById({
|
|
904
|
+
id: scheduleId,
|
|
905
|
+
select: {
|
|
906
|
+
timezone: true,
|
|
907
|
+
},
|
|
908
|
+
props: {
|
|
909
|
+
isRoot: true,
|
|
910
|
+
},
|
|
911
|
+
});
|
|
912
|
+
const scheduleTimezone = ((_a = schedule === null || schedule === void 0 ? void 0 : schedule.timezone) === null || _a === void 0 ? void 0 : _a.toString()) || undefined;
|
|
709
913
|
const layerProps = [];
|
|
710
914
|
const scheduleUserIds = [];
|
|
711
915
|
const seenUserIds = new Set();
|
|
@@ -723,7 +927,7 @@ export class Service extends DatabaseService {
|
|
|
723
927
|
return Boolean(user);
|
|
724
928
|
});
|
|
725
929
|
for (const user of usersForLayer) {
|
|
726
|
-
const idStr = ((
|
|
930
|
+
const idStr = ((_b = user.id) === null || _b === void 0 ? void 0 : _b.toString()) || "";
|
|
727
931
|
if (idStr && !seenUserIds.has(idStr)) {
|
|
728
932
|
seenUserIds.add(idStr);
|
|
729
933
|
scheduleUserIds.push(user.id);
|
|
@@ -735,9 +939,10 @@ export class Service extends DatabaseService {
|
|
|
735
939
|
restrictionTimes: layer.restrictionTimes,
|
|
736
940
|
rotation: layer.rotation,
|
|
737
941
|
handOffTime: layer.handOffTime,
|
|
942
|
+
timezone: scheduleTimezone,
|
|
738
943
|
});
|
|
739
944
|
}
|
|
740
|
-
const projectId = ((
|
|
945
|
+
const projectId = ((_c = layers[0]) === null || _c === void 0 ? void 0 : _c.projectId) || null;
|
|
741
946
|
return { layerProps, projectId, scheduleUserIds };
|
|
742
947
|
}
|
|
743
948
|
async fetchOverridesForSchedule(data) {
|
|
@@ -796,6 +1001,166 @@ export class Service extends DatabaseService {
|
|
|
796
1001
|
};
|
|
797
1002
|
});
|
|
798
1003
|
}
|
|
1004
|
+
/*
|
|
1005
|
+
* Compute how far out to resolve calendar events. Returns at least 1 year
|
|
1006
|
+
* from `from`, but extends far enough to contain (getNumberOfEvents + 1)
|
|
1007
|
+
* rotation periods for the slowest layer, so multi-year rotations still yield
|
|
1008
|
+
* a current and next event.
|
|
1009
|
+
*/
|
|
1010
|
+
computeResolutionWindowEnd(layerProps, from, getNumberOfEvents) {
|
|
1011
|
+
const periodsNeeded = Math.max(2, getNumberOfEvents + 1);
|
|
1012
|
+
let windowEnd = from;
|
|
1013
|
+
let anyLayerRestricted = false;
|
|
1014
|
+
for (const layer of layerProps) {
|
|
1015
|
+
if (this.isLayerRestricted(layer)) {
|
|
1016
|
+
anyLayerRestricted = true;
|
|
1017
|
+
}
|
|
1018
|
+
if (!layer.rotation) {
|
|
1019
|
+
continue;
|
|
1020
|
+
}
|
|
1021
|
+
let recurring;
|
|
1022
|
+
try {
|
|
1023
|
+
recurring =
|
|
1024
|
+
layer.rotation instanceof Recurring
|
|
1025
|
+
? layer.rotation
|
|
1026
|
+
: Recurring.fromJSON(layer.rotation);
|
|
1027
|
+
}
|
|
1028
|
+
catch (_a) {
|
|
1029
|
+
continue;
|
|
1030
|
+
}
|
|
1031
|
+
let candidate = from;
|
|
1032
|
+
for (let i = 0; i < periodsNeeded; i++) {
|
|
1033
|
+
candidate = Recurring.getNextDateInterval(candidate, recurring);
|
|
1034
|
+
}
|
|
1035
|
+
if (OneUptimeDate.isAfter(candidate, windowEnd)) {
|
|
1036
|
+
windowEnd = candidate;
|
|
1037
|
+
}
|
|
1038
|
+
}
|
|
1039
|
+
/*
|
|
1040
|
+
* Floor the window. Restricted layers can have long coverage gaps (a
|
|
1041
|
+
* weekend-only or business-hours restriction), so the next COVERED event may
|
|
1042
|
+
* be far past the naive (getNumberOfEvents + 1)-period horizon — keep a
|
|
1043
|
+
* generous 1-year floor for them so a current/next user is still found.
|
|
1044
|
+
* UNRESTRICTED layers produce exactly one event per rotation period, so the
|
|
1045
|
+
* per-layer candidate above already spans the events we need; flooring those
|
|
1046
|
+
* at a full year forced a fast (hourly/daily) rotation to materialize
|
|
1047
|
+
* thousands of events on every resolution, and getMultiLayerEvents then ran
|
|
1048
|
+
* an O(n^2) overlap merge over them — a real worker stall (audit H2). For
|
|
1049
|
+
* unrestricted layers a small 1-day floor is ample margin.
|
|
1050
|
+
*/
|
|
1051
|
+
const floor = anyLayerRestricted
|
|
1052
|
+
? OneUptimeDate.addRemoveYears(from, 1)
|
|
1053
|
+
: OneUptimeDate.addRemoveDays(from, 1);
|
|
1054
|
+
if (OneUptimeDate.isBefore(windowEnd, floor)) {
|
|
1055
|
+
windowEnd = floor;
|
|
1056
|
+
}
|
|
1057
|
+
return windowEnd;
|
|
1058
|
+
}
|
|
1059
|
+
/*
|
|
1060
|
+
* True when the layer carries an active (non-None) restriction. Handles both a
|
|
1061
|
+
* hydrated RestrictionTimes instance (exposes `restictionType`) and the raw
|
|
1062
|
+
* serialized JSON form ({ _type, value: { restictionType } }) so it is safe
|
|
1063
|
+
* regardless of where the LayerProps came from.
|
|
1064
|
+
*/
|
|
1065
|
+
isLayerRestricted(layer) {
|
|
1066
|
+
var _a;
|
|
1067
|
+
const rt = layer.restrictionTimes;
|
|
1068
|
+
if (!rt) {
|
|
1069
|
+
return false;
|
|
1070
|
+
}
|
|
1071
|
+
const anyRt = rt;
|
|
1072
|
+
const type = anyRt.restictionType || ((_a = anyRt.value) === null || _a === void 0 ? void 0 : _a.restictionType);
|
|
1073
|
+
return Boolean(type) && type !== RestrictionType.None;
|
|
1074
|
+
}
|
|
1075
|
+
/*
|
|
1076
|
+
* Mirror of computeResolutionWindowEnd, stepping BACKWARDS: the earliest of
|
|
1077
|
+
* `from` minus `periodsBack` rotation periods across the layers (at least a
|
|
1078
|
+
* day). Used to recover the true, un-clamped start of the current coverage
|
|
1079
|
+
* window for rosterStartAt (audit F12).
|
|
1080
|
+
*/
|
|
1081
|
+
computeResolutionWindowStart(layerProps, from, periodsBack) {
|
|
1082
|
+
let windowStart = OneUptimeDate.addRemoveDays(from, -1);
|
|
1083
|
+
for (const layer of layerProps) {
|
|
1084
|
+
if (!layer.rotation) {
|
|
1085
|
+
continue;
|
|
1086
|
+
}
|
|
1087
|
+
let recurring;
|
|
1088
|
+
try {
|
|
1089
|
+
recurring =
|
|
1090
|
+
layer.rotation instanceof Recurring
|
|
1091
|
+
? layer.rotation
|
|
1092
|
+
: Recurring.fromJSON(layer.rotation);
|
|
1093
|
+
}
|
|
1094
|
+
catch (_a) {
|
|
1095
|
+
continue;
|
|
1096
|
+
}
|
|
1097
|
+
let candidate = from;
|
|
1098
|
+
for (let i = 0; i < periodsBack; i++) {
|
|
1099
|
+
candidate = Recurring.getNextDateInterval(candidate, recurring, true);
|
|
1100
|
+
}
|
|
1101
|
+
if (OneUptimeDate.isBefore(candidate, windowStart)) {
|
|
1102
|
+
windowStart = candidate;
|
|
1103
|
+
}
|
|
1104
|
+
}
|
|
1105
|
+
return windowStart;
|
|
1106
|
+
}
|
|
1107
|
+
/*
|
|
1108
|
+
* Recover the TRUE start of the current on-call coverage window for
|
|
1109
|
+
* `currentUserId`. getEventByIndexInSchedule always expands from `now`, and
|
|
1110
|
+
* LayerUtil clamps the first event's start to the window start, so the current
|
|
1111
|
+
* event's start reads ~now rather than when the shift actually began. We
|
|
1112
|
+
* re-expand over a bounded lookback window and return the start of the segment
|
|
1113
|
+
* that covers `now` for the current user. Best-effort: any failure (or no
|
|
1114
|
+
* covering segment found) falls back to the clamped value, so the who-is-paged
|
|
1115
|
+
* path is never affected (audit F12).
|
|
1116
|
+
*/
|
|
1117
|
+
async getTrueRosterStartAt(data) {
|
|
1118
|
+
var _a;
|
|
1119
|
+
try {
|
|
1120
|
+
const { layerProps, projectId, scheduleUserIds } = await this.getScheduleLayerProps({ scheduleId: data.scheduleId });
|
|
1121
|
+
if (layerProps.length === 0) {
|
|
1122
|
+
return data.fallbackStart;
|
|
1123
|
+
}
|
|
1124
|
+
const windowStart = this.computeResolutionWindowStart(layerProps, data.now, 2);
|
|
1125
|
+
const windowEnd = OneUptimeDate.addRemoveSeconds(data.now, 1);
|
|
1126
|
+
let events = this.layerUtil.getMultiLayerEvents({
|
|
1127
|
+
layers: layerProps,
|
|
1128
|
+
calendarStartDate: windowStart,
|
|
1129
|
+
calendarEndDate: windowEnd,
|
|
1130
|
+
});
|
|
1131
|
+
if (projectId && events.length > 0) {
|
|
1132
|
+
const overrides = await this.fetchOverridesForSchedule({
|
|
1133
|
+
projectId,
|
|
1134
|
+
scheduleUserIds,
|
|
1135
|
+
windowStart,
|
|
1136
|
+
windowEnd,
|
|
1137
|
+
currentOnCallDutyPolicyId: data.onCallDutyPolicyId,
|
|
1138
|
+
});
|
|
1139
|
+
if (overrides.length > 0) {
|
|
1140
|
+
events = UserOverrideUtil.applyOverridesToEvents({
|
|
1141
|
+
events,
|
|
1142
|
+
overrides,
|
|
1143
|
+
currentOnCallDutyPolicyId: (_a = data.onCallDutyPolicyId) === null || _a === void 0 ? void 0 : _a.toString(),
|
|
1144
|
+
});
|
|
1145
|
+
}
|
|
1146
|
+
}
|
|
1147
|
+
const currentUserIdStr = data.currentUserId.toString();
|
|
1148
|
+
for (const event of events) {
|
|
1149
|
+
if (!event) {
|
|
1150
|
+
continue;
|
|
1151
|
+
}
|
|
1152
|
+
if (event.title === currentUserIdStr &&
|
|
1153
|
+
OneUptimeDate.isOnOrBefore(event.start, data.now) &&
|
|
1154
|
+
OneUptimeDate.isOnOrAfter(event.end, data.now)) {
|
|
1155
|
+
return event.start;
|
|
1156
|
+
}
|
|
1157
|
+
}
|
|
1158
|
+
}
|
|
1159
|
+
catch (err) {
|
|
1160
|
+
logger.error(err);
|
|
1161
|
+
}
|
|
1162
|
+
return data.fallbackStart;
|
|
1163
|
+
}
|
|
799
1164
|
async getEventByIndexInSchedule(data) {
|
|
800
1165
|
var _a;
|
|
801
1166
|
logger.debug("getEventByIndexInSchedule called with data: " + JSON.stringify(data), {
|
|
@@ -817,7 +1182,15 @@ export class Service extends DatabaseService {
|
|
|
817
1182
|
logger.debug("Current start time: " + currentStartTime.toISOString(), {
|
|
818
1183
|
onCallDutyPolicyScheduleId: data.scheduleId.toString(),
|
|
819
1184
|
});
|
|
820
|
-
|
|
1185
|
+
/*
|
|
1186
|
+
* Size the resolution window from the layers' rotations rather than a fixed
|
|
1187
|
+
* 1 year. A rotation period longer than a year (e.g. a 3-year rotation)
|
|
1188
|
+
* would otherwise clamp the current event's end to now+1yr and report a
|
|
1189
|
+
* bogus handoff time with no "next" user. The window is at least 1 year and
|
|
1190
|
+
* large enough to contain the requested number of events for the slowest
|
|
1191
|
+
* layer.
|
|
1192
|
+
*/
|
|
1193
|
+
const currentEndTime = this.computeResolutionWindowEnd(layerProps, currentStartTime, data.getNumberOfEvents);
|
|
821
1194
|
logger.debug("Current end time: " + currentEndTime.toISOString(), {
|
|
822
1195
|
onCallDutyPolicyScheduleId: data.scheduleId.toString(),
|
|
823
1196
|
});
|