@oneuptime/common 12.0.4 → 12.0.5
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 +38 -1
- package/Models/DatabaseModels/AlertFeed.ts +30 -0
- package/Models/DatabaseModels/Dashboard.ts +28 -0
- package/Models/DatabaseModels/Incident.ts +2 -0
- package/Models/DatabaseModels/IncidentEpisode.ts +1 -0
- package/Models/DatabaseModels/IncidentEpisodePublicNote.ts +1 -0
- package/Models/DatabaseModels/IncidentEpisodeStateTimeline.ts +1 -0
- package/Models/DatabaseModels/IncidentFeed.ts +30 -0
- package/Models/DatabaseModels/IncidentPublicNote.ts +1 -0
- package/Models/DatabaseModels/IncidentStateTimeline.ts +1 -0
- package/Models/DatabaseModels/LogSavedView.ts +33 -0
- package/Models/DatabaseModels/Project.ts +231 -9
- package/Models/DatabaseModels/ScheduledMaintenance.ts +1 -0
- package/Models/DatabaseModels/ScheduledMaintenancePublicNote.ts +1 -0
- package/Models/DatabaseModels/ScheduledMaintenanceStateTimeline.ts +1 -0
- package/Models/DatabaseModels/ScheduledMaintenanceTemplateOwnerUser.ts +4 -4
- package/Models/DatabaseModels/StatusPage.ts +28 -0
- package/Models/DatabaseModels/StatusPageAnnouncement.ts +1 -0
- package/Server/API/AIAgentDataAPI.ts +44 -55
- package/Server/API/AIInvestigationAPI.ts +175 -17
- package/Server/API/DashboardAPI.ts +10 -10
- package/Server/API/StatusPageAPI.ts +38 -19
- package/Server/EnvironmentConfig.ts +16 -0
- package/Server/Infrastructure/ClickhouseConfig.ts +15 -1
- package/Server/Infrastructure/ClickhouseDatabase.ts +1 -1
- package/Server/Infrastructure/Postgres/SchemaMigrations/1786096660558-AddTimeRangeToLogSavedView.ts +27 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/1786101798351-MigrationName.ts +89 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/1786105470826-MigrationName.ts +27 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/1786400000000-AddMasterPasswordSalt.ts +40 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/1786500000000-AddInvestigationCodeFixRecommendation.ts +26 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/Index.ts +10 -0
- package/Server/Services/AIRunService.ts +78 -10
- package/Server/Services/AIService.ts +94 -22
- package/Server/Services/AlertFeedService.ts +5 -0
- package/Server/Services/AnalyticsDatabaseService.ts +29 -0
- package/Server/Services/DatabaseService.ts +43 -2
- package/Server/Services/IncidentFeedService.ts +5 -0
- package/Server/Services/LlmLogService.ts +46 -2
- package/Server/Utils/AI/Chat/ObservabilityAssistant.ts +6 -0
- package/Server/Utils/AI/CodeFix/CodeFixAgentCompletion.ts +4 -0
- package/Server/Utils/AI/CodeFix/CodeFixReadiness.ts +8 -5
- package/Server/Utils/AI/CodeFix/FixRunBudget.ts +90 -16
- package/Server/Utils/AI/Remediation/RemediationExecutionRunner.ts +2 -0
- package/Server/Utils/AI/Remediation/RemediationPlanRunner.ts +2 -0
- package/Server/Utils/AI/SRE/AIInvestigationEngine.ts +223 -22
- package/Server/Utils/AI/SRE/AlertInvestigationRunner.ts +25 -24
- package/Server/Utils/AI/SRE/ConfidenceSignal.ts +182 -71
- package/Server/Utils/AI/SRE/FixFromIncidentTaskTrigger.ts +252 -43
- package/Server/Utils/AI/SRE/IncidentInvestigationRunner.ts +25 -24
- package/Server/Utils/AI/SRE/InstrumentationTaskTrigger.ts +47 -13
- package/Server/Utils/AI/SRE/InvestigationGrader.ts +9 -5
- package/Server/Utils/AI/SRE/InvestigationQueue.ts +141 -24
- package/Server/Utils/AI/SRE/InvestigationSubjectLock.ts +66 -0
- package/Server/Utils/AI/SRE/PostedRootCause.ts +94 -3
- package/Server/Utils/AI/SRE/README.md +1 -1
- package/Server/Utils/AI/SRE/SubjectCodeFixRun.ts +132 -10
- package/Server/Utils/AnalyticsDatabase/ClusterConfig.ts +24 -0
- package/Server/Utils/Monitor/Criteria/APIRequestCriteria.ts +29 -0
- package/Server/Utils/Monitor/Criteria/DomainMonitorCriteria.ts +56 -0
- package/Server/Utils/Monitor/MonitorMetricUtil.ts +46 -0
- package/Server/Utils/Monitor/MonitorTemplateUtil.ts +1 -0
- package/Server/Utils/PasswordHash.ts +28 -4
- package/Tests/App/Dashboard/AIInvestigationHeaderStatus.test.tsx +251 -0
- package/Tests/App/Dashboard/EventStatusPanel.test.tsx +747 -0
- package/Tests/App/Dashboard/InvestigationFeedRefresh.test.tsx +480 -0
- package/Tests/App/Dashboard/InvestigationPanel.test.tsx +1184 -0
- package/Tests/App/Dashboard/InvestigationPanelStatus.test.tsx +500 -0
- package/Tests/App/Dashboard/PortMonitorCriteriaFilter.test.ts +93 -0
- package/Tests/App/StatusPage/PublicStatusPageAPIErrorHandling.test.ts +117 -0
- package/Tests/App/StatusPage/StatusPageModelAPIPolymorphism.test.ts +223 -0
- package/Tests/Models/DatabaseModels/PermissionCatalogueCoverage.test.ts +243 -0
- package/Tests/Server/API/AIAgentDataFixFromIncidentContext.test.ts +266 -0
- package/Tests/Server/API/AIInvestigationAPI.test.ts +594 -0
- package/Tests/Server/API/AIInvestigationCreateFixTask.test.ts +80 -0
- package/Tests/Server/API/DashboardMasterPasswordAPI.test.ts +172 -1
- package/Tests/Server/API/StatusPageMasterPasswordAPI.test.ts +335 -0
- package/Tests/Server/Services/AIRunCodeFixClaim.test.ts +59 -5
- package/Tests/Server/Services/AIRunHumanVerdict.test.ts +127 -58
- package/Tests/Server/Services/AIServiceDailyBudget.test.ts +396 -20
- package/Tests/Server/Services/AddMasterPasswordSaltMigration.test.ts +283 -0
- package/Tests/Server/Services/AnalyticsDatabaseService.test.ts +25 -1
- package/Tests/Server/Services/DatabaseServicePerUserPasswordSalt.test.ts +45 -35
- package/Tests/Server/Services/DatabaseServiceUpdateColumnsWithoutHooks.test.ts +54 -0
- package/Tests/Server/Services/FeedAIRunAssociation.test.ts +125 -0
- package/Tests/Server/Services/FixFromIncidentTaskTrigger.test.ts +418 -57
- package/Tests/Server/Services/InstrumentationTaskTrigger.test.ts +173 -14
- package/Tests/Server/Services/MasterPasswordScrypt.test.ts +567 -0
- package/Tests/Server/Services/SeparateIncidentAlertAiSettingsMigration.test.ts +194 -0
- package/Tests/Server/Utils/AI/AIAlertGating.test.ts +37 -4
- package/Tests/Server/Utils/AI/AIConfidenceSignal.test.ts +330 -29
- package/Tests/Server/Utils/AI/AIIncidentGating.test.ts +25 -0
- package/Tests/Server/Utils/AI/AIInvestigationQueue.test.ts +293 -5
- package/Tests/Server/Utils/AI/CodeFixAgentCompletion.test.ts +59 -1
- package/Tests/Server/Utils/AI/FixFromIncidentAutoTrigger.test.ts +342 -19
- package/Tests/Server/Utils/AI/FixRunBudget.test.ts +214 -30
- package/Tests/Server/Utils/AI/Insights/InvestigationQueueInsightSubject.test.ts +2 -0
- package/Tests/Server/Utils/AI/InvestigationGrader.test.ts +65 -17
- package/Tests/Server/Utils/AI/InvestigationQueueRemediationExecution.test.ts +3 -0
- package/Tests/Server/Utils/AI/InvestigationSettlement.test.ts +313 -20
- package/Tests/Server/Utils/AI/PostedRootCause.test.ts +254 -0
- package/Tests/Server/Utils/AI/RemediationExecutionRunner.test.ts +4 -0
- package/Tests/Server/Utils/AI/RemediationPlanRunner.test.ts +2 -0
- package/Tests/Server/Utils/AI/SubjectCodeFixRunDedupe.test.ts +44 -0
- package/Tests/Server/Utils/AnalyticsDatabase/ClusterAwareSchema.test.ts +30 -0
- package/Tests/Server/Utils/Monitor/Criteria/APIRequestCriteriaPortTimings.test.ts +212 -0
- package/Tests/Server/Utils/Monitor/Criteria/DomainMonitorCriteria.test.ts +401 -0
- package/Tests/Server/Utils/Monitor/MonitorMetricUtilPortTimings.test.ts +243 -0
- package/Tests/Server/Utils/PasswordHash.test.ts +30 -0
- package/Tests/Types/Dashboard/DashboardTemplates.test.ts +111 -0
- package/Tests/Types/Monitor/CephMetricCatalog.test.ts +104 -0
- package/Tests/Types/Monitor/CriteriaFilter.test.ts +4 -0
- package/Tests/Types/Monitor/DockerMetricCatalog.test.ts +109 -0
- package/Tests/Types/Monitor/DockerSwarmMetricCatalog.test.ts +118 -0
- package/Tests/Types/Monitor/HostMetricCatalog.test.ts +104 -0
- package/Tests/Types/Monitor/IotMetricCatalog.test.ts +104 -0
- package/Tests/Types/Monitor/KubernetesMetricCatalog.test.ts +118 -0
- package/Tests/Types/Monitor/MonitorCriteriaInstance.test.ts +42 -0
- package/Tests/Types/Monitor/MonitorStep.test.ts +38 -0
- package/Tests/Types/Monitor/MonitorStepDomainMonitor.test.ts +75 -0
- package/Tests/Types/Monitor/PodmanMetricCatalog.test.ts +109 -0
- package/Tests/Types/Monitor/ProxmoxMetricCatalog.test.ts +107 -0
- package/Tests/Types/Monitor/SnmpMonitor/SnmpVendorTemplate.test.ts +151 -0
- package/Tests/Types/Permission.test.ts +137 -0
- package/Tests/Types/Rum/SessionReplayMaskingMode.test.ts +101 -0
- package/Tests/UI/Components/AiInvestigationSettingsCard.test.tsx +5 -5
- package/Tests/UI/Components/CardModelDetailEdit.test.tsx +3 -5
- package/Tests/UI/Components/ErrorBoundary.test.tsx +158 -0
- package/Tests/UI/Components/FeedItemSafeMode.test.tsx +74 -0
- package/Tests/UI/Components/MoreMenu.test.tsx +756 -0
- package/Tests/UI/Components/StatusPage/ResourceGroupSection.test.tsx +62 -0
- package/Tests/UI/Monitor/PortMonitorView.test.tsx +237 -0
- package/Tests/UI/Telemetry/TelemetrySnapshotWindowAlert.test.tsx +91 -0
- package/Tests/UI/Utils/DownloadFile.test.ts +103 -0
- package/Tests/UI/Utils/ErrorSupportBundle.test.ts +582 -0
- package/Tests/UI/Utils/Project.test.ts +31 -0
- package/Tests/UI/Utils/StatusPageModelAPIInjection.test.ts +245 -0
- package/Tests/UI/Utils/UseDashboardGridDnd.test.tsx +820 -0
- package/Tests/Utils/Dashboard/DashboardViewConfig.test.ts +329 -0
- package/Tests/Utils/Dashboard/GridLayout.test.ts +951 -0
- package/Tests/Utils/Monitor/MonitorMetricType.test.ts +117 -0
- package/Tests/Utils/RecordingRuleExpression.test.ts +327 -0
- package/Tests/Utils/StatusPage/GroupNestingLayout.test.ts +186 -0
- package/Tests/Utils/StatusPage/GroupTree.test.ts +456 -0
- package/Tests/Utils/StatusPage/OverviewGroupHierarchyVisibility.test.ts +588 -0
- package/Tests/Utils/Telemetry/SavedViewTimeRange.test.ts +244 -0
- package/Tests/Utils/Telemetry/TelemetryQueryTimeRange.test.ts +825 -0
- package/Types/AI/AIRunCodeFixRecommendation.ts +17 -0
- package/Types/AI/CodeFixTaskContext.ts +63 -3
- package/Types/AI/CodeFixTaskType.ts +3 -3
- package/Types/HashedString.ts +4 -3
- package/Types/Monitor/CriteriaFilter.ts +4 -0
- package/Types/Monitor/DomainMonitor/DomainLookupMethod.ts +23 -0
- package/Types/Monitor/DomainMonitor/DomainMonitorResponse.ts +7 -0
- package/Types/Monitor/MonitorCriteriaInstance.ts +24 -7
- package/Types/Monitor/MonitorMetricType.ts +8 -0
- package/Types/Monitor/MonitorStep.ts +10 -1
- package/Types/Monitor/MonitorStepDomainMonitor.ts +21 -0
- package/Types/Monitor/PortMonitor/PortMonitorTimings.ts +10 -0
- package/Types/Permission.ts +23 -175
- package/Types/Probe/ProbeMonitorResponse.ts +6 -0
- package/UI/Components/BulkUpdate/BulkUpdateForm.tsx +2 -3
- package/UI/Components/ErrorBoundary.tsx +138 -11
- package/UI/Components/Feed/FeedItem.tsx +9 -2
- package/UI/Components/ModelDetail/CardModelDetail.tsx +0 -12
- package/UI/Components/ModelTable/BaseModelTable.tsx +1 -1
- package/UI/Components/MonitorTemplateVariables/TemplateVariablesCatalog.ts +12 -2
- package/UI/Components/MoreMenu/MoreMenu.tsx +285 -97
- package/UI/Components/MoreMenu/MoreMenuItem.tsx +8 -5
- package/UI/Utils/DownloadFile.ts +16 -8
- package/UI/Utils/ErrorSupportBundle.ts +805 -0
- package/UI/Utils/ModelAPI/ModelAPI.ts +19 -8
- package/UI/Utils/Project.ts +4 -0
- package/UI/Utils/StatusPage.ts +8 -2
- package/UI/Utils/UseDashboardGridDnd.ts +917 -0
- package/Utils/Dashboard/DashboardViewConfig.ts +80 -36
- package/Utils/Dashboard/GridLayout.ts +523 -0
- package/Utils/Metrics/RecordingRuleExpression.ts +19 -17
- package/Utils/Monitor/MonitorMetricType.ts +44 -2
- package/Utils/StatusPage/GroupNestingLayout.ts +69 -0
- package/Utils/StatusPage/GroupTree.ts +157 -66
- package/Utils/StatusPage/ResourceUptime.ts +37 -2
- package/Utils/Telemetry/SavedViewTimeRange.ts +114 -0
- package/Utils/Telemetry/TelemetryQueryTimeRange.ts +334 -0
- package/build/dist/Models/DatabaseModels/AIRun.js +39 -1
- package/build/dist/Models/DatabaseModels/AIRun.js.map +1 -1
- package/build/dist/Models/DatabaseModels/AlertFeed.js +32 -0
- package/build/dist/Models/DatabaseModels/AlertFeed.js.map +1 -1
- package/build/dist/Models/DatabaseModels/Dashboard.js +29 -0
- package/build/dist/Models/DatabaseModels/Dashboard.js.map +1 -1
- package/build/dist/Models/DatabaseModels/Incident.js +2 -0
- package/build/dist/Models/DatabaseModels/Incident.js.map +1 -1
- package/build/dist/Models/DatabaseModels/IncidentEpisode.js +1 -0
- package/build/dist/Models/DatabaseModels/IncidentEpisode.js.map +1 -1
- package/build/dist/Models/DatabaseModels/IncidentEpisodePublicNote.js +1 -0
- package/build/dist/Models/DatabaseModels/IncidentEpisodePublicNote.js.map +1 -1
- package/build/dist/Models/DatabaseModels/IncidentEpisodeStateTimeline.js +1 -0
- package/build/dist/Models/DatabaseModels/IncidentEpisodeStateTimeline.js.map +1 -1
- package/build/dist/Models/DatabaseModels/IncidentFeed.js +32 -0
- package/build/dist/Models/DatabaseModels/IncidentFeed.js.map +1 -1
- package/build/dist/Models/DatabaseModels/IncidentPublicNote.js +1 -0
- package/build/dist/Models/DatabaseModels/IncidentPublicNote.js.map +1 -1
- package/build/dist/Models/DatabaseModels/IncidentStateTimeline.js +1 -0
- package/build/dist/Models/DatabaseModels/IncidentStateTimeline.js.map +1 -1
- package/build/dist/Models/DatabaseModels/LogSavedView.js +33 -0
- package/build/dist/Models/DatabaseModels/LogSavedView.js.map +1 -1
- package/build/dist/Models/DatabaseModels/Project.js +241 -11
- package/build/dist/Models/DatabaseModels/Project.js.map +1 -1
- package/build/dist/Models/DatabaseModels/ScheduledMaintenance.js +1 -0
- package/build/dist/Models/DatabaseModels/ScheduledMaintenance.js.map +1 -1
- package/build/dist/Models/DatabaseModels/ScheduledMaintenancePublicNote.js +1 -0
- package/build/dist/Models/DatabaseModels/ScheduledMaintenancePublicNote.js.map +1 -1
- package/build/dist/Models/DatabaseModels/ScheduledMaintenanceStateTimeline.js +1 -0
- package/build/dist/Models/DatabaseModels/ScheduledMaintenanceStateTimeline.js.map +1 -1
- package/build/dist/Models/DatabaseModels/ScheduledMaintenanceTemplateOwnerUser.js +4 -4
- package/build/dist/Models/DatabaseModels/StatusPage.js +29 -0
- package/build/dist/Models/DatabaseModels/StatusPage.js.map +1 -1
- package/build/dist/Models/DatabaseModels/StatusPageAnnouncement.js +1 -0
- package/build/dist/Models/DatabaseModels/StatusPageAnnouncement.js.map +1 -1
- package/build/dist/Server/API/AIAgentDataAPI.js +28 -46
- package/build/dist/Server/API/AIAgentDataAPI.js.map +1 -1
- package/build/dist/Server/API/AIInvestigationAPI.js +111 -11
- package/build/dist/Server/API/AIInvestigationAPI.js.map +1 -1
- package/build/dist/Server/API/DashboardAPI.js +8 -5
- package/build/dist/Server/API/DashboardAPI.js.map +1 -1
- package/build/dist/Server/API/StatusPageAPI.js +29 -11
- package/build/dist/Server/API/StatusPageAPI.js.map +1 -1
- package/build/dist/Server/EnvironmentConfig.js +14 -0
- package/build/dist/Server/EnvironmentConfig.js.map +1 -1
- package/build/dist/Server/Infrastructure/ClickhouseConfig.js +12 -1
- package/build/dist/Server/Infrastructure/ClickhouseConfig.js.map +1 -1
- package/build/dist/Server/Infrastructure/ClickhouseDatabase.js +1 -1
- package/build/dist/Server/Infrastructure/ClickhouseDatabase.js.map +1 -1
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1786096660558-AddTimeRangeToLogSavedView.js +22 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1786096660558-AddTimeRangeToLogSavedView.js.map +1 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1786101798351-MigrationName.js +36 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1786101798351-MigrationName.js.map +1 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1786105470826-MigrationName.js +18 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1786105470826-MigrationName.js.map +1 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1786400000000-AddMasterPasswordSalt.js +19 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1786400000000-AddMasterPasswordSalt.js.map +1 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1786500000000-AddInvestigationCodeFixRecommendation.js +19 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1786500000000-AddInvestigationCodeFixRecommendation.js.map +1 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js +10 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js.map +1 -1
- package/build/dist/Server/Services/AIRunService.js +58 -12
- package/build/dist/Server/Services/AIRunService.js.map +1 -1
- package/build/dist/Server/Services/AIService.js +74 -21
- package/build/dist/Server/Services/AIService.js.map +1 -1
- package/build/dist/Server/Services/AlertFeedService.js +3 -0
- package/build/dist/Server/Services/AlertFeedService.js.map +1 -1
- package/build/dist/Server/Services/AnalyticsDatabaseService.js +29 -1
- package/build/dist/Server/Services/AnalyticsDatabaseService.js.map +1 -1
- package/build/dist/Server/Services/DatabaseService.js +21 -2
- package/build/dist/Server/Services/DatabaseService.js.map +1 -1
- package/build/dist/Server/Services/IncidentFeedService.js +3 -0
- package/build/dist/Server/Services/IncidentFeedService.js.map +1 -1
- package/build/dist/Server/Services/LlmLogService.js +22 -1
- package/build/dist/Server/Services/LlmLogService.js.map +1 -1
- package/build/dist/Server/Utils/AI/Chat/ObservabilityAssistant.js +3 -0
- package/build/dist/Server/Utils/AI/Chat/ObservabilityAssistant.js.map +1 -1
- package/build/dist/Server/Utils/AI/CodeFix/CodeFixAgentCompletion.js +4 -0
- package/build/dist/Server/Utils/AI/CodeFix/CodeFixAgentCompletion.js.map +1 -1
- package/build/dist/Server/Utils/AI/CodeFix/CodeFixReadiness.js +8 -5
- package/build/dist/Server/Utils/AI/CodeFix/CodeFixReadiness.js.map +1 -1
- package/build/dist/Server/Utils/AI/CodeFix/FixRunBudget.js +63 -24
- package/build/dist/Server/Utils/AI/CodeFix/FixRunBudget.js.map +1 -1
- package/build/dist/Server/Utils/AI/Remediation/RemediationExecutionRunner.js +2 -0
- package/build/dist/Server/Utils/AI/Remediation/RemediationExecutionRunner.js.map +1 -1
- package/build/dist/Server/Utils/AI/Remediation/RemediationPlanRunner.js +2 -0
- package/build/dist/Server/Utils/AI/Remediation/RemediationPlanRunner.js.map +1 -1
- package/build/dist/Server/Utils/AI/SRE/AIInvestigationEngine.js +156 -28
- package/build/dist/Server/Utils/AI/SRE/AIInvestigationEngine.js.map +1 -1
- package/build/dist/Server/Utils/AI/SRE/AlertInvestigationRunner.js +21 -19
- package/build/dist/Server/Utils/AI/SRE/AlertInvestigationRunner.js.map +1 -1
- package/build/dist/Server/Utils/AI/SRE/ConfidenceSignal.js +128 -49
- package/build/dist/Server/Utils/AI/SRE/ConfidenceSignal.js.map +1 -1
- package/build/dist/Server/Utils/AI/SRE/FixFromIncidentTaskTrigger.js +170 -31
- package/build/dist/Server/Utils/AI/SRE/FixFromIncidentTaskTrigger.js.map +1 -1
- package/build/dist/Server/Utils/AI/SRE/IncidentInvestigationRunner.js +21 -19
- package/build/dist/Server/Utils/AI/SRE/IncidentInvestigationRunner.js.map +1 -1
- package/build/dist/Server/Utils/AI/SRE/InstrumentationTaskTrigger.js +33 -9
- package/build/dist/Server/Utils/AI/SRE/InstrumentationTaskTrigger.js.map +1 -1
- package/build/dist/Server/Utils/AI/SRE/InvestigationGrader.js +9 -5
- package/build/dist/Server/Utils/AI/SRE/InvestigationGrader.js.map +1 -1
- package/build/dist/Server/Utils/AI/SRE/InvestigationQueue.js +121 -41
- package/build/dist/Server/Utils/AI/SRE/InvestigationQueue.js.map +1 -1
- package/build/dist/Server/Utils/AI/SRE/InvestigationSubjectLock.js +46 -0
- package/build/dist/Server/Utils/AI/SRE/InvestigationSubjectLock.js.map +1 -0
- package/build/dist/Server/Utils/AI/SRE/PostedRootCause.js +61 -14
- package/build/dist/Server/Utils/AI/SRE/PostedRootCause.js.map +1 -1
- package/build/dist/Server/Utils/AI/SRE/SubjectCodeFixRun.js +79 -1
- package/build/dist/Server/Utils/AI/SRE/SubjectCodeFixRun.js.map +1 -1
- package/build/dist/Server/Utils/AnalyticsDatabase/ClusterConfig.js +21 -0
- package/build/dist/Server/Utils/AnalyticsDatabase/ClusterConfig.js.map +1 -1
- package/build/dist/Server/Utils/Monitor/Criteria/APIRequestCriteria.js +20 -3
- package/build/dist/Server/Utils/Monitor/Criteria/APIRequestCriteria.js.map +1 -1
- package/build/dist/Server/Utils/Monitor/Criteria/DomainMonitorCriteria.js +43 -0
- package/build/dist/Server/Utils/Monitor/Criteria/DomainMonitorCriteria.js.map +1 -1
- package/build/dist/Server/Utils/Monitor/MonitorMetricUtil.js +33 -0
- package/build/dist/Server/Utils/Monitor/MonitorMetricUtil.js.map +1 -1
- package/build/dist/Server/Utils/Monitor/MonitorTemplateUtil.js +1 -0
- package/build/dist/Server/Utils/Monitor/MonitorTemplateUtil.js.map +1 -1
- package/build/dist/Server/Utils/PasswordHash.js +25 -4
- package/build/dist/Server/Utils/PasswordHash.js.map +1 -1
- package/build/dist/Types/AI/AIRunCodeFixRecommendation.js +18 -0
- package/build/dist/Types/AI/AIRunCodeFixRecommendation.js.map +1 -0
- package/build/dist/Types/AI/CodeFixTaskContext.js +23 -3
- package/build/dist/Types/AI/CodeFixTaskContext.js.map +1 -1
- package/build/dist/Types/AI/CodeFixTaskType.js +3 -3
- package/build/dist/Types/HashedString.js +4 -3
- package/build/dist/Types/HashedString.js.map +1 -1
- package/build/dist/Types/Monitor/CriteriaFilter.js +4 -0
- package/build/dist/Types/Monitor/CriteriaFilter.js.map +1 -1
- package/build/dist/Types/Monitor/DomainMonitor/DomainLookupMethod.js +24 -0
- package/build/dist/Types/Monitor/DomainMonitor/DomainLookupMethod.js.map +1 -0
- package/build/dist/Types/Monitor/MonitorCriteriaInstance.js +24 -7
- package/build/dist/Types/Monitor/MonitorCriteriaInstance.js.map +1 -1
- package/build/dist/Types/Monitor/MonitorMetricType.js +7 -0
- package/build/dist/Types/Monitor/MonitorMetricType.js.map +1 -1
- package/build/dist/Types/Monitor/MonitorStep.js +6 -1
- package/build/dist/Types/Monitor/MonitorStep.js.map +1 -1
- package/build/dist/Types/Monitor/MonitorStepDomainMonitor.js +15 -0
- package/build/dist/Types/Monitor/MonitorStepDomainMonitor.js.map +1 -1
- package/build/dist/Types/Monitor/PortMonitor/PortMonitorTimings.js +2 -0
- package/build/dist/Types/Monitor/PortMonitor/PortMonitorTimings.js.map +1 -0
- package/build/dist/Types/Permission.js +23 -155
- package/build/dist/Types/Permission.js.map +1 -1
- package/build/dist/UI/Components/BulkUpdate/BulkUpdateForm.js +2 -5
- package/build/dist/UI/Components/BulkUpdate/BulkUpdateForm.js.map +1 -1
- package/build/dist/UI/Components/ErrorBoundary.js +67 -8
- package/build/dist/UI/Components/ErrorBoundary.js.map +1 -1
- package/build/dist/UI/Components/Feed/FeedItem.js +2 -2
- package/build/dist/UI/Components/Feed/FeedItem.js.map +1 -1
- package/build/dist/UI/Components/ModelDetail/CardModelDetail.js +0 -12
- package/build/dist/UI/Components/ModelDetail/CardModelDetail.js.map +1 -1
- package/build/dist/UI/Components/ModelTable/BaseModelTable.js +1 -1
- package/build/dist/UI/Components/ModelTable/BaseModelTable.js.map +1 -1
- package/build/dist/UI/Components/MonitorTemplateVariables/TemplateVariablesCatalog.js +12 -2
- package/build/dist/UI/Components/MonitorTemplateVariables/TemplateVariablesCatalog.js.map +1 -1
- package/build/dist/UI/Components/MoreMenu/MoreMenu.js +193 -65
- package/build/dist/UI/Components/MoreMenu/MoreMenu.js.map +1 -1
- package/build/dist/UI/Components/MoreMenu/MoreMenuItem.js +1 -1
- package/build/dist/UI/Components/MoreMenu/MoreMenuItem.js.map +1 -1
- package/build/dist/UI/Utils/DownloadFile.js +15 -8
- package/build/dist/UI/Utils/DownloadFile.js.map +1 -1
- package/build/dist/UI/Utils/ErrorSupportBundle.js +476 -0
- package/build/dist/UI/Utils/ErrorSupportBundle.js.map +1 -0
- package/build/dist/UI/Utils/ModelAPI/ModelAPI.js +18 -8
- package/build/dist/UI/Utils/ModelAPI/ModelAPI.js.map +1 -1
- package/build/dist/UI/Utils/Project.js +3 -0
- package/build/dist/UI/Utils/Project.js.map +1 -1
- package/build/dist/UI/Utils/StatusPage.js +4 -4
- package/build/dist/UI/Utils/StatusPage.js.map +1 -1
- package/build/dist/UI/Utils/UseDashboardGridDnd.js +551 -0
- package/build/dist/UI/Utils/UseDashboardGridDnd.js.map +1 -0
- package/build/dist/Utils/Dashboard/DashboardViewConfig.js +42 -26
- package/build/dist/Utils/Dashboard/DashboardViewConfig.js.map +1 -1
- package/build/dist/Utils/Dashboard/GridLayout.js +338 -0
- package/build/dist/Utils/Dashboard/GridLayout.js.map +1 -0
- package/build/dist/Utils/Metrics/RecordingRuleExpression.js +19 -17
- package/build/dist/Utils/Metrics/RecordingRuleExpression.js.map +1 -1
- package/build/dist/Utils/Monitor/MonitorMetricType.js +38 -6
- package/build/dist/Utils/Monitor/MonitorMetricType.js.map +1 -1
- package/build/dist/Utils/StatusPage/GroupNestingLayout.js +46 -0
- package/build/dist/Utils/StatusPage/GroupNestingLayout.js.map +1 -1
- package/build/dist/Utils/StatusPage/GroupTree.js +79 -34
- package/build/dist/Utils/StatusPage/GroupTree.js.map +1 -1
- package/build/dist/Utils/StatusPage/ResourceUptime.js +22 -1
- package/build/dist/Utils/StatusPage/ResourceUptime.js.map +1 -1
- package/build/dist/Utils/Telemetry/SavedViewTimeRange.js +79 -0
- package/build/dist/Utils/Telemetry/SavedViewTimeRange.js.map +1 -0
- package/build/dist/Utils/Telemetry/TelemetryQueryTimeRange.js +236 -0
- package/build/dist/Utils/Telemetry/TelemetryQueryTimeRange.js.map +1 -0
- package/package.json +1 -1
|
@@ -716,6 +716,8 @@ describe("StatusPageGroupNestingLayoutUtil", () => {
|
|
|
716
716
|
showCurrentStatus: true,
|
|
717
717
|
isCurrentlyDown: false,
|
|
718
718
|
uptimePercent: 99.9,
|
|
719
|
+
|
|
720
|
+
resourceCountInSubtree: 1,
|
|
719
721
|
}),
|
|
720
722
|
).toBe(StatusPageGroupRollupKind.UptimePercent);
|
|
721
723
|
});
|
|
@@ -731,6 +733,8 @@ describe("StatusPageGroupNestingLayoutUtil", () => {
|
|
|
731
733
|
showCurrentStatus: true,
|
|
732
734
|
isCurrentlyDown: true,
|
|
733
735
|
uptimePercent: 99.9,
|
|
736
|
+
|
|
737
|
+
resourceCountInSubtree: 1,
|
|
734
738
|
}),
|
|
735
739
|
).toBe(StatusPageGroupRollupKind.CurrentStatus);
|
|
736
740
|
});
|
|
@@ -746,6 +750,8 @@ describe("StatusPageGroupNestingLayoutUtil", () => {
|
|
|
746
750
|
showCurrentStatus: true,
|
|
747
751
|
isCurrentlyDown: false,
|
|
748
752
|
uptimePercent: null,
|
|
753
|
+
|
|
754
|
+
resourceCountInSubtree: 1,
|
|
749
755
|
}),
|
|
750
756
|
).toBe(StatusPageGroupRollupKind.None);
|
|
751
757
|
});
|
|
@@ -757,6 +763,8 @@ describe("StatusPageGroupNestingLayoutUtil", () => {
|
|
|
757
763
|
showCurrentStatus: true,
|
|
758
764
|
isCurrentlyDown: false,
|
|
759
765
|
uptimePercent: 100,
|
|
766
|
+
|
|
767
|
+
resourceCountInSubtree: 1,
|
|
760
768
|
}),
|
|
761
769
|
).toBe(StatusPageGroupRollupKind.CurrentStatus);
|
|
762
770
|
});
|
|
@@ -768,6 +776,8 @@ describe("StatusPageGroupNestingLayoutUtil", () => {
|
|
|
768
776
|
showCurrentStatus: false,
|
|
769
777
|
isCurrentlyDown: false,
|
|
770
778
|
uptimePercent: 100,
|
|
779
|
+
|
|
780
|
+
resourceCountInSubtree: 1,
|
|
771
781
|
}),
|
|
772
782
|
).toBe(StatusPageGroupRollupKind.None);
|
|
773
783
|
});
|
|
@@ -779,6 +789,8 @@ describe("StatusPageGroupNestingLayoutUtil", () => {
|
|
|
779
789
|
showCurrentStatus: false,
|
|
780
790
|
isCurrentlyDown: true,
|
|
781
791
|
uptimePercent: 99.9,
|
|
792
|
+
|
|
793
|
+
resourceCountInSubtree: 1,
|
|
782
794
|
}),
|
|
783
795
|
).toBe(StatusPageGroupRollupKind.None);
|
|
784
796
|
});
|
|
@@ -790,9 +802,49 @@ describe("StatusPageGroupNestingLayoutUtil", () => {
|
|
|
790
802
|
showCurrentStatus: true,
|
|
791
803
|
isCurrentlyDown: false,
|
|
792
804
|
uptimePercent: 0,
|
|
805
|
+
|
|
806
|
+
resourceCountInSubtree: 1,
|
|
793
807
|
}),
|
|
794
808
|
).toBe(StatusPageGroupRollupKind.UptimePercent);
|
|
795
809
|
});
|
|
810
|
+
|
|
811
|
+
/*
|
|
812
|
+
* A hierarchy is drawn while it is still being filled in, so groups with
|
|
813
|
+
* nothing under them yet are on the page. The rolled up status defaults to
|
|
814
|
+
* Operational when it finds no resources to look at, and publishing that
|
|
815
|
+
* would be telling visitors that a group containing no monitors is healthy.
|
|
816
|
+
*/
|
|
817
|
+
test("a group with nothing under it shows no rollup, whatever it is configured for", () => {
|
|
818
|
+
for (const showUptimePercent of [true, false]) {
|
|
819
|
+
for (const showCurrentStatus of [true, false]) {
|
|
820
|
+
expect(
|
|
821
|
+
StatusPageGroupNestingLayoutUtil.getRollupKind({
|
|
822
|
+
showUptimePercent: showUptimePercent,
|
|
823
|
+
showCurrentStatus: showCurrentStatus,
|
|
824
|
+
isCurrentlyDown: false,
|
|
825
|
+
uptimePercent: null,
|
|
826
|
+
resourceCountInSubtree: 0,
|
|
827
|
+
}),
|
|
828
|
+
).toBe(StatusPageGroupRollupKind.None);
|
|
829
|
+
}
|
|
830
|
+
}
|
|
831
|
+
});
|
|
832
|
+
|
|
833
|
+
/*
|
|
834
|
+
* The count is of the whole subtree, so a holder group whose own resource
|
|
835
|
+
* list is empty still reports for the monitors nested under it.
|
|
836
|
+
*/
|
|
837
|
+
test("a holder group reports the resources nested below it", () => {
|
|
838
|
+
expect(
|
|
839
|
+
StatusPageGroupNestingLayoutUtil.getRollupKind({
|
|
840
|
+
showUptimePercent: false,
|
|
841
|
+
showCurrentStatus: true,
|
|
842
|
+
isCurrentlyDown: false,
|
|
843
|
+
uptimePercent: null,
|
|
844
|
+
resourceCountInSubtree: 12,
|
|
845
|
+
}),
|
|
846
|
+
).toBe(StatusPageGroupRollupKind.CurrentStatus);
|
|
847
|
+
});
|
|
796
848
|
});
|
|
797
849
|
|
|
798
850
|
describe("shouldShowSubGroupDivider", () => {
|
|
@@ -878,6 +930,140 @@ describe("StatusPageGroupNestingLayoutUtil", () => {
|
|
|
878
930
|
).toBe(true);
|
|
879
931
|
});
|
|
880
932
|
});
|
|
933
|
+
|
|
934
|
+
/*
|
|
935
|
+
* The overview page used to gate its whole resources-and-groups block on
|
|
936
|
+
* `statusPageResources.length > 0`. That made a status page carrying a fully
|
|
937
|
+
* built group hierarchy and no monitors render nothing at all: the operator
|
|
938
|
+
* created their groups, saw them listed in the dashboard, and got an empty
|
|
939
|
+
* public page until the first monitor was attached to one of them.
|
|
940
|
+
*
|
|
941
|
+
* Groups are content. These two gates are what says so, and they have to
|
|
942
|
+
* agree with each other - a page that renders its hierarchy must not also
|
|
943
|
+
* render an empty state above it saying there is nothing here.
|
|
944
|
+
*/
|
|
945
|
+
describe("shouldRenderResourcesSection", () => {
|
|
946
|
+
test("renders for a page with groups and no resources at all", () => {
|
|
947
|
+
expect(
|
|
948
|
+
StatusPageGroupNestingLayoutUtil.shouldRenderResourcesSection({
|
|
949
|
+
statusPageResourceCount: 0,
|
|
950
|
+
statusPageGroupCount: 1,
|
|
951
|
+
}),
|
|
952
|
+
).toBe(true);
|
|
953
|
+
});
|
|
954
|
+
|
|
955
|
+
/* The size the bug was reported at. */
|
|
956
|
+
test("renders for a large hierarchy with no resources", () => {
|
|
957
|
+
expect(
|
|
958
|
+
StatusPageGroupNestingLayoutUtil.shouldRenderResourcesSection({
|
|
959
|
+
statusPageResourceCount: 0,
|
|
960
|
+
statusPageGroupCount: 1506,
|
|
961
|
+
}),
|
|
962
|
+
).toBe(true);
|
|
963
|
+
});
|
|
964
|
+
|
|
965
|
+
test("renders for a page with resources and no groups", () => {
|
|
966
|
+
expect(
|
|
967
|
+
StatusPageGroupNestingLayoutUtil.shouldRenderResourcesSection({
|
|
968
|
+
statusPageResourceCount: 3,
|
|
969
|
+
statusPageGroupCount: 0,
|
|
970
|
+
}),
|
|
971
|
+
).toBe(true);
|
|
972
|
+
});
|
|
973
|
+
|
|
974
|
+
test("renders for a page with both", () => {
|
|
975
|
+
expect(
|
|
976
|
+
StatusPageGroupNestingLayoutUtil.shouldRenderResourcesSection({
|
|
977
|
+
statusPageResourceCount: 3,
|
|
978
|
+
statusPageGroupCount: 4,
|
|
979
|
+
}),
|
|
980
|
+
).toBe(true);
|
|
981
|
+
});
|
|
982
|
+
|
|
983
|
+
test("renders nothing for a page with neither", () => {
|
|
984
|
+
expect(
|
|
985
|
+
StatusPageGroupNestingLayoutUtil.shouldRenderResourcesSection({
|
|
986
|
+
statusPageResourceCount: 0,
|
|
987
|
+
statusPageGroupCount: 0,
|
|
988
|
+
}),
|
|
989
|
+
).toBe(false);
|
|
990
|
+
});
|
|
991
|
+
});
|
|
992
|
+
|
|
993
|
+
describe("shouldRenderOverviewEmptyState", () => {
|
|
994
|
+
type EmptyStateInput = Parameters<
|
|
995
|
+
typeof StatusPageGroupNestingLayoutUtil.shouldRenderOverviewEmptyState
|
|
996
|
+
>[0];
|
|
997
|
+
|
|
998
|
+
function emptyPage(overrides: Partial<EmptyStateInput> = {}): boolean {
|
|
999
|
+
return StatusPageGroupNestingLayoutUtil.shouldRenderOverviewEmptyState({
|
|
1000
|
+
statusPageResourceCount: 0,
|
|
1001
|
+
statusPageGroupCount: 0,
|
|
1002
|
+
activeIncidentCount: 0,
|
|
1003
|
+
activeEpisodeCount: 0,
|
|
1004
|
+
activeScheduledMaintenanceCount: 0,
|
|
1005
|
+
activeAnnouncementCount: 0,
|
|
1006
|
+
...overrides,
|
|
1007
|
+
});
|
|
1008
|
+
}
|
|
1009
|
+
|
|
1010
|
+
test("a page with nothing on it shows the empty state", () => {
|
|
1011
|
+
expect(emptyPage()).toBe(true);
|
|
1012
|
+
});
|
|
1013
|
+
|
|
1014
|
+
/*
|
|
1015
|
+
* The half of the bug the visitor actually saw: 1506 groups on the page,
|
|
1016
|
+
* and an "all clear, nothing to see" panel rendered instead of them.
|
|
1017
|
+
*/
|
|
1018
|
+
test("a page with groups and no resources does not show the empty state", () => {
|
|
1019
|
+
expect(emptyPage({ statusPageGroupCount: 1506 })).toBe(false);
|
|
1020
|
+
});
|
|
1021
|
+
|
|
1022
|
+
test("a page with resources does not show the empty state", () => {
|
|
1023
|
+
expect(emptyPage({ statusPageResourceCount: 1 })).toBe(false);
|
|
1024
|
+
});
|
|
1025
|
+
|
|
1026
|
+
test.each([
|
|
1027
|
+
["an active incident", "activeIncidentCount"],
|
|
1028
|
+
["an active episode", "activeEpisodeCount"],
|
|
1029
|
+
["a scheduled maintenance event", "activeScheduledMaintenanceCount"],
|
|
1030
|
+
["an announcement", "activeAnnouncementCount"],
|
|
1031
|
+
])(
|
|
1032
|
+
"%s suppresses the empty state on its own",
|
|
1033
|
+
(_name: string, key: string) => {
|
|
1034
|
+
expect(emptyPage({ [key]: 1 } as Partial<EmptyStateInput>)).toBe(false);
|
|
1035
|
+
},
|
|
1036
|
+
);
|
|
1037
|
+
|
|
1038
|
+
/*
|
|
1039
|
+
* The two gates are read off the same two counts, and the page renders
|
|
1040
|
+
* both of them. If they ever disagree the visitor sees an empty state
|
|
1041
|
+
* stacked on top of the hierarchy it claims is not there, so the
|
|
1042
|
+
* relationship is asserted over every combination rather than sampled.
|
|
1043
|
+
*/
|
|
1044
|
+
test("never contradicts shouldRenderResourcesSection", () => {
|
|
1045
|
+
const counts: Array<number> = [0, 1, 2, 1506];
|
|
1046
|
+
|
|
1047
|
+
for (const statusPageResourceCount of counts) {
|
|
1048
|
+
for (const statusPageGroupCount of counts) {
|
|
1049
|
+
const rendersSection: boolean =
|
|
1050
|
+
StatusPageGroupNestingLayoutUtil.shouldRenderResourcesSection({
|
|
1051
|
+
statusPageResourceCount: statusPageResourceCount,
|
|
1052
|
+
statusPageGroupCount: statusPageGroupCount,
|
|
1053
|
+
});
|
|
1054
|
+
|
|
1055
|
+
const rendersEmptyState: boolean = emptyPage({
|
|
1056
|
+
statusPageResourceCount: statusPageResourceCount,
|
|
1057
|
+
statusPageGroupCount: statusPageGroupCount,
|
|
1058
|
+
});
|
|
1059
|
+
|
|
1060
|
+
expect(rendersSection && rendersEmptyState).toBe(false);
|
|
1061
|
+
expect(rendersSection || rendersEmptyState).toBe(true);
|
|
1062
|
+
}
|
|
1063
|
+
}
|
|
1064
|
+
});
|
|
1065
|
+
});
|
|
1066
|
+
|
|
881
1067
|
describe("shouldRenderTimeAxis", () => {
|
|
882
1068
|
const CHARTED: ObjectID = new ObjectID(
|
|
883
1069
|
"aaaaaaaa-aaaa-4aaa-8aaa-aaaaaaaaaaaa",
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import StatusPageGroupTreeUtil, {
|
|
2
|
+
StatusPageGroupTreeIndex,
|
|
2
3
|
StatusPageGroupTreeNode,
|
|
3
4
|
} from "../../../Utils/StatusPage/GroupTree";
|
|
4
5
|
import ObjectID from "../../../Types/ObjectID";
|
|
@@ -90,6 +91,36 @@ function namesOf(groups: Array<StatusPageGroup>): Array<string> {
|
|
|
90
91
|
});
|
|
91
92
|
}
|
|
92
93
|
|
|
94
|
+
/*
|
|
95
|
+
* Counts how many times the walk under test asks a group who its parent is.
|
|
96
|
+
* That read is the primitive every tree helper is built on, so counting it is
|
|
97
|
+
* a deterministic stand-in for "did this scan the whole list per node?" - a
|
|
98
|
+
* wall clock assertion would just be a flake on a loaded CI box.
|
|
99
|
+
*/
|
|
100
|
+
function watchParentReads(groups: Array<StatusPageGroup>): () => number {
|
|
101
|
+
let reads: number = 0;
|
|
102
|
+
|
|
103
|
+
const watch: (group: StatusPageGroup) => void = (
|
|
104
|
+
group: StatusPageGroup,
|
|
105
|
+
): void => {
|
|
106
|
+
const actualParentId: ObjectID | undefined = group.parentStatusPageGroupId;
|
|
107
|
+
|
|
108
|
+
Object.defineProperty(group, "parentStatusPageGroupId", {
|
|
109
|
+
configurable: true,
|
|
110
|
+
get: (): ObjectID | undefined => {
|
|
111
|
+
reads++;
|
|
112
|
+
return actualParentId;
|
|
113
|
+
},
|
|
114
|
+
});
|
|
115
|
+
};
|
|
116
|
+
|
|
117
|
+
groups.forEach(watch);
|
|
118
|
+
|
|
119
|
+
return (): number => {
|
|
120
|
+
return reads;
|
|
121
|
+
};
|
|
122
|
+
}
|
|
123
|
+
|
|
93
124
|
describe("StatusPageGroupTreeUtil", () => {
|
|
94
125
|
describe("getParentId", () => {
|
|
95
126
|
test("returns null for a top level group", () => {
|
|
@@ -580,4 +611,429 @@ describe("StatusPageGroupTreeUtil", () => {
|
|
|
580
611
|
).toHaveLength(0);
|
|
581
612
|
});
|
|
582
613
|
});
|
|
614
|
+
|
|
615
|
+
/*
|
|
616
|
+
* The index is an optimisation, and the only thing that makes an
|
|
617
|
+
* optimisation safe is that it cannot change an answer. Every helper is
|
|
618
|
+
* therefore asserted to give byte-identical results with and without one,
|
|
619
|
+
* across the same bad-data shapes the rest of this file covers.
|
|
620
|
+
*/
|
|
621
|
+
describe("buildIndex", () => {
|
|
622
|
+
test("buckets children under their parent, in order", () => {
|
|
623
|
+
const groups: Array<StatusPageGroup> = makeHierarchy();
|
|
624
|
+
|
|
625
|
+
const index: StatusPageGroupTreeIndex =
|
|
626
|
+
StatusPageGroupTreeUtil.buildIndex({ statusPageGroups: groups });
|
|
627
|
+
|
|
628
|
+
expect(
|
|
629
|
+
namesOf(index.childrenByParentId.get(CORPORATE.toString()) || []),
|
|
630
|
+
).toEqual(["Region 1000", "Region 2000"]);
|
|
631
|
+
expect(namesOf(index.childrenByParentId.get(null) || [])).toEqual([
|
|
632
|
+
"Corporate",
|
|
633
|
+
]);
|
|
634
|
+
expect(index.byId.size).toBe(groups.length);
|
|
635
|
+
expect(index.byId.get(UNIT.toString())?.name).toBe("Unit 0152");
|
|
636
|
+
});
|
|
637
|
+
|
|
638
|
+
/*
|
|
639
|
+
* `null` rather than "" is the key for parentless groups on purpose: a
|
|
640
|
+
* group with no id of its own reads as "" everywhere in this class, and a
|
|
641
|
+
* string sentinel would make it adopt every root on the page.
|
|
642
|
+
*/
|
|
643
|
+
test("a group with no id does not adopt the parentless groups", () => {
|
|
644
|
+
const idless: StatusPageGroup = new StatusPageGroup();
|
|
645
|
+
idless.name = "No id";
|
|
646
|
+
|
|
647
|
+
const groups: Array<StatusPageGroup> = [...makeHierarchy(), idless];
|
|
648
|
+
|
|
649
|
+
expect(
|
|
650
|
+
StatusPageGroupTreeUtil.getChildGroups({
|
|
651
|
+
statusPageGroupId: "",
|
|
652
|
+
statusPageGroups: groups,
|
|
653
|
+
}),
|
|
654
|
+
).toHaveLength(0);
|
|
655
|
+
});
|
|
656
|
+
|
|
657
|
+
test("mutating the array getChildGroups returns leaves the index intact", () => {
|
|
658
|
+
const groups: Array<StatusPageGroup> = makeHierarchy();
|
|
659
|
+
const index: StatusPageGroupTreeIndex =
|
|
660
|
+
StatusPageGroupTreeUtil.buildIndex({ statusPageGroups: groups });
|
|
661
|
+
|
|
662
|
+
const children: Array<StatusPageGroup> =
|
|
663
|
+
StatusPageGroupTreeUtil.getChildGroups({
|
|
664
|
+
statusPageGroupId: CORPORATE.toString(),
|
|
665
|
+
statusPageGroups: groups,
|
|
666
|
+
index: index,
|
|
667
|
+
});
|
|
668
|
+
|
|
669
|
+
children.pop();
|
|
670
|
+
|
|
671
|
+
expect(
|
|
672
|
+
namesOf(
|
|
673
|
+
StatusPageGroupTreeUtil.getChildGroups({
|
|
674
|
+
statusPageGroupId: CORPORATE.toString(),
|
|
675
|
+
statusPageGroups: groups,
|
|
676
|
+
index: index,
|
|
677
|
+
}),
|
|
678
|
+
),
|
|
679
|
+
).toEqual(["Region 1000", "Region 2000"]);
|
|
680
|
+
});
|
|
681
|
+
|
|
682
|
+
describe("answers are identical with and without a supplied index", () => {
|
|
683
|
+
function cycle(): Array<StatusPageGroup> {
|
|
684
|
+
return [
|
|
685
|
+
makeGroup({
|
|
686
|
+
id: CORPORATE,
|
|
687
|
+
name: "A",
|
|
688
|
+
parentId: REGION_ONE,
|
|
689
|
+
order: 1,
|
|
690
|
+
}),
|
|
691
|
+
makeGroup({
|
|
692
|
+
id: REGION_ONE,
|
|
693
|
+
name: "B",
|
|
694
|
+
parentId: CORPORATE,
|
|
695
|
+
order: 2,
|
|
696
|
+
}),
|
|
697
|
+
];
|
|
698
|
+
}
|
|
699
|
+
|
|
700
|
+
function orphanAndSelfParent(): Array<StatusPageGroup> {
|
|
701
|
+
return [
|
|
702
|
+
makeGroup({ id: CORPORATE, name: "Self", parentId: CORPORATE }),
|
|
703
|
+
makeGroup({ id: REGION_ONE, name: "Orphan", parentId: MISSING }),
|
|
704
|
+
makeGroup({ id: MARKET, name: "Unordered" }),
|
|
705
|
+
];
|
|
706
|
+
}
|
|
707
|
+
|
|
708
|
+
const SHAPES: Array<{
|
|
709
|
+
name: string;
|
|
710
|
+
groups: () => Array<StatusPageGroup>;
|
|
711
|
+
}> = [
|
|
712
|
+
{ name: "a normal hierarchy", groups: makeHierarchy },
|
|
713
|
+
{ name: "a cycle", groups: cycle },
|
|
714
|
+
{ name: "orphans and self parents", groups: orphanAndSelfParent },
|
|
715
|
+
{
|
|
716
|
+
name: "an empty list",
|
|
717
|
+
groups: (): Array<StatusPageGroup> => {
|
|
718
|
+
return [];
|
|
719
|
+
},
|
|
720
|
+
},
|
|
721
|
+
];
|
|
722
|
+
|
|
723
|
+
test.each(SHAPES)(
|
|
724
|
+
"$name",
|
|
725
|
+
({ groups }: { groups: () => Array<StatusPageGroup> }) => {
|
|
726
|
+
const list: Array<StatusPageGroup> = groups();
|
|
727
|
+
const index: StatusPageGroupTreeIndex =
|
|
728
|
+
StatusPageGroupTreeUtil.buildIndex({ statusPageGroups: list });
|
|
729
|
+
|
|
730
|
+
expect(
|
|
731
|
+
namesOf(
|
|
732
|
+
StatusPageGroupTreeUtil.getRootGroups({
|
|
733
|
+
statusPageGroups: list,
|
|
734
|
+
index: index,
|
|
735
|
+
}),
|
|
736
|
+
),
|
|
737
|
+
).toEqual(
|
|
738
|
+
namesOf(
|
|
739
|
+
StatusPageGroupTreeUtil.getRootGroups({ statusPageGroups: list }),
|
|
740
|
+
),
|
|
741
|
+
);
|
|
742
|
+
|
|
743
|
+
const flatten: (
|
|
744
|
+
nodes: Array<StatusPageGroupTreeNode>,
|
|
745
|
+
) => Array<string> = (
|
|
746
|
+
nodes: Array<StatusPageGroupTreeNode>,
|
|
747
|
+
): Array<string> => {
|
|
748
|
+
return nodes.flatMap((node: StatusPageGroupTreeNode) => {
|
|
749
|
+
return [
|
|
750
|
+
`${node.group.name}@${node.depth}`,
|
|
751
|
+
...flatten(node.children),
|
|
752
|
+
];
|
|
753
|
+
});
|
|
754
|
+
};
|
|
755
|
+
|
|
756
|
+
expect(
|
|
757
|
+
flatten(
|
|
758
|
+
StatusPageGroupTreeUtil.buildTree({
|
|
759
|
+
statusPageGroups: list,
|
|
760
|
+
index: index,
|
|
761
|
+
}),
|
|
762
|
+
),
|
|
763
|
+
).toEqual(
|
|
764
|
+
flatten(
|
|
765
|
+
StatusPageGroupTreeUtil.buildTree({ statusPageGroups: list }),
|
|
766
|
+
),
|
|
767
|
+
);
|
|
768
|
+
|
|
769
|
+
for (const group of list) {
|
|
770
|
+
expect(
|
|
771
|
+
namesOf(
|
|
772
|
+
StatusPageGroupTreeUtil.getGroupAndDescendants({
|
|
773
|
+
statusPageGroup: group,
|
|
774
|
+
statusPageGroups: list,
|
|
775
|
+
index: index,
|
|
776
|
+
}),
|
|
777
|
+
),
|
|
778
|
+
).toEqual(
|
|
779
|
+
namesOf(
|
|
780
|
+
StatusPageGroupTreeUtil.getGroupAndDescendants({
|
|
781
|
+
statusPageGroup: group,
|
|
782
|
+
statusPageGroups: list,
|
|
783
|
+
}),
|
|
784
|
+
),
|
|
785
|
+
);
|
|
786
|
+
|
|
787
|
+
expect(
|
|
788
|
+
namesOf(
|
|
789
|
+
StatusPageGroupTreeUtil.getAncestorGroups({
|
|
790
|
+
statusPageGroup: group,
|
|
791
|
+
statusPageGroups: list,
|
|
792
|
+
index: index,
|
|
793
|
+
}),
|
|
794
|
+
),
|
|
795
|
+
).toEqual(
|
|
796
|
+
namesOf(
|
|
797
|
+
StatusPageGroupTreeUtil.getAncestorGroups({
|
|
798
|
+
statusPageGroup: group,
|
|
799
|
+
statusPageGroups: list,
|
|
800
|
+
}),
|
|
801
|
+
),
|
|
802
|
+
);
|
|
803
|
+
|
|
804
|
+
expect(
|
|
805
|
+
StatusPageGroupTreeUtil.getDepth({
|
|
806
|
+
statusPageGroup: group,
|
|
807
|
+
statusPageGroups: list,
|
|
808
|
+
index: index,
|
|
809
|
+
}),
|
|
810
|
+
).toBe(
|
|
811
|
+
StatusPageGroupTreeUtil.getDepth({
|
|
812
|
+
statusPageGroup: group,
|
|
813
|
+
statusPageGroups: list,
|
|
814
|
+
}),
|
|
815
|
+
);
|
|
816
|
+
}
|
|
817
|
+
},
|
|
818
|
+
);
|
|
819
|
+
});
|
|
820
|
+
});
|
|
821
|
+
|
|
822
|
+
/*
|
|
823
|
+
* The bug this file's fix belongs to was filed against a status page with
|
|
824
|
+
* 1506 groups. Nothing below asserts a wall clock time - a test that fails
|
|
825
|
+
* on a loaded CI box is worse than no test - but the shape of the work is
|
|
826
|
+
* pinned: every group has to come back, at the right depth, and the walk has
|
|
827
|
+
* to stay near linear rather than degrading into a scan of the whole list
|
|
828
|
+
* per node.
|
|
829
|
+
*/
|
|
830
|
+
describe("at the size the bug was reported at", () => {
|
|
831
|
+
const CORPORATE_UNITS: number = 6;
|
|
832
|
+
const REGIONS_PER_UNIT: number = 5;
|
|
833
|
+
const MARKETS_PER_REGION: number = 5;
|
|
834
|
+
const SITES_PER_MARKET: number = 10;
|
|
835
|
+
|
|
836
|
+
function makeLargeHierarchy(): Array<StatusPageGroup> {
|
|
837
|
+
const groups: Array<StatusPageGroup> = [];
|
|
838
|
+
let order: number = 0;
|
|
839
|
+
|
|
840
|
+
const idFor: (label: string) => ObjectID = (label: string): ObjectID => {
|
|
841
|
+
return new ObjectID(label);
|
|
842
|
+
};
|
|
843
|
+
|
|
844
|
+
for (let unit: number = 0; unit < CORPORATE_UNITS; unit++) {
|
|
845
|
+
const unitId: ObjectID = idFor(`unit-${unit}`);
|
|
846
|
+
groups.push(
|
|
847
|
+
makeGroup({ id: unitId, name: `Unit ${unit}`, order: order++ }),
|
|
848
|
+
);
|
|
849
|
+
|
|
850
|
+
for (let region: number = 0; region < REGIONS_PER_UNIT; region++) {
|
|
851
|
+
const regionId: ObjectID = idFor(`unit-${unit}-region-${region}`);
|
|
852
|
+
groups.push(
|
|
853
|
+
makeGroup({
|
|
854
|
+
id: regionId,
|
|
855
|
+
name: `Region ${unit}-${region}`,
|
|
856
|
+
parentId: unitId,
|
|
857
|
+
order: order++,
|
|
858
|
+
}),
|
|
859
|
+
);
|
|
860
|
+
|
|
861
|
+
for (let market: number = 0; market < MARKETS_PER_REGION; market++) {
|
|
862
|
+
const marketId: ObjectID = idFor(
|
|
863
|
+
`unit-${unit}-region-${region}-market-${market}`,
|
|
864
|
+
);
|
|
865
|
+
groups.push(
|
|
866
|
+
makeGroup({
|
|
867
|
+
id: marketId,
|
|
868
|
+
name: `Market ${unit}-${region}-${market}`,
|
|
869
|
+
parentId: regionId,
|
|
870
|
+
order: order++,
|
|
871
|
+
}),
|
|
872
|
+
);
|
|
873
|
+
|
|
874
|
+
for (let site: number = 0; site < SITES_PER_MARKET; site++) {
|
|
875
|
+
groups.push(
|
|
876
|
+
makeGroup({
|
|
877
|
+
id: idFor(
|
|
878
|
+
`unit-${unit}-region-${region}-market-${market}-site-${site}`,
|
|
879
|
+
),
|
|
880
|
+
name: `Site ${unit}-${region}-${market}-${site}`,
|
|
881
|
+
parentId: marketId,
|
|
882
|
+
order: order++,
|
|
883
|
+
}),
|
|
884
|
+
);
|
|
885
|
+
}
|
|
886
|
+
}
|
|
887
|
+
}
|
|
888
|
+
}
|
|
889
|
+
|
|
890
|
+
return groups;
|
|
891
|
+
}
|
|
892
|
+
|
|
893
|
+
const EXPECTED_TOTAL: number =
|
|
894
|
+
CORPORATE_UNITS *
|
|
895
|
+
(1 +
|
|
896
|
+
REGIONS_PER_UNIT * (1 + MARKETS_PER_REGION * (1 + SITES_PER_MARKET)));
|
|
897
|
+
|
|
898
|
+
test("the fixture really is the reported size", () => {
|
|
899
|
+
expect(makeLargeHierarchy()).toHaveLength(EXPECTED_TOTAL);
|
|
900
|
+
expect(EXPECTED_TOTAL).toBeGreaterThan(1500);
|
|
901
|
+
});
|
|
902
|
+
|
|
903
|
+
test("every one of the groups reaches the tree, at the right depth", () => {
|
|
904
|
+
const groups: Array<StatusPageGroup> = makeLargeHierarchy();
|
|
905
|
+
|
|
906
|
+
const depthByName: Map<string, number> = new Map<string, number>();
|
|
907
|
+
|
|
908
|
+
const visit: (nodes: Array<StatusPageGroupTreeNode>) => void = (
|
|
909
|
+
nodes: Array<StatusPageGroupTreeNode>,
|
|
910
|
+
): void => {
|
|
911
|
+
for (const node of nodes) {
|
|
912
|
+
depthByName.set(node.group.name || "", node.depth);
|
|
913
|
+
visit(node.children);
|
|
914
|
+
}
|
|
915
|
+
};
|
|
916
|
+
|
|
917
|
+
visit(StatusPageGroupTreeUtil.buildTree({ statusPageGroups: groups }));
|
|
918
|
+
|
|
919
|
+
expect(depthByName.size).toBe(groups.length);
|
|
920
|
+
|
|
921
|
+
for (const group of groups) {
|
|
922
|
+
const name: string = group.name || "";
|
|
923
|
+
const expectedDepth: number = name.startsWith("Unit ")
|
|
924
|
+
? 0
|
|
925
|
+
: name.startsWith("Region ")
|
|
926
|
+
? 1
|
|
927
|
+
: name.startsWith("Market ")
|
|
928
|
+
? 2
|
|
929
|
+
: 3;
|
|
930
|
+
|
|
931
|
+
expect(depthByName.get(name)).toBe(expectedDepth);
|
|
932
|
+
}
|
|
933
|
+
});
|
|
934
|
+
|
|
935
|
+
test("a subtree rollup covers exactly its own branch", () => {
|
|
936
|
+
const groups: Array<StatusPageGroup> = makeLargeHierarchy();
|
|
937
|
+
const index: StatusPageGroupTreeIndex =
|
|
938
|
+
StatusPageGroupTreeUtil.buildIndex({ statusPageGroups: groups });
|
|
939
|
+
|
|
940
|
+
const oneUnit: StatusPageGroup = groups.find((group: StatusPageGroup) => {
|
|
941
|
+
return group.name === "Unit 3";
|
|
942
|
+
})!;
|
|
943
|
+
|
|
944
|
+
const subtree: Array<StatusPageGroup> =
|
|
945
|
+
StatusPageGroupTreeUtil.getGroupAndDescendants({
|
|
946
|
+
statusPageGroup: oneUnit,
|
|
947
|
+
statusPageGroups: groups,
|
|
948
|
+
index: index,
|
|
949
|
+
});
|
|
950
|
+
|
|
951
|
+
expect(subtree).toHaveLength(
|
|
952
|
+
1 +
|
|
953
|
+
REGIONS_PER_UNIT * (1 + MARKETS_PER_REGION * (1 + SITES_PER_MARKET)),
|
|
954
|
+
);
|
|
955
|
+
|
|
956
|
+
for (const group of subtree) {
|
|
957
|
+
expect(group.name).toMatch(/(Unit 3|[A-Za-z]+ 3-)/);
|
|
958
|
+
}
|
|
959
|
+
});
|
|
960
|
+
|
|
961
|
+
/*
|
|
962
|
+
* The regression guard for the walk itself. Before the index, answering
|
|
963
|
+
* "who are this group's children?" filtered the whole list, so one full
|
|
964
|
+
* pass over the tree touched every group once per node - at this size,
|
|
965
|
+
* millions of comparisons. Counting parent reads rather than milliseconds
|
|
966
|
+
* keeps this deterministic: a scan-per-node implementation lands at
|
|
967
|
+
* groups x nodes, which is three orders of magnitude above the bound here.
|
|
968
|
+
*/
|
|
969
|
+
test("building the tree does not re-scan the group list per node", () => {
|
|
970
|
+
const groups: Array<StatusPageGroup> = makeLargeHierarchy();
|
|
971
|
+
const countParentReads: () => number = watchParentReads(groups);
|
|
972
|
+
|
|
973
|
+
StatusPageGroupTreeUtil.buildTree({ statusPageGroups: groups });
|
|
974
|
+
|
|
975
|
+
// One bucketing pass plus the root scan, not one scan per node.
|
|
976
|
+
expect(countParentReads()).toBeLessThan(groups.length * 4);
|
|
977
|
+
});
|
|
978
|
+
|
|
979
|
+
test("a rollup per group stays near linear when the index is shared", () => {
|
|
980
|
+
const groups: Array<StatusPageGroup> = makeLargeHierarchy();
|
|
981
|
+
const index: StatusPageGroupTreeIndex =
|
|
982
|
+
StatusPageGroupTreeUtil.buildIndex({ statusPageGroups: groups });
|
|
983
|
+
|
|
984
|
+
const countParentReads: () => number = watchParentReads(groups);
|
|
985
|
+
|
|
986
|
+
for (const group of groups) {
|
|
987
|
+
StatusPageGroupTreeUtil.getGroupAndDescendants({
|
|
988
|
+
statusPageGroup: group,
|
|
989
|
+
statusPageGroups: groups,
|
|
990
|
+
index: index,
|
|
991
|
+
});
|
|
992
|
+
}
|
|
993
|
+
|
|
994
|
+
/*
|
|
995
|
+
* A shared index means a subtree walk reads no parent pointers at all.
|
|
996
|
+
* Without one this loop is groups x groups reads - over two million.
|
|
997
|
+
*/
|
|
998
|
+
expect(countParentReads()).toBe(0);
|
|
999
|
+
});
|
|
1000
|
+
|
|
1001
|
+
/*
|
|
1002
|
+
* Nesting is capped on write, but rows written straight to the database
|
|
1003
|
+
* are not, and a chain that deep must not blow the recursion in buildTree.
|
|
1004
|
+
*/
|
|
1005
|
+
test("a long chain of nested groups still builds", () => {
|
|
1006
|
+
const groups: Array<StatusPageGroup> = [];
|
|
1007
|
+
let previousId: ObjectID | undefined = undefined;
|
|
1008
|
+
|
|
1009
|
+
for (let i: number = 0; i < 1000; i++) {
|
|
1010
|
+
const id: ObjectID = new ObjectID(`chain-${i}`);
|
|
1011
|
+
groups.push(
|
|
1012
|
+
makeGroup({
|
|
1013
|
+
id: id,
|
|
1014
|
+
name: `Level ${i}`,
|
|
1015
|
+
parentId: previousId,
|
|
1016
|
+
order: i,
|
|
1017
|
+
}),
|
|
1018
|
+
);
|
|
1019
|
+
previousId = id;
|
|
1020
|
+
}
|
|
1021
|
+
|
|
1022
|
+
const tree: Array<StatusPageGroupTreeNode> =
|
|
1023
|
+
StatusPageGroupTreeUtil.buildTree({ statusPageGroups: groups });
|
|
1024
|
+
|
|
1025
|
+
expect(tree).toHaveLength(1);
|
|
1026
|
+
|
|
1027
|
+
let node: StatusPageGroupTreeNode = tree[0]!;
|
|
1028
|
+
let depth: number = 0;
|
|
1029
|
+
|
|
1030
|
+
while (node.children.length > 0) {
|
|
1031
|
+
node = node.children[0]!;
|
|
1032
|
+
depth++;
|
|
1033
|
+
}
|
|
1034
|
+
|
|
1035
|
+
expect(depth).toBe(999);
|
|
1036
|
+
expect(node.group.name).toBe("Level 999");
|
|
1037
|
+
});
|
|
1038
|
+
});
|
|
583
1039
|
});
|