@oneuptime/common 12.0.3 → 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/CephCluster.ts +14 -0
- package/Models/DatabaseModels/CodeRepository.ts +14 -6
- package/Models/DatabaseModels/Dashboard.ts +28 -0
- package/Models/DatabaseModels/DockerHost.ts +14 -0
- package/Models/DatabaseModels/GlobalOidcProject.ts +18 -0
- package/Models/DatabaseModels/GlobalSsoProject.ts +18 -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/IoTFleet.ts +14 -0
- package/Models/DatabaseModels/KubernetesCluster.ts +14 -0
- package/Models/DatabaseModels/LogSavedView.ts +33 -0
- package/Models/DatabaseModels/NetworkDevice.ts +14 -0
- package/Models/DatabaseModels/NetworkInterface.ts +18 -0
- package/Models/DatabaseModels/Project.ts +247 -11
- package/Models/DatabaseModels/ProxmoxCluster.ts +14 -0
- 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/Service.ts +30 -0
- package/Models/DatabaseModels/StatusPage.ts +28 -0
- package/Models/DatabaseModels/StatusPageAnnouncement.ts +1 -0
- package/Models/DatabaseModels/StatusPagePrivateUser.ts +30 -0
- package/Models/DatabaseModels/User.ts +30 -0
- package/Server/API/AIAgentDataAPI.ts +75 -55
- package/Server/API/AIInvestigationAPI.ts +175 -17
- package/Server/API/DashboardAPI.ts +429 -22
- package/Server/API/GitHubAPI.ts +119 -283
- package/Server/API/StatusPageAPI.ts +38 -19
- package/Server/API/UserAPI.ts +263 -1
- package/Server/EnvironmentConfig.ts +23 -3
- package/Server/Infrastructure/ClickhouseConfig.ts +15 -1
- package/Server/Infrastructure/ClickhouseDatabase.ts +1 -1
- package/Server/Infrastructure/GlobalCache.ts +37 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/1786018109307-AddPerUserPasswordSalt.ts +21 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/1786023262402-WidenHashedStringColumnsForScrypt.ts +68 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/1786096660558-AddTimeRangeToLogSavedView.ts +27 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/1786100000000-RestoreServiceLowerNameIndex.ts +100 -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/1786200000000-RestoreDroppedUniqueIndexes.ts +214 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/1786300000000-QuarantineUnboundGitHubInstallations.ts +69 -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 +20 -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 +101 -1
- package/Server/Services/CodeRepositoryService.ts +105 -2
- package/Server/Services/DatabaseService.ts +227 -3
- package/Server/Services/IncidentFeedService.ts +5 -0
- package/Server/Services/LlmLogService.ts +46 -2
- package/Server/Services/OpenTelemetryIngestService.ts +230 -26
- package/Server/Services/UserService.ts +184 -0
- 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/AI/Toolbox/CodeTools.ts +45 -4
- package/Server/Utils/AnalyticsDatabase/ClusterConfig.ts +24 -0
- package/Server/Utils/AnalyticsDatabase/StatementGenerator.ts +13 -0
- package/Server/Utils/CodeRepository/GitHub/GitHub.ts +157 -4
- package/Server/Utils/CodeRepository/GitHub/GitHubInstallationBinding.ts +130 -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 +330 -0
- 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/OverviewCustomFields.test.tsx +333 -0
- package/Tests/App/Dashboard/PortMonitorCriteriaFilter.test.ts +93 -0
- package/Tests/App/Dashboard/UserCustomFields.test.tsx +392 -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/AIAgentDataRepositoryToken.test.ts +381 -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/DashboardPublicAttributeValuesAPI.test.ts +943 -0
- package/Tests/Server/API/DashboardPublicMetricsAggregateAPI.test.ts +1296 -0
- package/Tests/Server/API/DashboardPublicTemplatePayloads.test.ts +598 -0
- package/Tests/Server/API/GitHubAppInstallationBindingAPI.test.ts +539 -0
- package/Tests/Server/API/Helpers.ts +6 -1
- package/Tests/Server/API/StatusPageMasterPasswordAPI.test.ts +335 -0
- package/Tests/Server/API/UserProjectsAPI.test.ts +852 -0
- package/Tests/Server/Infrastructure/GlobalCache.test.ts +128 -0
- package/Tests/Server/Infrastructure/SemaphoreMutex.test.ts +215 -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/AddPerUserPasswordSaltMigration.test.ts +178 -0
- package/Tests/Server/Services/AnalyticsDatabasePaginationStability.test.ts +582 -0
- package/Tests/Server/Services/AnalyticsDatabaseService.test.ts +30 -1
- package/Tests/Server/Services/CodeRepositoryInstallationBinding.test.ts +305 -0
- package/Tests/Server/Services/CodeRepositoryResolutionBinding.test.ts +147 -0
- package/Tests/Server/Services/DatabaseServicePerUserPasswordSalt.test.ts +880 -0
- 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/OpenTelemetryServiceResolutionCache.test.ts +568 -0
- package/Tests/Server/Services/RestoreDroppedUniqueIndexesMigration.test.ts +386 -0
- package/Tests/Server/Services/RestoreServiceLowerNameIndexMigration.test.ts +240 -0
- package/Tests/Server/Services/SeparateIncidentAlertAiSettingsMigration.test.ts +194 -0
- package/Tests/Server/Services/UserServiceFirstMasterAdminElection.test.ts +885 -0
- package/Tests/Server/Services/WidenHashedStringColumnsForScryptMigration.test.ts +220 -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/CodeTools.test.ts +47 -1
- package/Tests/Server/Utils/AI/CodeWriteTools.test.ts +10 -0
- 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/AnalyticsDatabase/StatementGenerator.test.ts +120 -0
- package/Tests/Server/Utils/CodeRepository/GitHubInstallationBinding.test.ts +292 -0
- package/Tests/Server/Utils/GitHubInstallationOwnershipVerification.test.ts +340 -0
- package/Tests/Server/Utils/GitHubWebhookAndTreeCacheIsolation.test.ts +250 -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 +585 -0
- package/Tests/Types/Dashboard/DashboardTemplates.test.ts +111 -0
- package/Tests/Types/Database/ColumnLength.test.ts +7 -1
- package/Tests/Types/HashedStringPerUserSalt.test.ts +476 -0
- package/Tests/Types/Monitor/CephMetricCatalog.test.ts +104 -0
- package/Tests/Types/Monitor/CriteriaFilter.test.ts +4 -0
- package/Tests/Types/Monitor/DockerAlertTemplates.test.ts +293 -0
- package/Tests/Types/Monitor/DockerMetricCatalog.test.ts +109 -0
- package/Tests/Types/Monitor/DockerSwarmMetricCatalog.test.ts +118 -0
- package/Tests/Types/Monitor/HostAlertTemplates.test.ts +249 -0
- package/Tests/Types/Monitor/HostMetricCatalog.test.ts +104 -0
- package/Tests/Types/Monitor/IotAlertTemplates.test.ts +320 -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/PodmanAlertTemplates.test.ts +271 -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/CustomFields/CustomFieldsDetail.test.tsx +696 -0
- 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/UI/Utils/UserProjectsModelAPI.test.ts +714 -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/TeamMembersByProject.test.ts +655 -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/Database/ColumnLength.ts +9 -1
- package/Types/Database/TableColumn.ts +11 -0
- package/Types/Database/UnsynchronizedIndex.ts +48 -0
- package/Types/HashedString.ts +140 -4
- 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/CustomFields/CustomFieldsDetail.tsx +85 -11
- 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/Components/Navbar/NavBar.tsx +12 -2
- package/UI/Components/Navbar/NavBarMenuModal.tsx +19 -7
- 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/ModelAPI/UserProjectsModelAPI.ts +257 -0
- 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/TeamMembersByProject.ts +237 -0
- 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/CephCluster.js +16 -1
- package/build/dist/Models/DatabaseModels/CephCluster.js.map +1 -1
- package/build/dist/Models/DatabaseModels/CodeRepository.js +14 -6
- package/build/dist/Models/DatabaseModels/CodeRepository.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/DockerHost.js +16 -1
- package/build/dist/Models/DatabaseModels/DockerHost.js.map +1 -1
- package/build/dist/Models/DatabaseModels/GlobalOidcProject.js +16 -1
- package/build/dist/Models/DatabaseModels/GlobalOidcProject.js.map +1 -1
- package/build/dist/Models/DatabaseModels/GlobalSsoProject.js +16 -1
- package/build/dist/Models/DatabaseModels/GlobalSsoProject.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/IoTFleet.js +16 -1
- package/build/dist/Models/DatabaseModels/IoTFleet.js.map +1 -1
- package/build/dist/Models/DatabaseModels/KubernetesCluster.js +16 -1
- package/build/dist/Models/DatabaseModels/KubernetesCluster.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/NetworkDevice.js +16 -1
- package/build/dist/Models/DatabaseModels/NetworkDevice.js.map +1 -1
- package/build/dist/Models/DatabaseModels/NetworkInterface.js +16 -1
- package/build/dist/Models/DatabaseModels/NetworkInterface.js.map +1 -1
- package/build/dist/Models/DatabaseModels/Project.js +257 -13
- package/build/dist/Models/DatabaseModels/Project.js.map +1 -1
- package/build/dist/Models/DatabaseModels/ProxmoxCluster.js +16 -1
- package/build/dist/Models/DatabaseModels/ProxmoxCluster.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/Service.js +28 -1
- package/build/dist/Models/DatabaseModels/Service.js.map +1 -1
- 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/Models/DatabaseModels/StatusPagePrivateUser.js +32 -0
- package/build/dist/Models/DatabaseModels/StatusPagePrivateUser.js.map +1 -1
- package/build/dist/Models/DatabaseModels/User.js +32 -0
- package/build/dist/Models/DatabaseModels/User.js.map +1 -1
- package/build/dist/Server/API/AIAgentDataAPI.js +49 -48
- 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 +312 -15
- package/build/dist/Server/API/DashboardAPI.js.map +1 -1
- package/build/dist/Server/API/GitHubAPI.js +94 -167
- package/build/dist/Server/API/GitHubAPI.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/API/UserAPI.js +187 -2
- package/build/dist/Server/API/UserAPI.js.map +1 -1
- package/build/dist/Server/EnvironmentConfig.js +21 -3
- 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/GlobalCache.js +29 -0
- package/build/dist/Server/Infrastructure/GlobalCache.js.map +1 -1
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1786018109307-AddPerUserPasswordSalt.js +14 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1786018109307-AddPerUserPasswordSalt.js.map +1 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1786023262402-WidenHashedStringColumnsForScrypt.js +59 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1786023262402-WidenHashedStringColumnsForScrypt.js.map +1 -0
- 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/1786100000000-RestoreServiceLowerNameIndex.js +93 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1786100000000-RestoreServiceLowerNameIndex.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/1786200000000-RestoreDroppedUniqueIndexes.js +101 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1786200000000-RestoreDroppedUniqueIndexes.js.map +1 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1786300000000-QuarantineUnboundGitHubInstallations.js +66 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1786300000000-QuarantineUnboundGitHubInstallations.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 +20 -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 +86 -2
- package/build/dist/Server/Services/AnalyticsDatabaseService.js.map +1 -1
- package/build/dist/Server/Services/CodeRepositoryService.js +90 -2
- package/build/dist/Server/Services/CodeRepositoryService.js.map +1 -1
- package/build/dist/Server/Services/DatabaseService.js +180 -3
- 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/Services/OpenTelemetryIngestService.js +155 -25
- package/build/dist/Server/Services/OpenTelemetryIngestService.js.map +1 -1
- package/build/dist/Server/Services/UserService.js +165 -0
- package/build/dist/Server/Services/UserService.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/AI/Toolbox/CodeTools.js +31 -1
- package/build/dist/Server/Utils/AI/Toolbox/CodeTools.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/AnalyticsDatabase/StatementGenerator.js +13 -0
- package/build/dist/Server/Utils/AnalyticsDatabase/StatementGenerator.js.map +1 -1
- package/build/dist/Server/Utils/CodeRepository/GitHub/GitHub.js +126 -4
- package/build/dist/Server/Utils/CodeRepository/GitHub/GitHub.js.map +1 -1
- package/build/dist/Server/Utils/CodeRepository/GitHub/GitHubInstallationBinding.js +136 -0
- package/build/dist/Server/Utils/CodeRepository/GitHub/GitHubInstallationBinding.js.map +1 -0
- 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 +258 -0
- package/build/dist/Server/Utils/PasswordHash.js.map +1 -0
- 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/Database/ColumnLength.js +9 -1
- package/build/dist/Types/Database/ColumnLength.js.map +1 -1
- package/build/dist/Types/Database/TableColumn.js.map +1 -1
- package/build/dist/Types/Database/UnsynchronizedIndex.js +46 -0
- package/build/dist/Types/Database/UnsynchronizedIndex.js.map +1 -0
- package/build/dist/Types/HashedString.js +96 -5
- 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/CustomFields/CustomFieldsDetail.js +51 -10
- package/build/dist/UI/Components/CustomFields/CustomFieldsDetail.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/Components/Navbar/NavBar.js +3 -1
- package/build/dist/UI/Components/Navbar/NavBar.js.map +1 -1
- package/build/dist/UI/Components/Navbar/NavBarMenuModal.js +19 -7
- package/build/dist/UI/Components/Navbar/NavBarMenuModal.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/ModelAPI/UserProjectsModelAPI.js +156 -0
- package/build/dist/UI/Utils/ModelAPI/UserProjectsModelAPI.js.map +1 -0
- 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/TeamMembersByProject.js +145 -0
- package/build/dist/Utils/TeamMembersByProject.js.map +1 -0
- 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
|
@@ -0,0 +1,1184 @@
|
|
|
1
|
+
import {
|
|
2
|
+
afterEach,
|
|
3
|
+
beforeEach,
|
|
4
|
+
describe,
|
|
5
|
+
expect,
|
|
6
|
+
jest,
|
|
7
|
+
test,
|
|
8
|
+
} from "@jest/globals";
|
|
9
|
+
import "@testing-library/jest-dom";
|
|
10
|
+
import {
|
|
11
|
+
act,
|
|
12
|
+
cleanup,
|
|
13
|
+
fireEvent,
|
|
14
|
+
render,
|
|
15
|
+
screen,
|
|
16
|
+
} from "@testing-library/react";
|
|
17
|
+
import * as React from "react";
|
|
18
|
+
import getJestMockFunction, { MockFunction } from "../../MockType";
|
|
19
|
+
|
|
20
|
+
/*
|
|
21
|
+
* InvestigationPanel joins three independently changing pieces of state: the
|
|
22
|
+
* live AIRun, the report posted just after completion, and the persisted
|
|
23
|
+
* recommendation that authorizes a code-fix action. These tests drive real
|
|
24
|
+
* React effects and timers so completion, navigation, and request races stay
|
|
25
|
+
* covered rather than testing only static snapshots.
|
|
26
|
+
*/
|
|
27
|
+
|
|
28
|
+
const postMock: MockFunction = getJestMockFunction();
|
|
29
|
+
const getFriendlyMessageMock: MockFunction = getJestMockFunction();
|
|
30
|
+
const getCommonHeadersMock: MockFunction = getJestMockFunction();
|
|
31
|
+
const markdownViewerMock: MockFunction = getJestMockFunction();
|
|
32
|
+
const activityFeedMock: MockFunction = getJestMockFunction();
|
|
33
|
+
|
|
34
|
+
jest.mock("../../../UI/Utils/API/API", () => {
|
|
35
|
+
return {
|
|
36
|
+
__esModule: true,
|
|
37
|
+
default: {
|
|
38
|
+
post: (...args: Array<unknown>) => {
|
|
39
|
+
return postMock(...args);
|
|
40
|
+
},
|
|
41
|
+
getFriendlyMessage: (...args: Array<unknown>) => {
|
|
42
|
+
return getFriendlyMessageMock(...args);
|
|
43
|
+
},
|
|
44
|
+
},
|
|
45
|
+
};
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
jest.mock("../../../UI/Utils/ModelAPI/ModelAPI", () => {
|
|
49
|
+
return {
|
|
50
|
+
__esModule: true,
|
|
51
|
+
default: {
|
|
52
|
+
getCommonHeaders: (...args: Array<unknown>) => {
|
|
53
|
+
return getCommonHeadersMock(...args);
|
|
54
|
+
},
|
|
55
|
+
},
|
|
56
|
+
};
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
/*
|
|
60
|
+
* Record MarkdownViewer's props so safeMode remains an asserted part of the
|
|
61
|
+
* contract even when the Common Jest config replaces its markdown renderer.
|
|
62
|
+
*/
|
|
63
|
+
jest.mock("../../../UI/Components/Markdown.tsx/MarkdownViewer", () => {
|
|
64
|
+
return {
|
|
65
|
+
__esModule: true,
|
|
66
|
+
default: (props: MarkdownViewerProps): React.ReactElement => {
|
|
67
|
+
markdownViewerMock(props);
|
|
68
|
+
return React.createElement(
|
|
69
|
+
"div",
|
|
70
|
+
{ "data-testid": "investigation-markdown" },
|
|
71
|
+
props.text,
|
|
72
|
+
);
|
|
73
|
+
},
|
|
74
|
+
};
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
jest.mock(
|
|
78
|
+
"../../../../App/FeatureSet/Dashboard/src/Components/AIChat/ChatActivityFeed",
|
|
79
|
+
() => {
|
|
80
|
+
return {
|
|
81
|
+
__esModule: true,
|
|
82
|
+
default: (props: ActivityFeedProps): React.ReactElement => {
|
|
83
|
+
activityFeedMock(props);
|
|
84
|
+
return React.createElement("div", {
|
|
85
|
+
"data-testid": "investigation-activity",
|
|
86
|
+
});
|
|
87
|
+
},
|
|
88
|
+
};
|
|
89
|
+
},
|
|
90
|
+
);
|
|
91
|
+
|
|
92
|
+
import InvestigationPanel, {
|
|
93
|
+
InvestigationSubjectType,
|
|
94
|
+
} from "../../../../App/FeatureSet/Dashboard/src/Components/AI/InvestigationPanel";
|
|
95
|
+
import AIRunEvent from "../../../Models/DatabaseModels/AIRunEvent";
|
|
96
|
+
import HTTPErrorResponse from "../../../Types/API/HTTPErrorResponse";
|
|
97
|
+
import AIRunCodeFixRecommendation from "../../../Types/AI/AIRunCodeFixRecommendation";
|
|
98
|
+
import AIRunEventType from "../../../Types/AI/AIRunEventType";
|
|
99
|
+
import AIRunStatus from "../../../Types/AI/AIRunStatus";
|
|
100
|
+
import { JSONArray, JSONObject } from "../../../Types/JSON";
|
|
101
|
+
import ObjectID from "../../../Types/ObjectID";
|
|
102
|
+
|
|
103
|
+
interface MarkdownViewerProps {
|
|
104
|
+
text: string;
|
|
105
|
+
safeMode?: boolean | undefined;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
interface ActivityFeedProps {
|
|
109
|
+
events: Array<AIRunEvent>;
|
|
110
|
+
title?: string | undefined;
|
|
111
|
+
showLiveIndicator?: boolean | undefined;
|
|
112
|
+
maxVisibleSteps?: number | undefined;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
interface InvestigationPayloadOptions {
|
|
116
|
+
status: AIRunStatus;
|
|
117
|
+
runId?: string | undefined;
|
|
118
|
+
events?: JSONArray | undefined;
|
|
119
|
+
analysisMarkdown?: string | null | undefined;
|
|
120
|
+
isAnalysisPending?: boolean | undefined;
|
|
121
|
+
errorMessage?: string | null | undefined;
|
|
122
|
+
toolCallCount?: number | undefined;
|
|
123
|
+
totalTokens?: number | undefined;
|
|
124
|
+
humanVerdict?: string | null | undefined;
|
|
125
|
+
codeFixRecommendation?: AIRunCodeFixRecommendation | undefined;
|
|
126
|
+
completedAt?: string | undefined;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
interface ApiResponse {
|
|
130
|
+
data: JSONObject;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
interface PostRequest {
|
|
134
|
+
url: { toString: () => string };
|
|
135
|
+
data: JSONObject;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
interface Deferred<T> {
|
|
139
|
+
promise: Promise<T>;
|
|
140
|
+
resolve: (value: T) => void;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
const POLL_INTERVAL_MS: number = 2500;
|
|
144
|
+
const SETTLED_POLL_INTERVAL_MS: number = 30_000;
|
|
145
|
+
const RECOMMENDATION_SETTLEMENT_MAX_AGE_MS: number = 3 * 60 * 1000;
|
|
146
|
+
const MAX_RECOMMENDATION_POLL_RESPONSES: number = Math.ceil(
|
|
147
|
+
RECOMMENDATION_SETTLEMENT_MAX_AGE_MS / POLL_INTERVAL_MS,
|
|
148
|
+
);
|
|
149
|
+
const COMPLETED_AT: string = "2026-08-07T12:00:00.000Z";
|
|
150
|
+
const OLD_COMPLETED_AT: string = "2026-08-07T11:56:59.000Z";
|
|
151
|
+
const RUN_ID: string = "11111111-1111-4111-8111-111111111111";
|
|
152
|
+
const FIX_RUN_ID: string = "22222222-2222-4222-8222-222222222222";
|
|
153
|
+
const NEXT_RUN_ID: string = "aaaaaaaa-aaaa-4aaa-8aaa-aaaaaaaaaaaa";
|
|
154
|
+
const INCIDENT_ID: ObjectID = new ObjectID(
|
|
155
|
+
"33333333-3333-4333-8333-333333333333",
|
|
156
|
+
);
|
|
157
|
+
const ALERT_ID: ObjectID = new ObjectID("44444444-4444-4444-8444-444444444444");
|
|
158
|
+
const EVENT_ID: string = "55555555-5555-4555-8555-555555555555";
|
|
159
|
+
const ANALYSIS: string =
|
|
160
|
+
"## Root cause\n\nThe database connection pool was exhausted.";
|
|
161
|
+
|
|
162
|
+
const activityEvent: JSONObject = {
|
|
163
|
+
_id: EVENT_ID,
|
|
164
|
+
sequence: 1,
|
|
165
|
+
eventType: AIRunEventType.ToolCallStarted,
|
|
166
|
+
toolName: "search_logs",
|
|
167
|
+
createdAt: new Date("2026-08-07T10:00:00.000Z"),
|
|
168
|
+
};
|
|
169
|
+
|
|
170
|
+
function investigationPayload(
|
|
171
|
+
options: InvestigationPayloadOptions,
|
|
172
|
+
): JSONObject {
|
|
173
|
+
const run: JSONObject = {
|
|
174
|
+
_id: options.runId || RUN_ID,
|
|
175
|
+
status: options.status,
|
|
176
|
+
errorMessage: options.errorMessage ?? null,
|
|
177
|
+
toolCallCount: options.toolCallCount ?? 0,
|
|
178
|
+
totalTokens: options.totalTokens ?? 0,
|
|
179
|
+
humanVerdict: options.humanVerdict ?? null,
|
|
180
|
+
};
|
|
181
|
+
|
|
182
|
+
if (options.codeFixRecommendation !== undefined) {
|
|
183
|
+
run["codeFixRecommendation"] = options.codeFixRecommendation;
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
if (options.completedAt !== undefined) {
|
|
187
|
+
run["completedAt"] = options.completedAt;
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
return {
|
|
191
|
+
run,
|
|
192
|
+
events: options.events || [],
|
|
193
|
+
analysisMarkdown: options.analysisMarkdown ?? null,
|
|
194
|
+
isAnalysisPending: options.isAnalysisPending === true,
|
|
195
|
+
};
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
function successfulResponse(payload: JSONObject): ApiResponse {
|
|
199
|
+
return { data: payload };
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
function noInvestigationResponse(): ApiResponse {
|
|
203
|
+
return {
|
|
204
|
+
data: {
|
|
205
|
+
run: null,
|
|
206
|
+
events: [],
|
|
207
|
+
analysisMarkdown: null,
|
|
208
|
+
isAnalysisPending: false,
|
|
209
|
+
},
|
|
210
|
+
};
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
function completedResponse(
|
|
214
|
+
overrides: Partial<InvestigationPayloadOptions> = {},
|
|
215
|
+
): ApiResponse {
|
|
216
|
+
return successfulResponse(
|
|
217
|
+
investigationPayload({
|
|
218
|
+
status: AIRunStatus.Completed,
|
|
219
|
+
analysisMarkdown: ANALYSIS,
|
|
220
|
+
events: [activityEvent],
|
|
221
|
+
toolCallCount: 2,
|
|
222
|
+
totalTokens: 1234,
|
|
223
|
+
codeFixRecommendation: AIRunCodeFixRecommendation.Recommended,
|
|
224
|
+
completedAt: COMPLETED_AT,
|
|
225
|
+
...overrides,
|
|
226
|
+
}),
|
|
227
|
+
);
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
function createDeferred<T>(): Deferred<T> {
|
|
231
|
+
let resolvePromise: ((value: T) => void) | undefined;
|
|
232
|
+
const promise: Promise<T> = new Promise<T>((resolve: (value: T) => void) => {
|
|
233
|
+
resolvePromise = resolve;
|
|
234
|
+
});
|
|
235
|
+
|
|
236
|
+
return {
|
|
237
|
+
promise,
|
|
238
|
+
resolve: (value: T): void => {
|
|
239
|
+
resolvePromise!(value);
|
|
240
|
+
},
|
|
241
|
+
};
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
function renderPanel(data?: {
|
|
245
|
+
subjectType?: InvestigationSubjectType | undefined;
|
|
246
|
+
subjectId?: ObjectID | undefined;
|
|
247
|
+
onAnalysisAvailable?: (() => void) | undefined;
|
|
248
|
+
onStatusChange?: ((status: AIRunStatus | null) => void) | undefined;
|
|
249
|
+
}): ReturnType<typeof render> {
|
|
250
|
+
return render(
|
|
251
|
+
<InvestigationPanel
|
|
252
|
+
subjectType={data?.subjectType || "incident"}
|
|
253
|
+
subjectId={data?.subjectId || INCIDENT_ID}
|
|
254
|
+
onAnalysisAvailable={data?.onAnalysisAvailable}
|
|
255
|
+
onStatusChange={data?.onStatusChange}
|
|
256
|
+
/>,
|
|
257
|
+
);
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
/* Let the awaits inside fetchData and the following React effects settle. */
|
|
261
|
+
async function flush(): Promise<void> {
|
|
262
|
+
await act(async (): Promise<void> => {
|
|
263
|
+
await Promise.resolve();
|
|
264
|
+
await Promise.resolve();
|
|
265
|
+
await Promise.resolve();
|
|
266
|
+
await Promise.resolve();
|
|
267
|
+
});
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
async function tick(milliseconds: number): Promise<void> {
|
|
271
|
+
await act(async (): Promise<void> => {
|
|
272
|
+
jest.advanceTimersByTime(milliseconds);
|
|
273
|
+
await Promise.resolve();
|
|
274
|
+
await Promise.resolve();
|
|
275
|
+
await Promise.resolve();
|
|
276
|
+
await Promise.resolve();
|
|
277
|
+
});
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
async function advanceFastPolls(count: number = 1): Promise<void> {
|
|
281
|
+
for (let index: number = 0; index < count; index++) {
|
|
282
|
+
await tick(POLL_INTERVAL_MS);
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
async function resolveDeferred<T>(
|
|
287
|
+
deferred: Deferred<T>,
|
|
288
|
+
value: T,
|
|
289
|
+
): Promise<void> {
|
|
290
|
+
await act(async (): Promise<void> => {
|
|
291
|
+
deferred.resolve(value);
|
|
292
|
+
await Promise.resolve();
|
|
293
|
+
await Promise.resolve();
|
|
294
|
+
await Promise.resolve();
|
|
295
|
+
await Promise.resolve();
|
|
296
|
+
});
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
function postRequestAt(index: number): PostRequest {
|
|
300
|
+
return postMock.mock.calls[index]![0] as PostRequest;
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
function requestPath(index: number): string {
|
|
304
|
+
return postRequestAt(index).url.toString();
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
function fixButton(): HTMLElement | null {
|
|
308
|
+
return screen.queryByRole("button", {
|
|
309
|
+
name: "Open Fix PR from this analysis",
|
|
310
|
+
});
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
function lastActivityProps(): ActivityFeedProps {
|
|
314
|
+
const calls: Array<Array<ActivityFeedProps>> = activityFeedMock.mock
|
|
315
|
+
.calls as Array<Array<ActivityFeedProps>>;
|
|
316
|
+
return calls[calls.length - 1]![0]!;
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
beforeEach(() => {
|
|
320
|
+
jest.useFakeTimers();
|
|
321
|
+
jest.setSystemTime(new Date(COMPLETED_AT));
|
|
322
|
+
getCommonHeadersMock.mockReturnValue({});
|
|
323
|
+
getFriendlyMessageMock.mockImplementation((error: unknown): string => {
|
|
324
|
+
if (
|
|
325
|
+
typeof error === "object" &&
|
|
326
|
+
error !== null &&
|
|
327
|
+
"message" in error &&
|
|
328
|
+
typeof (error as { message?: unknown }).message === "string"
|
|
329
|
+
) {
|
|
330
|
+
return (error as { message: string }).message;
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
return "Request failed";
|
|
334
|
+
});
|
|
335
|
+
});
|
|
336
|
+
|
|
337
|
+
afterEach(() => {
|
|
338
|
+
cleanup();
|
|
339
|
+
jest.clearAllTimers();
|
|
340
|
+
jest.useRealTimers();
|
|
341
|
+
postMock.mockReset();
|
|
342
|
+
getFriendlyMessageMock.mockReset();
|
|
343
|
+
getCommonHeadersMock.mockReset();
|
|
344
|
+
markdownViewerMock.mockReset();
|
|
345
|
+
activityFeedMock.mockReset();
|
|
346
|
+
});
|
|
347
|
+
|
|
348
|
+
describe("InvestigationPanel report lifecycle", () => {
|
|
349
|
+
test("renders nothing and briefly discovers a run when none exists yet", async () => {
|
|
350
|
+
postMock.mockResolvedValue(noInvestigationResponse() as never);
|
|
351
|
+
|
|
352
|
+
const { container } = renderPanel();
|
|
353
|
+
await flush();
|
|
354
|
+
|
|
355
|
+
expect(container).toBeEmptyDOMElement();
|
|
356
|
+
expect(jest.getTimerCount()).toBe(1);
|
|
357
|
+
|
|
358
|
+
await advanceFastPolls(4);
|
|
359
|
+
expect(postMock).toHaveBeenCalledTimes(5);
|
|
360
|
+
expect(jest.getTimerCount()).toBe(1);
|
|
361
|
+
});
|
|
362
|
+
|
|
363
|
+
test("shows live activity and requests the incident endpoint", async () => {
|
|
364
|
+
postMock.mockResolvedValue(
|
|
365
|
+
successfulResponse(
|
|
366
|
+
investigationPayload({
|
|
367
|
+
status: AIRunStatus.Running,
|
|
368
|
+
events: [activityEvent],
|
|
369
|
+
}),
|
|
370
|
+
) as never,
|
|
371
|
+
);
|
|
372
|
+
|
|
373
|
+
renderPanel();
|
|
374
|
+
await flush();
|
|
375
|
+
|
|
376
|
+
expect(screen.getByText("Investigating…")).toBeInTheDocument();
|
|
377
|
+
expect(screen.getByTestId("investigation-activity")).toBeInTheDocument();
|
|
378
|
+
expect(lastActivityProps().events).toHaveLength(1);
|
|
379
|
+
expect(lastActivityProps().showLiveIndicator).toBe(true);
|
|
380
|
+
expect(
|
|
381
|
+
document.querySelector('[class~="motion-safe:animate-ping"]'),
|
|
382
|
+
).not.toBeNull();
|
|
383
|
+
|
|
384
|
+
const request: PostRequest = postRequestAt(0);
|
|
385
|
+
expect(request.url.toString()).toContain("/ai-investigation/incident");
|
|
386
|
+
expect(request.data).toEqual({ incidentId: INCIDENT_ID.toString() });
|
|
387
|
+
expect(getCommonHeadersMock).toHaveBeenCalledTimes(1);
|
|
388
|
+
});
|
|
389
|
+
|
|
390
|
+
test("renders the completed report safely and demotes activity to a disclosure", async () => {
|
|
391
|
+
const onAnalysisAvailable: MockFunction = getJestMockFunction();
|
|
392
|
+
postMock.mockResolvedValue(completedResponse() as never);
|
|
393
|
+
|
|
394
|
+
renderPanel({ onAnalysisAvailable });
|
|
395
|
+
await flush();
|
|
396
|
+
|
|
397
|
+
expect(screen.getByText("Investigation complete")).toBeInTheDocument();
|
|
398
|
+
expect(screen.getByLabelText("Investigation report")).toBeInTheDocument();
|
|
399
|
+
expect(screen.getByTestId("investigation-markdown")).toHaveTextContent(
|
|
400
|
+
"The database connection pool was exhausted.",
|
|
401
|
+
);
|
|
402
|
+
expect(markdownViewerMock).toHaveBeenCalledWith({
|
|
403
|
+
text: ANALYSIS,
|
|
404
|
+
safeMode: true,
|
|
405
|
+
});
|
|
406
|
+
expect(screen.getByText("Investigation activity")).toBeInTheDocument();
|
|
407
|
+
expect(lastActivityProps()).toEqual(
|
|
408
|
+
expect.objectContaining({
|
|
409
|
+
title: "Completed activity",
|
|
410
|
+
showLiveIndicator: false,
|
|
411
|
+
maxVisibleSteps: 10,
|
|
412
|
+
}),
|
|
413
|
+
);
|
|
414
|
+
|
|
415
|
+
const usage: HTMLElement = screen.getByLabelText("Investigation usage");
|
|
416
|
+
expect(usage).toHaveTextContent("2 telemetry queries");
|
|
417
|
+
expect(usage).toHaveTextContent("1,234 tokens");
|
|
418
|
+
expect(fixButton()).toBeEnabled();
|
|
419
|
+
expect(screen.getByRole("button", { name: "Confirmed" })).toBeEnabled();
|
|
420
|
+
expect(screen.getByRole("button", { name: "Rejected" })).toBeEnabled();
|
|
421
|
+
expect(onAnalysisAvailable).toHaveBeenCalledTimes(1);
|
|
422
|
+
expect(jest.getTimerCount()).toBe(1);
|
|
423
|
+
});
|
|
424
|
+
|
|
425
|
+
test("keeps polling across Completed until the same run report appears", async () => {
|
|
426
|
+
const onAnalysisAvailable: MockFunction = getJestMockFunction();
|
|
427
|
+
postMock
|
|
428
|
+
.mockResolvedValueOnce(
|
|
429
|
+
completedResponse({
|
|
430
|
+
analysisMarkdown: null,
|
|
431
|
+
isAnalysisPending: true,
|
|
432
|
+
}) as never,
|
|
433
|
+
)
|
|
434
|
+
.mockResolvedValueOnce(
|
|
435
|
+
completedResponse({
|
|
436
|
+
analysisMarkdown: ANALYSIS,
|
|
437
|
+
isAnalysisPending: false,
|
|
438
|
+
}) as never,
|
|
439
|
+
);
|
|
440
|
+
|
|
441
|
+
renderPanel({ onAnalysisAvailable });
|
|
442
|
+
await flush();
|
|
443
|
+
|
|
444
|
+
expect(screen.getByText("Preparing investigation report…")).toBeVisible();
|
|
445
|
+
expect(screen.getByText("Preparing the final report")).toBeVisible();
|
|
446
|
+
expect(screen.queryByTestId("investigation-markdown")).toBeNull();
|
|
447
|
+
expect(onAnalysisAvailable).not.toHaveBeenCalled();
|
|
448
|
+
|
|
449
|
+
/* Status, run id, events and recommendation remain identical. */
|
|
450
|
+
await tick(POLL_INTERVAL_MS);
|
|
451
|
+
|
|
452
|
+
expect(screen.getByText("Investigation complete")).toBeVisible();
|
|
453
|
+
expect(screen.getByTestId("investigation-markdown")).toHaveTextContent(
|
|
454
|
+
"The database connection pool was exhausted.",
|
|
455
|
+
);
|
|
456
|
+
expect(postMock).toHaveBeenCalledTimes(2);
|
|
457
|
+
expect(onAnalysisAvailable).toHaveBeenCalledTimes(1);
|
|
458
|
+
|
|
459
|
+
await tick(POLL_INTERVAL_MS * 4);
|
|
460
|
+
expect(postMock).toHaveBeenCalledTimes(2);
|
|
461
|
+
});
|
|
462
|
+
|
|
463
|
+
test("does not overlap a slow active-run poll", async () => {
|
|
464
|
+
const slowPoll: Deferred<ApiResponse> = createDeferred<ApiResponse>();
|
|
465
|
+
postMock
|
|
466
|
+
.mockResolvedValueOnce(
|
|
467
|
+
successfulResponse(
|
|
468
|
+
investigationPayload({ status: AIRunStatus.Running }),
|
|
469
|
+
) as never,
|
|
470
|
+
)
|
|
471
|
+
.mockReturnValueOnce(slowPoll.promise as never)
|
|
472
|
+
.mockResolvedValue(completedResponse() as never);
|
|
473
|
+
|
|
474
|
+
const view: ReturnType<typeof render> = renderPanel();
|
|
475
|
+
await flush();
|
|
476
|
+
await tick(POLL_INTERVAL_MS);
|
|
477
|
+
|
|
478
|
+
expect(postMock).toHaveBeenCalledTimes(2);
|
|
479
|
+
expect(screen.getByText("Investigating…")).toBeVisible();
|
|
480
|
+
|
|
481
|
+
view.rerender(
|
|
482
|
+
<InvestigationPanel
|
|
483
|
+
subjectType="incident"
|
|
484
|
+
subjectId={new ObjectID(INCIDENT_ID.toString())}
|
|
485
|
+
/>,
|
|
486
|
+
);
|
|
487
|
+
await flush();
|
|
488
|
+
await tick(POLL_INTERVAL_MS * 3);
|
|
489
|
+
expect(postMock).toHaveBeenCalledTimes(2);
|
|
490
|
+
|
|
491
|
+
await resolveDeferred(slowPoll, completedResponse());
|
|
492
|
+
expect(screen.getByText("Investigation complete")).toBeVisible();
|
|
493
|
+
expect(postMock).toHaveBeenCalledTimes(2);
|
|
494
|
+
});
|
|
495
|
+
|
|
496
|
+
test("ignores a previous subject response after route navigation", async () => {
|
|
497
|
+
const previousSubject: Deferred<ApiResponse> =
|
|
498
|
+
createDeferred<ApiResponse>();
|
|
499
|
+
const statuses: Array<AIRunStatus | null> = [];
|
|
500
|
+
postMock
|
|
501
|
+
.mockReturnValueOnce(previousSubject.promise as never)
|
|
502
|
+
.mockResolvedValueOnce(completedResponse() as never);
|
|
503
|
+
|
|
504
|
+
const view: ReturnType<typeof render> = renderPanel({
|
|
505
|
+
onStatusChange: (status: AIRunStatus | null): void => {
|
|
506
|
+
statuses.push(status);
|
|
507
|
+
},
|
|
508
|
+
});
|
|
509
|
+
await flush();
|
|
510
|
+
|
|
511
|
+
view.rerender(
|
|
512
|
+
<InvestigationPanel
|
|
513
|
+
subjectType="alert"
|
|
514
|
+
subjectId={ALERT_ID}
|
|
515
|
+
onStatusChange={(status: AIRunStatus | null): void => {
|
|
516
|
+
statuses.push(status);
|
|
517
|
+
}}
|
|
518
|
+
/>,
|
|
519
|
+
);
|
|
520
|
+
await flush();
|
|
521
|
+
|
|
522
|
+
expect(screen.getByText("Investigation complete")).toBeVisible();
|
|
523
|
+
expect(postRequestAt(1).data).toEqual({ alertId: ALERT_ID.toString() });
|
|
524
|
+
|
|
525
|
+
await resolveDeferred(
|
|
526
|
+
previousSubject,
|
|
527
|
+
successfulResponse(investigationPayload({ status: AIRunStatus.Running })),
|
|
528
|
+
);
|
|
529
|
+
|
|
530
|
+
expect(screen.getByText("Investigation complete")).toBeVisible();
|
|
531
|
+
expect(screen.queryByText("Investigating…")).toBeNull();
|
|
532
|
+
expect(statuses[statuses.length - 1]).toBe(AIRunStatus.Completed);
|
|
533
|
+
});
|
|
534
|
+
|
|
535
|
+
test("does not notify a new subject with the previous report", async () => {
|
|
536
|
+
const nextSubject: Deferred<ApiResponse> = createDeferred<ApiResponse>();
|
|
537
|
+
const onAnalysisAvailable: MockFunction = getJestMockFunction();
|
|
538
|
+
const nextAnalysis: string =
|
|
539
|
+
"## Alert root cause\n\nThe upstream dependency rejected requests.";
|
|
540
|
+
postMock
|
|
541
|
+
.mockResolvedValueOnce(completedResponse() as never)
|
|
542
|
+
.mockReturnValueOnce(nextSubject.promise as never);
|
|
543
|
+
|
|
544
|
+
const view: ReturnType<typeof render> = renderPanel({
|
|
545
|
+
onAnalysisAvailable,
|
|
546
|
+
});
|
|
547
|
+
await flush();
|
|
548
|
+
expect(onAnalysisAvailable).toHaveBeenCalledTimes(1);
|
|
549
|
+
onAnalysisAvailable.mockClear();
|
|
550
|
+
|
|
551
|
+
view.rerender(
|
|
552
|
+
<InvestigationPanel
|
|
553
|
+
subjectType="alert"
|
|
554
|
+
subjectId={ALERT_ID}
|
|
555
|
+
onAnalysisAvailable={onAnalysisAvailable}
|
|
556
|
+
/>,
|
|
557
|
+
);
|
|
558
|
+
await flush();
|
|
559
|
+
|
|
560
|
+
expect(view.container).toBeEmptyDOMElement();
|
|
561
|
+
expect(onAnalysisAvailable).not.toHaveBeenCalled();
|
|
562
|
+
|
|
563
|
+
await resolveDeferred(
|
|
564
|
+
nextSubject,
|
|
565
|
+
completedResponse({ runId: NEXT_RUN_ID, analysisMarkdown: nextAnalysis }),
|
|
566
|
+
);
|
|
567
|
+
|
|
568
|
+
expect(screen.getByTestId("investigation-markdown")).toHaveTextContent(
|
|
569
|
+
"The upstream dependency rejected requests.",
|
|
570
|
+
);
|
|
571
|
+
expect(onAnalysisAvailable).toHaveBeenCalledTimes(1);
|
|
572
|
+
});
|
|
573
|
+
|
|
574
|
+
test("shows the no-report outcome and disables analysis-only actions", async () => {
|
|
575
|
+
postMock.mockResolvedValue(
|
|
576
|
+
completedResponse({
|
|
577
|
+
analysisMarkdown: null,
|
|
578
|
+
isAnalysisPending: false,
|
|
579
|
+
events: [],
|
|
580
|
+
toolCallCount: 0,
|
|
581
|
+
totalTokens: 0,
|
|
582
|
+
}) as never,
|
|
583
|
+
);
|
|
584
|
+
|
|
585
|
+
renderPanel();
|
|
586
|
+
await flush();
|
|
587
|
+
|
|
588
|
+
expect(
|
|
589
|
+
screen.getByText("Investigation completed without a report"),
|
|
590
|
+
).toBeVisible();
|
|
591
|
+
expect(
|
|
592
|
+
screen.getByText("No investigation report was published."),
|
|
593
|
+
).toBeVisible();
|
|
594
|
+
expect(fixButton()).toBeDisabled();
|
|
595
|
+
expect(screen.getByRole("button", { name: "Confirmed" })).toBeDisabled();
|
|
596
|
+
expect(screen.getByRole("button", { name: "Rejected" })).toBeDisabled();
|
|
597
|
+
});
|
|
598
|
+
|
|
599
|
+
test("shows a terminal failure without completed actions", async () => {
|
|
600
|
+
postMock.mockResolvedValue(
|
|
601
|
+
successfulResponse(
|
|
602
|
+
investigationPayload({
|
|
603
|
+
status: AIRunStatus.Error,
|
|
604
|
+
errorMessage: "The model provider timed out.",
|
|
605
|
+
events: [activityEvent],
|
|
606
|
+
}),
|
|
607
|
+
) as never,
|
|
608
|
+
);
|
|
609
|
+
|
|
610
|
+
renderPanel();
|
|
611
|
+
await flush();
|
|
612
|
+
|
|
613
|
+
expect(screen.getByText("Investigation did not finish")).toBeVisible();
|
|
614
|
+
expect(screen.getByText("The model provider timed out.")).toBeVisible();
|
|
615
|
+
expect(lastActivityProps()).toEqual(
|
|
616
|
+
expect.objectContaining({
|
|
617
|
+
title: "Investigation activity",
|
|
618
|
+
showLiveIndicator: false,
|
|
619
|
+
}),
|
|
620
|
+
);
|
|
621
|
+
expect(fixButton()).toBeNull();
|
|
622
|
+
expect(screen.queryByText("Rate this investigation")).toBeNull();
|
|
623
|
+
});
|
|
624
|
+
|
|
625
|
+
test("uses the alert endpoint and alert id", async () => {
|
|
626
|
+
postMock.mockResolvedValue(
|
|
627
|
+
successfulResponse(
|
|
628
|
+
investigationPayload({ status: AIRunStatus.Running }),
|
|
629
|
+
) as never,
|
|
630
|
+
);
|
|
631
|
+
|
|
632
|
+
renderPanel({ subjectType: "alert", subjectId: ALERT_ID });
|
|
633
|
+
await flush();
|
|
634
|
+
|
|
635
|
+
expect(requestPath(0)).toContain("/ai-investigation/alert");
|
|
636
|
+
expect(postRequestAt(0).data).toEqual({ alertId: ALERT_ID.toString() });
|
|
637
|
+
});
|
|
638
|
+
});
|
|
639
|
+
|
|
640
|
+
describe("InvestigationPanel code-fix recommendation", () => {
|
|
641
|
+
test("shows the fix action only for an explicit Recommended decision", async () => {
|
|
642
|
+
postMock.mockResolvedValue(completedResponse() as never);
|
|
643
|
+
|
|
644
|
+
renderPanel();
|
|
645
|
+
await flush();
|
|
646
|
+
|
|
647
|
+
expect(fixButton()).toBeEnabled();
|
|
648
|
+
expect(screen.getByText("Act on this investigation")).toBeVisible();
|
|
649
|
+
expect(screen.getByText("Rate this investigation")).toBeVisible();
|
|
650
|
+
});
|
|
651
|
+
|
|
652
|
+
test("hides every fix-task element for NotRecommended but retains verdict controls", async () => {
|
|
653
|
+
postMock.mockResolvedValue(
|
|
654
|
+
completedResponse({
|
|
655
|
+
codeFixRecommendation: AIRunCodeFixRecommendation.NotRecommended,
|
|
656
|
+
}) as never,
|
|
657
|
+
);
|
|
658
|
+
|
|
659
|
+
renderPanel();
|
|
660
|
+
await flush();
|
|
661
|
+
|
|
662
|
+
expect(fixButton()).toBeNull();
|
|
663
|
+
expect(screen.queryByText("Act on this investigation")).toBeNull();
|
|
664
|
+
expect(screen.queryByText(/Fix task created/)).toBeNull();
|
|
665
|
+
expect(screen.getByText("Rate this investigation")).toBeVisible();
|
|
666
|
+
expect(screen.getByRole("button", { name: "Confirmed" })).toBeEnabled();
|
|
667
|
+
expect(screen.getByRole("button", { name: "Rejected" })).toBeEnabled();
|
|
668
|
+
});
|
|
669
|
+
|
|
670
|
+
test("fails closed for a legacy row with no recommendation or completion time", async () => {
|
|
671
|
+
postMock.mockResolvedValue(
|
|
672
|
+
completedResponse({
|
|
673
|
+
codeFixRecommendation: undefined,
|
|
674
|
+
completedAt: undefined,
|
|
675
|
+
}) as never,
|
|
676
|
+
);
|
|
677
|
+
|
|
678
|
+
renderPanel();
|
|
679
|
+
await flush();
|
|
680
|
+
|
|
681
|
+
expect(fixButton()).toBeNull();
|
|
682
|
+
expect(screen.getByText("Rate this investigation")).toBeVisible();
|
|
683
|
+
expect(jest.getTimerCount()).toBe(1);
|
|
684
|
+
|
|
685
|
+
await advanceFastPolls(4);
|
|
686
|
+
expect(postMock).toHaveBeenCalledTimes(1);
|
|
687
|
+
});
|
|
688
|
+
|
|
689
|
+
test("fails closed at fast cadence for an old row with no recommendation", async () => {
|
|
690
|
+
postMock.mockResolvedValue(
|
|
691
|
+
completedResponse({
|
|
692
|
+
codeFixRecommendation: undefined,
|
|
693
|
+
completedAt: OLD_COMPLETED_AT,
|
|
694
|
+
}) as never,
|
|
695
|
+
);
|
|
696
|
+
|
|
697
|
+
renderPanel();
|
|
698
|
+
await flush();
|
|
699
|
+
|
|
700
|
+
expect(fixButton()).toBeNull();
|
|
701
|
+
expect(jest.getTimerCount()).toBe(1);
|
|
702
|
+
|
|
703
|
+
await advanceFastPolls(4);
|
|
704
|
+
expect(postMock).toHaveBeenCalledTimes(1);
|
|
705
|
+
});
|
|
706
|
+
|
|
707
|
+
test("polls a recent missing decision and reveals the action when it settles", async () => {
|
|
708
|
+
postMock
|
|
709
|
+
.mockResolvedValueOnce(
|
|
710
|
+
completedResponse({ codeFixRecommendation: undefined }) as never,
|
|
711
|
+
)
|
|
712
|
+
.mockResolvedValueOnce(completedResponse() as never);
|
|
713
|
+
|
|
714
|
+
renderPanel();
|
|
715
|
+
await flush();
|
|
716
|
+
|
|
717
|
+
expect(fixButton()).toBeNull();
|
|
718
|
+
expect(screen.getByText("Rate this investigation")).toBeVisible();
|
|
719
|
+
expect(jest.getTimerCount()).toBe(2);
|
|
720
|
+
|
|
721
|
+
await advanceFastPolls();
|
|
722
|
+
|
|
723
|
+
expect(postMock).toHaveBeenCalledTimes(2);
|
|
724
|
+
expect(fixButton()).toBeEnabled();
|
|
725
|
+
expect(jest.getTimerCount()).toBe(1);
|
|
726
|
+
});
|
|
727
|
+
|
|
728
|
+
test("applies Pending to Recommended when no other signature field changes", async () => {
|
|
729
|
+
postMock
|
|
730
|
+
.mockResolvedValueOnce(
|
|
731
|
+
completedResponse({
|
|
732
|
+
codeFixRecommendation: AIRunCodeFixRecommendation.Pending,
|
|
733
|
+
}) as never,
|
|
734
|
+
)
|
|
735
|
+
.mockResolvedValueOnce(completedResponse() as never);
|
|
736
|
+
|
|
737
|
+
renderPanel();
|
|
738
|
+
await flush();
|
|
739
|
+
|
|
740
|
+
expect(screen.getByText("Investigation complete")).toBeVisible();
|
|
741
|
+
expect(fixButton()).toBeNull();
|
|
742
|
+
expect(postMock).toHaveBeenCalledTimes(1);
|
|
743
|
+
expect(jest.getTimerCount()).toBe(2);
|
|
744
|
+
|
|
745
|
+
await advanceFastPolls();
|
|
746
|
+
|
|
747
|
+
expect(postMock).toHaveBeenCalledTimes(2);
|
|
748
|
+
expect(fixButton()).toBeEnabled();
|
|
749
|
+
expect(jest.getTimerCount()).toBe(1);
|
|
750
|
+
});
|
|
751
|
+
|
|
752
|
+
test("does not present explicit Pending as an active investigation", async () => {
|
|
753
|
+
postMock.mockResolvedValue(
|
|
754
|
+
completedResponse({
|
|
755
|
+
codeFixRecommendation: AIRunCodeFixRecommendation.Pending,
|
|
756
|
+
}) as never,
|
|
757
|
+
);
|
|
758
|
+
|
|
759
|
+
renderPanel();
|
|
760
|
+
await flush();
|
|
761
|
+
|
|
762
|
+
expect(screen.getByText("Investigation complete")).toBeVisible();
|
|
763
|
+
expect(screen.queryByText("Investigating…")).toBeNull();
|
|
764
|
+
expect(
|
|
765
|
+
document.querySelector('[class~="motion-safe:animate-ping"]'),
|
|
766
|
+
).toBeNull();
|
|
767
|
+
expect(fixButton()).toBeNull();
|
|
768
|
+
});
|
|
769
|
+
|
|
770
|
+
test("tolerates a browser clock behind the server while Pending settles", async () => {
|
|
771
|
+
jest.setSystemTime(new Date("2026-08-07T11:59:55.000Z"));
|
|
772
|
+
postMock
|
|
773
|
+
.mockResolvedValueOnce(
|
|
774
|
+
completedResponse({
|
|
775
|
+
codeFixRecommendation: AIRunCodeFixRecommendation.Pending,
|
|
776
|
+
}) as never,
|
|
777
|
+
)
|
|
778
|
+
.mockResolvedValueOnce(completedResponse() as never);
|
|
779
|
+
|
|
780
|
+
renderPanel();
|
|
781
|
+
await flush();
|
|
782
|
+
expect(fixButton()).toBeNull();
|
|
783
|
+
|
|
784
|
+
await advanceFastPolls();
|
|
785
|
+
expect(fixButton()).toBeEnabled();
|
|
786
|
+
});
|
|
787
|
+
|
|
788
|
+
test("tolerates a browser clock ahead of the server for explicit Pending", async () => {
|
|
789
|
+
jest.setSystemTime(new Date("2026-08-07T12:05:00.000Z"));
|
|
790
|
+
postMock
|
|
791
|
+
.mockResolvedValueOnce(
|
|
792
|
+
completedResponse({
|
|
793
|
+
codeFixRecommendation: AIRunCodeFixRecommendation.Pending,
|
|
794
|
+
}) as never,
|
|
795
|
+
)
|
|
796
|
+
.mockResolvedValueOnce(completedResponse() as never);
|
|
797
|
+
|
|
798
|
+
renderPanel();
|
|
799
|
+
await flush();
|
|
800
|
+
expect(fixButton()).toBeNull();
|
|
801
|
+
|
|
802
|
+
await advanceFastPolls();
|
|
803
|
+
expect(fixButton()).toBeEnabled();
|
|
804
|
+
});
|
|
805
|
+
|
|
806
|
+
test("bounds a permanently Pending decision by time and response count", async () => {
|
|
807
|
+
postMock.mockResolvedValue(
|
|
808
|
+
completedResponse({
|
|
809
|
+
codeFixRecommendation: AIRunCodeFixRecommendation.Pending,
|
|
810
|
+
}) as never,
|
|
811
|
+
);
|
|
812
|
+
|
|
813
|
+
renderPanel();
|
|
814
|
+
await flush();
|
|
815
|
+
|
|
816
|
+
expect(fixButton()).toBeNull();
|
|
817
|
+
expect(jest.getTimerCount()).toBe(2);
|
|
818
|
+
|
|
819
|
+
await advanceFastPolls(MAX_RECOMMENDATION_POLL_RESPONSES);
|
|
820
|
+
|
|
821
|
+
const callsAtSettlement: number = postMock.mock.calls.length;
|
|
822
|
+
expect(callsAtSettlement).toBeGreaterThan(1);
|
|
823
|
+
expect(callsAtSettlement).toBeLessThanOrEqual(
|
|
824
|
+
MAX_RECOMMENDATION_POLL_RESPONSES + 1,
|
|
825
|
+
);
|
|
826
|
+
expect(fixButton()).toBeNull();
|
|
827
|
+
expect(screen.queryByRole("alert")).toBeNull();
|
|
828
|
+
expect(jest.getTimerCount()).toBe(1);
|
|
829
|
+
|
|
830
|
+
await advanceFastPolls(4);
|
|
831
|
+
expect(postMock).toHaveBeenCalledTimes(callsAtSettlement);
|
|
832
|
+
});
|
|
833
|
+
|
|
834
|
+
test("keeps Pending polls sequential when a response is slow", async () => {
|
|
835
|
+
const slowPending: Deferred<ApiResponse> = createDeferred<ApiResponse>();
|
|
836
|
+
postMock
|
|
837
|
+
.mockResolvedValueOnce(
|
|
838
|
+
completedResponse({
|
|
839
|
+
codeFixRecommendation: AIRunCodeFixRecommendation.Pending,
|
|
840
|
+
}) as never,
|
|
841
|
+
)
|
|
842
|
+
.mockReturnValueOnce(slowPending.promise as never)
|
|
843
|
+
.mockResolvedValueOnce(completedResponse() as never);
|
|
844
|
+
|
|
845
|
+
renderPanel();
|
|
846
|
+
await flush();
|
|
847
|
+
await advanceFastPolls();
|
|
848
|
+
expect(postMock).toHaveBeenCalledTimes(2);
|
|
849
|
+
|
|
850
|
+
await advanceFastPolls(5);
|
|
851
|
+
expect(postMock).toHaveBeenCalledTimes(2);
|
|
852
|
+
|
|
853
|
+
await resolveDeferred(
|
|
854
|
+
slowPending,
|
|
855
|
+
completedResponse({
|
|
856
|
+
codeFixRecommendation: AIRunCodeFixRecommendation.Pending,
|
|
857
|
+
}),
|
|
858
|
+
);
|
|
859
|
+
await advanceFastPolls();
|
|
860
|
+
|
|
861
|
+
expect(postMock).toHaveBeenCalledTimes(3);
|
|
862
|
+
expect(fixButton()).toBeEnabled();
|
|
863
|
+
});
|
|
864
|
+
|
|
865
|
+
test("stops failed Pending polls at the independent deadline", async () => {
|
|
866
|
+
const failedPoll: HTTPErrorResponse = new HTTPErrorResponse(
|
|
867
|
+
503,
|
|
868
|
+
{ message: "temporarily unavailable" },
|
|
869
|
+
{},
|
|
870
|
+
);
|
|
871
|
+
postMock
|
|
872
|
+
.mockResolvedValueOnce(
|
|
873
|
+
completedResponse({
|
|
874
|
+
codeFixRecommendation: AIRunCodeFixRecommendation.Pending,
|
|
875
|
+
}) as never,
|
|
876
|
+
)
|
|
877
|
+
.mockResolvedValue(failedPoll as never);
|
|
878
|
+
|
|
879
|
+
renderPanel();
|
|
880
|
+
await flush();
|
|
881
|
+
await advanceFastPolls(MAX_RECOMMENDATION_POLL_RESPONSES);
|
|
882
|
+
|
|
883
|
+
const callsAtDeadline: number = postMock.mock.calls.length;
|
|
884
|
+
expect(callsAtDeadline).toBeGreaterThan(1);
|
|
885
|
+
expect(fixButton()).toBeNull();
|
|
886
|
+
expect(screen.queryByRole("alert")).toBeNull();
|
|
887
|
+
|
|
888
|
+
await advanceFastPolls(4);
|
|
889
|
+
expect(postMock).toHaveBeenCalledTimes(callsAtDeadline);
|
|
890
|
+
});
|
|
891
|
+
|
|
892
|
+
test("a hung Pending request never overlaps before or after the deadline", async () => {
|
|
893
|
+
const hungPoll: Deferred<ApiResponse> = createDeferred<ApiResponse>();
|
|
894
|
+
postMock
|
|
895
|
+
.mockResolvedValueOnce(
|
|
896
|
+
completedResponse({
|
|
897
|
+
codeFixRecommendation: AIRunCodeFixRecommendation.Pending,
|
|
898
|
+
}) as never,
|
|
899
|
+
)
|
|
900
|
+
.mockReturnValueOnce(hungPoll.promise as never);
|
|
901
|
+
|
|
902
|
+
renderPanel();
|
|
903
|
+
await flush();
|
|
904
|
+
await advanceFastPolls();
|
|
905
|
+
expect(postMock).toHaveBeenCalledTimes(2);
|
|
906
|
+
|
|
907
|
+
await tick(RECOMMENDATION_SETTLEMENT_MAX_AGE_MS);
|
|
908
|
+
expect(fixButton()).toBeNull();
|
|
909
|
+
expect(postMock).toHaveBeenCalledTimes(2);
|
|
910
|
+
|
|
911
|
+
/* Settled cadence must reuse the still-hung request, not overlap it. */
|
|
912
|
+
await tick(SETTLED_POLL_INTERVAL_MS);
|
|
913
|
+
expect(postMock).toHaveBeenCalledTimes(2);
|
|
914
|
+
});
|
|
915
|
+
});
|
|
916
|
+
|
|
917
|
+
describe("InvestigationPanel completed actions", () => {
|
|
918
|
+
test("creates a run-bound fix task and replaces the action with success", async () => {
|
|
919
|
+
postMock
|
|
920
|
+
.mockResolvedValueOnce(completedResponse() as never)
|
|
921
|
+
.mockResolvedValueOnce(
|
|
922
|
+
successfulResponse({ aiRunId: FIX_RUN_ID }) as never,
|
|
923
|
+
);
|
|
924
|
+
|
|
925
|
+
renderPanel();
|
|
926
|
+
await flush();
|
|
927
|
+
fireEvent.click(fixButton()!);
|
|
928
|
+
await flush();
|
|
929
|
+
|
|
930
|
+
expect(screen.getByRole("alert")).toHaveTextContent("Fix task created");
|
|
931
|
+
expect(fixButton()).toBeNull();
|
|
932
|
+
expect(requestPath(1)).toContain("/ai-investigation/create-fix-task");
|
|
933
|
+
expect(postRequestAt(1).data).toEqual({
|
|
934
|
+
subjectType: "incident",
|
|
935
|
+
subjectId: INCIDENT_ID.toString(),
|
|
936
|
+
investigationRunId: RUN_ID,
|
|
937
|
+
});
|
|
938
|
+
expect(screen.getByText("View task progress")).toBeVisible();
|
|
939
|
+
});
|
|
940
|
+
|
|
941
|
+
test("keeps the Recommended action and shows a friendly task error", async () => {
|
|
942
|
+
postMock
|
|
943
|
+
.mockResolvedValueOnce(completedResponse() as never)
|
|
944
|
+
.mockResolvedValueOnce(
|
|
945
|
+
new HTTPErrorResponse(
|
|
946
|
+
400,
|
|
947
|
+
{ message: "No connected repository exists." },
|
|
948
|
+
{},
|
|
949
|
+
) as never,
|
|
950
|
+
);
|
|
951
|
+
|
|
952
|
+
renderPanel();
|
|
953
|
+
await flush();
|
|
954
|
+
fireEvent.click(fixButton()!);
|
|
955
|
+
await flush();
|
|
956
|
+
|
|
957
|
+
expect(screen.getByRole("alert")).toHaveTextContent(
|
|
958
|
+
"Could not create the fix task",
|
|
959
|
+
);
|
|
960
|
+
expect(screen.getByRole("alert")).toHaveTextContent(
|
|
961
|
+
"No connected repository exists",
|
|
962
|
+
);
|
|
963
|
+
expect(fixButton()).toBeEnabled();
|
|
964
|
+
});
|
|
965
|
+
|
|
966
|
+
test("clears settled fix-task state after subject navigation", async () => {
|
|
967
|
+
const nextAnalysis: string =
|
|
968
|
+
"## Alert root cause\n\nA deployment removed the required credential.";
|
|
969
|
+
postMock
|
|
970
|
+
.mockResolvedValueOnce(completedResponse() as never)
|
|
971
|
+
.mockResolvedValueOnce(
|
|
972
|
+
successfulResponse({ aiRunId: FIX_RUN_ID }) as never,
|
|
973
|
+
)
|
|
974
|
+
.mockResolvedValueOnce(
|
|
975
|
+
completedResponse({
|
|
976
|
+
runId: NEXT_RUN_ID,
|
|
977
|
+
analysisMarkdown: nextAnalysis,
|
|
978
|
+
}) as never,
|
|
979
|
+
);
|
|
980
|
+
|
|
981
|
+
const view: ReturnType<typeof render> = renderPanel();
|
|
982
|
+
await flush();
|
|
983
|
+
fireEvent.click(fixButton()!);
|
|
984
|
+
await flush();
|
|
985
|
+
expect(screen.getByRole("alert")).toHaveTextContent("Fix task created");
|
|
986
|
+
|
|
987
|
+
view.rerender(
|
|
988
|
+
<InvestigationPanel subjectType="alert" subjectId={ALERT_ID} />,
|
|
989
|
+
);
|
|
990
|
+
await flush();
|
|
991
|
+
|
|
992
|
+
expect(screen.queryByText(/Fix task created/)).toBeNull();
|
|
993
|
+
expect(screen.getByTestId("investigation-markdown")).toHaveTextContent(
|
|
994
|
+
"A deployment removed the required credential.",
|
|
995
|
+
);
|
|
996
|
+
expect(fixButton()).toBeEnabled();
|
|
997
|
+
});
|
|
998
|
+
|
|
999
|
+
test("ignores a fix-task result that resolves on another subject", async () => {
|
|
1000
|
+
const staleTask: Deferred<ApiResponse> = createDeferred<ApiResponse>();
|
|
1001
|
+
postMock
|
|
1002
|
+
.mockResolvedValueOnce(completedResponse() as never)
|
|
1003
|
+
.mockReturnValueOnce(staleTask.promise as never)
|
|
1004
|
+
.mockResolvedValueOnce(
|
|
1005
|
+
completedResponse({ runId: NEXT_RUN_ID }) as never,
|
|
1006
|
+
);
|
|
1007
|
+
|
|
1008
|
+
const view: ReturnType<typeof render> = renderPanel();
|
|
1009
|
+
await flush();
|
|
1010
|
+
fireEvent.click(fixButton()!);
|
|
1011
|
+
await flush();
|
|
1012
|
+
|
|
1013
|
+
view.rerender(
|
|
1014
|
+
<InvestigationPanel subjectType="alert" subjectId={ALERT_ID} />,
|
|
1015
|
+
);
|
|
1016
|
+
await flush();
|
|
1017
|
+
await resolveDeferred(
|
|
1018
|
+
staleTask,
|
|
1019
|
+
successfulResponse({ aiRunId: FIX_RUN_ID }),
|
|
1020
|
+
);
|
|
1021
|
+
|
|
1022
|
+
expect(screen.queryByRole("alert")).toBeNull();
|
|
1023
|
+
expect(fixButton()).toBeEnabled();
|
|
1024
|
+
expect(screen.getByText("Investigation complete")).toBeVisible();
|
|
1025
|
+
});
|
|
1026
|
+
|
|
1027
|
+
test("resets fix-task and verdict state when the same subject gets a new run", async () => {
|
|
1028
|
+
const nextAnalysis: string =
|
|
1029
|
+
"## New root cause\n\nA later investigation found a certificate rollover.";
|
|
1030
|
+
postMock
|
|
1031
|
+
.mockResolvedValueOnce(completedResponse() as never)
|
|
1032
|
+
.mockResolvedValueOnce(
|
|
1033
|
+
successfulResponse({ aiRunId: FIX_RUN_ID }) as never,
|
|
1034
|
+
)
|
|
1035
|
+
.mockResolvedValueOnce(successfulResponse({}) as never)
|
|
1036
|
+
.mockResolvedValueOnce(
|
|
1037
|
+
completedResponse({
|
|
1038
|
+
runId: NEXT_RUN_ID,
|
|
1039
|
+
analysisMarkdown: nextAnalysis,
|
|
1040
|
+
}) as never,
|
|
1041
|
+
);
|
|
1042
|
+
|
|
1043
|
+
renderPanel();
|
|
1044
|
+
await flush();
|
|
1045
|
+
fireEvent.click(fixButton()!);
|
|
1046
|
+
await flush();
|
|
1047
|
+
fireEvent.click(screen.getByRole("button", { name: "Confirmed" }));
|
|
1048
|
+
await flush();
|
|
1049
|
+
|
|
1050
|
+
expect(screen.getByRole("alert")).toHaveTextContent("Fix task created");
|
|
1051
|
+
expect(screen.getByText(/You confirmed this analysis/)).toBeVisible();
|
|
1052
|
+
|
|
1053
|
+
await tick(SETTLED_POLL_INTERVAL_MS);
|
|
1054
|
+
|
|
1055
|
+
expect(screen.getByTestId("investigation-markdown")).toHaveTextContent(
|
|
1056
|
+
"A later investigation found a certificate rollover.",
|
|
1057
|
+
);
|
|
1058
|
+
expect(screen.queryByText(/Fix task created/)).toBeNull();
|
|
1059
|
+
expect(screen.queryByText(/You confirmed this analysis/)).toBeNull();
|
|
1060
|
+
expect(fixButton()).toBeEnabled();
|
|
1061
|
+
expect(screen.getByRole("button", { name: "Confirmed" })).toBeEnabled();
|
|
1062
|
+
});
|
|
1063
|
+
|
|
1064
|
+
test("binds a verdict to the displayed run even without a fix recommendation", async () => {
|
|
1065
|
+
postMock
|
|
1066
|
+
.mockResolvedValueOnce(
|
|
1067
|
+
completedResponse({
|
|
1068
|
+
codeFixRecommendation: AIRunCodeFixRecommendation.NotRecommended,
|
|
1069
|
+
}) as never,
|
|
1070
|
+
)
|
|
1071
|
+
.mockResolvedValueOnce(successfulResponse({}) as never);
|
|
1072
|
+
|
|
1073
|
+
renderPanel();
|
|
1074
|
+
await flush();
|
|
1075
|
+
fireEvent.click(screen.getByRole("button", { name: "Confirmed" }));
|
|
1076
|
+
await flush();
|
|
1077
|
+
|
|
1078
|
+
expect(requestPath(1)).toContain("/ai-investigation/verdict");
|
|
1079
|
+
expect(postRequestAt(1).data).toEqual({
|
|
1080
|
+
subjectType: "incident",
|
|
1081
|
+
subjectId: INCIDENT_ID.toString(),
|
|
1082
|
+
investigationRunId: RUN_ID,
|
|
1083
|
+
verdict: "Confirmed",
|
|
1084
|
+
});
|
|
1085
|
+
expect(screen.getByText(/You confirmed this analysis/)).toBeVisible();
|
|
1086
|
+
});
|
|
1087
|
+
|
|
1088
|
+
test("retains an existing verdict when a fix is not recommended", async () => {
|
|
1089
|
+
postMock.mockResolvedValue(
|
|
1090
|
+
completedResponse({
|
|
1091
|
+
codeFixRecommendation: AIRunCodeFixRecommendation.NotRecommended,
|
|
1092
|
+
humanVerdict: "Confirmed",
|
|
1093
|
+
}) as never,
|
|
1094
|
+
);
|
|
1095
|
+
|
|
1096
|
+
renderPanel();
|
|
1097
|
+
await flush();
|
|
1098
|
+
|
|
1099
|
+
expect(fixButton()).toBeNull();
|
|
1100
|
+
expect(screen.getByText(/You confirmed this analysis/)).toBeVisible();
|
|
1101
|
+
expect(screen.getByText("Change")).toBeVisible();
|
|
1102
|
+
expect(screen.getByText("Rate this investigation")).toBeVisible();
|
|
1103
|
+
});
|
|
1104
|
+
|
|
1105
|
+
test("a GET started before verdict save cannot overwrite the saved verdict", async () => {
|
|
1106
|
+
const stalePoll: Deferred<ApiResponse> = createDeferred<ApiResponse>();
|
|
1107
|
+
postMock
|
|
1108
|
+
.mockResolvedValueOnce(completedResponse() as never)
|
|
1109
|
+
.mockReturnValueOnce(stalePoll.promise as never)
|
|
1110
|
+
.mockResolvedValueOnce(successfulResponse({}) as never);
|
|
1111
|
+
|
|
1112
|
+
renderPanel();
|
|
1113
|
+
await flush();
|
|
1114
|
+
await tick(SETTLED_POLL_INTERVAL_MS);
|
|
1115
|
+
expect(postMock).toHaveBeenCalledTimes(2);
|
|
1116
|
+
|
|
1117
|
+
fireEvent.click(screen.getByRole("button", { name: "Confirmed" }));
|
|
1118
|
+
await flush();
|
|
1119
|
+
expect(screen.getByText(/You confirmed this analysis/)).toBeVisible();
|
|
1120
|
+
|
|
1121
|
+
await resolveDeferred(stalePoll, completedResponse({ humanVerdict: null }));
|
|
1122
|
+
|
|
1123
|
+
expect(screen.getByText(/You confirmed this analysis/)).toBeVisible();
|
|
1124
|
+
expect(screen.queryByRole("button", { name: "Confirmed" })).toBeNull();
|
|
1125
|
+
});
|
|
1126
|
+
|
|
1127
|
+
test("rolls back an optimistic verdict when save fails", async () => {
|
|
1128
|
+
postMock
|
|
1129
|
+
.mockResolvedValueOnce(completedResponse() as never)
|
|
1130
|
+
.mockResolvedValueOnce(
|
|
1131
|
+
new HTTPErrorResponse(
|
|
1132
|
+
500,
|
|
1133
|
+
{ message: "Verdict storage is unavailable." },
|
|
1134
|
+
{},
|
|
1135
|
+
) as never,
|
|
1136
|
+
);
|
|
1137
|
+
|
|
1138
|
+
renderPanel();
|
|
1139
|
+
await flush();
|
|
1140
|
+
fireEvent.click(screen.getByRole("button", { name: "Rejected" }));
|
|
1141
|
+
await flush();
|
|
1142
|
+
|
|
1143
|
+
expect(screen.getByRole("alert")).toHaveTextContent(
|
|
1144
|
+
"Could not save your verdict",
|
|
1145
|
+
);
|
|
1146
|
+
expect(screen.getByRole("alert")).toHaveTextContent(
|
|
1147
|
+
"Verdict storage is unavailable.",
|
|
1148
|
+
);
|
|
1149
|
+
expect(screen.queryByText(/You rejected this analysis/)).toBeNull();
|
|
1150
|
+
expect(screen.getByRole("button", { name: "Rejected" })).toBeEnabled();
|
|
1151
|
+
});
|
|
1152
|
+
|
|
1153
|
+
test("ignores a verdict result that resolves after navigation", async () => {
|
|
1154
|
+
const staleVerdict: Deferred<ApiResponse> = createDeferred<ApiResponse>();
|
|
1155
|
+
postMock
|
|
1156
|
+
.mockResolvedValueOnce(
|
|
1157
|
+
completedResponse({
|
|
1158
|
+
codeFixRecommendation: AIRunCodeFixRecommendation.NotRecommended,
|
|
1159
|
+
}) as never,
|
|
1160
|
+
)
|
|
1161
|
+
.mockReturnValueOnce(staleVerdict.promise as never)
|
|
1162
|
+
.mockResolvedValueOnce(
|
|
1163
|
+
completedResponse({
|
|
1164
|
+
runId: NEXT_RUN_ID,
|
|
1165
|
+
codeFixRecommendation: AIRunCodeFixRecommendation.NotRecommended,
|
|
1166
|
+
}) as never,
|
|
1167
|
+
);
|
|
1168
|
+
|
|
1169
|
+
const view: ReturnType<typeof render> = renderPanel();
|
|
1170
|
+
await flush();
|
|
1171
|
+
fireEvent.click(screen.getByRole("button", { name: "Confirmed" }));
|
|
1172
|
+
await flush();
|
|
1173
|
+
|
|
1174
|
+
view.rerender(
|
|
1175
|
+
<InvestigationPanel subjectType="alert" subjectId={ALERT_ID} />,
|
|
1176
|
+
);
|
|
1177
|
+
await flush();
|
|
1178
|
+
await resolveDeferred(staleVerdict, successfulResponse({}));
|
|
1179
|
+
|
|
1180
|
+
expect(screen.queryByText(/You confirmed this analysis/)).toBeNull();
|
|
1181
|
+
expect(screen.queryByRole("alert")).toBeNull();
|
|
1182
|
+
expect(screen.getByRole("button", { name: "Confirmed" })).toBeEnabled();
|
|
1183
|
+
});
|
|
1184
|
+
});
|