@oneuptime/common 12.0.4 → 12.0.6
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 +71 -1
- package/Models/DatabaseModels/AlertFeed.ts +30 -0
- package/Models/DatabaseModels/Dashboard.ts +28 -0
- package/Models/DatabaseModels/DataSource.ts +744 -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/Index.ts +3 -0
- package/Models/DatabaseModels/LogSavedView.ts +33 -0
- package/Models/DatabaseModels/Project.ts +231 -9
- package/Models/DatabaseModels/ProjectCallSMSConfig.ts +10 -2
- 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/Models/DatabaseModels/User.ts +2 -0
- package/Models/DatabaseModels/UserOnCallLog.ts +2 -0
- package/Models/DatabaseModels/UserOnCallLogTimeline.ts +2 -0
- package/Models/DatabaseModels/UserSession.ts +2 -0
- package/Models/DatabaseModels/UserTotpAuth.ts +6 -4
- package/Models/DatabaseModels/UserWebAuthn.ts +5 -3
- package/Server/API/AIAgentDataAPI.ts +44 -55
- package/Server/API/AIInsightAPI.ts +47 -4
- package/Server/API/AIInvestigationAPI.ts +200 -17
- package/Server/API/DashboardAPI.ts +186 -502
- package/Server/API/DataSourceAPI.ts +268 -0
- package/Server/API/StatusPageAPI.ts +49 -20
- package/Server/API/UserAPI.ts +119 -0
- package/Server/EnvironmentConfig.ts +16 -0
- package/Server/Infrastructure/ClickhouseConfig.ts +74 -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/1786303472848-AddDataSourceTable.ts +39 -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/1786600000000-AddInvestigationAnalysisTldr.ts +24 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/1786700000000-DropProjectCallSMSConfigCredentialUniques.ts +76 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/Index.ts +16 -0
- package/Server/Infrastructure/Queue.ts +26 -0
- package/Server/Infrastructure/QueueWorker.ts +17 -0
- package/Server/Middleware/TelemetryIngest.ts +8 -3
- package/Server/Services/AIInsightService.ts +132 -12
- package/Server/Services/AIRunService.ts +112 -10
- package/Server/Services/AIService.ts +109 -22
- package/Server/Services/AlertFeedService.ts +5 -0
- package/Server/Services/AlertService.ts +38 -32
- package/Server/Services/AnalyticsDatabaseService.ts +37 -4
- package/Server/Services/DataSourceService.ts +200 -0
- package/Server/Services/DatabaseService.ts +43 -2
- package/Server/Services/HostService.ts +92 -0
- package/Server/Services/IncidentFeedService.ts +5 -0
- package/Server/Services/IncidentService.ts +45 -0
- package/Server/Services/Index.ts +2 -0
- package/Server/Services/LlmLogService.ts +46 -2
- package/Server/Services/MonitorService.ts +49 -0
- package/Server/Services/MonitorStatusService.ts +166 -0
- package/Server/Services/OpenTelemetryIngestService.ts +255 -184
- package/Server/Services/RumSessionPinService.ts +7 -21
- package/Server/Services/StatusPageSubscriberService.ts +13 -0
- package/Server/Services/UserService.ts +280 -9
- package/Server/Static/Vendor/README.md +62 -0
- package/Server/Static/Vendor/fonts/InterVariable.woff2 +0 -0
- package/Server/Static/Vendor/highlight/highlight.min.js +1244 -0
- package/Server/Static/Vendor/highlight/languages/apache.min.js +15 -0
- package/Server/Static/Vendor/highlight/languages/bash.min.js +21 -0
- package/Server/Static/Vendor/highlight/languages/c.min.js +41 -0
- package/Server/Static/Vendor/highlight/languages/cpp.min.js +47 -0
- package/Server/Static/Vendor/highlight/languages/csharp.min.js +49 -0
- package/Server/Static/Vendor/highlight/languages/css.min.js +31 -0
- package/Server/Static/Vendor/highlight/languages/diff.min.js +9 -0
- package/Server/Static/Vendor/highlight/languages/dns.min.js +11 -0
- package/Server/Static/Vendor/highlight/languages/dockerfile.min.js +8 -0
- package/Server/Static/Vendor/highlight/languages/go.min.js +20 -0
- package/Server/Static/Vendor/highlight/languages/graphql.min.js +12 -0
- package/Server/Static/Vendor/highlight/languages/http.min.js +14 -0
- package/Server/Static/Vendor/highlight/languages/ini.min.js +15 -0
- package/Server/Static/Vendor/highlight/languages/java.min.js +38 -0
- package/Server/Static/Vendor/highlight/languages/javascript.min.js +81 -0
- package/Server/Static/Vendor/highlight/languages/json.min.js +8 -0
- package/Server/Static/Vendor/highlight/languages/kotlin.min.js +46 -0
- package/Server/Static/Vendor/highlight/languages/lua.min.js +15 -0
- package/Server/Static/Vendor/highlight/languages/makefile.min.js +14 -0
- package/Server/Static/Vendor/highlight/languages/nginx.min.js +21 -0
- package/Server/Static/Vendor/highlight/languages/perl.min.js +41 -0
- package/Server/Static/Vendor/highlight/languages/php.min.js +59 -0
- package/Server/Static/Vendor/highlight/languages/powershell.min.js +39 -0
- package/Server/Static/Vendor/highlight/languages/python.min.js +42 -0
- package/Server/Static/Vendor/highlight/languages/r.min.js +26 -0
- package/Server/Static/Vendor/highlight/languages/ruby.min.js +54 -0
- package/Server/Static/Vendor/highlight/languages/rust.min.js +27 -0
- package/Server/Static/Vendor/highlight/languages/scala.min.js +28 -0
- package/Server/Static/Vendor/highlight/languages/sql.min.js +21 -0
- package/Server/Static/Vendor/highlight/languages/swift.min.js +68 -0
- package/Server/Static/Vendor/highlight/languages/typescript.min.js +99 -0
- package/Server/Static/Vendor/highlight/languages/xml.min.js +29 -0
- package/Server/Static/Vendor/highlight/languages/yaml.min.js +28 -0
- package/Server/Static/Vendor/highlight/styles/github-dark.min.css +10 -0
- package/Server/Static/Vendor/highlight/styles/vs2015.min.css +1 -0
- package/Server/Static/Vendor/tailwind/tailwind-3.4.5.js +21028 -0
- package/Server/Types/Database/Permissions/BasePermission.ts +7 -0
- package/Server/Types/Database/Permissions/DeletePermission.ts +2 -0
- package/Server/Types/Database/Permissions/ReadPermission.ts +3 -0
- package/Server/Types/Database/Permissions/TenantPermission.ts +101 -16
- 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 +311 -31
- package/Server/Utils/AI/SRE/AlertInvestigationRunner.ts +26 -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 +26 -24
- package/Server/Utils/AI/SRE/Insights/Detectors/ExceptionIdentity.ts +125 -0
- package/Server/Utils/AI/SRE/Insights/Detectors/ExceptionSpikeDetector.ts +25 -15
- package/Server/Utils/AI/SRE/Insights/Detectors/NewExceptionDetector.ts +256 -57
- package/Server/Utils/AI/SRE/Insights/InsightStore.ts +76 -18
- 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/InvestigationTldr.ts +352 -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/AnalyticsDatabase/InsertDedupContext.ts +7 -2
- package/Server/Utils/Dashboard/PublicDashboardResourceListPolicy.ts +1793 -0
- package/Server/Utils/Dashboard/PublicDashboardViewConfig.ts +65 -0
- package/Server/Utils/DataSource/ConnectorRegistry.ts +53 -0
- package/Server/Utils/DataSource/Connectors/ClickHouseConnector.ts +338 -0
- package/Server/Utils/DataSource/Connectors/ElasticsearchConnector.ts +717 -0
- package/Server/Utils/DataSource/Connectors/LokiConnector.ts +606 -0
- package/Server/Utils/DataSource/Connectors/MySqlConnector.ts +307 -0
- package/Server/Utils/DataSource/Connectors/PostgresConnector.ts +260 -0
- package/Server/Utils/DataSource/Connectors/PrometheusConnector.ts +555 -0
- package/Server/Utils/DataSource/Connectors/RestApiConnector.ts +597 -0
- package/Server/Utils/DataSource/Connectors/SqlServerConnector.ts +318 -0
- package/Server/Utils/DataSource/EgressGuard.ts +446 -0
- package/Server/Utils/DataSource/HttpFetch.ts +221 -0
- package/Server/Utils/DataSource/SqlQueryGuard.ts +383 -0
- package/Server/Utils/DataSource/SqlResultShaper.ts +372 -0
- package/Server/Utils/DataSource/Types.ts +93 -0
- package/Server/Utils/Database/PostgresErrorTranslator.ts +172 -17
- package/Server/Utils/InProcessMemo.ts +141 -0
- package/Server/Utils/InstanceHealth/TelemetryIngestion.ts +628 -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 +104 -0
- package/Server/Utils/Monitor/MonitorResource.ts +14 -0
- package/Server/Utils/Monitor/MonitorTemplateUtil.ts +1 -0
- package/Server/Utils/PasswordHash.ts +28 -4
- package/Server/Utils/Response.ts +12 -1
- package/Server/Utils/SSRFProtection.ts +237 -0
- package/Server/Utils/StartServer.ts +8 -0
- package/Server/Utils/StatusPageSubscriberWebhook.ts +30 -0
- package/Server/Utils/Telemetry/CaptureSpan.ts +30 -18
- package/Server/Utils/Telemetry/IngestionTimestamp.ts +75 -0
- package/Server/Utils/Telemetry/ResourceHeartbeat.ts +99 -7
- package/Server/Utils/Telemetry/Telemetry.ts +112 -1
- package/Server/Utils/Telemetry.ts +104 -3
- package/Server/Utils/VendorAssets.ts +165 -0
- package/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeams.ts +28 -5
- package/Server/Utils/Workspace/Slack/Slack.ts +5 -0
- package/Server/Views/Partials/Head.ejs +21 -7
- package/Tests/App/AdminDashboard/AdminProjectsIdFilter.test.tsx +207 -0
- package/Tests/App/AdminDashboard/AdminUsersIdFilter.test.tsx +216 -0
- package/Tests/App/Dashboard/AIInsightViewActions.test.tsx +422 -0
- package/Tests/App/Dashboard/AIInvestigationHeaderStatus.test.tsx +251 -0
- package/Tests/App/Dashboard/AddWidgetModal.test.tsx +10 -3
- package/Tests/App/Dashboard/AlertsSideMenu.test.tsx +387 -0
- package/Tests/App/Dashboard/ChatActivityFeed.test.tsx +199 -0
- package/Tests/App/Dashboard/DataSourceChartSyntheticConfig.test.tsx +736 -0
- package/Tests/App/Dashboard/DataSourceQueryEditor.test.tsx +868 -0
- package/Tests/App/Dashboard/DataSourceWidgetCatalog.test.ts +440 -0
- package/Tests/App/Dashboard/DataSourceWidgetFetching.test.tsx +2109 -0
- package/Tests/App/Dashboard/DataSourceWidgetViews.test.tsx +726 -0
- package/Tests/App/Dashboard/EventStatusPanel.test.tsx +747 -0
- package/Tests/App/Dashboard/IncidentsSideMenu.test.tsx +447 -0
- package/Tests/App/Dashboard/InvestigationFeedRefresh.test.tsx +480 -0
- package/Tests/App/Dashboard/InvestigationPanel.test.tsx +1695 -0
- package/Tests/App/Dashboard/InvestigationPanelStatus.test.tsx +500 -0
- package/Tests/App/Dashboard/PortMonitorCriteriaFilter.test.ts +93 -0
- package/Tests/App/Dashboard/ProductMenuBreadcrumbs.test.tsx +391 -0
- package/Tests/App/Dashboard/ScheduledMaintenanceSideMenu.test.tsx +326 -0
- package/Tests/App/Dashboard/SideMenuHarness.tsx +187 -0
- package/Tests/App/Dashboard/WidgetCatalog.test.ts +6 -1
- package/Tests/App/StatusPage/PublicStatusPageAPIErrorHandling.test.ts +117 -0
- package/Tests/App/StatusPage/StatusPageModelAPIPolymorphism.test.ts +223 -0
- package/Tests/App/StatusPage/StatusPageResourceExplorer.test.tsx +1858 -0
- package/Tests/Models/DatabaseModels/DataSourceModelSecrets.test.ts +70 -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 +781 -0
- package/Tests/Server/API/AIInvestigationCreateFixTask.test.ts +80 -0
- package/Tests/Server/API/DashboardMasterPasswordAPI.test.ts +172 -1
- package/Tests/Server/API/DashboardPublicDataSourceStrip.test.ts +686 -0
- package/Tests/Server/API/DashboardPublicResourceListAPI.test.ts +585 -823
- package/Tests/Server/API/DataSourceAPIRoutes.test.ts +486 -0
- package/Tests/Server/API/StatusPageMasterPasswordAPI.test.ts +335 -0
- package/Tests/Server/API/StatusPageSubscriberOwnership.test.ts +291 -0
- package/Tests/Server/API/UserAuthenticationAPI.test.ts +641 -0
- package/Tests/Server/Infrastructure/ClickhouseKeepAliveOptions.test.ts +441 -0
- package/Tests/Server/Middleware/TelemetryIngestTokenLog.test.ts +140 -0
- package/Tests/Server/Services/AIInsightService.test.ts +266 -11
- package/Tests/Server/Services/AIRunAnalysisTldrPersistence.test.ts +160 -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 +400 -20
- package/Tests/Server/Services/AddMasterPasswordSaltMigration.test.ts +283 -0
- package/Tests/Server/Services/AlertMetricResourceAttributes.test.ts +352 -0
- package/Tests/Server/Services/AnalyticsDatabaseService.test.ts +25 -1
- package/Tests/Server/Services/DataSourceServiceSettings.test.ts +244 -0
- 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/HostServiceFindOrCreateMemo.test.ts +397 -0
- package/Tests/Server/Services/HostServiceUpdateLastSeen.test.ts +41 -2
- package/Tests/Server/Services/IncidentMetricResourceAttributes.test.ts +408 -0
- package/Tests/Server/Services/InstrumentationTaskTrigger.test.ts +173 -14
- package/Tests/Server/Services/MasterPasswordScrypt.test.ts +567 -0
- package/Tests/Server/Services/MonitorStatusServiceDeleteReferenceCleanup.test.ts +351 -0
- package/Tests/Server/Services/MonitorStatusServiceValidation.test.ts +171 -0
- package/Tests/Server/Services/OpenTelemetryResourceRetentionMemo.test.ts +398 -0
- package/Tests/Server/Services/OpenTelemetryServiceResolutionCache.test.ts +19 -0
- package/Tests/Server/Services/ProjectCallSMSConfigCredentialUniqueness.test.ts +305 -0
- package/Tests/Server/Services/ResourceUpdateLastSeenLivenessFallback.test.ts +36 -1
- package/Tests/Server/Services/RumSessionPinUniqueViolationRecovery.test.ts +163 -0
- package/Tests/Server/Services/SeparateIncidentAlertAiSettingsMigration.test.ts +194 -0
- package/Tests/Server/Services/UserAuthenticationService.test.ts +973 -0
- package/Tests/Server/Types/Database/ObjectIdColumnQuery.test.ts +262 -0
- package/Tests/Server/Types/Database/Permissions/TenantPermission.test.ts +358 -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/ExceptionSpikeDetector.test.ts +6 -1
- package/Tests/Server/Utils/AI/Insights/InsightStore.test.ts +121 -4
- package/Tests/Server/Utils/AI/Insights/InvestigationQueueInsightSubject.test.ts +2 -0
- package/Tests/Server/Utils/AI/Insights/NewExceptionDetector.test.ts +361 -10
- 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 +605 -20
- package/Tests/Server/Utils/AI/InvestigationTldr.test.ts +623 -0
- package/Tests/Server/Utils/AI/InvestigationTldrWiring.test.ts +98 -0
- 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/Dashboard/PublicDashboardResourceListPolicy.test.ts +857 -0
- package/Tests/Server/Utils/Dashboard/PublicDashboardViewConfig.test.ts +480 -0
- package/Tests/Server/Utils/DataSource/ClickHouseConnector.test.ts +759 -0
- package/Tests/Server/Utils/DataSource/ConnectorRegistry.test.ts +37 -0
- package/Tests/Server/Utils/DataSource/EgressGuard.test.ts +645 -0
- package/Tests/Server/Utils/DataSource/ElasticsearchConnector.test.ts +1206 -0
- package/Tests/Server/Utils/DataSource/HttpFetch.test.ts +616 -0
- package/Tests/Server/Utils/DataSource/LokiConnector.test.ts +970 -0
- package/Tests/Server/Utils/DataSource/MySqlConnector.test.ts +935 -0
- package/Tests/Server/Utils/DataSource/PostgresConnector.test.ts +854 -0
- package/Tests/Server/Utils/DataSource/PrometheusConnector.test.ts +881 -0
- package/Tests/Server/Utils/DataSource/RestApiConnector.test.ts +1020 -0
- package/Tests/Server/Utils/DataSource/SqlQueryGuard.test.ts +425 -0
- package/Tests/Server/Utils/DataSource/SqlResultShaper.test.ts +555 -0
- package/Tests/Server/Utils/DataSource/SqlServerConnector.test.ts +934 -0
- package/Tests/Server/Utils/Database/PostgresErrorTranslator.test.ts +228 -7
- package/Tests/Server/Utils/InProcessMemo.test.ts +292 -0
- package/Tests/Server/Utils/InstanceHealth/TelemetryIngestion.test.ts +960 -0
- package/Tests/Server/Utils/MicrosoftTeamsWebhookUrlValidation.test.ts +103 -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/MonitorMetricResourceAttributes.test.ts +336 -0
- package/Tests/Server/Utils/Monitor/MonitorMetricUtilPortTimings.test.ts +243 -0
- package/Tests/Server/Utils/OfflineAssetHygiene.test.ts +521 -0
- package/Tests/Server/Utils/PasswordHash.test.ts +30 -0
- package/Tests/Server/Utils/ResponseRenderAnalytics.test.ts +143 -0
- package/Tests/Server/Utils/SSRFProtection.test.ts +193 -0
- package/Tests/Server/Utils/StatusPageSubscriberWebhook.test.ts +101 -0
- package/Tests/Server/Utils/TailwindBuildCopy.test.ts +225 -0
- package/Tests/Server/Utils/Telemetry/ResourceHeartbeatL1Memo.test.ts +355 -0
- package/Tests/Server/Utils/Telemetry/TelemetryFanInWriter.test.ts +128 -0
- package/Tests/Server/Utils/Telemetry/TelemetryUtilAttributeKeys.test.ts +381 -0
- package/Tests/Server/Utils/Telemetry.test.ts +87 -0
- package/Tests/Server/Utils/VendorAssetIntegrity.test.ts +343 -0
- package/Tests/Server/Utils/VendorAssets.test.ts +671 -0
- package/Tests/Server/Views/OfflineRender.test.ts +297 -0
- package/Tests/Types/Dashboard/DashboardTemplates.test.ts +111 -0
- package/Tests/Types/Dashboard/DataSourceComponentTypes.test.ts +618 -0
- package/Tests/Types/DateClickhouseFormat.test.ts +192 -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/Password.test.ts +363 -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/ConfirmModal.test.tsx +7 -0
- package/Tests/UI/Components/ErrorBoundary.test.tsx +211 -0
- package/Tests/UI/Components/FeedItemSafeMode.test.tsx +74 -0
- package/Tests/UI/Components/Modal.test.tsx +281 -1
- package/Tests/UI/Components/MoreMenu.test.tsx +756 -0
- package/Tests/UI/Components/ObjectIdFilterPipeline.test.tsx +396 -0
- package/Tests/UI/Components/StatusPage/ResourceGroupNavigator.test.tsx +1147 -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/Breadcrumb/fixtures/RealBreadcrumbTrails.ts +116 -1
- package/Tests/UI/Utils/Breadcrumb/fixtures/RealRoutePatterns.ts +2 -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/Analytics.test.ts +23 -0
- package/Tests/Utils/Dashboard/Components/DashboardDataSourceComponents.test.ts +640 -0
- package/Tests/Utils/Dashboard/DashboardViewConfig.test.ts +329 -0
- package/Tests/Utils/Dashboard/GridLayout.test.ts +951 -0
- package/Tests/Utils/DataSource/DataSourceQueryText.test.ts +138 -0
- package/Tests/Utils/DataSource/DataSourceValueReducer.test.ts +526 -0
- package/Tests/Utils/Metrics/MetricResourceAttributeUtil.test.ts +691 -0
- package/Tests/Utils/Monitor/MonitorMetricType.test.ts +117 -0
- package/Tests/Utils/RecordingRuleExpression.test.ts +327 -0
- package/Tests/Utils/StatusPage/GroupHierarchyView.test.ts +1452 -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/StatusPage/ResourceExplorer.test.ts +931 -0
- package/Tests/Utils/Telemetry/CaptureSpanExportGating.test.ts +288 -0
- package/Tests/Utils/Telemetry/IngestionTimestamp.test.ts +170 -0
- package/Tests/Utils/Telemetry/SavedViewTimeRange.test.ts +244 -0
- package/Tests/Utils/Telemetry/TelemetryQueryTimeRange.test.ts +825 -0
- package/Types/AI/AIInsightEvidence.ts +6 -0
- package/Types/AI/AIRunCodeFixRecommendation.ts +17 -0
- package/Types/AI/CodeFixTaskContext.ts +63 -3
- package/Types/AI/CodeFixTaskType.ts +3 -3
- package/Types/Analytics/RevenueEvent.ts +31 -0
- package/Types/Dashboard/DashboardComponentType.ts +31 -0
- package/Types/Dashboard/DashboardComponents/DashboardDataSourceChartComponent.ts +27 -0
- package/Types/Dashboard/DashboardComponents/DashboardDataSourceGaugeComponent.ts +32 -0
- package/Types/Dashboard/DashboardComponents/DashboardDataSourceTableComponent.ts +30 -0
- package/Types/Dashboard/DashboardComponents/DashboardDataSourceValueComponent.ts +55 -0
- package/Types/DataSource/DataSourceLimits.ts +62 -0
- package/Types/DataSource/DataSourceQueryConfig.ts +41 -0
- package/Types/DataSource/DataSourceTableResult.ts +38 -0
- package/Types/DataSource/DataSourceType.ts +213 -0
- package/Types/Date.ts +43 -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/Password.ts +64 -0
- package/Types/Permission.ts +69 -175
- package/Types/Probe/ProbeMonitorResponse.ts +6 -0
- package/Types/UserAuthenticationStatus.ts +34 -0
- package/UI/Components/BulkUpdate/BulkUpdateForm.tsx +2 -3
- package/UI/Components/Button/Button.tsx +1 -1
- package/UI/Components/ErrorBoundary.tsx +549 -71
- package/UI/Components/Feed/FeedItem.tsx +9 -2
- package/UI/Components/Icon/Icon.tsx +10 -9
- package/UI/Components/Modal/ConfirmModal.tsx +6 -1
- package/UI/Components/Modal/Modal.tsx +267 -35
- package/UI/Components/Modal/ModalFooter.tsx +6 -1
- 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 +301 -98
- package/UI/Components/MoreMenu/MoreMenuItem.tsx +13 -5
- package/UI/Components/StatusPage/ResourceGrid.tsx +314 -0
- package/UI/Components/StatusPage/ResourceGroupNavigator.tsx +808 -0
- package/UI/Components/StatusPage/ResourceList.tsx +570 -0
- package/UI/Styles/Theme.css +18 -0
- package/UI/Utils/API/RequestOptions.ts +7 -0
- package/UI/Utils/AnalyticsModelAPI/AnalyticsModelAPI.ts +1 -0
- package/UI/Utils/DownloadFile.ts +16 -8
- package/UI/Utils/ErrorSupportBundle.ts +805 -0
- package/UI/Utils/ModelAPI/ModelAPI.ts +20 -8
- package/UI/Utils/Project.ts +4 -0
- package/UI/Utils/StatusPage.ts +8 -2
- package/UI/Utils/TestDataSource.ts +61 -0
- package/UI/Utils/UseDashboardGridDnd.ts +917 -0
- package/UI/esbuild-config.js +45 -0
- package/Utils/Analytics.ts +15 -0
- package/Utils/Dashboard/Components/DashboardDataSourceChartComponent.ts +88 -0
- package/Utils/Dashboard/Components/DashboardDataSourceGaugeComponent.ts +123 -0
- package/Utils/Dashboard/Components/DashboardDataSourceShared.ts +16 -0
- package/Utils/Dashboard/Components/DashboardDataSourceTableComponent.ts +72 -0
- package/Utils/Dashboard/Components/DashboardDataSourceValueComponent.ts +126 -0
- package/Utils/Dashboard/Components/Index.ts +28 -0
- package/Utils/Dashboard/DashboardViewConfig.ts +80 -36
- package/Utils/Dashboard/GridLayout.ts +523 -0
- package/Utils/DataSource/DataSourceQueryText.ts +94 -0
- package/Utils/DataSource/DataSourceValueReducer.ts +156 -0
- package/Utils/Metrics/MetricResourceAttributeUtil.ts +380 -0
- package/Utils/Metrics/RecordingRuleExpression.ts +19 -17
- package/Utils/Monitor/MonitorMetricType.ts +44 -2
- package/Utils/NetworkDiscovery/ScanTargetUtil.ts +8 -6
- package/Utils/StatusPage/GroupHierarchyView.ts +931 -0
- package/Utils/StatusPage/GroupNestingLayout.ts +69 -0
- package/Utils/StatusPage/GroupTree.ts +157 -66
- package/Utils/StatusPage/ResourceExplorer.ts +853 -0
- 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 +73 -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/DataSource.js +774 -0
- package/build/dist/Models/DatabaseModels/DataSource.js.map +1 -0
- 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/Index.js +2 -0
- package/build/dist/Models/DatabaseModels/Index.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/ProjectCallSMSConfig.js +14 -4
- package/build/dist/Models/DatabaseModels/ProjectCallSMSConfig.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/Models/DatabaseModels/User.js +2 -0
- package/build/dist/Models/DatabaseModels/User.js.map +1 -1
- package/build/dist/Models/DatabaseModels/UserOnCallLog.js +2 -0
- package/build/dist/Models/DatabaseModels/UserOnCallLog.js.map +1 -1
- package/build/dist/Models/DatabaseModels/UserOnCallLogTimeline.js +2 -0
- package/build/dist/Models/DatabaseModels/UserOnCallLogTimeline.js.map +1 -1
- package/build/dist/Models/DatabaseModels/UserSession.js +2 -0
- package/build/dist/Models/DatabaseModels/UserSession.js.map +1 -1
- package/build/dist/Models/DatabaseModels/UserTotpAuth.js +6 -4
- package/build/dist/Models/DatabaseModels/UserTotpAuth.js.map +1 -1
- package/build/dist/Models/DatabaseModels/UserWebAuthn.js +5 -3
- package/build/dist/Models/DatabaseModels/UserWebAuthn.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/AIInsightAPI.js +34 -4
- package/build/dist/Server/API/AIInsightAPI.js.map +1 -1
- package/build/dist/Server/API/AIInvestigationAPI.js +134 -11
- package/build/dist/Server/API/AIInvestigationAPI.js.map +1 -1
- package/build/dist/Server/API/DashboardAPI.js +127 -438
- package/build/dist/Server/API/DashboardAPI.js.map +1 -1
- package/build/dist/Server/API/DataSourceAPI.js +173 -0
- package/build/dist/Server/API/DataSourceAPI.js.map +1 -0
- package/build/dist/Server/API/StatusPageAPI.js +40 -12
- package/build/dist/Server/API/StatusPageAPI.js.map +1 -1
- package/build/dist/Server/API/UserAPI.js +94 -2
- package/build/dist/Server/API/UserAPI.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 +61 -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/1786303472848-AddDataSourceTable.js +20 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1786303472848-AddDataSourceTable.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/1786600000000-AddInvestigationAnalysisTldr.js +19 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1786600000000-AddInvestigationAnalysisTldr.js.map +1 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1786700000000-DropProjectCallSMSConfigCredentialUniques.js +64 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1786700000000-DropProjectCallSMSConfigCredentialUniques.js.map +1 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js +16 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js.map +1 -1
- package/build/dist/Server/Infrastructure/Queue.js +27 -0
- package/build/dist/Server/Infrastructure/Queue.js.map +1 -1
- package/build/dist/Server/Infrastructure/QueueWorker.js +13 -0
- package/build/dist/Server/Infrastructure/QueueWorker.js.map +1 -1
- package/build/dist/Server/Middleware/TelemetryIngest.js +8 -3
- package/build/dist/Server/Middleware/TelemetryIngest.js.map +1 -1
- package/build/dist/Server/Services/AIInsightService.js +110 -10
- package/build/dist/Server/Services/AIInsightService.js.map +1 -1
- package/build/dist/Server/Services/AIRunService.js +92 -12
- package/build/dist/Server/Services/AIRunService.js.map +1 -1
- package/build/dist/Server/Services/AIService.js +88 -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/AlertService.js +28 -35
- package/build/dist/Server/Services/AlertService.js.map +1 -1
- package/build/dist/Server/Services/AnalyticsDatabaseService.js +37 -5
- package/build/dist/Server/Services/AnalyticsDatabaseService.js.map +1 -1
- package/build/dist/Server/Services/DataSourceService.js +159 -0
- package/build/dist/Server/Services/DataSourceService.js.map +1 -0
- package/build/dist/Server/Services/DatabaseService.js +21 -2
- package/build/dist/Server/Services/DatabaseService.js.map +1 -1
- package/build/dist/Server/Services/HostService.js +74 -0
- package/build/dist/Server/Services/HostService.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/IncidentService.js +35 -24
- package/build/dist/Server/Services/IncidentService.js.map +1 -1
- package/build/dist/Server/Services/Index.js +2 -0
- package/build/dist/Server/Services/Index.js.map +1 -1
- package/build/dist/Server/Services/LlmLogService.js +22 -1
- package/build/dist/Server/Services/LlmLogService.js.map +1 -1
- package/build/dist/Server/Services/MonitorService.js +45 -0
- package/build/dist/Server/Services/MonitorService.js.map +1 -1
- package/build/dist/Server/Services/MonitorStatusService.js +129 -0
- package/build/dist/Server/Services/MonitorStatusService.js.map +1 -1
- package/build/dist/Server/Services/OpenTelemetryIngestService.js +222 -159
- package/build/dist/Server/Services/OpenTelemetryIngestService.js.map +1 -1
- package/build/dist/Server/Services/RumSessionPinService.js +7 -15
- package/build/dist/Server/Services/RumSessionPinService.js.map +1 -1
- package/build/dist/Server/Services/StatusPageSubscriberService.js +10 -0
- package/build/dist/Server/Services/StatusPageSubscriberService.js.map +1 -1
- package/build/dist/Server/Services/UserService.js +241 -10
- package/build/dist/Server/Services/UserService.js.map +1 -1
- package/build/dist/Server/Types/Database/Permissions/BasePermission.js +6 -0
- package/build/dist/Server/Types/Database/Permissions/BasePermission.js.map +1 -1
- package/build/dist/Server/Types/Database/Permissions/DeletePermission.js +1 -0
- package/build/dist/Server/Types/Database/Permissions/DeletePermission.js.map +1 -1
- package/build/dist/Server/Types/Database/Permissions/ReadPermission.js +2 -0
- package/build/dist/Server/Types/Database/Permissions/ReadPermission.js.map +1 -1
- package/build/dist/Server/Types/Database/Permissions/TenantPermission.js +68 -12
- package/build/dist/Server/Types/Database/Permissions/TenantPermission.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 +222 -34
- package/build/dist/Server/Utils/AI/SRE/AIInvestigationEngine.js.map +1 -1
- package/build/dist/Server/Utils/AI/SRE/AlertInvestigationRunner.js +22 -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 +22 -19
- package/build/dist/Server/Utils/AI/SRE/IncidentInvestigationRunner.js.map +1 -1
- package/build/dist/Server/Utils/AI/SRE/Insights/Detectors/ExceptionIdentity.js +97 -0
- package/build/dist/Server/Utils/AI/SRE/Insights/Detectors/ExceptionIdentity.js.map +1 -0
- package/build/dist/Server/Utils/AI/SRE/Insights/Detectors/ExceptionSpikeDetector.js +17 -15
- package/build/dist/Server/Utils/AI/SRE/Insights/Detectors/ExceptionSpikeDetector.js.map +1 -1
- package/build/dist/Server/Utils/AI/SRE/Insights/Detectors/NewExceptionDetector.js +178 -54
- package/build/dist/Server/Utils/AI/SRE/Insights/Detectors/NewExceptionDetector.js.map +1 -1
- package/build/dist/Server/Utils/AI/SRE/Insights/InsightStore.js +65 -16
- package/build/dist/Server/Utils/AI/SRE/Insights/InsightStore.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/InvestigationTldr.js +303 -0
- package/build/dist/Server/Utils/AI/SRE/InvestigationTldr.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/AnalyticsDatabase/InsertDedupContext.js.map +1 -1
- package/build/dist/Server/Utils/Dashboard/PublicDashboardResourceListPolicy.js +1261 -0
- package/build/dist/Server/Utils/Dashboard/PublicDashboardResourceListPolicy.js.map +1 -0
- package/build/dist/Server/Utils/Dashboard/PublicDashboardViewConfig.js +47 -0
- package/build/dist/Server/Utils/Dashboard/PublicDashboardViewConfig.js.map +1 -0
- package/build/dist/Server/Utils/DataSource/ConnectorRegistry.js +42 -0
- package/build/dist/Server/Utils/DataSource/ConnectorRegistry.js.map +1 -0
- package/build/dist/Server/Utils/DataSource/Connectors/ClickHouseConnector.js +187 -0
- package/build/dist/Server/Utils/DataSource/Connectors/ClickHouseConnector.js.map +1 -0
- package/build/dist/Server/Utils/DataSource/Connectors/ElasticsearchConnector.js +493 -0
- package/build/dist/Server/Utils/DataSource/Connectors/ElasticsearchConnector.js.map +1 -0
- package/build/dist/Server/Utils/DataSource/Connectors/LokiConnector.js +399 -0
- package/build/dist/Server/Utils/DataSource/Connectors/LokiConnector.js.map +1 -0
- package/build/dist/Server/Utils/DataSource/Connectors/MySqlConnector.js +195 -0
- package/build/dist/Server/Utils/DataSource/Connectors/MySqlConnector.js.map +1 -0
- package/build/dist/Server/Utils/DataSource/Connectors/PostgresConnector.js +149 -0
- package/build/dist/Server/Utils/DataSource/Connectors/PostgresConnector.js.map +1 -0
- package/build/dist/Server/Utils/DataSource/Connectors/PrometheusConnector.js +362 -0
- package/build/dist/Server/Utils/DataSource/Connectors/PrometheusConnector.js.map +1 -0
- package/build/dist/Server/Utils/DataSource/Connectors/RestApiConnector.js +342 -0
- package/build/dist/Server/Utils/DataSource/Connectors/RestApiConnector.js.map +1 -0
- package/build/dist/Server/Utils/DataSource/Connectors/SqlServerConnector.js +186 -0
- package/build/dist/Server/Utils/DataSource/Connectors/SqlServerConnector.js.map +1 -0
- package/build/dist/Server/Utils/DataSource/EgressGuard.js +305 -0
- package/build/dist/Server/Utils/DataSource/EgressGuard.js.map +1 -0
- package/build/dist/Server/Utils/DataSource/HttpFetch.js +134 -0
- package/build/dist/Server/Utils/DataSource/HttpFetch.js.map +1 -0
- package/build/dist/Server/Utils/DataSource/SqlQueryGuard.js +331 -0
- package/build/dist/Server/Utils/DataSource/SqlQueryGuard.js.map +1 -0
- package/build/dist/Server/Utils/DataSource/SqlResultShaper.js +280 -0
- package/build/dist/Server/Utils/DataSource/SqlResultShaper.js.map +1 -0
- package/build/dist/Server/Utils/DataSource/Types.js +2 -0
- package/build/dist/Server/Utils/DataSource/Types.js.map +1 -0
- package/build/dist/Server/Utils/Database/PostgresErrorTranslator.js +103 -9
- package/build/dist/Server/Utils/Database/PostgresErrorTranslator.js.map +1 -1
- package/build/dist/Server/Utils/InProcessMemo.js +113 -0
- package/build/dist/Server/Utils/InProcessMemo.js.map +1 -0
- package/build/dist/Server/Utils/InstanceHealth/TelemetryIngestion.js +406 -0
- package/build/dist/Server/Utils/InstanceHealth/TelemetryIngestion.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 +67 -0
- package/build/dist/Server/Utils/Monitor/MonitorMetricUtil.js.map +1 -1
- package/build/dist/Server/Utils/Monitor/MonitorResource.js +14 -0
- package/build/dist/Server/Utils/Monitor/MonitorResource.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/Server/Utils/Response.js +9 -1
- package/build/dist/Server/Utils/Response.js.map +1 -1
- package/build/dist/Server/Utils/SSRFProtection.js +192 -0
- package/build/dist/Server/Utils/SSRFProtection.js.map +1 -0
- package/build/dist/Server/Utils/StartServer.js +7 -0
- package/build/dist/Server/Utils/StartServer.js.map +1 -1
- package/build/dist/Server/Utils/StatusPageSubscriberWebhook.js +24 -0
- package/build/dist/Server/Utils/StatusPageSubscriberWebhook.js.map +1 -1
- package/build/dist/Server/Utils/Telemetry/CaptureSpan.js +25 -11
- package/build/dist/Server/Utils/Telemetry/CaptureSpan.js.map +1 -1
- package/build/dist/Server/Utils/Telemetry/IngestionTimestamp.js +32 -0
- package/build/dist/Server/Utils/Telemetry/IngestionTimestamp.js.map +1 -0
- package/build/dist/Server/Utils/Telemetry/ResourceHeartbeat.js +92 -5
- package/build/dist/Server/Utils/Telemetry/ResourceHeartbeat.js.map +1 -1
- package/build/dist/Server/Utils/Telemetry/Telemetry.js +92 -6
- package/build/dist/Server/Utils/Telemetry/Telemetry.js.map +1 -1
- package/build/dist/Server/Utils/Telemetry.js +93 -6
- package/build/dist/Server/Utils/Telemetry.js.map +1 -1
- package/build/dist/Server/Utils/VendorAssets.js +121 -0
- package/build/dist/Server/Utils/VendorAssets.js.map +1 -0
- package/build/dist/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeams.js +25 -4
- package/build/dist/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeams.js.map +1 -1
- package/build/dist/Server/Utils/Workspace/Slack/Slack.js +5 -0
- package/build/dist/Server/Utils/Workspace/Slack/Slack.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/Analytics/RevenueEvent.js +26 -0
- package/build/dist/Types/Analytics/RevenueEvent.js.map +1 -0
- package/build/dist/Types/Dashboard/DashboardComponentType.js +27 -0
- package/build/dist/Types/Dashboard/DashboardComponentType.js.map +1 -1
- package/build/dist/Types/Dashboard/DashboardComponents/DashboardDataSourceChartComponent.js +2 -0
- package/build/dist/Types/Dashboard/DashboardComponents/DashboardDataSourceChartComponent.js.map +1 -0
- package/build/dist/Types/Dashboard/DashboardComponents/DashboardDataSourceGaugeComponent.js +2 -0
- package/build/dist/Types/Dashboard/DashboardComponents/DashboardDataSourceGaugeComponent.js.map +1 -0
- package/build/dist/Types/Dashboard/DashboardComponents/DashboardDataSourceTableComponent.js +2 -0
- package/build/dist/Types/Dashboard/DashboardComponents/DashboardDataSourceTableComponent.js.map +1 -0
- package/build/dist/Types/Dashboard/DashboardComponents/DashboardDataSourceValueComponent.js +16 -0
- package/build/dist/Types/Dashboard/DashboardComponents/DashboardDataSourceValueComponent.js.map +1 -0
- package/build/dist/Types/DataSource/DataSourceLimits.js +47 -0
- package/build/dist/Types/DataSource/DataSourceLimits.js.map +1 -0
- package/build/dist/Types/DataSource/DataSourceQueryConfig.js +10 -0
- package/build/dist/Types/DataSource/DataSourceQueryConfig.js.map +1 -0
- package/build/dist/Types/DataSource/DataSourceTableResult.js +14 -0
- package/build/dist/Types/DataSource/DataSourceTableResult.js.map +1 -0
- package/build/dist/Types/DataSource/DataSourceType.js +183 -0
- package/build/dist/Types/DataSource/DataSourceType.js.map +1 -0
- package/build/dist/Types/Date.js +38 -3
- package/build/dist/Types/Date.js.map +1 -1
- 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/Password.js +56 -0
- package/build/dist/Types/Password.js.map +1 -0
- package/build/dist/Types/Permission.js +65 -155
- package/build/dist/Types/Permission.js.map +1 -1
- package/build/dist/Types/UserAuthenticationStatus.js +2 -0
- package/build/dist/Types/UserAuthenticationStatus.js.map +1 -0
- 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 +427 -52
- 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/Icon/Icon.js +10 -4
- package/build/dist/UI/Components/Icon/Icon.js.map +1 -1
- package/build/dist/UI/Components/Modal/ConfirmModal.js +1 -1
- package/build/dist/UI/Components/Modal/ConfirmModal.js.map +1 -1
- package/build/dist/UI/Components/Modal/Modal.js +178 -19
- package/build/dist/UI/Components/Modal/Modal.js.map +1 -1
- package/build/dist/UI/Components/Modal/ModalFooter.js +3 -1
- package/build/dist/UI/Components/Modal/ModalFooter.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 +197 -66
- package/build/dist/UI/Components/MoreMenu/MoreMenu.js.map +1 -1
- package/build/dist/UI/Components/MoreMenu/MoreMenuItem.js +7 -1
- package/build/dist/UI/Components/MoreMenu/MoreMenuItem.js.map +1 -1
- package/build/dist/UI/Components/StatusPage/ResourceGrid.js +119 -0
- package/build/dist/UI/Components/StatusPage/ResourceGrid.js.map +1 -0
- package/build/dist/UI/Components/StatusPage/ResourceGroupNavigator.js +354 -0
- package/build/dist/UI/Components/StatusPage/ResourceGroupNavigator.js.map +1 -0
- package/build/dist/UI/Components/StatusPage/ResourceList.js +203 -0
- package/build/dist/UI/Components/StatusPage/ResourceList.js.map +1 -0
- package/build/dist/UI/Utils/AnalyticsModelAPI/AnalyticsModelAPI.js +1 -6
- package/build/dist/UI/Utils/AnalyticsModelAPI/AnalyticsModelAPI.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 +20 -15
- 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/TestDataSource.js +39 -0
- package/build/dist/UI/Utils/TestDataSource.js.map +1 -0
- package/build/dist/UI/Utils/UseDashboardGridDnd.js +551 -0
- package/build/dist/UI/Utils/UseDashboardGridDnd.js.map +1 -0
- package/build/dist/Utils/Analytics.js +4 -0
- package/build/dist/Utils/Analytics.js.map +1 -1
- package/build/dist/Utils/Dashboard/Components/DashboardDataSourceChartComponent.js +79 -0
- package/build/dist/Utils/Dashboard/Components/DashboardDataSourceChartComponent.js.map +1 -0
- package/build/dist/Utils/Dashboard/Components/DashboardDataSourceGaugeComponent.js +111 -0
- package/build/dist/Utils/Dashboard/Components/DashboardDataSourceGaugeComponent.js.map +1 -0
- package/build/dist/Utils/Dashboard/Components/DashboardDataSourceShared.js +15 -0
- package/build/dist/Utils/Dashboard/Components/DashboardDataSourceShared.js.map +1 -0
- package/build/dist/Utils/Dashboard/Components/DashboardDataSourceTableComponent.js +62 -0
- package/build/dist/Utils/Dashboard/Components/DashboardDataSourceTableComponent.js.map +1 -0
- package/build/dist/Utils/Dashboard/Components/DashboardDataSourceValueComponent.js +112 -0
- package/build/dist/Utils/Dashboard/Components/DashboardDataSourceValueComponent.js.map +1 -0
- package/build/dist/Utils/Dashboard/Components/Index.js +16 -0
- package/build/dist/Utils/Dashboard/Components/Index.js.map +1 -1
- 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/DataSource/DataSourceQueryText.js +64 -0
- package/build/dist/Utils/DataSource/DataSourceQueryText.js.map +1 -0
- package/build/dist/Utils/DataSource/DataSourceValueReducer.js +124 -0
- package/build/dist/Utils/DataSource/DataSourceValueReducer.js.map +1 -0
- package/build/dist/Utils/Metrics/MetricResourceAttributeUtil.js +281 -0
- package/build/dist/Utils/Metrics/MetricResourceAttributeUtil.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/NetworkDiscovery/ScanTargetUtil.js +8 -6
- package/build/dist/Utils/NetworkDiscovery/ScanTargetUtil.js.map +1 -1
- package/build/dist/Utils/StatusPage/GroupHierarchyView.js +560 -0
- package/build/dist/Utils/StatusPage/GroupHierarchyView.js.map +1 -0
- 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/ResourceExplorer.js +519 -0
- package/build/dist/Utils/StatusPage/ResourceExplorer.js.map +1 -0
- 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/jest.config.json +1 -0
- package/package.json +5 -1
- package/tsconfig.json +12 -10
|
@@ -0,0 +1,1695 @@
|
|
|
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
|
+
/*
|
|
78
|
+
* The feed owns the "will this draw any steps?" predicate, because several
|
|
79
|
+
* event types only close a step an earlier event opened. The panel asks it
|
|
80
|
+
* instead of counting raw events, so the mock has to answer too — and
|
|
81
|
+
* hasRenderableActivityMock lets a test make the two disagree, which is the
|
|
82
|
+
* case that used to render an empty framed box.
|
|
83
|
+
*/
|
|
84
|
+
const hasRenderableActivityMock: MockFunction = getJestMockFunction();
|
|
85
|
+
|
|
86
|
+
jest.mock(
|
|
87
|
+
"../../../../App/FeatureSet/Dashboard/src/Components/AIChat/ChatActivityFeed",
|
|
88
|
+
() => {
|
|
89
|
+
return {
|
|
90
|
+
__esModule: true,
|
|
91
|
+
default: (props: ActivityFeedProps): React.ReactElement => {
|
|
92
|
+
activityFeedMock(props);
|
|
93
|
+
return React.createElement("div", {
|
|
94
|
+
"data-testid": "investigation-activity",
|
|
95
|
+
});
|
|
96
|
+
},
|
|
97
|
+
hasRenderableActivity: (events: Array<AIRunEvent>): boolean => {
|
|
98
|
+
return hasRenderableActivityMock(events) as boolean;
|
|
99
|
+
},
|
|
100
|
+
};
|
|
101
|
+
},
|
|
102
|
+
);
|
|
103
|
+
|
|
104
|
+
import InvestigationPanel, {
|
|
105
|
+
InvestigationSubjectType,
|
|
106
|
+
} from "../../../../App/FeatureSet/Dashboard/src/Components/AI/InvestigationPanel";
|
|
107
|
+
import AIRunEvent from "../../../Models/DatabaseModels/AIRunEvent";
|
|
108
|
+
import HTTPErrorResponse from "../../../Types/API/HTTPErrorResponse";
|
|
109
|
+
import AIRunCodeFixRecommendation from "../../../Types/AI/AIRunCodeFixRecommendation";
|
|
110
|
+
import AIRunEventType from "../../../Types/AI/AIRunEventType";
|
|
111
|
+
import AIRunStatus from "../../../Types/AI/AIRunStatus";
|
|
112
|
+
import { JSONArray, JSONObject } from "../../../Types/JSON";
|
|
113
|
+
import ObjectID from "../../../Types/ObjectID";
|
|
114
|
+
|
|
115
|
+
interface MarkdownViewerProps {
|
|
116
|
+
text: string;
|
|
117
|
+
safeMode?: boolean | undefined;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
interface ActivityFeedProps {
|
|
121
|
+
events: Array<AIRunEvent>;
|
|
122
|
+
title?: string | undefined;
|
|
123
|
+
showLiveIndicator?: boolean | undefined;
|
|
124
|
+
maxVisibleSteps?: number | undefined;
|
|
125
|
+
hideChrome?: boolean | undefined;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
interface InvestigationPayloadOptions {
|
|
129
|
+
status: AIRunStatus;
|
|
130
|
+
runId?: string | undefined;
|
|
131
|
+
events?: JSONArray | undefined;
|
|
132
|
+
analysisMarkdown?: string | null | undefined;
|
|
133
|
+
analysisTldr?: string | null | undefined;
|
|
134
|
+
isAnalysisPending?: boolean | undefined;
|
|
135
|
+
errorMessage?: string | null | undefined;
|
|
136
|
+
toolCallCount?: number | undefined;
|
|
137
|
+
totalTokens?: number | undefined;
|
|
138
|
+
humanVerdict?: string | null | undefined;
|
|
139
|
+
codeFixRecommendation?: AIRunCodeFixRecommendation | undefined;
|
|
140
|
+
completedAt?: string | undefined;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
interface ApiResponse {
|
|
144
|
+
data: JSONObject;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
interface PostRequest {
|
|
148
|
+
url: { toString: () => string };
|
|
149
|
+
data: JSONObject;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
interface Deferred<T> {
|
|
153
|
+
promise: Promise<T>;
|
|
154
|
+
resolve: (value: T) => void;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
const POLL_INTERVAL_MS: number = 2500;
|
|
158
|
+
const SETTLED_POLL_INTERVAL_MS: number = 30_000;
|
|
159
|
+
const RECOMMENDATION_SETTLEMENT_MAX_AGE_MS: number = 3 * 60 * 1000;
|
|
160
|
+
const MAX_RECOMMENDATION_POLL_RESPONSES: number = Math.ceil(
|
|
161
|
+
RECOMMENDATION_SETTLEMENT_MAX_AGE_MS / POLL_INTERVAL_MS,
|
|
162
|
+
);
|
|
163
|
+
const COMPLETED_AT: string = "2026-08-07T12:00:00.000Z";
|
|
164
|
+
const OLD_COMPLETED_AT: string = "2026-08-07T11:56:59.000Z";
|
|
165
|
+
const RUN_ID: string = "11111111-1111-4111-8111-111111111111";
|
|
166
|
+
const FIX_RUN_ID: string = "22222222-2222-4222-8222-222222222222";
|
|
167
|
+
const NEXT_RUN_ID: string = "aaaaaaaa-aaaa-4aaa-8aaa-aaaaaaaaaaaa";
|
|
168
|
+
const INCIDENT_ID: ObjectID = new ObjectID(
|
|
169
|
+
"33333333-3333-4333-8333-333333333333",
|
|
170
|
+
);
|
|
171
|
+
const ALERT_ID: ObjectID = new ObjectID("44444444-4444-4444-8444-444444444444");
|
|
172
|
+
const EVENT_ID: string = "55555555-5555-4555-8555-555555555555";
|
|
173
|
+
const ANALYSIS: string =
|
|
174
|
+
"## Root cause\n\nThe database connection pool was exhausted.";
|
|
175
|
+
const TLDR: string =
|
|
176
|
+
"Checkout is returning 500s because the database connection pool is exhausted.";
|
|
177
|
+
|
|
178
|
+
const activityEvent: JSONObject = {
|
|
179
|
+
_id: EVENT_ID,
|
|
180
|
+
sequence: 1,
|
|
181
|
+
eventType: AIRunEventType.ToolCallStarted,
|
|
182
|
+
toolName: "search_logs",
|
|
183
|
+
createdAt: new Date("2026-08-07T10:00:00.000Z"),
|
|
184
|
+
};
|
|
185
|
+
|
|
186
|
+
function investigationPayload(
|
|
187
|
+
options: InvestigationPayloadOptions,
|
|
188
|
+
): JSONObject {
|
|
189
|
+
const run: JSONObject = {
|
|
190
|
+
_id: options.runId || RUN_ID,
|
|
191
|
+
status: options.status,
|
|
192
|
+
errorMessage: options.errorMessage ?? null,
|
|
193
|
+
toolCallCount: options.toolCallCount ?? 0,
|
|
194
|
+
totalTokens: options.totalTokens ?? 0,
|
|
195
|
+
humanVerdict: options.humanVerdict ?? null,
|
|
196
|
+
};
|
|
197
|
+
|
|
198
|
+
if (options.codeFixRecommendation !== undefined) {
|
|
199
|
+
run["codeFixRecommendation"] = options.codeFixRecommendation;
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
if (options.completedAt !== undefined) {
|
|
203
|
+
run["completedAt"] = options.completedAt;
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
return {
|
|
207
|
+
run,
|
|
208
|
+
events: options.events || [],
|
|
209
|
+
analysisMarkdown: options.analysisMarkdown ?? null,
|
|
210
|
+
analysisTldr: options.analysisTldr ?? null,
|
|
211
|
+
isAnalysisPending: options.isAnalysisPending === true,
|
|
212
|
+
};
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
function successfulResponse(payload: JSONObject): ApiResponse {
|
|
216
|
+
return { data: payload };
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
function noInvestigationResponse(): ApiResponse {
|
|
220
|
+
return {
|
|
221
|
+
data: {
|
|
222
|
+
run: null,
|
|
223
|
+
events: [],
|
|
224
|
+
analysisMarkdown: null,
|
|
225
|
+
analysisTldr: null,
|
|
226
|
+
isAnalysisPending: false,
|
|
227
|
+
},
|
|
228
|
+
};
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
function completedResponse(
|
|
232
|
+
overrides: Partial<InvestigationPayloadOptions> = {},
|
|
233
|
+
): ApiResponse {
|
|
234
|
+
return successfulResponse(
|
|
235
|
+
investigationPayload({
|
|
236
|
+
status: AIRunStatus.Completed,
|
|
237
|
+
analysisMarkdown: ANALYSIS,
|
|
238
|
+
events: [activityEvent],
|
|
239
|
+
toolCallCount: 2,
|
|
240
|
+
totalTokens: 1234,
|
|
241
|
+
codeFixRecommendation: AIRunCodeFixRecommendation.Recommended,
|
|
242
|
+
completedAt: COMPLETED_AT,
|
|
243
|
+
...overrides,
|
|
244
|
+
}),
|
|
245
|
+
);
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
function createDeferred<T>(): Deferred<T> {
|
|
249
|
+
let resolvePromise: ((value: T) => void) | undefined;
|
|
250
|
+
const promise: Promise<T> = new Promise<T>((resolve: (value: T) => void) => {
|
|
251
|
+
resolvePromise = resolve;
|
|
252
|
+
});
|
|
253
|
+
|
|
254
|
+
return {
|
|
255
|
+
promise,
|
|
256
|
+
resolve: (value: T): void => {
|
|
257
|
+
resolvePromise!(value);
|
|
258
|
+
},
|
|
259
|
+
};
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
function renderPanel(data?: {
|
|
263
|
+
subjectType?: InvestigationSubjectType | undefined;
|
|
264
|
+
subjectId?: ObjectID | undefined;
|
|
265
|
+
onAnalysisAvailable?: (() => void) | undefined;
|
|
266
|
+
onStatusChange?: ((status: AIRunStatus | null) => void) | undefined;
|
|
267
|
+
}): ReturnType<typeof render> {
|
|
268
|
+
return render(
|
|
269
|
+
<InvestigationPanel
|
|
270
|
+
subjectType={data?.subjectType || "incident"}
|
|
271
|
+
subjectId={data?.subjectId || INCIDENT_ID}
|
|
272
|
+
onAnalysisAvailable={data?.onAnalysisAvailable}
|
|
273
|
+
onStatusChange={data?.onStatusChange}
|
|
274
|
+
/>,
|
|
275
|
+
);
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
/* Let the awaits inside fetchData and the following React effects settle. */
|
|
279
|
+
async function flush(): Promise<void> {
|
|
280
|
+
await act(async (): Promise<void> => {
|
|
281
|
+
await Promise.resolve();
|
|
282
|
+
await Promise.resolve();
|
|
283
|
+
await Promise.resolve();
|
|
284
|
+
await Promise.resolve();
|
|
285
|
+
});
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
async function tick(milliseconds: number): Promise<void> {
|
|
289
|
+
await act(async (): Promise<void> => {
|
|
290
|
+
jest.advanceTimersByTime(milliseconds);
|
|
291
|
+
await Promise.resolve();
|
|
292
|
+
await Promise.resolve();
|
|
293
|
+
await Promise.resolve();
|
|
294
|
+
await Promise.resolve();
|
|
295
|
+
});
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
async function advanceFastPolls(count: number = 1): Promise<void> {
|
|
299
|
+
for (let index: number = 0; index < count; index++) {
|
|
300
|
+
await tick(POLL_INTERVAL_MS);
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
async function resolveDeferred<T>(
|
|
305
|
+
deferred: Deferred<T>,
|
|
306
|
+
value: T,
|
|
307
|
+
): Promise<void> {
|
|
308
|
+
await act(async (): Promise<void> => {
|
|
309
|
+
deferred.resolve(value);
|
|
310
|
+
await Promise.resolve();
|
|
311
|
+
await Promise.resolve();
|
|
312
|
+
await Promise.resolve();
|
|
313
|
+
await Promise.resolve();
|
|
314
|
+
});
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
function postRequestAt(index: number): PostRequest {
|
|
318
|
+
return postMock.mock.calls[index]![0] as PostRequest;
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
function requestPath(index: number): string {
|
|
322
|
+
return postRequestAt(index).url.toString();
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
function fixButton(): HTMLElement | null {
|
|
326
|
+
return screen.queryByRole("button", {
|
|
327
|
+
name: "Open Fix PR from this analysis",
|
|
328
|
+
});
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
function lastActivityProps(): ActivityFeedProps {
|
|
332
|
+
const calls: Array<Array<ActivityFeedProps>> = activityFeedMock.mock
|
|
333
|
+
.calls as Array<Array<ActivityFeedProps>>;
|
|
334
|
+
return calls[calls.length - 1]![0]!;
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
beforeEach(() => {
|
|
338
|
+
jest.useFakeTimers();
|
|
339
|
+
jest.setSystemTime(new Date(COMPLETED_AT));
|
|
340
|
+
getCommonHeadersMock.mockReturnValue({});
|
|
341
|
+
/*
|
|
342
|
+
* Default to the real component's behaviour for the ordinary event shapes
|
|
343
|
+
* these tests use: every step-opening event draws a step.
|
|
344
|
+
*/
|
|
345
|
+
hasRenderableActivityMock.mockImplementation(
|
|
346
|
+
(events: Array<AIRunEvent>): boolean => {
|
|
347
|
+
return events.length > 0;
|
|
348
|
+
},
|
|
349
|
+
);
|
|
350
|
+
getFriendlyMessageMock.mockImplementation((error: unknown): string => {
|
|
351
|
+
if (
|
|
352
|
+
typeof error === "object" &&
|
|
353
|
+
error !== null &&
|
|
354
|
+
"message" in error &&
|
|
355
|
+
typeof (error as { message?: unknown }).message === "string"
|
|
356
|
+
) {
|
|
357
|
+
return (error as { message: string }).message;
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
return "Request failed";
|
|
361
|
+
});
|
|
362
|
+
});
|
|
363
|
+
|
|
364
|
+
afterEach(() => {
|
|
365
|
+
cleanup();
|
|
366
|
+
jest.clearAllTimers();
|
|
367
|
+
jest.useRealTimers();
|
|
368
|
+
postMock.mockReset();
|
|
369
|
+
getFriendlyMessageMock.mockReset();
|
|
370
|
+
getCommonHeadersMock.mockReset();
|
|
371
|
+
markdownViewerMock.mockReset();
|
|
372
|
+
activityFeedMock.mockReset();
|
|
373
|
+
hasRenderableActivityMock.mockReset();
|
|
374
|
+
});
|
|
375
|
+
|
|
376
|
+
describe("InvestigationPanel report lifecycle", () => {
|
|
377
|
+
test("renders nothing and briefly discovers a run when none exists yet", async () => {
|
|
378
|
+
postMock.mockResolvedValue(noInvestigationResponse() as never);
|
|
379
|
+
|
|
380
|
+
const { container } = renderPanel();
|
|
381
|
+
await flush();
|
|
382
|
+
|
|
383
|
+
expect(container).toBeEmptyDOMElement();
|
|
384
|
+
expect(jest.getTimerCount()).toBe(1);
|
|
385
|
+
|
|
386
|
+
await advanceFastPolls(4);
|
|
387
|
+
expect(postMock).toHaveBeenCalledTimes(5);
|
|
388
|
+
expect(jest.getTimerCount()).toBe(1);
|
|
389
|
+
});
|
|
390
|
+
|
|
391
|
+
test("shows live activity and requests the incident endpoint", async () => {
|
|
392
|
+
postMock.mockResolvedValue(
|
|
393
|
+
successfulResponse(
|
|
394
|
+
investigationPayload({
|
|
395
|
+
status: AIRunStatus.Running,
|
|
396
|
+
events: [activityEvent],
|
|
397
|
+
}),
|
|
398
|
+
) as never,
|
|
399
|
+
);
|
|
400
|
+
|
|
401
|
+
renderPanel();
|
|
402
|
+
await flush();
|
|
403
|
+
|
|
404
|
+
expect(screen.getByText("Investigating…")).toBeInTheDocument();
|
|
405
|
+
expect(screen.getByTestId("investigation-activity")).toBeInTheDocument();
|
|
406
|
+
expect(lastActivityProps().events).toHaveLength(1);
|
|
407
|
+
/*
|
|
408
|
+
* The panel frames the feed itself, so the feed drops its own bubble and
|
|
409
|
+
* heading — the "Investigating…" wording must appear exactly once.
|
|
410
|
+
*/
|
|
411
|
+
expect(lastActivityProps().hideChrome).toBe(true);
|
|
412
|
+
expect(screen.getAllByText("Investigating…")).toHaveLength(1);
|
|
413
|
+
expect(
|
|
414
|
+
document.querySelector('[class~="motion-safe:animate-ping"]'),
|
|
415
|
+
).not.toBeNull();
|
|
416
|
+
|
|
417
|
+
const request: PostRequest = postRequestAt(0);
|
|
418
|
+
expect(request.url.toString()).toContain("/ai-investigation/incident");
|
|
419
|
+
expect(request.data).toEqual({ incidentId: INCIDENT_ID.toString() });
|
|
420
|
+
expect(getCommonHeadersMock).toHaveBeenCalledTimes(1);
|
|
421
|
+
});
|
|
422
|
+
|
|
423
|
+
test("renders the completed report safely and demotes activity to a disclosure", async () => {
|
|
424
|
+
const onAnalysisAvailable: MockFunction = getJestMockFunction();
|
|
425
|
+
postMock.mockResolvedValue(completedResponse() as never);
|
|
426
|
+
|
|
427
|
+
renderPanel({ onAnalysisAvailable });
|
|
428
|
+
await flush();
|
|
429
|
+
|
|
430
|
+
expect(screen.getByText("Investigation complete")).toBeInTheDocument();
|
|
431
|
+
expect(screen.getByLabelText("Investigation report")).toBeInTheDocument();
|
|
432
|
+
expect(screen.getByTestId("investigation-markdown")).toHaveTextContent(
|
|
433
|
+
"The database connection pool was exhausted.",
|
|
434
|
+
);
|
|
435
|
+
expect(markdownViewerMock).toHaveBeenCalledWith({
|
|
436
|
+
text: ANALYSIS,
|
|
437
|
+
safeMode: true,
|
|
438
|
+
});
|
|
439
|
+
expect(screen.getByText("Investigation activity")).toBeInTheDocument();
|
|
440
|
+
expect(lastActivityProps()).toEqual(
|
|
441
|
+
expect.objectContaining({
|
|
442
|
+
hideChrome: true,
|
|
443
|
+
showLiveIndicator: false,
|
|
444
|
+
maxVisibleSteps: 10,
|
|
445
|
+
}),
|
|
446
|
+
);
|
|
447
|
+
|
|
448
|
+
const usage: HTMLElement = screen.getByLabelText("Investigation usage");
|
|
449
|
+
expect(usage).toHaveTextContent("2 telemetry queries");
|
|
450
|
+
expect(usage).toHaveTextContent("1,234 tokens");
|
|
451
|
+
expect(usage).toHaveTextContent(
|
|
452
|
+
"Read-only — nothing in your systems was changed",
|
|
453
|
+
);
|
|
454
|
+
expect(fixButton()).toBeEnabled();
|
|
455
|
+
expect(screen.getByRole("button", { name: "Confirmed" })).toBeEnabled();
|
|
456
|
+
expect(screen.getByRole("button", { name: "Rejected" })).toBeEnabled();
|
|
457
|
+
expect(onAnalysisAvailable).toHaveBeenCalledTimes(1);
|
|
458
|
+
expect(jest.getTimerCount()).toBe(1);
|
|
459
|
+
});
|
|
460
|
+
|
|
461
|
+
test("keeps polling across Completed until the same run report appears", async () => {
|
|
462
|
+
const onAnalysisAvailable: MockFunction = getJestMockFunction();
|
|
463
|
+
postMock
|
|
464
|
+
.mockResolvedValueOnce(
|
|
465
|
+
completedResponse({
|
|
466
|
+
analysisMarkdown: null,
|
|
467
|
+
isAnalysisPending: true,
|
|
468
|
+
}) as never,
|
|
469
|
+
)
|
|
470
|
+
.mockResolvedValueOnce(
|
|
471
|
+
completedResponse({
|
|
472
|
+
analysisMarkdown: ANALYSIS,
|
|
473
|
+
isAnalysisPending: false,
|
|
474
|
+
}) as never,
|
|
475
|
+
);
|
|
476
|
+
|
|
477
|
+
renderPanel({ onAnalysisAvailable });
|
|
478
|
+
await flush();
|
|
479
|
+
|
|
480
|
+
expect(screen.getByText("Preparing investigation report…")).toBeVisible();
|
|
481
|
+
expect(screen.getByText("Preparing the final report")).toBeVisible();
|
|
482
|
+
expect(screen.queryByTestId("investigation-markdown")).toBeNull();
|
|
483
|
+
expect(onAnalysisAvailable).not.toHaveBeenCalled();
|
|
484
|
+
|
|
485
|
+
/* Status, run id, events and recommendation remain identical. */
|
|
486
|
+
await tick(POLL_INTERVAL_MS);
|
|
487
|
+
|
|
488
|
+
expect(screen.getByText("Investigation complete")).toBeVisible();
|
|
489
|
+
expect(screen.getByTestId("investigation-markdown")).toHaveTextContent(
|
|
490
|
+
"The database connection pool was exhausted.",
|
|
491
|
+
);
|
|
492
|
+
expect(postMock).toHaveBeenCalledTimes(2);
|
|
493
|
+
expect(onAnalysisAvailable).toHaveBeenCalledTimes(1);
|
|
494
|
+
|
|
495
|
+
await tick(POLL_INTERVAL_MS * 4);
|
|
496
|
+
expect(postMock).toHaveBeenCalledTimes(2);
|
|
497
|
+
});
|
|
498
|
+
|
|
499
|
+
test("does not overlap a slow active-run poll", async () => {
|
|
500
|
+
const slowPoll: Deferred<ApiResponse> = createDeferred<ApiResponse>();
|
|
501
|
+
postMock
|
|
502
|
+
.mockResolvedValueOnce(
|
|
503
|
+
successfulResponse(
|
|
504
|
+
investigationPayload({ status: AIRunStatus.Running }),
|
|
505
|
+
) as never,
|
|
506
|
+
)
|
|
507
|
+
.mockReturnValueOnce(slowPoll.promise as never)
|
|
508
|
+
.mockResolvedValue(completedResponse() as never);
|
|
509
|
+
|
|
510
|
+
const view: ReturnType<typeof render> = renderPanel();
|
|
511
|
+
await flush();
|
|
512
|
+
await tick(POLL_INTERVAL_MS);
|
|
513
|
+
|
|
514
|
+
expect(postMock).toHaveBeenCalledTimes(2);
|
|
515
|
+
expect(screen.getByText("Investigating…")).toBeVisible();
|
|
516
|
+
|
|
517
|
+
view.rerender(
|
|
518
|
+
<InvestigationPanel
|
|
519
|
+
subjectType="incident"
|
|
520
|
+
subjectId={new ObjectID(INCIDENT_ID.toString())}
|
|
521
|
+
/>,
|
|
522
|
+
);
|
|
523
|
+
await flush();
|
|
524
|
+
await tick(POLL_INTERVAL_MS * 3);
|
|
525
|
+
expect(postMock).toHaveBeenCalledTimes(2);
|
|
526
|
+
|
|
527
|
+
await resolveDeferred(slowPoll, completedResponse());
|
|
528
|
+
expect(screen.getByText("Investigation complete")).toBeVisible();
|
|
529
|
+
expect(postMock).toHaveBeenCalledTimes(2);
|
|
530
|
+
});
|
|
531
|
+
|
|
532
|
+
test("ignores a previous subject response after route navigation", async () => {
|
|
533
|
+
const previousSubject: Deferred<ApiResponse> =
|
|
534
|
+
createDeferred<ApiResponse>();
|
|
535
|
+
const statuses: Array<AIRunStatus | null> = [];
|
|
536
|
+
postMock
|
|
537
|
+
.mockReturnValueOnce(previousSubject.promise as never)
|
|
538
|
+
.mockResolvedValueOnce(completedResponse() as never);
|
|
539
|
+
|
|
540
|
+
const view: ReturnType<typeof render> = renderPanel({
|
|
541
|
+
onStatusChange: (status: AIRunStatus | null): void => {
|
|
542
|
+
statuses.push(status);
|
|
543
|
+
},
|
|
544
|
+
});
|
|
545
|
+
await flush();
|
|
546
|
+
|
|
547
|
+
view.rerender(
|
|
548
|
+
<InvestigationPanel
|
|
549
|
+
subjectType="alert"
|
|
550
|
+
subjectId={ALERT_ID}
|
|
551
|
+
onStatusChange={(status: AIRunStatus | null): void => {
|
|
552
|
+
statuses.push(status);
|
|
553
|
+
}}
|
|
554
|
+
/>,
|
|
555
|
+
);
|
|
556
|
+
await flush();
|
|
557
|
+
|
|
558
|
+
expect(screen.getByText("Investigation complete")).toBeVisible();
|
|
559
|
+
expect(postRequestAt(1).data).toEqual({ alertId: ALERT_ID.toString() });
|
|
560
|
+
|
|
561
|
+
await resolveDeferred(
|
|
562
|
+
previousSubject,
|
|
563
|
+
successfulResponse(investigationPayload({ status: AIRunStatus.Running })),
|
|
564
|
+
);
|
|
565
|
+
|
|
566
|
+
expect(screen.getByText("Investigation complete")).toBeVisible();
|
|
567
|
+
expect(screen.queryByText("Investigating…")).toBeNull();
|
|
568
|
+
expect(statuses[statuses.length - 1]).toBe(AIRunStatus.Completed);
|
|
569
|
+
});
|
|
570
|
+
|
|
571
|
+
test("does not notify a new subject with the previous report", async () => {
|
|
572
|
+
const nextSubject: Deferred<ApiResponse> = createDeferred<ApiResponse>();
|
|
573
|
+
const onAnalysisAvailable: MockFunction = getJestMockFunction();
|
|
574
|
+
const nextAnalysis: string =
|
|
575
|
+
"## Alert root cause\n\nThe upstream dependency rejected requests.";
|
|
576
|
+
postMock
|
|
577
|
+
.mockResolvedValueOnce(completedResponse() as never)
|
|
578
|
+
.mockReturnValueOnce(nextSubject.promise as never);
|
|
579
|
+
|
|
580
|
+
const view: ReturnType<typeof render> = renderPanel({
|
|
581
|
+
onAnalysisAvailable,
|
|
582
|
+
});
|
|
583
|
+
await flush();
|
|
584
|
+
expect(onAnalysisAvailable).toHaveBeenCalledTimes(1);
|
|
585
|
+
onAnalysisAvailable.mockClear();
|
|
586
|
+
|
|
587
|
+
view.rerender(
|
|
588
|
+
<InvestigationPanel
|
|
589
|
+
subjectType="alert"
|
|
590
|
+
subjectId={ALERT_ID}
|
|
591
|
+
onAnalysisAvailable={onAnalysisAvailable}
|
|
592
|
+
/>,
|
|
593
|
+
);
|
|
594
|
+
await flush();
|
|
595
|
+
|
|
596
|
+
expect(view.container).toBeEmptyDOMElement();
|
|
597
|
+
expect(onAnalysisAvailable).not.toHaveBeenCalled();
|
|
598
|
+
|
|
599
|
+
await resolveDeferred(
|
|
600
|
+
nextSubject,
|
|
601
|
+
completedResponse({ runId: NEXT_RUN_ID, analysisMarkdown: nextAnalysis }),
|
|
602
|
+
);
|
|
603
|
+
|
|
604
|
+
expect(screen.getByTestId("investigation-markdown")).toHaveTextContent(
|
|
605
|
+
"The upstream dependency rejected requests.",
|
|
606
|
+
);
|
|
607
|
+
expect(onAnalysisAvailable).toHaveBeenCalledTimes(1);
|
|
608
|
+
});
|
|
609
|
+
|
|
610
|
+
test("shows the no-report outcome and disables analysis-only actions", async () => {
|
|
611
|
+
postMock.mockResolvedValue(
|
|
612
|
+
completedResponse({
|
|
613
|
+
analysisMarkdown: null,
|
|
614
|
+
isAnalysisPending: false,
|
|
615
|
+
events: [],
|
|
616
|
+
toolCallCount: 0,
|
|
617
|
+
totalTokens: 0,
|
|
618
|
+
}) as never,
|
|
619
|
+
);
|
|
620
|
+
|
|
621
|
+
renderPanel();
|
|
622
|
+
await flush();
|
|
623
|
+
|
|
624
|
+
expect(
|
|
625
|
+
screen.getByText("Investigation completed without a report"),
|
|
626
|
+
).toBeVisible();
|
|
627
|
+
expect(
|
|
628
|
+
screen.getByText("No investigation report was published."),
|
|
629
|
+
).toBeVisible();
|
|
630
|
+
expect(fixButton()).toBeDisabled();
|
|
631
|
+
expect(screen.getByRole("button", { name: "Confirmed" })).toBeDisabled();
|
|
632
|
+
expect(screen.getByRole("button", { name: "Rejected" })).toBeDisabled();
|
|
633
|
+
});
|
|
634
|
+
|
|
635
|
+
test("shows a terminal failure without completed actions", async () => {
|
|
636
|
+
postMock.mockResolvedValue(
|
|
637
|
+
successfulResponse(
|
|
638
|
+
investigationPayload({
|
|
639
|
+
status: AIRunStatus.Error,
|
|
640
|
+
errorMessage: "The model provider timed out.",
|
|
641
|
+
events: [activityEvent],
|
|
642
|
+
}),
|
|
643
|
+
) as never,
|
|
644
|
+
);
|
|
645
|
+
|
|
646
|
+
renderPanel();
|
|
647
|
+
await flush();
|
|
648
|
+
|
|
649
|
+
expect(screen.getByText("Investigation did not finish")).toBeVisible();
|
|
650
|
+
expect(screen.getByText("The model provider timed out.")).toBeVisible();
|
|
651
|
+
expect(
|
|
652
|
+
screen.getByText("The investigation stopped before it could report."),
|
|
653
|
+
).toBeVisible();
|
|
654
|
+
expect(
|
|
655
|
+
screen.getByText("What the investigation got through"),
|
|
656
|
+
).toBeVisible();
|
|
657
|
+
expect(lastActivityProps()).toEqual(
|
|
658
|
+
expect.objectContaining({
|
|
659
|
+
hideChrome: true,
|
|
660
|
+
showLiveIndicator: false,
|
|
661
|
+
}),
|
|
662
|
+
);
|
|
663
|
+
expect(fixButton()).toBeNull();
|
|
664
|
+
expect(screen.queryByText("Rate this investigation")).toBeNull();
|
|
665
|
+
});
|
|
666
|
+
|
|
667
|
+
test("uses the alert endpoint and alert id", async () => {
|
|
668
|
+
postMock.mockResolvedValue(
|
|
669
|
+
successfulResponse(
|
|
670
|
+
investigationPayload({ status: AIRunStatus.Running }),
|
|
671
|
+
) as never,
|
|
672
|
+
);
|
|
673
|
+
|
|
674
|
+
renderPanel({ subjectType: "alert", subjectId: ALERT_ID });
|
|
675
|
+
await flush();
|
|
676
|
+
|
|
677
|
+
expect(requestPath(0)).toContain("/ai-investigation/alert");
|
|
678
|
+
expect(postRequestAt(0).data).toEqual({ alertId: ALERT_ID.toString() });
|
|
679
|
+
});
|
|
680
|
+
});
|
|
681
|
+
|
|
682
|
+
/*
|
|
683
|
+
* Every state of the card has to stand on its own: a responder can land on it
|
|
684
|
+
* while the run is queued, live, failed, or finished. These tests pin the
|
|
685
|
+
* framing and the plain-language explanation each state owes the reader.
|
|
686
|
+
*/
|
|
687
|
+
describe("InvestigationPanel card states", () => {
|
|
688
|
+
test("frames the live run and says what it is doing and that it cannot change anything", async () => {
|
|
689
|
+
postMock.mockResolvedValue(
|
|
690
|
+
successfulResponse(
|
|
691
|
+
investigationPayload({
|
|
692
|
+
status: AIRunStatus.Running,
|
|
693
|
+
events: [activityEvent],
|
|
694
|
+
}),
|
|
695
|
+
) as never,
|
|
696
|
+
);
|
|
697
|
+
|
|
698
|
+
renderPanel();
|
|
699
|
+
await flush();
|
|
700
|
+
|
|
701
|
+
const live: HTMLElement = screen.getByLabelText("Live investigation");
|
|
702
|
+
expect(live).toHaveTextContent("OneUptime AI is investigating");
|
|
703
|
+
expect(live).toHaveTextContent("Read-only — nothing is changed");
|
|
704
|
+
expect(live).toContainElement(screen.getByTestId("investigation-activity"));
|
|
705
|
+
});
|
|
706
|
+
|
|
707
|
+
test("explains the wait while the run is only queued", async () => {
|
|
708
|
+
postMock.mockResolvedValue(
|
|
709
|
+
successfulResponse(
|
|
710
|
+
investigationPayload({ status: AIRunStatus.Queued }),
|
|
711
|
+
) as never,
|
|
712
|
+
);
|
|
713
|
+
|
|
714
|
+
renderPanel();
|
|
715
|
+
await flush();
|
|
716
|
+
|
|
717
|
+
expect(screen.getByLabelText("Live investigation")).toHaveTextContent(
|
|
718
|
+
"Waiting for a worker to pick this up",
|
|
719
|
+
);
|
|
720
|
+
expect(screen.getByText("Starting investigation…")).toBeVisible();
|
|
721
|
+
});
|
|
722
|
+
|
|
723
|
+
test("puts a copy control on the report so it can be pasted elsewhere", async () => {
|
|
724
|
+
postMock.mockResolvedValue(completedResponse() as never);
|
|
725
|
+
|
|
726
|
+
renderPanel();
|
|
727
|
+
await flush();
|
|
728
|
+
|
|
729
|
+
const report: HTMLElement = screen.getByLabelText("Investigation report");
|
|
730
|
+
expect(report).toHaveTextContent("AI generated");
|
|
731
|
+
expect(report).toContainElement(
|
|
732
|
+
screen.getByRole("button", { name: "Copy report" }),
|
|
733
|
+
);
|
|
734
|
+
});
|
|
735
|
+
|
|
736
|
+
/*
|
|
737
|
+
* A queued run carries a stats object whose counts are all zero, so an
|
|
738
|
+
* ungated strip would tell the reader the AI ran zero queries and changed
|
|
739
|
+
* nothing — a past-tense report on work that has not started, directly
|
|
740
|
+
* under "waiting for a worker".
|
|
741
|
+
*/
|
|
742
|
+
test.each([AIRunStatus.Running, AIRunStatus.Queued])(
|
|
743
|
+
"shows no usage strip while a %s run has not produced anything yet",
|
|
744
|
+
async (status: AIRunStatus) => {
|
|
745
|
+
postMock.mockResolvedValue(
|
|
746
|
+
successfulResponse(
|
|
747
|
+
investigationPayload({
|
|
748
|
+
status,
|
|
749
|
+
toolCallCount: 0,
|
|
750
|
+
totalTokens: 0,
|
|
751
|
+
}),
|
|
752
|
+
) as never,
|
|
753
|
+
);
|
|
754
|
+
|
|
755
|
+
renderPanel();
|
|
756
|
+
await flush();
|
|
757
|
+
|
|
758
|
+
expect(screen.queryByLabelText("Investigation usage")).toBeNull();
|
|
759
|
+
expect(
|
|
760
|
+
screen.queryByText(/nothing in your systems was changed/),
|
|
761
|
+
).toBeNull();
|
|
762
|
+
},
|
|
763
|
+
);
|
|
764
|
+
|
|
765
|
+
/*
|
|
766
|
+
* Events and rendered steps are not the same thing: RunFailed and the
|
|
767
|
+
* completion halves of tool/LLM calls only close a step an earlier event
|
|
768
|
+
* opened. A run whose RunStarted failed to persist and then failed outright
|
|
769
|
+
* carries one event and draws nothing, and counting raw events would frame
|
|
770
|
+
* an empty panel instead of saying what happened.
|
|
771
|
+
*/
|
|
772
|
+
test("explains an empty trail instead of framing a blank panel", async () => {
|
|
773
|
+
hasRenderableActivityMock.mockReturnValue(false);
|
|
774
|
+
postMock.mockResolvedValue(
|
|
775
|
+
successfulResponse(
|
|
776
|
+
investigationPayload({
|
|
777
|
+
status: AIRunStatus.Error,
|
|
778
|
+
errorMessage: "The provider timed out.",
|
|
779
|
+
events: [activityEvent],
|
|
780
|
+
}),
|
|
781
|
+
) as never,
|
|
782
|
+
);
|
|
783
|
+
|
|
784
|
+
renderPanel();
|
|
785
|
+
await flush();
|
|
786
|
+
|
|
787
|
+
expect(screen.queryByTestId("investigation-activity")).toBeNull();
|
|
788
|
+
expect(
|
|
789
|
+
screen.getByText("No investigation steps were recorded."),
|
|
790
|
+
).toBeVisible();
|
|
791
|
+
});
|
|
792
|
+
|
|
793
|
+
test("hides the completed activity disclosure when no step would render", async () => {
|
|
794
|
+
hasRenderableActivityMock.mockReturnValue(false);
|
|
795
|
+
postMock.mockResolvedValue(completedResponse() as never);
|
|
796
|
+
|
|
797
|
+
renderPanel();
|
|
798
|
+
await flush();
|
|
799
|
+
|
|
800
|
+
expect(screen.getByLabelText("Investigation report")).toBeInTheDocument();
|
|
801
|
+
expect(screen.queryByText("Investigation activity")).toBeNull();
|
|
802
|
+
});
|
|
803
|
+
|
|
804
|
+
test("reports a single query without pluralising it", async () => {
|
|
805
|
+
postMock.mockResolvedValue(
|
|
806
|
+
completedResponse({ toolCallCount: 1, totalTokens: 0 }) as never,
|
|
807
|
+
);
|
|
808
|
+
|
|
809
|
+
renderPanel();
|
|
810
|
+
await flush();
|
|
811
|
+
|
|
812
|
+
const usage: HTMLElement = screen.getByLabelText("Investigation usage");
|
|
813
|
+
expect(usage).toHaveTextContent("1 telemetry query");
|
|
814
|
+
expect(usage).not.toHaveTextContent("tokens");
|
|
815
|
+
});
|
|
816
|
+
});
|
|
817
|
+
|
|
818
|
+
/*
|
|
819
|
+
* The TL;DR is the first thing on the card, and it is AI prose about the
|
|
820
|
+
* report below it. So it must never appear without that report, must never be
|
|
821
|
+
* rendered as markdown, and must be dropped the instant the panel changes
|
|
822
|
+
* subject — a summary of the previous incident is worse than no summary.
|
|
823
|
+
*/
|
|
824
|
+
describe("InvestigationPanel TL;DR", () => {
|
|
825
|
+
function markdownTexts(): Array<string> {
|
|
826
|
+
return (
|
|
827
|
+
markdownViewerMock.mock.calls as Array<Array<MarkdownViewerProps>>
|
|
828
|
+
).map((call: Array<MarkdownViewerProps>): string => {
|
|
829
|
+
return call[0]!.text;
|
|
830
|
+
});
|
|
831
|
+
}
|
|
832
|
+
|
|
833
|
+
test("leads the completed card with the AI summary, as plain text above the report", async () => {
|
|
834
|
+
postMock.mockResolvedValue(
|
|
835
|
+
completedResponse({ analysisTldr: TLDR }) as never,
|
|
836
|
+
);
|
|
837
|
+
|
|
838
|
+
renderPanel();
|
|
839
|
+
await flush();
|
|
840
|
+
|
|
841
|
+
const summary: HTMLElement = screen.getByLabelText("Investigation summary");
|
|
842
|
+
expect(summary).toHaveTextContent("TL;DR");
|
|
843
|
+
expect(summary).toHaveTextContent(TLDR);
|
|
844
|
+
|
|
845
|
+
// The summary is never routed through the markdown renderer.
|
|
846
|
+
expect(markdownTexts().length).toBeGreaterThan(0);
|
|
847
|
+
expect(
|
|
848
|
+
markdownTexts().every((text: string): boolean => {
|
|
849
|
+
return text === ANALYSIS;
|
|
850
|
+
}),
|
|
851
|
+
).toBe(true);
|
|
852
|
+
|
|
853
|
+
// It sits above the report it summarizes.
|
|
854
|
+
const report: HTMLElement = screen.getByLabelText("Investigation report");
|
|
855
|
+
expect(
|
|
856
|
+
summary.compareDocumentPosition(report) &
|
|
857
|
+
Node.DOCUMENT_POSITION_FOLLOWING,
|
|
858
|
+
).toBeTruthy();
|
|
859
|
+
});
|
|
860
|
+
|
|
861
|
+
test("omits the block for a run that has no stored summary", async () => {
|
|
862
|
+
postMock.mockResolvedValue(completedResponse() as never);
|
|
863
|
+
|
|
864
|
+
renderPanel();
|
|
865
|
+
await flush();
|
|
866
|
+
|
|
867
|
+
expect(screen.getByLabelText("Investigation report")).toBeInTheDocument();
|
|
868
|
+
expect(screen.queryByLabelText("Investigation summary")).toBeNull();
|
|
869
|
+
expect(screen.queryByText("TL;DR")).toBeNull();
|
|
870
|
+
});
|
|
871
|
+
|
|
872
|
+
test("omits a blank summary rather than rendering an empty banner", async () => {
|
|
873
|
+
postMock.mockResolvedValue(
|
|
874
|
+
completedResponse({ analysisTldr: " \n " }) as never,
|
|
875
|
+
);
|
|
876
|
+
|
|
877
|
+
renderPanel();
|
|
878
|
+
await flush();
|
|
879
|
+
|
|
880
|
+
expect(screen.queryByLabelText("Investigation summary")).toBeNull();
|
|
881
|
+
});
|
|
882
|
+
|
|
883
|
+
test("never shows a summary before the report it describes is published", async () => {
|
|
884
|
+
postMock.mockResolvedValue(
|
|
885
|
+
completedResponse({
|
|
886
|
+
analysisMarkdown: null,
|
|
887
|
+
analysisTldr: TLDR,
|
|
888
|
+
isAnalysisPending: true,
|
|
889
|
+
}) as never,
|
|
890
|
+
);
|
|
891
|
+
|
|
892
|
+
renderPanel();
|
|
893
|
+
await flush();
|
|
894
|
+
|
|
895
|
+
expect(screen.queryByLabelText("Investigation summary")).toBeNull();
|
|
896
|
+
expect(screen.queryByText(TLDR)).toBeNull();
|
|
897
|
+
expect(screen.getByText("Preparing the final report")).toBeVisible();
|
|
898
|
+
});
|
|
899
|
+
|
|
900
|
+
test("appears on the poll that publishes the report", async () => {
|
|
901
|
+
postMock
|
|
902
|
+
.mockResolvedValueOnce(
|
|
903
|
+
completedResponse({
|
|
904
|
+
analysisMarkdown: null,
|
|
905
|
+
analysisTldr: null,
|
|
906
|
+
isAnalysisPending: true,
|
|
907
|
+
}) as never,
|
|
908
|
+
)
|
|
909
|
+
.mockResolvedValue(completedResponse({ analysisTldr: TLDR }) as never);
|
|
910
|
+
|
|
911
|
+
renderPanel();
|
|
912
|
+
await flush();
|
|
913
|
+
expect(screen.queryByLabelText("Investigation summary")).toBeNull();
|
|
914
|
+
|
|
915
|
+
await advanceFastPolls();
|
|
916
|
+
|
|
917
|
+
expect(screen.getByLabelText("Investigation summary")).toHaveTextContent(
|
|
918
|
+
TLDR,
|
|
919
|
+
);
|
|
920
|
+
});
|
|
921
|
+
|
|
922
|
+
test("is not shown for a run that never completed", async () => {
|
|
923
|
+
postMock.mockResolvedValue(
|
|
924
|
+
successfulResponse(
|
|
925
|
+
investigationPayload({
|
|
926
|
+
status: AIRunStatus.Error,
|
|
927
|
+
analysisTldr: TLDR,
|
|
928
|
+
errorMessage: "The provider timed out.",
|
|
929
|
+
}),
|
|
930
|
+
) as never,
|
|
931
|
+
);
|
|
932
|
+
|
|
933
|
+
renderPanel();
|
|
934
|
+
await flush();
|
|
935
|
+
|
|
936
|
+
expect(screen.queryByLabelText("Investigation summary")).toBeNull();
|
|
937
|
+
expect(screen.getByText("The provider timed out.")).toBeVisible();
|
|
938
|
+
});
|
|
939
|
+
|
|
940
|
+
/*
|
|
941
|
+
* The report is recomputed from the feed on every poll, so it can vanish
|
|
942
|
+
* (a deleted feed item) while the run stays Completed. The summary must go
|
|
943
|
+
* with it — a summary of a report the reader cannot see is worse than none.
|
|
944
|
+
* Cadence matters: a settled Recommended run polls at the SETTLED interval,
|
|
945
|
+
* so advancing by the fast interval would make this pass vacuously.
|
|
946
|
+
*/
|
|
947
|
+
test("drops the summary if the report it describes disappears on a later poll", async () => {
|
|
948
|
+
postMock
|
|
949
|
+
.mockResolvedValueOnce(completedResponse({ analysisTldr: TLDR }) as never)
|
|
950
|
+
.mockResolvedValue(
|
|
951
|
+
completedResponse({
|
|
952
|
+
analysisMarkdown: null,
|
|
953
|
+
analysisTldr: TLDR,
|
|
954
|
+
}) as never,
|
|
955
|
+
);
|
|
956
|
+
|
|
957
|
+
renderPanel();
|
|
958
|
+
await flush();
|
|
959
|
+
expect(screen.getByLabelText("Investigation summary")).toHaveTextContent(
|
|
960
|
+
TLDR,
|
|
961
|
+
);
|
|
962
|
+
|
|
963
|
+
await tick(SETTLED_POLL_INTERVAL_MS);
|
|
964
|
+
|
|
965
|
+
expect(postMock).toHaveBeenCalledTimes(2);
|
|
966
|
+
expect(screen.queryByLabelText("Investigation summary")).toBeNull();
|
|
967
|
+
expect(screen.queryByText(TLDR)).toBeNull();
|
|
968
|
+
});
|
|
969
|
+
|
|
970
|
+
/*
|
|
971
|
+
* "Never attributed to the wrong run" — the same subject can be
|
|
972
|
+
* re-investigated, and the previous run's summary must not survive onto the
|
|
973
|
+
* new run's report.
|
|
974
|
+
*/
|
|
975
|
+
test("does not carry a summary over to a new run on the same subject", async () => {
|
|
976
|
+
const nextAnalysis: string = "## Root cause\n\nA second, different cause.";
|
|
977
|
+
postMock
|
|
978
|
+
.mockResolvedValueOnce(completedResponse({ analysisTldr: TLDR }) as never)
|
|
979
|
+
.mockResolvedValue(
|
|
980
|
+
completedResponse({
|
|
981
|
+
runId: NEXT_RUN_ID,
|
|
982
|
+
analysisMarkdown: nextAnalysis,
|
|
983
|
+
analysisTldr: null,
|
|
984
|
+
}) as never,
|
|
985
|
+
);
|
|
986
|
+
|
|
987
|
+
renderPanel();
|
|
988
|
+
await flush();
|
|
989
|
+
expect(screen.getByLabelText("Investigation summary")).toBeInTheDocument();
|
|
990
|
+
|
|
991
|
+
await tick(SETTLED_POLL_INTERVAL_MS);
|
|
992
|
+
|
|
993
|
+
expect(screen.queryByLabelText("Investigation summary")).toBeNull();
|
|
994
|
+
expect(screen.queryByText(TLDR)).toBeNull();
|
|
995
|
+
expect(screen.getByTestId("investigation-markdown")).toHaveTextContent(
|
|
996
|
+
"A second, different cause.",
|
|
997
|
+
);
|
|
998
|
+
});
|
|
999
|
+
|
|
1000
|
+
/*
|
|
1001
|
+
* The module's threat model is "the worst a prompt injection achieves is a
|
|
1002
|
+
* misleading sentence". That holds only while the summary is rendered as
|
|
1003
|
+
* inert text — never as markdown, never as HTML.
|
|
1004
|
+
*/
|
|
1005
|
+
test("renders hostile summary content inertly, as text", async () => {
|
|
1006
|
+
const hostile: string =
|
|
1007
|
+
'<img src=x onerror="window.__pwned = true"> **bold** [link](https://evil.example)';
|
|
1008
|
+
postMock.mockResolvedValue(
|
|
1009
|
+
completedResponse({ analysisTldr: hostile }) as never,
|
|
1010
|
+
);
|
|
1011
|
+
|
|
1012
|
+
renderPanel();
|
|
1013
|
+
await flush();
|
|
1014
|
+
|
|
1015
|
+
const summary: HTMLElement = screen.getByLabelText("Investigation summary");
|
|
1016
|
+
expect(summary).toHaveTextContent(hostile);
|
|
1017
|
+
expect(summary.querySelector("img")).toBeNull();
|
|
1018
|
+
expect(summary.querySelector("a")).toBeNull();
|
|
1019
|
+
expect(
|
|
1020
|
+
(window as unknown as { __pwned?: boolean }).__pwned,
|
|
1021
|
+
).toBeUndefined();
|
|
1022
|
+
// The summary never reaches the markdown renderer.
|
|
1023
|
+
expect(
|
|
1024
|
+
markdownTexts().every((text: string): boolean => {
|
|
1025
|
+
return text === ANALYSIS;
|
|
1026
|
+
}),
|
|
1027
|
+
).toBe(true);
|
|
1028
|
+
});
|
|
1029
|
+
|
|
1030
|
+
test("drops the previous subject's summary the moment the subject changes", async () => {
|
|
1031
|
+
const nextSubject: Deferred<ApiResponse> = createDeferred<ApiResponse>();
|
|
1032
|
+
postMock
|
|
1033
|
+
.mockResolvedValueOnce(completedResponse({ analysisTldr: TLDR }) as never)
|
|
1034
|
+
.mockReturnValueOnce(nextSubject.promise as never);
|
|
1035
|
+
|
|
1036
|
+
const view: ReturnType<typeof render> = renderPanel();
|
|
1037
|
+
await flush();
|
|
1038
|
+
expect(screen.getByLabelText("Investigation summary")).toBeInTheDocument();
|
|
1039
|
+
|
|
1040
|
+
view.rerender(
|
|
1041
|
+
<InvestigationPanel subjectType="alert" subjectId={ALERT_ID} />,
|
|
1042
|
+
);
|
|
1043
|
+
await flush();
|
|
1044
|
+
|
|
1045
|
+
expect(screen.queryByLabelText("Investigation summary")).toBeNull();
|
|
1046
|
+
|
|
1047
|
+
await resolveDeferred(
|
|
1048
|
+
nextSubject,
|
|
1049
|
+
completedResponse({
|
|
1050
|
+
runId: NEXT_RUN_ID,
|
|
1051
|
+
analysisTldr: "The alert cleared on its own after the pod restarted.",
|
|
1052
|
+
}),
|
|
1053
|
+
);
|
|
1054
|
+
|
|
1055
|
+
expect(screen.getByLabelText("Investigation summary")).toHaveTextContent(
|
|
1056
|
+
"The alert cleared on its own after the pod restarted.",
|
|
1057
|
+
);
|
|
1058
|
+
});
|
|
1059
|
+
});
|
|
1060
|
+
|
|
1061
|
+
/*
|
|
1062
|
+
* The verdict prompt is a single question with two answers, so both choices
|
|
1063
|
+
* live in one labelled group; once answered it collapses to the recorded
|
|
1064
|
+
* verdict plus a way back. These tests pin that shape — the earlier suites
|
|
1065
|
+
* cover the request/optimistic-state contract behind it.
|
|
1066
|
+
*/
|
|
1067
|
+
describe("InvestigationPanel verdict control", () => {
|
|
1068
|
+
function verdictGroup(): HTMLElement | null {
|
|
1069
|
+
return screen.queryByRole("group", { name: "Rate this investigation" });
|
|
1070
|
+
}
|
|
1071
|
+
|
|
1072
|
+
test("offers both verdicts inside one labelled group", async () => {
|
|
1073
|
+
postMock.mockResolvedValue(completedResponse() as never);
|
|
1074
|
+
|
|
1075
|
+
renderPanel();
|
|
1076
|
+
await flush();
|
|
1077
|
+
|
|
1078
|
+
const group: HTMLElement = verdictGroup()!;
|
|
1079
|
+
expect(group).toBeInTheDocument();
|
|
1080
|
+
expect(
|
|
1081
|
+
screen.getByRole("button", { name: "Confirmed" }),
|
|
1082
|
+
).toBeInTheDocument();
|
|
1083
|
+
expect(
|
|
1084
|
+
screen.getByRole("button", { name: "Rejected" }),
|
|
1085
|
+
).toBeInTheDocument();
|
|
1086
|
+
expect(group).toContainElement(
|
|
1087
|
+
screen.getByRole("button", { name: "Confirmed" }),
|
|
1088
|
+
);
|
|
1089
|
+
expect(group).toContainElement(
|
|
1090
|
+
screen.getByRole("button", { name: "Rejected" }),
|
|
1091
|
+
);
|
|
1092
|
+
});
|
|
1093
|
+
|
|
1094
|
+
test("collapses to the recorded verdict and back again through Change", async () => {
|
|
1095
|
+
postMock
|
|
1096
|
+
.mockResolvedValueOnce(completedResponse() as never)
|
|
1097
|
+
.mockResolvedValueOnce(successfulResponse({}) as never);
|
|
1098
|
+
|
|
1099
|
+
renderPanel();
|
|
1100
|
+
await flush();
|
|
1101
|
+
fireEvent.click(screen.getByRole("button", { name: "Rejected" }));
|
|
1102
|
+
await flush();
|
|
1103
|
+
|
|
1104
|
+
expect(verdictGroup()).toBeNull();
|
|
1105
|
+
expect(screen.getByText(/You rejected this analysis/)).toBeVisible();
|
|
1106
|
+
|
|
1107
|
+
fireEvent.click(screen.getByRole("button", { name: "Change" }));
|
|
1108
|
+
|
|
1109
|
+
expect(verdictGroup()).toBeInTheDocument();
|
|
1110
|
+
expect(screen.getByRole("button", { name: "Confirmed" })).toBeEnabled();
|
|
1111
|
+
expect(screen.getByRole("button", { name: "Rejected" })).toBeEnabled();
|
|
1112
|
+
// Reopening the prompt must not itself save anything.
|
|
1113
|
+
expect(postMock).toHaveBeenCalledTimes(2);
|
|
1114
|
+
});
|
|
1115
|
+
|
|
1116
|
+
test("locks both choices while a verdict is saving", async () => {
|
|
1117
|
+
const save: Deferred<ApiResponse> = createDeferred<ApiResponse>();
|
|
1118
|
+
postMock
|
|
1119
|
+
.mockResolvedValueOnce(completedResponse() as never)
|
|
1120
|
+
.mockReturnValueOnce(save.promise as never);
|
|
1121
|
+
|
|
1122
|
+
renderPanel();
|
|
1123
|
+
await flush();
|
|
1124
|
+
fireEvent.click(screen.getByRole("button", { name: "Confirmed" }));
|
|
1125
|
+
await flush();
|
|
1126
|
+
|
|
1127
|
+
// Optimistic: the pill replaces the prompt immediately.
|
|
1128
|
+
expect(screen.getByText(/You confirmed this analysis/)).toBeVisible();
|
|
1129
|
+
|
|
1130
|
+
fireEvent.click(screen.getByRole("button", { name: "Change" }));
|
|
1131
|
+
expect(screen.getByRole("button", { name: "Confirmed" })).toBeDisabled();
|
|
1132
|
+
expect(screen.getByRole("button", { name: "Rejected" })).toBeDisabled();
|
|
1133
|
+
|
|
1134
|
+
await resolveDeferred(save, successfulResponse({}));
|
|
1135
|
+
|
|
1136
|
+
expect(screen.getByRole("button", { name: "Confirmed" })).toBeEnabled();
|
|
1137
|
+
});
|
|
1138
|
+
|
|
1139
|
+
test("shows the run status as a badge in the card header", async () => {
|
|
1140
|
+
postMock.mockResolvedValue(completedResponse() as never);
|
|
1141
|
+
|
|
1142
|
+
renderPanel();
|
|
1143
|
+
await flush();
|
|
1144
|
+
|
|
1145
|
+
expect(screen.getByLabelText("Investigation status")).toHaveTextContent(
|
|
1146
|
+
"Investigation complete",
|
|
1147
|
+
);
|
|
1148
|
+
});
|
|
1149
|
+
});
|
|
1150
|
+
|
|
1151
|
+
describe("InvestigationPanel code-fix recommendation", () => {
|
|
1152
|
+
test("shows the fix action only for an explicit Recommended decision", async () => {
|
|
1153
|
+
postMock.mockResolvedValue(completedResponse() as never);
|
|
1154
|
+
|
|
1155
|
+
renderPanel();
|
|
1156
|
+
await flush();
|
|
1157
|
+
|
|
1158
|
+
expect(fixButton()).toBeEnabled();
|
|
1159
|
+
expect(screen.getByText("Act on this investigation")).toBeVisible();
|
|
1160
|
+
expect(screen.getByText("Rate this investigation")).toBeVisible();
|
|
1161
|
+
});
|
|
1162
|
+
|
|
1163
|
+
test("hides every fix-task element for NotRecommended but retains verdict controls", async () => {
|
|
1164
|
+
postMock.mockResolvedValue(
|
|
1165
|
+
completedResponse({
|
|
1166
|
+
codeFixRecommendation: AIRunCodeFixRecommendation.NotRecommended,
|
|
1167
|
+
}) as never,
|
|
1168
|
+
);
|
|
1169
|
+
|
|
1170
|
+
renderPanel();
|
|
1171
|
+
await flush();
|
|
1172
|
+
|
|
1173
|
+
expect(fixButton()).toBeNull();
|
|
1174
|
+
expect(screen.queryByText("Act on this investigation")).toBeNull();
|
|
1175
|
+
expect(screen.queryByText(/Fix task created/)).toBeNull();
|
|
1176
|
+
expect(screen.getByText("Rate this investigation")).toBeVisible();
|
|
1177
|
+
expect(screen.getByRole("button", { name: "Confirmed" })).toBeEnabled();
|
|
1178
|
+
expect(screen.getByRole("button", { name: "Rejected" })).toBeEnabled();
|
|
1179
|
+
});
|
|
1180
|
+
|
|
1181
|
+
test("fails closed for a legacy row with no recommendation or completion time", async () => {
|
|
1182
|
+
postMock.mockResolvedValue(
|
|
1183
|
+
completedResponse({
|
|
1184
|
+
codeFixRecommendation: undefined,
|
|
1185
|
+
completedAt: undefined,
|
|
1186
|
+
}) as never,
|
|
1187
|
+
);
|
|
1188
|
+
|
|
1189
|
+
renderPanel();
|
|
1190
|
+
await flush();
|
|
1191
|
+
|
|
1192
|
+
expect(fixButton()).toBeNull();
|
|
1193
|
+
expect(screen.getByText("Rate this investigation")).toBeVisible();
|
|
1194
|
+
expect(jest.getTimerCount()).toBe(1);
|
|
1195
|
+
|
|
1196
|
+
await advanceFastPolls(4);
|
|
1197
|
+
expect(postMock).toHaveBeenCalledTimes(1);
|
|
1198
|
+
});
|
|
1199
|
+
|
|
1200
|
+
test("fails closed at fast cadence for an old row with no recommendation", async () => {
|
|
1201
|
+
postMock.mockResolvedValue(
|
|
1202
|
+
completedResponse({
|
|
1203
|
+
codeFixRecommendation: undefined,
|
|
1204
|
+
completedAt: OLD_COMPLETED_AT,
|
|
1205
|
+
}) as never,
|
|
1206
|
+
);
|
|
1207
|
+
|
|
1208
|
+
renderPanel();
|
|
1209
|
+
await flush();
|
|
1210
|
+
|
|
1211
|
+
expect(fixButton()).toBeNull();
|
|
1212
|
+
expect(jest.getTimerCount()).toBe(1);
|
|
1213
|
+
|
|
1214
|
+
await advanceFastPolls(4);
|
|
1215
|
+
expect(postMock).toHaveBeenCalledTimes(1);
|
|
1216
|
+
});
|
|
1217
|
+
|
|
1218
|
+
test("polls a recent missing decision and reveals the action when it settles", async () => {
|
|
1219
|
+
postMock
|
|
1220
|
+
.mockResolvedValueOnce(
|
|
1221
|
+
completedResponse({ codeFixRecommendation: undefined }) as never,
|
|
1222
|
+
)
|
|
1223
|
+
.mockResolvedValueOnce(completedResponse() as never);
|
|
1224
|
+
|
|
1225
|
+
renderPanel();
|
|
1226
|
+
await flush();
|
|
1227
|
+
|
|
1228
|
+
expect(fixButton()).toBeNull();
|
|
1229
|
+
expect(screen.getByText("Rate this investigation")).toBeVisible();
|
|
1230
|
+
expect(jest.getTimerCount()).toBe(2);
|
|
1231
|
+
|
|
1232
|
+
await advanceFastPolls();
|
|
1233
|
+
|
|
1234
|
+
expect(postMock).toHaveBeenCalledTimes(2);
|
|
1235
|
+
expect(fixButton()).toBeEnabled();
|
|
1236
|
+
expect(jest.getTimerCount()).toBe(1);
|
|
1237
|
+
});
|
|
1238
|
+
|
|
1239
|
+
test("applies Pending to Recommended when no other signature field changes", async () => {
|
|
1240
|
+
postMock
|
|
1241
|
+
.mockResolvedValueOnce(
|
|
1242
|
+
completedResponse({
|
|
1243
|
+
codeFixRecommendation: AIRunCodeFixRecommendation.Pending,
|
|
1244
|
+
}) as never,
|
|
1245
|
+
)
|
|
1246
|
+
.mockResolvedValueOnce(completedResponse() as never);
|
|
1247
|
+
|
|
1248
|
+
renderPanel();
|
|
1249
|
+
await flush();
|
|
1250
|
+
|
|
1251
|
+
expect(screen.getByText("Investigation complete")).toBeVisible();
|
|
1252
|
+
expect(fixButton()).toBeNull();
|
|
1253
|
+
expect(postMock).toHaveBeenCalledTimes(1);
|
|
1254
|
+
expect(jest.getTimerCount()).toBe(2);
|
|
1255
|
+
|
|
1256
|
+
await advanceFastPolls();
|
|
1257
|
+
|
|
1258
|
+
expect(postMock).toHaveBeenCalledTimes(2);
|
|
1259
|
+
expect(fixButton()).toBeEnabled();
|
|
1260
|
+
expect(jest.getTimerCount()).toBe(1);
|
|
1261
|
+
});
|
|
1262
|
+
|
|
1263
|
+
test("does not present explicit Pending as an active investigation", async () => {
|
|
1264
|
+
postMock.mockResolvedValue(
|
|
1265
|
+
completedResponse({
|
|
1266
|
+
codeFixRecommendation: AIRunCodeFixRecommendation.Pending,
|
|
1267
|
+
}) as never,
|
|
1268
|
+
);
|
|
1269
|
+
|
|
1270
|
+
renderPanel();
|
|
1271
|
+
await flush();
|
|
1272
|
+
|
|
1273
|
+
expect(screen.getByText("Investigation complete")).toBeVisible();
|
|
1274
|
+
expect(screen.queryByText("Investigating…")).toBeNull();
|
|
1275
|
+
expect(
|
|
1276
|
+
document.querySelector('[class~="motion-safe:animate-ping"]'),
|
|
1277
|
+
).toBeNull();
|
|
1278
|
+
expect(fixButton()).toBeNull();
|
|
1279
|
+
});
|
|
1280
|
+
|
|
1281
|
+
test("tolerates a browser clock behind the server while Pending settles", async () => {
|
|
1282
|
+
jest.setSystemTime(new Date("2026-08-07T11:59:55.000Z"));
|
|
1283
|
+
postMock
|
|
1284
|
+
.mockResolvedValueOnce(
|
|
1285
|
+
completedResponse({
|
|
1286
|
+
codeFixRecommendation: AIRunCodeFixRecommendation.Pending,
|
|
1287
|
+
}) as never,
|
|
1288
|
+
)
|
|
1289
|
+
.mockResolvedValueOnce(completedResponse() as never);
|
|
1290
|
+
|
|
1291
|
+
renderPanel();
|
|
1292
|
+
await flush();
|
|
1293
|
+
expect(fixButton()).toBeNull();
|
|
1294
|
+
|
|
1295
|
+
await advanceFastPolls();
|
|
1296
|
+
expect(fixButton()).toBeEnabled();
|
|
1297
|
+
});
|
|
1298
|
+
|
|
1299
|
+
test("tolerates a browser clock ahead of the server for explicit Pending", async () => {
|
|
1300
|
+
jest.setSystemTime(new Date("2026-08-07T12:05:00.000Z"));
|
|
1301
|
+
postMock
|
|
1302
|
+
.mockResolvedValueOnce(
|
|
1303
|
+
completedResponse({
|
|
1304
|
+
codeFixRecommendation: AIRunCodeFixRecommendation.Pending,
|
|
1305
|
+
}) as never,
|
|
1306
|
+
)
|
|
1307
|
+
.mockResolvedValueOnce(completedResponse() as never);
|
|
1308
|
+
|
|
1309
|
+
renderPanel();
|
|
1310
|
+
await flush();
|
|
1311
|
+
expect(fixButton()).toBeNull();
|
|
1312
|
+
|
|
1313
|
+
await advanceFastPolls();
|
|
1314
|
+
expect(fixButton()).toBeEnabled();
|
|
1315
|
+
});
|
|
1316
|
+
|
|
1317
|
+
test("bounds a permanently Pending decision by time and response count", async () => {
|
|
1318
|
+
postMock.mockResolvedValue(
|
|
1319
|
+
completedResponse({
|
|
1320
|
+
codeFixRecommendation: AIRunCodeFixRecommendation.Pending,
|
|
1321
|
+
}) as never,
|
|
1322
|
+
);
|
|
1323
|
+
|
|
1324
|
+
renderPanel();
|
|
1325
|
+
await flush();
|
|
1326
|
+
|
|
1327
|
+
expect(fixButton()).toBeNull();
|
|
1328
|
+
expect(jest.getTimerCount()).toBe(2);
|
|
1329
|
+
|
|
1330
|
+
await advanceFastPolls(MAX_RECOMMENDATION_POLL_RESPONSES);
|
|
1331
|
+
|
|
1332
|
+
const callsAtSettlement: number = postMock.mock.calls.length;
|
|
1333
|
+
expect(callsAtSettlement).toBeGreaterThan(1);
|
|
1334
|
+
expect(callsAtSettlement).toBeLessThanOrEqual(
|
|
1335
|
+
MAX_RECOMMENDATION_POLL_RESPONSES + 1,
|
|
1336
|
+
);
|
|
1337
|
+
expect(fixButton()).toBeNull();
|
|
1338
|
+
expect(screen.queryByRole("alert")).toBeNull();
|
|
1339
|
+
expect(jest.getTimerCount()).toBe(1);
|
|
1340
|
+
|
|
1341
|
+
await advanceFastPolls(4);
|
|
1342
|
+
expect(postMock).toHaveBeenCalledTimes(callsAtSettlement);
|
|
1343
|
+
});
|
|
1344
|
+
|
|
1345
|
+
test("keeps Pending polls sequential when a response is slow", async () => {
|
|
1346
|
+
const slowPending: Deferred<ApiResponse> = createDeferred<ApiResponse>();
|
|
1347
|
+
postMock
|
|
1348
|
+
.mockResolvedValueOnce(
|
|
1349
|
+
completedResponse({
|
|
1350
|
+
codeFixRecommendation: AIRunCodeFixRecommendation.Pending,
|
|
1351
|
+
}) as never,
|
|
1352
|
+
)
|
|
1353
|
+
.mockReturnValueOnce(slowPending.promise as never)
|
|
1354
|
+
.mockResolvedValueOnce(completedResponse() as never);
|
|
1355
|
+
|
|
1356
|
+
renderPanel();
|
|
1357
|
+
await flush();
|
|
1358
|
+
await advanceFastPolls();
|
|
1359
|
+
expect(postMock).toHaveBeenCalledTimes(2);
|
|
1360
|
+
|
|
1361
|
+
await advanceFastPolls(5);
|
|
1362
|
+
expect(postMock).toHaveBeenCalledTimes(2);
|
|
1363
|
+
|
|
1364
|
+
await resolveDeferred(
|
|
1365
|
+
slowPending,
|
|
1366
|
+
completedResponse({
|
|
1367
|
+
codeFixRecommendation: AIRunCodeFixRecommendation.Pending,
|
|
1368
|
+
}),
|
|
1369
|
+
);
|
|
1370
|
+
await advanceFastPolls();
|
|
1371
|
+
|
|
1372
|
+
expect(postMock).toHaveBeenCalledTimes(3);
|
|
1373
|
+
expect(fixButton()).toBeEnabled();
|
|
1374
|
+
});
|
|
1375
|
+
|
|
1376
|
+
test("stops failed Pending polls at the independent deadline", async () => {
|
|
1377
|
+
const failedPoll: HTTPErrorResponse = new HTTPErrorResponse(
|
|
1378
|
+
503,
|
|
1379
|
+
{ message: "temporarily unavailable" },
|
|
1380
|
+
{},
|
|
1381
|
+
);
|
|
1382
|
+
postMock
|
|
1383
|
+
.mockResolvedValueOnce(
|
|
1384
|
+
completedResponse({
|
|
1385
|
+
codeFixRecommendation: AIRunCodeFixRecommendation.Pending,
|
|
1386
|
+
}) as never,
|
|
1387
|
+
)
|
|
1388
|
+
.mockResolvedValue(failedPoll as never);
|
|
1389
|
+
|
|
1390
|
+
renderPanel();
|
|
1391
|
+
await flush();
|
|
1392
|
+
await advanceFastPolls(MAX_RECOMMENDATION_POLL_RESPONSES);
|
|
1393
|
+
|
|
1394
|
+
const callsAtDeadline: number = postMock.mock.calls.length;
|
|
1395
|
+
expect(callsAtDeadline).toBeGreaterThan(1);
|
|
1396
|
+
expect(fixButton()).toBeNull();
|
|
1397
|
+
expect(screen.queryByRole("alert")).toBeNull();
|
|
1398
|
+
|
|
1399
|
+
await advanceFastPolls(4);
|
|
1400
|
+
expect(postMock).toHaveBeenCalledTimes(callsAtDeadline);
|
|
1401
|
+
});
|
|
1402
|
+
|
|
1403
|
+
test("a hung Pending request never overlaps before or after the deadline", async () => {
|
|
1404
|
+
const hungPoll: Deferred<ApiResponse> = createDeferred<ApiResponse>();
|
|
1405
|
+
postMock
|
|
1406
|
+
.mockResolvedValueOnce(
|
|
1407
|
+
completedResponse({
|
|
1408
|
+
codeFixRecommendation: AIRunCodeFixRecommendation.Pending,
|
|
1409
|
+
}) as never,
|
|
1410
|
+
)
|
|
1411
|
+
.mockReturnValueOnce(hungPoll.promise as never);
|
|
1412
|
+
|
|
1413
|
+
renderPanel();
|
|
1414
|
+
await flush();
|
|
1415
|
+
await advanceFastPolls();
|
|
1416
|
+
expect(postMock).toHaveBeenCalledTimes(2);
|
|
1417
|
+
|
|
1418
|
+
await tick(RECOMMENDATION_SETTLEMENT_MAX_AGE_MS);
|
|
1419
|
+
expect(fixButton()).toBeNull();
|
|
1420
|
+
expect(postMock).toHaveBeenCalledTimes(2);
|
|
1421
|
+
|
|
1422
|
+
/* Settled cadence must reuse the still-hung request, not overlap it. */
|
|
1423
|
+
await tick(SETTLED_POLL_INTERVAL_MS);
|
|
1424
|
+
expect(postMock).toHaveBeenCalledTimes(2);
|
|
1425
|
+
});
|
|
1426
|
+
});
|
|
1427
|
+
|
|
1428
|
+
describe("InvestigationPanel completed actions", () => {
|
|
1429
|
+
test("creates a run-bound fix task and replaces the action with success", async () => {
|
|
1430
|
+
postMock
|
|
1431
|
+
.mockResolvedValueOnce(completedResponse() as never)
|
|
1432
|
+
.mockResolvedValueOnce(
|
|
1433
|
+
successfulResponse({ aiRunId: FIX_RUN_ID }) as never,
|
|
1434
|
+
);
|
|
1435
|
+
|
|
1436
|
+
renderPanel();
|
|
1437
|
+
await flush();
|
|
1438
|
+
fireEvent.click(fixButton()!);
|
|
1439
|
+
await flush();
|
|
1440
|
+
|
|
1441
|
+
expect(screen.getByRole("alert")).toHaveTextContent("Fix task created");
|
|
1442
|
+
expect(fixButton()).toBeNull();
|
|
1443
|
+
expect(requestPath(1)).toContain("/ai-investigation/create-fix-task");
|
|
1444
|
+
expect(postRequestAt(1).data).toEqual({
|
|
1445
|
+
subjectType: "incident",
|
|
1446
|
+
subjectId: INCIDENT_ID.toString(),
|
|
1447
|
+
investigationRunId: RUN_ID,
|
|
1448
|
+
});
|
|
1449
|
+
expect(screen.getByText("View task progress")).toBeVisible();
|
|
1450
|
+
});
|
|
1451
|
+
|
|
1452
|
+
test("keeps the Recommended action and shows a friendly task error", async () => {
|
|
1453
|
+
postMock
|
|
1454
|
+
.mockResolvedValueOnce(completedResponse() as never)
|
|
1455
|
+
.mockResolvedValueOnce(
|
|
1456
|
+
new HTTPErrorResponse(
|
|
1457
|
+
400,
|
|
1458
|
+
{ message: "No connected repository exists." },
|
|
1459
|
+
{},
|
|
1460
|
+
) as never,
|
|
1461
|
+
);
|
|
1462
|
+
|
|
1463
|
+
renderPanel();
|
|
1464
|
+
await flush();
|
|
1465
|
+
fireEvent.click(fixButton()!);
|
|
1466
|
+
await flush();
|
|
1467
|
+
|
|
1468
|
+
expect(screen.getByRole("alert")).toHaveTextContent(
|
|
1469
|
+
"Could not create the fix task",
|
|
1470
|
+
);
|
|
1471
|
+
expect(screen.getByRole("alert")).toHaveTextContent(
|
|
1472
|
+
"No connected repository exists",
|
|
1473
|
+
);
|
|
1474
|
+
expect(fixButton()).toBeEnabled();
|
|
1475
|
+
});
|
|
1476
|
+
|
|
1477
|
+
test("clears settled fix-task state after subject navigation", async () => {
|
|
1478
|
+
const nextAnalysis: string =
|
|
1479
|
+
"## Alert root cause\n\nA deployment removed the required credential.";
|
|
1480
|
+
postMock
|
|
1481
|
+
.mockResolvedValueOnce(completedResponse() as never)
|
|
1482
|
+
.mockResolvedValueOnce(
|
|
1483
|
+
successfulResponse({ aiRunId: FIX_RUN_ID }) as never,
|
|
1484
|
+
)
|
|
1485
|
+
.mockResolvedValueOnce(
|
|
1486
|
+
completedResponse({
|
|
1487
|
+
runId: NEXT_RUN_ID,
|
|
1488
|
+
analysisMarkdown: nextAnalysis,
|
|
1489
|
+
}) as never,
|
|
1490
|
+
);
|
|
1491
|
+
|
|
1492
|
+
const view: ReturnType<typeof render> = renderPanel();
|
|
1493
|
+
await flush();
|
|
1494
|
+
fireEvent.click(fixButton()!);
|
|
1495
|
+
await flush();
|
|
1496
|
+
expect(screen.getByRole("alert")).toHaveTextContent("Fix task created");
|
|
1497
|
+
|
|
1498
|
+
view.rerender(
|
|
1499
|
+
<InvestigationPanel subjectType="alert" subjectId={ALERT_ID} />,
|
|
1500
|
+
);
|
|
1501
|
+
await flush();
|
|
1502
|
+
|
|
1503
|
+
expect(screen.queryByText(/Fix task created/)).toBeNull();
|
|
1504
|
+
expect(screen.getByTestId("investigation-markdown")).toHaveTextContent(
|
|
1505
|
+
"A deployment removed the required credential.",
|
|
1506
|
+
);
|
|
1507
|
+
expect(fixButton()).toBeEnabled();
|
|
1508
|
+
});
|
|
1509
|
+
|
|
1510
|
+
test("ignores a fix-task result that resolves on another subject", async () => {
|
|
1511
|
+
const staleTask: Deferred<ApiResponse> = createDeferred<ApiResponse>();
|
|
1512
|
+
postMock
|
|
1513
|
+
.mockResolvedValueOnce(completedResponse() as never)
|
|
1514
|
+
.mockReturnValueOnce(staleTask.promise as never)
|
|
1515
|
+
.mockResolvedValueOnce(
|
|
1516
|
+
completedResponse({ runId: NEXT_RUN_ID }) as never,
|
|
1517
|
+
);
|
|
1518
|
+
|
|
1519
|
+
const view: ReturnType<typeof render> = renderPanel();
|
|
1520
|
+
await flush();
|
|
1521
|
+
fireEvent.click(fixButton()!);
|
|
1522
|
+
await flush();
|
|
1523
|
+
|
|
1524
|
+
view.rerender(
|
|
1525
|
+
<InvestigationPanel subjectType="alert" subjectId={ALERT_ID} />,
|
|
1526
|
+
);
|
|
1527
|
+
await flush();
|
|
1528
|
+
await resolveDeferred(
|
|
1529
|
+
staleTask,
|
|
1530
|
+
successfulResponse({ aiRunId: FIX_RUN_ID }),
|
|
1531
|
+
);
|
|
1532
|
+
|
|
1533
|
+
expect(screen.queryByRole("alert")).toBeNull();
|
|
1534
|
+
expect(fixButton()).toBeEnabled();
|
|
1535
|
+
expect(screen.getByText("Investigation complete")).toBeVisible();
|
|
1536
|
+
});
|
|
1537
|
+
|
|
1538
|
+
test("resets fix-task and verdict state when the same subject gets a new run", async () => {
|
|
1539
|
+
const nextAnalysis: string =
|
|
1540
|
+
"## New root cause\n\nA later investigation found a certificate rollover.";
|
|
1541
|
+
postMock
|
|
1542
|
+
.mockResolvedValueOnce(completedResponse() as never)
|
|
1543
|
+
.mockResolvedValueOnce(
|
|
1544
|
+
successfulResponse({ aiRunId: FIX_RUN_ID }) as never,
|
|
1545
|
+
)
|
|
1546
|
+
.mockResolvedValueOnce(successfulResponse({}) as never)
|
|
1547
|
+
.mockResolvedValueOnce(
|
|
1548
|
+
completedResponse({
|
|
1549
|
+
runId: NEXT_RUN_ID,
|
|
1550
|
+
analysisMarkdown: nextAnalysis,
|
|
1551
|
+
}) as never,
|
|
1552
|
+
);
|
|
1553
|
+
|
|
1554
|
+
renderPanel();
|
|
1555
|
+
await flush();
|
|
1556
|
+
fireEvent.click(fixButton()!);
|
|
1557
|
+
await flush();
|
|
1558
|
+
fireEvent.click(screen.getByRole("button", { name: "Confirmed" }));
|
|
1559
|
+
await flush();
|
|
1560
|
+
|
|
1561
|
+
expect(screen.getByRole("alert")).toHaveTextContent("Fix task created");
|
|
1562
|
+
expect(screen.getByText(/You confirmed this analysis/)).toBeVisible();
|
|
1563
|
+
|
|
1564
|
+
await tick(SETTLED_POLL_INTERVAL_MS);
|
|
1565
|
+
|
|
1566
|
+
expect(screen.getByTestId("investigation-markdown")).toHaveTextContent(
|
|
1567
|
+
"A later investigation found a certificate rollover.",
|
|
1568
|
+
);
|
|
1569
|
+
expect(screen.queryByText(/Fix task created/)).toBeNull();
|
|
1570
|
+
expect(screen.queryByText(/You confirmed this analysis/)).toBeNull();
|
|
1571
|
+
expect(fixButton()).toBeEnabled();
|
|
1572
|
+
expect(screen.getByRole("button", { name: "Confirmed" })).toBeEnabled();
|
|
1573
|
+
});
|
|
1574
|
+
|
|
1575
|
+
test("binds a verdict to the displayed run even without a fix recommendation", async () => {
|
|
1576
|
+
postMock
|
|
1577
|
+
.mockResolvedValueOnce(
|
|
1578
|
+
completedResponse({
|
|
1579
|
+
codeFixRecommendation: AIRunCodeFixRecommendation.NotRecommended,
|
|
1580
|
+
}) as never,
|
|
1581
|
+
)
|
|
1582
|
+
.mockResolvedValueOnce(successfulResponse({}) as never);
|
|
1583
|
+
|
|
1584
|
+
renderPanel();
|
|
1585
|
+
await flush();
|
|
1586
|
+
fireEvent.click(screen.getByRole("button", { name: "Confirmed" }));
|
|
1587
|
+
await flush();
|
|
1588
|
+
|
|
1589
|
+
expect(requestPath(1)).toContain("/ai-investigation/verdict");
|
|
1590
|
+
expect(postRequestAt(1).data).toEqual({
|
|
1591
|
+
subjectType: "incident",
|
|
1592
|
+
subjectId: INCIDENT_ID.toString(),
|
|
1593
|
+
investigationRunId: RUN_ID,
|
|
1594
|
+
verdict: "Confirmed",
|
|
1595
|
+
});
|
|
1596
|
+
expect(screen.getByText(/You confirmed this analysis/)).toBeVisible();
|
|
1597
|
+
});
|
|
1598
|
+
|
|
1599
|
+
test("retains an existing verdict when a fix is not recommended", async () => {
|
|
1600
|
+
postMock.mockResolvedValue(
|
|
1601
|
+
completedResponse({
|
|
1602
|
+
codeFixRecommendation: AIRunCodeFixRecommendation.NotRecommended,
|
|
1603
|
+
humanVerdict: "Confirmed",
|
|
1604
|
+
}) as never,
|
|
1605
|
+
);
|
|
1606
|
+
|
|
1607
|
+
renderPanel();
|
|
1608
|
+
await flush();
|
|
1609
|
+
|
|
1610
|
+
expect(fixButton()).toBeNull();
|
|
1611
|
+
expect(screen.getByText(/You confirmed this analysis/)).toBeVisible();
|
|
1612
|
+
expect(screen.getByText("Change")).toBeVisible();
|
|
1613
|
+
expect(screen.getByText("Rate this investigation")).toBeVisible();
|
|
1614
|
+
});
|
|
1615
|
+
|
|
1616
|
+
test("a GET started before verdict save cannot overwrite the saved verdict", async () => {
|
|
1617
|
+
const stalePoll: Deferred<ApiResponse> = createDeferred<ApiResponse>();
|
|
1618
|
+
postMock
|
|
1619
|
+
.mockResolvedValueOnce(completedResponse() as never)
|
|
1620
|
+
.mockReturnValueOnce(stalePoll.promise as never)
|
|
1621
|
+
.mockResolvedValueOnce(successfulResponse({}) as never);
|
|
1622
|
+
|
|
1623
|
+
renderPanel();
|
|
1624
|
+
await flush();
|
|
1625
|
+
await tick(SETTLED_POLL_INTERVAL_MS);
|
|
1626
|
+
expect(postMock).toHaveBeenCalledTimes(2);
|
|
1627
|
+
|
|
1628
|
+
fireEvent.click(screen.getByRole("button", { name: "Confirmed" }));
|
|
1629
|
+
await flush();
|
|
1630
|
+
expect(screen.getByText(/You confirmed this analysis/)).toBeVisible();
|
|
1631
|
+
|
|
1632
|
+
await resolveDeferred(stalePoll, completedResponse({ humanVerdict: null }));
|
|
1633
|
+
|
|
1634
|
+
expect(screen.getByText(/You confirmed this analysis/)).toBeVisible();
|
|
1635
|
+
expect(screen.queryByRole("button", { name: "Confirmed" })).toBeNull();
|
|
1636
|
+
});
|
|
1637
|
+
|
|
1638
|
+
test("rolls back an optimistic verdict when save fails", async () => {
|
|
1639
|
+
postMock
|
|
1640
|
+
.mockResolvedValueOnce(completedResponse() as never)
|
|
1641
|
+
.mockResolvedValueOnce(
|
|
1642
|
+
new HTTPErrorResponse(
|
|
1643
|
+
500,
|
|
1644
|
+
{ message: "Verdict storage is unavailable." },
|
|
1645
|
+
{},
|
|
1646
|
+
) as never,
|
|
1647
|
+
);
|
|
1648
|
+
|
|
1649
|
+
renderPanel();
|
|
1650
|
+
await flush();
|
|
1651
|
+
fireEvent.click(screen.getByRole("button", { name: "Rejected" }));
|
|
1652
|
+
await flush();
|
|
1653
|
+
|
|
1654
|
+
expect(screen.getByRole("alert")).toHaveTextContent(
|
|
1655
|
+
"Could not save your verdict",
|
|
1656
|
+
);
|
|
1657
|
+
expect(screen.getByRole("alert")).toHaveTextContent(
|
|
1658
|
+
"Verdict storage is unavailable.",
|
|
1659
|
+
);
|
|
1660
|
+
expect(screen.queryByText(/You rejected this analysis/)).toBeNull();
|
|
1661
|
+
expect(screen.getByRole("button", { name: "Rejected" })).toBeEnabled();
|
|
1662
|
+
});
|
|
1663
|
+
|
|
1664
|
+
test("ignores a verdict result that resolves after navigation", async () => {
|
|
1665
|
+
const staleVerdict: Deferred<ApiResponse> = createDeferred<ApiResponse>();
|
|
1666
|
+
postMock
|
|
1667
|
+
.mockResolvedValueOnce(
|
|
1668
|
+
completedResponse({
|
|
1669
|
+
codeFixRecommendation: AIRunCodeFixRecommendation.NotRecommended,
|
|
1670
|
+
}) as never,
|
|
1671
|
+
)
|
|
1672
|
+
.mockReturnValueOnce(staleVerdict.promise as never)
|
|
1673
|
+
.mockResolvedValueOnce(
|
|
1674
|
+
completedResponse({
|
|
1675
|
+
runId: NEXT_RUN_ID,
|
|
1676
|
+
codeFixRecommendation: AIRunCodeFixRecommendation.NotRecommended,
|
|
1677
|
+
}) as never,
|
|
1678
|
+
);
|
|
1679
|
+
|
|
1680
|
+
const view: ReturnType<typeof render> = renderPanel();
|
|
1681
|
+
await flush();
|
|
1682
|
+
fireEvent.click(screen.getByRole("button", { name: "Confirmed" }));
|
|
1683
|
+
await flush();
|
|
1684
|
+
|
|
1685
|
+
view.rerender(
|
|
1686
|
+
<InvestigationPanel subjectType="alert" subjectId={ALERT_ID} />,
|
|
1687
|
+
);
|
|
1688
|
+
await flush();
|
|
1689
|
+
await resolveDeferred(staleVerdict, successfulResponse({}));
|
|
1690
|
+
|
|
1691
|
+
expect(screen.queryByText(/You confirmed this analysis/)).toBeNull();
|
|
1692
|
+
expect(screen.queryByRole("alert")).toBeNull();
|
|
1693
|
+
expect(screen.getByRole("button", { name: "Confirmed" })).toBeEnabled();
|
|
1694
|
+
});
|
|
1695
|
+
});
|