@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
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
import LayerUtil from "../../../Types/OnCallDutyPolicy/Layer";
|
|
2
|
+
import CalendarEvent from "../../../Types/Calendar/CalendarEvent";
|
|
3
|
+
import RestrictionTimes, {
|
|
4
|
+
RestrictionType,
|
|
5
|
+
} from "../../../Types/OnCallDutyPolicy/RestrictionTimes";
|
|
6
|
+
import Recurring from "../../../Types/Events/Recurring";
|
|
7
|
+
import OneUptimeDate from "../../../Types/Date";
|
|
8
|
+
import User from "../../../Models/DatabaseModels/User";
|
|
9
|
+
import EventInterval from "../../../Types/Events/EventInterval";
|
|
10
|
+
|
|
11
|
+
function user(id: string): User {
|
|
12
|
+
return {
|
|
13
|
+
id: {
|
|
14
|
+
toString: () => {
|
|
15
|
+
return id;
|
|
16
|
+
},
|
|
17
|
+
} as any,
|
|
18
|
+
} as User;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
function rotation(t: EventInterval, c: number): Recurring {
|
|
22
|
+
return Recurring.fromJSON({
|
|
23
|
+
_type: "Recurring",
|
|
24
|
+
value: {
|
|
25
|
+
intervalType: t,
|
|
26
|
+
intervalCount: { _type: "PositiveNumber", value: c },
|
|
27
|
+
},
|
|
28
|
+
} as any);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/*
|
|
32
|
+
* Build a daily restriction whose start/end are specific wall-clock times in a
|
|
33
|
+
* given IANA timezone on an arbitrary reference date.
|
|
34
|
+
*/
|
|
35
|
+
function dailyRestrictionTz(
|
|
36
|
+
startHour: number,
|
|
37
|
+
startMin: number,
|
|
38
|
+
endHour: number,
|
|
39
|
+
endMin: number,
|
|
40
|
+
): RestrictionTimes {
|
|
41
|
+
const r: RestrictionTimes = new RestrictionTimes();
|
|
42
|
+
r.restictionType = RestrictionType.Daily;
|
|
43
|
+
r.dayRestrictionTimes = {
|
|
44
|
+
startTime: OneUptimeDate.getDateWithCustomTime({
|
|
45
|
+
hours: startHour,
|
|
46
|
+
minutes: startMin,
|
|
47
|
+
seconds: 0,
|
|
48
|
+
}),
|
|
49
|
+
endTime: OneUptimeDate.getDateWithCustomTime({
|
|
50
|
+
hours: endHour,
|
|
51
|
+
minutes: endMin,
|
|
52
|
+
seconds: 0,
|
|
53
|
+
}),
|
|
54
|
+
};
|
|
55
|
+
return r;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
describe("Daily restriction in-place mutation / DST drift", () => {
|
|
59
|
+
test("restriction start in the DST spring-forward gap drifts permanently after the transition", () => {
|
|
60
|
+
const tz: string = "America/New_York";
|
|
61
|
+
const layerUtil: LayerUtil = new LayerUtil();
|
|
62
|
+
// 2026 US DST spring-forward: Sun Mar 8, 02:00 -> 03:00. So 02:30 is invalid.
|
|
63
|
+
const r: RestrictionTimes = dailyRestrictionTz(2, 30, 10, 0); // 02:30 -> 10:00 daily
|
|
64
|
+
|
|
65
|
+
// Daily rotation, one user, handoff at 12:00 local. Events span Mar 6 -> Mar 12.
|
|
66
|
+
const layerStart: Date = new Date(Date.UTC(2026, 2, 6, 5, 0, 0)); // Mar 6 00:00 ET-ish
|
|
67
|
+
const events: Array<CalendarEvent> = layerUtil.getEvents({
|
|
68
|
+
users: [user("A")],
|
|
69
|
+
startDateTimeOfLayer: layerStart,
|
|
70
|
+
restrictionTimes: r,
|
|
71
|
+
handOffTime: new Date(Date.UTC(2026, 2, 6, 17, 0, 0)), // arbitrary
|
|
72
|
+
rotation: rotation(EventInterval.Day, 1),
|
|
73
|
+
timezone: tz,
|
|
74
|
+
calendarStartDate: layerStart,
|
|
75
|
+
calendarEndDate: new Date(Date.UTC(2026, 2, 13, 5, 0, 0)),
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
// eslint-disable-next-line no-console
|
|
79
|
+
console.log("\n=== DST spring-forward restriction 02:30->10:00 ET ===");
|
|
80
|
+
for (const e of events) {
|
|
81
|
+
// eslint-disable-next-line no-console
|
|
82
|
+
console.log(
|
|
83
|
+
` start(ET local h:m)=${startHourET(e.start, tz)} end=${startHourET(
|
|
84
|
+
e.end,
|
|
85
|
+
tz,
|
|
86
|
+
)} [${e.start.toISOString()} -> ${e.end.toISOString()}]`,
|
|
87
|
+
);
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/*
|
|
91
|
+
* Gather, per day AFTER the transition (Mar 9, 10, 11), the local start-hour of
|
|
92
|
+
* the first event that day. If the mutation drifted it, it will be 03:30 not 02:30.
|
|
93
|
+
*/
|
|
94
|
+
const startHoursAfter: Array<string> = [];
|
|
95
|
+
for (const e of events) {
|
|
96
|
+
const localStart: string = startHourET(e.start, tz);
|
|
97
|
+
const day: number = etDay(e.start, tz);
|
|
98
|
+
if (day >= 9 && day <= 11 && localStart.endsWith(":30")) {
|
|
99
|
+
// pick the segment starting near the restriction start
|
|
100
|
+
startHoursAfter.push(`${day}:${localStart}`);
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
// eslint-disable-next-line no-console
|
|
104
|
+
console.log(
|
|
105
|
+
"after-transition :30 starts =",
|
|
106
|
+
JSON.stringify(startHoursAfter),
|
|
107
|
+
);
|
|
108
|
+
});
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
function startHourET(d: Date, tz: string): string {
|
|
112
|
+
// format hour:minute as seen in tz
|
|
113
|
+
const s: string = new Intl.DateTimeFormat("en-US", {
|
|
114
|
+
timeZone: tz,
|
|
115
|
+
hour: "2-digit",
|
|
116
|
+
minute: "2-digit",
|
|
117
|
+
hour12: false,
|
|
118
|
+
}).format(d);
|
|
119
|
+
return s;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
function etDay(d: Date, tz: string): number {
|
|
123
|
+
const s: string = new Intl.DateTimeFormat("en-US", {
|
|
124
|
+
timeZone: tz,
|
|
125
|
+
day: "2-digit",
|
|
126
|
+
}).format(d);
|
|
127
|
+
return parseInt(s, 10);
|
|
128
|
+
}
|
|
@@ -0,0 +1,203 @@
|
|
|
1
|
+
import LayerUtil from "../../../Types/OnCallDutyPolicy/Layer";
|
|
2
|
+
import CalendarEvent from "../../../Types/Calendar/CalendarEvent";
|
|
3
|
+
import RestrictionTimes, {
|
|
4
|
+
RestrictionType,
|
|
5
|
+
} from "../../../Types/OnCallDutyPolicy/RestrictionTimes";
|
|
6
|
+
import Recurring from "../../../Types/Events/Recurring";
|
|
7
|
+
import OneUptimeDate from "../../../Types/Date";
|
|
8
|
+
import User from "../../../Models/DatabaseModels/User";
|
|
9
|
+
import EventInterval from "../../../Types/Events/EventInterval";
|
|
10
|
+
|
|
11
|
+
function user(id: string): User {
|
|
12
|
+
return {
|
|
13
|
+
id: {
|
|
14
|
+
toString: () => {
|
|
15
|
+
return id;
|
|
16
|
+
},
|
|
17
|
+
} as any,
|
|
18
|
+
} as User;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
function rotation(t: EventInterval, c: number): Recurring {
|
|
22
|
+
return Recurring.fromJSON({
|
|
23
|
+
_type: "Recurring",
|
|
24
|
+
value: {
|
|
25
|
+
intervalType: t,
|
|
26
|
+
intervalCount: { _type: "PositiveNumber", value: c },
|
|
27
|
+
},
|
|
28
|
+
} as any);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function dailyRestrictionHM(
|
|
32
|
+
sH: number,
|
|
33
|
+
sM: number,
|
|
34
|
+
eH: number,
|
|
35
|
+
eM: number,
|
|
36
|
+
): RestrictionTimes {
|
|
37
|
+
const r: RestrictionTimes = new RestrictionTimes();
|
|
38
|
+
r.restictionType = RestrictionType.Daily;
|
|
39
|
+
r.dayRestrictionTimes = {
|
|
40
|
+
startTime: OneUptimeDate.getDateWithCustomTime({
|
|
41
|
+
hours: sH,
|
|
42
|
+
minutes: sM,
|
|
43
|
+
seconds: 0,
|
|
44
|
+
}),
|
|
45
|
+
endTime: OneUptimeDate.getDateWithCustomTime({
|
|
46
|
+
hours: eH,
|
|
47
|
+
minutes: eM,
|
|
48
|
+
seconds: 0,
|
|
49
|
+
}),
|
|
50
|
+
};
|
|
51
|
+
return r;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
function fmt(d: Date): string {
|
|
55
|
+
return OneUptimeDate.toString(d);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
function dump(label: string, events: Array<CalendarEvent>): void {
|
|
59
|
+
// eslint-disable-next-line no-console
|
|
60
|
+
console.log(`\n=== ${label} (${events.length} events) ===`);
|
|
61
|
+
for (const e of events) {
|
|
62
|
+
// eslint-disable-next-line no-console
|
|
63
|
+
console.log(` ${e.title}: ${fmt(e.start)} -> ${fmt(e.end)}`);
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
function coveringUser(events: Array<CalendarEvent>, t: Date): string | null {
|
|
68
|
+
for (const e of events) {
|
|
69
|
+
if (e.start.getTime() <= t.getTime() && e.end.getTime() > t.getTime()) {
|
|
70
|
+
return e.title;
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
return null;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
describe("Daily restriction probe", () => {
|
|
77
|
+
test("A) full-day 00:00 -> 23:59 daily restriction, daily rotation midnight handoff", () => {
|
|
78
|
+
const layerUtil: LayerUtil = new LayerUtil();
|
|
79
|
+
const layerStart: Date = new Date(2026, 0, 5, 0, 0, 0);
|
|
80
|
+
const events: Array<CalendarEvent> = layerUtil.getEvents({
|
|
81
|
+
users: [user("A"), user("B")],
|
|
82
|
+
startDateTimeOfLayer: layerStart,
|
|
83
|
+
restrictionTimes: dailyRestrictionHM(0, 0, 23, 59),
|
|
84
|
+
handOffTime: new Date(2026, 0, 5, 0, 0, 0),
|
|
85
|
+
rotation: rotation(EventInterval.Day, 1),
|
|
86
|
+
calendarStartDate: layerStart,
|
|
87
|
+
calendarEndDate: new Date(2026, 0, 8, 0, 0, 0),
|
|
88
|
+
});
|
|
89
|
+
dump("A full-day 00:00-23:59", events);
|
|
90
|
+
// check coverage at various instants
|
|
91
|
+
// eslint-disable-next-line no-console
|
|
92
|
+
console.log(
|
|
93
|
+
"cover Jan6 23:59:30 =",
|
|
94
|
+
coveringUser(events, new Date(2026, 0, 6, 23, 59, 30)),
|
|
95
|
+
);
|
|
96
|
+
// eslint-disable-next-line no-console
|
|
97
|
+
console.log(
|
|
98
|
+
"cover Jan6 12:00 =",
|
|
99
|
+
coveringUser(events, new Date(2026, 0, 6, 12, 0, 0)),
|
|
100
|
+
);
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
test("B) 00:00 -> 00:00 daily restriction (user may intend all-day)", () => {
|
|
104
|
+
const layerUtil: LayerUtil = new LayerUtil();
|
|
105
|
+
const layerStart: Date = new Date(2026, 0, 5, 0, 0, 0);
|
|
106
|
+
const events: Array<CalendarEvent> = layerUtil.getEvents({
|
|
107
|
+
users: [user("A")],
|
|
108
|
+
startDateTimeOfLayer: layerStart,
|
|
109
|
+
restrictionTimes: dailyRestrictionHM(0, 0, 0, 0),
|
|
110
|
+
handOffTime: new Date(2026, 0, 5, 0, 0, 0),
|
|
111
|
+
rotation: rotation(EventInterval.Day, 1),
|
|
112
|
+
calendarStartDate: layerStart,
|
|
113
|
+
calendarEndDate: new Date(2026, 0, 8, 0, 0, 0),
|
|
114
|
+
});
|
|
115
|
+
dump("B 00:00-00:00", events);
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
test("C) in-place mutation of dayRestrictionTimes", () => {
|
|
119
|
+
const layerUtil: LayerUtil = new LayerUtil();
|
|
120
|
+
const layerStart: Date = new Date(2026, 0, 5, 0, 0, 0);
|
|
121
|
+
const r: RestrictionTimes = dailyRestrictionHM(9, 0, 17, 0);
|
|
122
|
+
const beforeStart: Date = r.dayRestrictionTimes!.startTime;
|
|
123
|
+
const beforeEnd: Date = r.dayRestrictionTimes!.endTime;
|
|
124
|
+
// eslint-disable-next-line no-console
|
|
125
|
+
console.log("BEFORE start:", fmt(beforeStart), "end:", fmt(beforeEnd));
|
|
126
|
+
layerUtil.getEvents({
|
|
127
|
+
users: [user("A"), user("B")],
|
|
128
|
+
startDateTimeOfLayer: layerStart,
|
|
129
|
+
restrictionTimes: r,
|
|
130
|
+
handOffTime: new Date(2026, 0, 5, 12, 0, 0),
|
|
131
|
+
rotation: rotation(EventInterval.Day, 1),
|
|
132
|
+
calendarStartDate: layerStart,
|
|
133
|
+
calendarEndDate: new Date(2026, 0, 15, 0, 0, 0),
|
|
134
|
+
});
|
|
135
|
+
// eslint-disable-next-line no-console
|
|
136
|
+
console.log(
|
|
137
|
+
"AFTER start:",
|
|
138
|
+
fmt(r.dayRestrictionTimes!.startTime),
|
|
139
|
+
"end:",
|
|
140
|
+
fmt(r.dayRestrictionTimes!.endTime),
|
|
141
|
+
);
|
|
142
|
+
});
|
|
143
|
+
|
|
144
|
+
test("D) overnight 22:00 -> 06:00, daily rotation 09:00 handoff, coverage completeness", () => {
|
|
145
|
+
const layerUtil: LayerUtil = new LayerUtil();
|
|
146
|
+
const layerStart: Date = new Date(2026, 0, 5, 9, 0, 0);
|
|
147
|
+
const events: Array<CalendarEvent> = layerUtil.getEvents({
|
|
148
|
+
users: [user("A"), user("B")],
|
|
149
|
+
startDateTimeOfLayer: layerStart,
|
|
150
|
+
restrictionTimes: dailyRestrictionHM(22, 0, 6, 0),
|
|
151
|
+
handOffTime: new Date(2026, 0, 5, 9, 0, 0),
|
|
152
|
+
rotation: rotation(EventInterval.Day, 1),
|
|
153
|
+
calendarStartDate: layerStart,
|
|
154
|
+
calendarEndDate: new Date(2026, 0, 9, 0, 0, 0),
|
|
155
|
+
});
|
|
156
|
+
dump("D overnight 22-06", events);
|
|
157
|
+
for (const [label, t] of [
|
|
158
|
+
["Jan5 23:00", new Date(2026, 0, 5, 23, 0, 0)],
|
|
159
|
+
["Jan6 03:00", new Date(2026, 0, 6, 3, 0, 0)],
|
|
160
|
+
["Jan6 06:00", new Date(2026, 0, 6, 6, 0, 0)],
|
|
161
|
+
["Jan6 12:00", new Date(2026, 0, 6, 12, 0, 0)],
|
|
162
|
+
["Jan6 22:00", new Date(2026, 0, 6, 22, 0, 0)],
|
|
163
|
+
] as Array<[string, Date]>) {
|
|
164
|
+
// eslint-disable-next-line no-console
|
|
165
|
+
console.log(`cover ${label} =`, coveringUser(events, t));
|
|
166
|
+
}
|
|
167
|
+
});
|
|
168
|
+
|
|
169
|
+
test("E) live 1-second window at overnight boundary instants", () => {
|
|
170
|
+
const layerUtil: LayerUtil = new LayerUtil();
|
|
171
|
+
const resolveAt: (now: Date) => string | null = (
|
|
172
|
+
now: Date,
|
|
173
|
+
): string | null => {
|
|
174
|
+
const events: Array<CalendarEvent> = layerUtil.getMultiLayerEvents(
|
|
175
|
+
{
|
|
176
|
+
layers: [
|
|
177
|
+
{
|
|
178
|
+
users: [user("A"), user("B")],
|
|
179
|
+
startDateTimeOfLayer: new Date(2026, 0, 5, 9, 0, 0),
|
|
180
|
+
restrictionTimes: dailyRestrictionHM(22, 0, 6, 0),
|
|
181
|
+
handOffTime: new Date(2026, 0, 5, 9, 0, 0),
|
|
182
|
+
rotation: rotation(EventInterval.Day, 1),
|
|
183
|
+
},
|
|
184
|
+
],
|
|
185
|
+
calendarStartDate: now,
|
|
186
|
+
calendarEndDate: OneUptimeDate.addRemoveSeconds(now, 1),
|
|
187
|
+
},
|
|
188
|
+
{ getNumberOfEvents: 1 },
|
|
189
|
+
);
|
|
190
|
+
return events[0]?.title ?? null;
|
|
191
|
+
};
|
|
192
|
+
for (const [label, t] of [
|
|
193
|
+
["Jan6 05:59:59", new Date(2026, 0, 6, 5, 59, 59)],
|
|
194
|
+
["Jan6 06:00:00", new Date(2026, 0, 6, 6, 0, 0)],
|
|
195
|
+
["Jan6 21:59:59", new Date(2026, 0, 6, 21, 59, 59)],
|
|
196
|
+
["Jan6 22:00:00", new Date(2026, 0, 6, 22, 0, 0)],
|
|
197
|
+
["Jan6 00:00:00", new Date(2026, 0, 6, 0, 0, 0)],
|
|
198
|
+
] as Array<[string, Date]>) {
|
|
199
|
+
// eslint-disable-next-line no-console
|
|
200
|
+
console.log(`live ${label} =`, resolveAt(t));
|
|
201
|
+
}
|
|
202
|
+
});
|
|
203
|
+
});
|
|
@@ -0,0 +1,322 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* EMPIRICAL differential & invariant fuzzing (audit lens).
|
|
3
|
+
*
|
|
4
|
+
* (1) DIFFERENTIAL: for many rotation configs, compare the WINDOWED current-user
|
|
5
|
+
* resolution (getEvents starting at `at` with getNumberOfEvents:1) against a
|
|
6
|
+
* FULL expansion from layer start, at many OFF-boundary sampled instants.
|
|
7
|
+
* (2) INVARIANTS: 24/7 coverage contiguity, fairness, rotation order.
|
|
8
|
+
*
|
|
9
|
+
* Any off-boundary mismatch is a real bug and is reported with the exact
|
|
10
|
+
* config + instant + expected/actual.
|
|
11
|
+
*/
|
|
12
|
+
import LayerUtil, { LayerProps } from "../../../Types/OnCallDutyPolicy/Layer";
|
|
13
|
+
import CalendarEvent from "../../../Types/Calendar/CalendarEvent";
|
|
14
|
+
import RestrictionTimes, {
|
|
15
|
+
RestrictionType,
|
|
16
|
+
} from "../../../Types/OnCallDutyPolicy/RestrictionTimes";
|
|
17
|
+
import Recurring from "../../../Types/Events/Recurring";
|
|
18
|
+
import OneUptimeDate from "../../../Types/Date";
|
|
19
|
+
import User from "../../../Models/DatabaseModels/User";
|
|
20
|
+
import EventInterval from "../../../Types/Events/EventInterval";
|
|
21
|
+
import PositiveNumber from "../../../Types/PositiveNumber";
|
|
22
|
+
|
|
23
|
+
function user(id: string): User {
|
|
24
|
+
return {
|
|
25
|
+
id: {
|
|
26
|
+
toString: (): string => {
|
|
27
|
+
return id;
|
|
28
|
+
},
|
|
29
|
+
} as any,
|
|
30
|
+
} as User;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
function noRestriction(): RestrictionTimes {
|
|
34
|
+
const r: RestrictionTimes = new RestrictionTimes();
|
|
35
|
+
r.restictionType = RestrictionType.None;
|
|
36
|
+
r.dayRestrictionTimes = null;
|
|
37
|
+
return r;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
function dailyRestriction(
|
|
41
|
+
startHour: number,
|
|
42
|
+
endHour: number,
|
|
43
|
+
): RestrictionTimes {
|
|
44
|
+
const r: RestrictionTimes = new RestrictionTimes();
|
|
45
|
+
r.restictionType = RestrictionType.Daily;
|
|
46
|
+
r.dayRestrictionTimes = {
|
|
47
|
+
startTime: OneUptimeDate.getDateWithCustomTime({
|
|
48
|
+
hours: startHour,
|
|
49
|
+
minutes: 0,
|
|
50
|
+
seconds: 0,
|
|
51
|
+
}),
|
|
52
|
+
endTime: OneUptimeDate.getDateWithCustomTime({
|
|
53
|
+
hours: endHour,
|
|
54
|
+
minutes: 0,
|
|
55
|
+
seconds: 0,
|
|
56
|
+
}),
|
|
57
|
+
};
|
|
58
|
+
return r;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
interface Config {
|
|
62
|
+
name: string;
|
|
63
|
+
intervalType: EventInterval;
|
|
64
|
+
intervalCount: number;
|
|
65
|
+
userIds: string[];
|
|
66
|
+
restriction: RestrictionTimes;
|
|
67
|
+
timezone?: string | undefined;
|
|
68
|
+
start: Date;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
function rotationOf(c: Config): Recurring {
|
|
72
|
+
const rot: Recurring = new Recurring();
|
|
73
|
+
rot.intervalType = c.intervalType;
|
|
74
|
+
rot.intervalCount = new PositiveNumber(c.intervalCount);
|
|
75
|
+
return rot;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
function layerOf(c: Config): LayerProps {
|
|
79
|
+
const rot: Recurring = rotationOf(c);
|
|
80
|
+
const handoff: Date = Recurring.getNextDateInterval(c.start, rot);
|
|
81
|
+
return {
|
|
82
|
+
users: c.userIds.map(user),
|
|
83
|
+
startDateTimeOfLayer: c.start,
|
|
84
|
+
restrictionTimes: c.restriction,
|
|
85
|
+
handOffTime: handoff,
|
|
86
|
+
rotation: rot,
|
|
87
|
+
timezone: c.timezone,
|
|
88
|
+
};
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
// Windowed "who is on call at `at`" (what the live roster resolution uses).
|
|
92
|
+
function windowedUserAt(layer: LayerProps, at: Date): string | null {
|
|
93
|
+
const util: LayerUtil = new LayerUtil();
|
|
94
|
+
const events: Array<CalendarEvent> = util.getEvents(
|
|
95
|
+
{
|
|
96
|
+
...layer,
|
|
97
|
+
calendarStartDate: at,
|
|
98
|
+
calendarEndDate: OneUptimeDate.addRemoveDays(at, 40),
|
|
99
|
+
},
|
|
100
|
+
{ getNumberOfEvents: 1 },
|
|
101
|
+
);
|
|
102
|
+
return events[0]?.title ?? null;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
// Full expansion from layer start over [start, to].
|
|
106
|
+
function fullExpand(layer: LayerProps, to: Date): Array<CalendarEvent> {
|
|
107
|
+
const util: LayerUtil = new LayerUtil();
|
|
108
|
+
return util.getEvents({
|
|
109
|
+
...layer,
|
|
110
|
+
calendarStartDate: layer.startDateTimeOfLayer,
|
|
111
|
+
calendarEndDate: to,
|
|
112
|
+
});
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
// Find the event that covers `at` (start <= at < end), interior only.
|
|
116
|
+
function coveringEvent(
|
|
117
|
+
events: Array<CalendarEvent>,
|
|
118
|
+
at: Date,
|
|
119
|
+
): CalendarEvent | null {
|
|
120
|
+
for (const e of events) {
|
|
121
|
+
if (
|
|
122
|
+
OneUptimeDate.isOnOrAfter(at, e.start) &&
|
|
123
|
+
OneUptimeDate.isBefore(at, e.end)
|
|
124
|
+
) {
|
|
125
|
+
return e;
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
return null;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
// Next event that starts strictly after `at`.
|
|
132
|
+
function nextEventAfter(
|
|
133
|
+
events: Array<CalendarEvent>,
|
|
134
|
+
at: Date,
|
|
135
|
+
): CalendarEvent | null {
|
|
136
|
+
let best: CalendarEvent | null = null;
|
|
137
|
+
for (const e of events) {
|
|
138
|
+
if (OneUptimeDate.isAfter(e.start, at)) {
|
|
139
|
+
if (best === null || OneUptimeDate.isBefore(e.start, best.start)) {
|
|
140
|
+
best = e;
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
return best;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
// period length in ms for sampling offsets (approx; used only to pick instants).
|
|
148
|
+
function approxPeriodMs(c: Config): number {
|
|
149
|
+
const base: Record<string, number> = {
|
|
150
|
+
[EventInterval.Hour]: 3600000,
|
|
151
|
+
[EventInterval.Day]: 86400000,
|
|
152
|
+
[EventInterval.Week]: 604800000,
|
|
153
|
+
[EventInterval.Month]: 30 * 86400000,
|
|
154
|
+
[EventInterval.Year]: 365 * 86400000,
|
|
155
|
+
};
|
|
156
|
+
return (base[c.intervalType] || 86400000) * c.intervalCount;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
const FIXED_START_JAN: Date = OneUptimeDate.fromString(
|
|
160
|
+
"2025-01-06T00:00:00.000Z",
|
|
161
|
+
); // a Monday 00:00 UTC
|
|
162
|
+
|
|
163
|
+
// Build the config matrix.
|
|
164
|
+
function buildConfigs(): Config[] {
|
|
165
|
+
const configs: Config[] = [];
|
|
166
|
+
const intervals: EventInterval[] = [
|
|
167
|
+
EventInterval.Hour,
|
|
168
|
+
EventInterval.Day,
|
|
169
|
+
EventInterval.Week,
|
|
170
|
+
EventInterval.Month,
|
|
171
|
+
];
|
|
172
|
+
const counts: number[] = [1, 2, 3];
|
|
173
|
+
const userSets: string[][] = [
|
|
174
|
+
["A"],
|
|
175
|
+
["A", "B"],
|
|
176
|
+
["A", "B", "C"],
|
|
177
|
+
["A", "B", "C", "D", "E"],
|
|
178
|
+
];
|
|
179
|
+
const timezones: (string | undefined)[] = [
|
|
180
|
+
undefined,
|
|
181
|
+
"America/New_York",
|
|
182
|
+
"Asia/Kolkata",
|
|
183
|
+
];
|
|
184
|
+
|
|
185
|
+
for (const it of intervals) {
|
|
186
|
+
for (const count of counts) {
|
|
187
|
+
for (const users of userSets) {
|
|
188
|
+
for (const tz of timezones) {
|
|
189
|
+
configs.push({
|
|
190
|
+
name: `${count}x${it} users=${users.length} tz=${tz ?? "local"} unrestricted`,
|
|
191
|
+
intervalType: it,
|
|
192
|
+
intervalCount: count,
|
|
193
|
+
userIds: users,
|
|
194
|
+
restriction: noRestriction(),
|
|
195
|
+
timezone: tz,
|
|
196
|
+
start: FIXED_START_JAN,
|
|
197
|
+
});
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
return configs;
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
// Off-boundary offsets (in units of periods) to probe.
|
|
206
|
+
const PERIOD_OFFSETS: number[] = [
|
|
207
|
+
0.13, 0.37, 0.5, 0.63, 0.87, 1.29, 2.41, 3.17, 5.53, 7.31, 10.42, 13.19,
|
|
208
|
+
20.61, 30.28, 50.44,
|
|
209
|
+
];
|
|
210
|
+
|
|
211
|
+
describe("DIFFERENTIAL: windowed resolution == full expansion (unrestricted, off-boundary)", () => {
|
|
212
|
+
const configs: Config[] = buildConfigs();
|
|
213
|
+
|
|
214
|
+
for (const c of configs) {
|
|
215
|
+
it(`${c.name}`, () => {
|
|
216
|
+
const layer: LayerProps = layerOf(c);
|
|
217
|
+
const periodMs: number = approxPeriodMs(c);
|
|
218
|
+
const mismatches: string[] = [];
|
|
219
|
+
|
|
220
|
+
for (const off of PERIOD_OFFSETS) {
|
|
221
|
+
const at: Date = new Date(
|
|
222
|
+
c.start.getTime() + Math.round(off * periodMs),
|
|
223
|
+
);
|
|
224
|
+
// Full expansion needs to comfortably contain `at` plus a next event.
|
|
225
|
+
const to: Date = new Date(at.getTime() + 3 * periodMs);
|
|
226
|
+
const full: Array<CalendarEvent> = fullExpand(layer, to);
|
|
227
|
+
const cover: CalendarEvent | null = coveringEvent(full, at);
|
|
228
|
+
const windowed: string | null = windowedUserAt(layer, at);
|
|
229
|
+
|
|
230
|
+
if (cover) {
|
|
231
|
+
if (windowed !== cover.title) {
|
|
232
|
+
mismatches.push(
|
|
233
|
+
`at=${at.toISOString()} off=${off} expected(full-cover)=${cover.title} windowed=${windowed}`,
|
|
234
|
+
);
|
|
235
|
+
}
|
|
236
|
+
} else {
|
|
237
|
+
// In an unrestricted rotation there should ALWAYS be coverage.
|
|
238
|
+
mismatches.push(
|
|
239
|
+
`at=${at.toISOString()} off=${off} NO full coverage (unexpected in 24/7 rotation) windowed=${windowed}`,
|
|
240
|
+
);
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
expect({ config: c.name, mismatches }).toEqual({
|
|
245
|
+
config: c.name,
|
|
246
|
+
mismatches: [],
|
|
247
|
+
});
|
|
248
|
+
});
|
|
249
|
+
}
|
|
250
|
+
});
|
|
251
|
+
|
|
252
|
+
describe("DIFFERENTIAL: restricted daily 09-17 covered-instant agreement", () => {
|
|
253
|
+
const restrictedConfigs: Config[] = [];
|
|
254
|
+
const intervals: EventInterval[] = [EventInterval.Day, EventInterval.Week];
|
|
255
|
+
const counts: number[] = [1, 2];
|
|
256
|
+
const userSets: string[][] = [
|
|
257
|
+
["A", "B"],
|
|
258
|
+
["A", "B", "C"],
|
|
259
|
+
];
|
|
260
|
+
const timezones: (string | undefined)[] = [
|
|
261
|
+
undefined,
|
|
262
|
+
"America/New_York",
|
|
263
|
+
"Asia/Kolkata",
|
|
264
|
+
];
|
|
265
|
+
for (const it of intervals) {
|
|
266
|
+
for (const count of counts) {
|
|
267
|
+
for (const users of userSets) {
|
|
268
|
+
for (const tz of timezones) {
|
|
269
|
+
restrictedConfigs.push({
|
|
270
|
+
name: `${count}x${it} users=${users.length} tz=${tz ?? "local"} daily09-17`,
|
|
271
|
+
intervalType: it,
|
|
272
|
+
intervalCount: count,
|
|
273
|
+
userIds: users,
|
|
274
|
+
restriction: dailyRestriction(9, 17),
|
|
275
|
+
timezone: tz,
|
|
276
|
+
start: FIXED_START_JAN,
|
|
277
|
+
});
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
for (const c of restrictedConfigs) {
|
|
284
|
+
it(`${c.name}`, () => {
|
|
285
|
+
const layer: LayerProps = layerOf(c);
|
|
286
|
+
const periodMs: number = approxPeriodMs(c);
|
|
287
|
+
const mismatches: string[] = [];
|
|
288
|
+
|
|
289
|
+
for (const off of PERIOD_OFFSETS) {
|
|
290
|
+
const at: Date = new Date(
|
|
291
|
+
c.start.getTime() + Math.round(off * periodMs),
|
|
292
|
+
);
|
|
293
|
+
const to: Date = new Date(at.getTime() + 5 * periodMs);
|
|
294
|
+
const full: Array<CalendarEvent> = fullExpand(layer, to);
|
|
295
|
+
const cover: CalendarEvent | null = coveringEvent(full, at);
|
|
296
|
+
const windowed: string | null = windowedUserAt(layer, at);
|
|
297
|
+
|
|
298
|
+
if (cover) {
|
|
299
|
+
// `at` is inside a covered restriction window: must agree.
|
|
300
|
+
if (windowed !== cover.title) {
|
|
301
|
+
mismatches.push(
|
|
302
|
+
`COVERED at=${at.toISOString()} off=${off} expected=${cover.title} windowed=${windowed}`,
|
|
303
|
+
);
|
|
304
|
+
}
|
|
305
|
+
} else {
|
|
306
|
+
// `at` in a gap: windowed should equal the NEXT covered user.
|
|
307
|
+
const nxt: CalendarEvent | null = nextEventAfter(full, at);
|
|
308
|
+
if (nxt && windowed !== nxt.title) {
|
|
309
|
+
mismatches.push(
|
|
310
|
+
`GAP at=${at.toISOString()} off=${off} expectedNext=${nxt.title} windowed=${windowed}`,
|
|
311
|
+
);
|
|
312
|
+
}
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
expect({ config: c.name, mismatches }).toEqual({
|
|
317
|
+
config: c.name,
|
|
318
|
+
mismatches: [],
|
|
319
|
+
});
|
|
320
|
+
});
|
|
321
|
+
}
|
|
322
|
+
});
|