@oneuptime/common 14.0.8 → 14.0.9
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/Index.ts +2 -0
- package/Models/DatabaseModels/KubernetesAiAgent.ts +364 -0
- package/Models/DatabaseModels/KubernetesCluster.ts +3 -3
- package/Models/DatabaseModels/Project.ts +55 -5
- package/Models/DatabaseModels/ProxmoxResource.ts +49 -1
- package/Models/DatabaseModels/RunnerJob.ts +75 -0
- package/Models/DatabaseModels/TeamComplianceSetting.ts +143 -2
- package/Models/DatabaseModels/WorkspaceProjectAuthToken.ts +17 -0
- package/Server/API/AIChatAPI.ts +6 -3
- package/Server/API/AIInvestigationAPI.ts +17 -10
- package/Server/API/AutoRemediationAPI.ts +207 -25
- package/Server/API/KubernetesClusterAiAccessAPI.ts +623 -21
- package/Server/API/MicrosoftTeamsAPI.ts +93 -14
- package/Server/API/ProxmoxResourceAPI.ts +244 -14
- package/Server/Infrastructure/GlobalCache.ts +32 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/1796000000000-AddTeamComplianceRuleScope.ts +99 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/1796100000000-AddKubernetesAiAgentAndAiDefaults.ts +137 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/1796200000000-AddProxmoxResourceNativePushColumns.ts +42 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/Index.ts +6 -0
- package/Server/Middleware/TelemetryIngest.ts +41 -18
- package/Server/Services/AIService.ts +70 -1
- package/Server/Services/AlertService.ts +352 -11
- package/Server/Services/AlertSeverityService.ts +65 -15
- package/Server/Services/AutoRemediationRuleService.ts +1 -1
- package/Server/Services/IncidentService.ts +280 -104
- package/Server/Services/IncidentSeverityService.ts +66 -15
- package/Server/Services/KubernetesAiAgentJobService.ts +185 -0
- package/Server/Services/KubernetesAiAgentService.ts +1634 -0
- package/Server/Services/KubernetesClusterAiAccessService.ts +1118 -451
- package/Server/Services/KubernetesClusterService.ts +129 -21
- package/Server/Services/ProjectService.ts +32 -0
- package/Server/Services/ProxmoxResourceService.ts +322 -5
- package/Server/Services/RunnerJobService.ts +241 -104
- package/Server/Services/RunnerService.ts +1 -1
- package/Server/Services/ScheduledMaintenanceService.ts +737 -1
- package/Server/Services/ScheduledMaintenanceStateTimelineService.ts +414 -56
- package/Server/Services/TeamComplianceSettingService.ts +1125 -9
- package/Server/Utils/AI/ClusterAccess/ClusterAccessContext.ts +67 -9
- package/Server/Utils/AI/ClusterAccess/KubectlInvestigationToolkit.ts +39 -10
- package/Server/Utils/AI/ClusterAccess/KubectlJobRunner.ts +14 -3
- package/Server/Utils/AI/Remediation/RemediationCommandTools.ts +37 -15
- package/Server/Utils/AI/Remediation/RemediationExecutionRunner.ts +67 -24
- package/Server/Utils/AI/SRE/AIInvestigationEngine.ts +48 -5
- package/Server/Utils/AI/SRE/IncidentPostmortemRunner.ts +40 -8
- package/Server/Utils/AI/SRE/InvestigationEligibility.ts +13 -2
- package/Server/Utils/AI/SRE/InvestigationGrader.ts +1 -1
- package/Server/Utils/AutoRemediation/CommandPlanExecutor.ts +6 -1
- package/Server/Utils/Telemetry/InventoryEntityRegistry.ts +133 -7
- package/Server/Utils/Telemetry/ProxmoxCephSnapshotScan.ts +58 -4
- package/Server/Utils/Telemetry/ProxmoxNativeNodeLiveness.ts +447 -0
- package/Server/Utils/Telemetry/ProxmoxNativePush.ts +903 -0
- package/Server/Utils/Telemetry/TelemetryEntity.ts +87 -27
- package/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeams.ts +684 -47
- package/Tests/App/AdminDashboard/RowActionPlacementOverrides.test.tsx +278 -0
- package/Tests/App/Dashboard/AIChatChartWidgetZoom.test.tsx +738 -0
- package/Tests/App/Dashboard/AIFeaturesSettingsPointers.test.tsx +147 -0
- package/Tests/App/Dashboard/AIInsightsKubernetesClusterPage.test.tsx +1523 -0
- package/Tests/App/Dashboard/AdminUserNotificationMethodsPage.test.tsx +463 -3
- package/Tests/App/Dashboard/AffectedResourcesPickerDropdownPlacement.test.tsx +958 -0
- package/Tests/App/Dashboard/AffectedResourcesPickerFocusLeave.test.tsx +595 -0
- package/Tests/App/Dashboard/AskAiAvailability.test.ts +20 -3
- package/Tests/App/Dashboard/AskAiDisabled.test.tsx +8 -3
- package/Tests/App/Dashboard/CephRateChartReload.test.tsx +1456 -0
- package/Tests/App/Dashboard/CephTimeRangeZoom.test.tsx +1229 -0
- package/Tests/App/Dashboard/ChartCardTimeRangeZoom.test.tsx +151 -0
- package/Tests/App/Dashboard/ChartCardZoomAffordances.test.tsx +257 -0
- package/Tests/App/Dashboard/ChartRefetchFrame.test.tsx +141 -0
- package/Tests/App/Dashboard/ChartZoomHarness.tsx +249 -0
- package/Tests/App/Dashboard/ChartZoomStandIn.tsx +131 -0
- package/Tests/App/Dashboard/CloudServerlessSlowLoads.test.tsx +628 -0
- package/Tests/App/Dashboard/CloudServerlessTimeRangeZoom.test.tsx +607 -0
- package/Tests/App/Dashboard/ClusterAccessNotice.test.tsx +91 -19
- package/Tests/App/Dashboard/CompanionMetricsTabZoom.test.tsx +632 -0
- package/Tests/App/Dashboard/ContainerChartZoomStandIn.tsx +195 -0
- package/Tests/App/Dashboard/ContainerHostOverviewSlowLoads.test.tsx +542 -0
- package/Tests/App/Dashboard/ContainerHostOverviewTimeRangeZoom.test.tsx +848 -0
- package/Tests/App/Dashboard/ContainerMetricsTimeRangeZoom.test.tsx +561 -0
- package/Tests/App/Dashboard/DashboardBoardWideZoomShell.test.tsx +1147 -0
- package/Tests/App/Dashboard/DashboardEditPermissions.test.tsx +3 -2
- package/Tests/App/Dashboard/DashboardHistogramWidgetDragCursor.test.tsx +399 -0
- package/Tests/App/Dashboard/DashboardHistogramWidgetZoom.test.tsx +985 -0
- package/Tests/App/Dashboard/DashboardHistogramWidgetZoomHint.test.tsx +803 -0
- package/Tests/App/Dashboard/DashboardLazyPages.test.tsx +1 -0
- package/Tests/App/Dashboard/DashboardSettingsPreviewZoom.test.tsx +679 -0
- package/Tests/App/Dashboard/DashboardSloAndDataSourceChartZoom.test.tsx +881 -0
- package/Tests/App/Dashboard/DashboardTraceStatusWidgets.test.tsx +878 -0
- package/Tests/App/Dashboard/DatabaseMetricChartModalTimeRangeZoom.test.tsx +442 -0
- package/Tests/App/Dashboard/DatabaseOverviewTimeRangeZoom.test.tsx +831 -0
- package/Tests/App/Dashboard/DatabaseServerComponents.test.tsx +43 -0
- package/Tests/App/Dashboard/DockerSwarmInsightsTimeRangeZoom.test.tsx +524 -0
- package/Tests/App/Dashboard/DockerSwarmRateChartRefresh.test.tsx +259 -0
- package/Tests/App/Dashboard/EdgeDetailPanelHistory.test.tsx +763 -0
- package/Tests/App/Dashboard/EdgeDetailPanelTimeRangeZoom.test.tsx +547 -0
- package/Tests/App/Dashboard/EdgeHistoryBucketsCases.tsx +565 -0
- package/Tests/App/Dashboard/EdgeHistoryBucketsKathmandu.test.tsx +73 -0
- package/Tests/App/Dashboard/EdgeHistoryBucketsKolkata.test.tsx +74 -0
- package/Tests/App/Dashboard/EdgeHistoryBucketsNewYork.test.tsx +72 -0
- package/Tests/App/Dashboard/EdgeHistoryBucketsUtc.test.tsx +72 -0
- package/Tests/App/Dashboard/EmbeddedMetricCardRefresh.test.tsx +310 -0
- package/Tests/App/Dashboard/EmbeddedMetricCardTimeRangeZoom.test.tsx +536 -0
- package/Tests/App/Dashboard/EscalationRuleReadiness.test.tsx +3 -1
- package/Tests/App/Dashboard/EscalationRuleRowActions.test.tsx +660 -0
- package/Tests/App/Dashboard/EventNotes.test.tsx +2 -2
- package/Tests/App/Dashboard/EventOverviewPages.test.tsx +587 -13
- package/Tests/App/Dashboard/EventOverviewSnapshotExplorerZoom.test.tsx +1139 -0
- package/Tests/App/Dashboard/EventOverviewSnapshotZoomAcrossTabs.test.tsx +1057 -0
- package/Tests/App/Dashboard/EventOverviewTelemetrySnapshotZoom.test.tsx +757 -0
- package/Tests/App/Dashboard/ExceptionOccurrenceTrendZoom.test.tsx +675 -0
- package/Tests/App/Dashboard/ExceptionOccurrencesZoomSurvivesViewSwitch.test.tsx +405 -0
- package/Tests/App/Dashboard/ExceptionSignalPages.test.tsx +67 -3
- package/Tests/App/Dashboard/ExceptionTablesSpanStatus.test.tsx +515 -0
- package/Tests/App/Dashboard/ExplorerOwnZoomRegression.test.tsx +739 -0
- package/Tests/App/Dashboard/FlowBandwidthAxisCases.tsx +461 -0
- package/Tests/App/Dashboard/FlowBandwidthAxisKathmandu.test.tsx +25 -0
- package/Tests/App/Dashboard/FlowBandwidthAxisKolkata.test.tsx +25 -0
- package/Tests/App/Dashboard/FlowBandwidthAxisNewYork.test.tsx +25 -0
- package/Tests/App/Dashboard/FlowBandwidthAxisUtc.test.tsx +24 -0
- package/Tests/App/Dashboard/FlowTopTalkersZoomRefetch.test.tsx +722 -0
- package/Tests/App/Dashboard/FlowViewportGuard.test.tsx +2526 -0
- package/Tests/App/Dashboard/HostPagesSlowLoads.test.tsx +872 -0
- package/Tests/App/Dashboard/HostPagesTimeRangeZoom.test.tsx +1208 -0
- package/Tests/App/Dashboard/IncidentRootCauseTimeRangeZoom.test.tsx +358 -0
- package/Tests/App/Dashboard/InfrastructureGraph.test.tsx +100 -9
- package/Tests/App/Dashboard/InfrastructureMetricsTabTimeRangeZoom.test.tsx +485 -0
- package/Tests/App/Dashboard/InvestigationDrawerCompanionZoom.test.tsx +655 -0
- package/Tests/App/Dashboard/InvestigationDrawerZoom.test.tsx +830 -0
- package/Tests/App/Dashboard/InvestigationNotStarted.test.tsx +338 -2
- package/Tests/App/Dashboard/InvoicesPayInvoice.test.tsx +454 -16
- package/Tests/App/Dashboard/KubernetesClusterAiAccessSettings.test.ts +799 -819
- package/Tests/App/Dashboard/KubernetesClusterAiAgentOverviewCard.test.tsx +367 -0
- package/Tests/App/Dashboard/KubernetesClusterAiAgentStatus.test.ts +1202 -0
- package/Tests/App/Dashboard/KubernetesClusterAiDocumentationMarkdown.test.ts +106 -0
- package/Tests/App/Dashboard/KubernetesClusterAiNavigation.test.tsx +354 -0
- package/Tests/App/Dashboard/KubernetesClusterAiPage.test.tsx +1884 -2054
- package/Tests/App/Dashboard/KubernetesClusterAiPageServerParity.test.ts +478 -179
- package/Tests/App/Dashboard/KubernetesClusterMetricTooltips.test.ts +50 -0
- package/Tests/App/Dashboard/KubernetesClusterOverviewTooltips.test.tsx +2 -1
- package/Tests/App/Dashboard/KubernetesClusterOverviewZoom.test.tsx +1217 -0
- package/Tests/App/Dashboard/KubernetesClusterPagesZoom.test.tsx +731 -0
- package/Tests/App/Dashboard/KubernetesCostsLoadRecovery.test.tsx +869 -0
- package/Tests/App/Dashboard/KubernetesCostsZoom.test.tsx +1214 -0
- package/Tests/App/Dashboard/KubernetesCostsZoomExactHours.test.tsx +692 -0
- package/Tests/App/Dashboard/KubernetesNetworkThroughputReload.test.tsx +902 -0
- package/Tests/App/Dashboard/KubernetesNetworkZoomBucketEdges.test.tsx +571 -0
- package/Tests/App/Dashboard/KubernetesOverviewZoomBucketEdges.test.tsx +697 -0
- package/Tests/App/Dashboard/KubernetesResourceMetricsTabZoom.test.tsx +633 -0
- package/Tests/App/Dashboard/LogsExplorerTimeRangeZoom.test.tsx +470 -0
- package/Tests/App/Dashboard/LogsInsightsTimeRangeZoom.test.tsx +948 -0
- package/Tests/App/Dashboard/LogsViewerPinnedWindowZoom.test.tsx +486 -0
- package/Tests/App/Dashboard/MetricChartsBucketInspectorDoubleClick.test.tsx +1001 -0
- package/Tests/App/Dashboard/MetricExplorerInvestigationAutoRefresh.test.tsx +514 -0
- package/Tests/App/Dashboard/MetricExplorerSavedViewZoomRoundTrip.test.tsx +581 -0
- package/Tests/App/Dashboard/MetricExplorerTimeRangeZoom.test.tsx +1162 -0
- package/Tests/App/Dashboard/MetricMonitorPreviewZoomLabel.test.ts +72 -0
- package/Tests/App/Dashboard/MetricRowSparklineTimeRangeZoom.test.tsx +204 -0
- package/Tests/App/Dashboard/MetricViewFirstResultLands.test.tsx +408 -0
- package/Tests/App/Dashboard/MetricViewOwnZoomResetPlacement.test.tsx +952 -0
- package/Tests/App/Dashboard/MetricViewTimeRange.test.ts +139 -0
- package/Tests/App/Dashboard/MetricViewTimeRangeZoom.test.tsx +556 -0
- package/Tests/App/Dashboard/MonitorMetricsChartWrappersZoom.test.tsx +552 -0
- package/Tests/App/Dashboard/MonitorMetricsTabsTimeRangeZoom.test.tsx +740 -0
- package/Tests/App/Dashboard/MonitorOverviewChartsTimeRangeZoom.test.tsx +735 -0
- package/Tests/App/Dashboard/MonitorStepPreviewsTimeRangeZoom.test.tsx +835 -0
- package/Tests/App/Dashboard/NetworkDeviceTimeRangeZoom.test.tsx +1115 -0
- package/Tests/App/Dashboard/OnCallPreventionGuards.test.tsx +186 -17
- package/Tests/App/Dashboard/OnCallRulesDeleteGuard.test.tsx +155 -46
- package/Tests/App/Dashboard/PasskeySettings.test.tsx +87 -2
- package/Tests/App/Dashboard/ProjectAISettingsPages.test.tsx +884 -0
- package/Tests/App/Dashboard/ProxmoxNodeRemove.test.tsx +621 -0
- package/Tests/App/Dashboard/ProxmoxOverviewSlowLoads.test.tsx +584 -0
- package/Tests/App/Dashboard/ProxmoxPagesTimeRangeZoom.test.tsx +823 -0
- package/Tests/App/Dashboard/ProxmoxRateChartReload.test.tsx +1253 -0
- package/Tests/App/Dashboard/PublicDashboardBoardWideZoom.test.tsx +605 -0
- package/Tests/App/Dashboard/RealRowsLineChart.tsx +250 -0
- package/Tests/App/Dashboard/RechartsHostsPlotCursor.test.tsx +513 -0
- package/Tests/App/Dashboard/RowActionPlacementOverrides.test.tsx +865 -0
- package/Tests/App/Dashboard/RumOverviewTimeRangeZoom.test.tsx +877 -0
- package/Tests/App/Dashboard/RunnerKubernetesAgentForm.test.tsx +20 -4
- package/Tests/App/Dashboard/RunnerSupersededByAiAgentNote.test.tsx +296 -0
- package/Tests/App/Dashboard/SecurityEventConnectionsTable.test.tsx +146 -36
- package/Tests/App/Dashboard/ServiceDatabaseSlowLoads.test.tsx +1166 -0
- package/Tests/App/Dashboard/ServiceMapGraph.test.tsx +1243 -104
- package/Tests/App/Dashboard/ServiceOverviewTimeRangeZoom.test.tsx +792 -0
- package/Tests/App/Dashboard/SloBudgetBurnDownZoom.test.tsx +771 -0
- package/Tests/App/Dashboard/SloBudgetBurnDownZoomAffordances.test.tsx +380 -0
- package/Tests/App/Dashboard/SloEventMetricsBarChartZoom.test.tsx +369 -0
- package/Tests/App/Dashboard/SloHistoryChartsTimeRangeZoom.test.tsx +694 -0
- package/Tests/App/Dashboard/SloHistoryChartsZoomAffordances.test.tsx +497 -0
- package/Tests/App/Dashboard/SloMetricsTimeRangeZoom.test.tsx +541 -0
- package/Tests/App/Dashboard/SlowLoadHarness.ts +123 -0
- package/Tests/App/Dashboard/SnapshotExplorerPrimaryZoom.test.tsx +1194 -0
- package/Tests/App/Dashboard/SnapshotViewerPinnedWindow.test.tsx +620 -0
- package/Tests/App/Dashboard/TelemetrySnapshotPanelTimeRangeZoom.test.tsx +765 -0
- package/Tests/App/Dashboard/TelemetrySnapshotZoom.test.tsx +516 -0
- package/Tests/App/Dashboard/TelemetrySnapshotZoomAcrossTabs.test.tsx +827 -0
- package/Tests/App/Dashboard/TimeRangeZoomPageHarness.tsx +463 -0
- package/Tests/App/Dashboard/TraceChartEditorStatusSplit.test.tsx +543 -0
- package/Tests/App/Dashboard/TraceStatusPresentation.test.tsx +967 -0
- package/Tests/App/Dashboard/TraceTimelineMinimapZoom.test.tsx +315 -0
- package/Tests/App/Dashboard/TracesAnalyticsStatusSplit.test.tsx +778 -0
- package/Tests/App/Dashboard/TracesAnalyticsViewZoom.test.tsx +349 -0
- package/Tests/App/Dashboard/TracesExplorerTimeRangeZoom.test.tsx +551 -0
- package/Tests/App/Dashboard/TracesStatusChartAndFacet.test.tsx +687 -0
- package/Tests/App/Dashboard/TracesViewerStaleResponses.test.tsx +458 -0
- package/Tests/App/Dashboard/UptimeStripsExcludedFromZoom.test.tsx +406 -0
- package/Tests/App/Dashboard/VMwareOverviewSlowLoads.test.tsx +535 -0
- package/Tests/App/Dashboard/VMwarePagesTimeRangeZoom.test.tsx +606 -0
- package/Tests/App/Dashboard/ValueWidgetSparklineStaleHover.test.tsx +1292 -0
- package/Tests/App/Dashboard/ValueWidgetSparklineZoom.test.tsx +765 -0
- package/Tests/App/StatusPage/StatusPageResourceExplorer.test.tsx +98 -0
- package/Tests/Models/DatabaseModels/KubernetesAiAgentModel.test.ts +295 -0
- package/Tests/Models/DatabaseModels/TeamComplianceSettingModel.test.ts +347 -0
- package/Tests/Server/API/AIChatRouteAuthorization.test.ts +58 -0
- package/Tests/Server/API/AIGenerationProjectAIToggle.test.ts +1 -1
- package/Tests/Server/API/AIInvestigationAPI.test.ts +123 -45
- package/Tests/Server/API/AutoRemediationApproveClusterRound.test.ts +1329 -0
- package/Tests/Server/API/AutoRemediationApproveKubectlRecheck.test.ts +25 -2
- package/Tests/Server/API/KubernetesClusterAiAccessAPI.test.ts +2 -0
- package/Tests/Server/API/KubernetesClusterAiAccessAPIAgent.test.ts +903 -0
- package/Tests/Server/API/MicrosoftTeamsChatsRefreshRoute.test.ts +667 -0
- package/Tests/Server/API/MicrosoftTeamsManifest.test.ts +294 -6
- package/Tests/Server/API/ProxmoxResourceAPIRemoveNode.test.ts +1932 -0
- package/Tests/Server/Infrastructure/GlobalCache.test.ts +195 -0
- package/Tests/Server/Infrastructure/Postgres/AddKubernetesAiAgentAndAiDefaultsMigration.test.ts +623 -0
- package/Tests/Server/Infrastructure/Postgres/AddKubernetesClusterAiAccessMigration.test.ts +87 -10
- package/Tests/Server/Infrastructure/Postgres/AddTeamComplianceRuleScopeMigration.test.ts +622 -0
- package/Tests/Server/Middleware/TelemetryIngestBrowserKey.test.ts +61 -4
- package/Tests/Server/Middleware/TelemetryIngestKubernetesAgentRunnerPinnedKey.test.ts +98 -2
- package/Tests/Server/Middleware/TelemetryIngestKubernetesAgentRunnerRateLimit.test.ts +105 -2
- package/Tests/Server/Services/AIServiceBalanceBlocker.test.ts +282 -0
- package/Tests/Server/Services/AlertMonitorClearWritesNull.test.ts +409 -0
- package/Tests/Server/Services/AlertMonitorEditFeed.test.ts +474 -0
- package/Tests/Server/Services/AlertMonitorEditGuard.test.ts +610 -0
- package/Tests/Server/Services/AutoRemediationClusterRuleEngine.test.ts +2 -0
- package/Tests/Server/Services/AutoRemediationRuleCommandRunners.test.ts +2 -1
- package/Tests/Server/Services/IncidentAffectedMonitorsEdit.test.ts +1360 -0
- package/Tests/Server/Services/IncidentAlertSloLinkSurvivesEdit.test.ts +3 -1
- package/Tests/Server/Services/IncidentSeverityDeletePausesComplianceRules.test.ts +488 -0
- package/Tests/Server/Services/KubernetesAiAgentJobService.test.ts +329 -0
- package/Tests/Server/Services/KubernetesAiAgentServiceHelpers.test.ts +618 -0
- package/Tests/Server/Services/KubernetesAiAgentServiceLifecycle.test.ts +1444 -0
- package/Tests/Server/Services/KubernetesAiAgentServiceRegister.test.ts +1666 -0
- package/Tests/Server/Services/KubernetesClusterAiAccessDeletedRunnerRemedy.test.ts +140 -111
- package/Tests/Server/Services/KubernetesClusterAiAccessGapNextSteps.test.ts +267 -105
- package/Tests/Server/Services/KubernetesClusterAiAccessLegacySuperseded.test.ts +440 -0
- package/Tests/Server/Services/KubernetesClusterAiAccessService.test.ts +567 -411
- package/Tests/Server/Services/KubernetesClusterAiAccessTarget.test.ts +1168 -0
- package/Tests/Server/Services/KubernetesClusterAiSettingsPermission.test.ts +410 -12
- package/Tests/Server/Services/KubernetesClusterServiceAiAccessFeed.test.ts +55 -0
- package/Tests/Server/Services/ProjectServiceNewProjectAiDefaults.test.ts +288 -0
- package/Tests/Server/Services/ProxmoxResourcePostgres.test.ts +5633 -0
- package/Tests/Server/Services/ProxmoxResourceService.test.ts +6458 -5
- package/Tests/Server/Services/RunnerJobEnqueueKubectl.test.ts +48 -15
- package/Tests/Server/Services/RunnerJobEnqueueKubectlAiAgent.test.ts +620 -0
- package/Tests/Server/Services/RunnerJobKubectlWriteScope.test.ts +170 -3
- package/Tests/Server/Services/RunnerJobTimeoutReason.test.ts +72 -0
- package/Tests/Server/Services/RunnerServiceAgentName.test.ts +6 -4
- package/Tests/Server/Services/ScheduledMaintenanceAttachmentEdit.test.ts +2113 -0
- package/Tests/Server/Services/ScheduledMaintenanceMonitorRelease.test.ts +972 -0
- package/Tests/Server/Services/TeamComplianceSettingPostgres.test.ts +2237 -0
- package/Tests/Server/Services/TeamComplianceSettingService.test.ts +3988 -138
- package/Tests/Server/Utils/AI/ClusterAccessContext.test.ts +107 -1
- package/Tests/Server/Utils/AI/IncidentPostmortemRunner.test.ts +351 -0
- package/Tests/Server/Utils/AI/InvestigationClusterAccessWiring.test.ts +2 -0
- package/Tests/Server/Utils/AI/InvestigationEligibility.test.ts +310 -0
- package/Tests/Server/Utils/AI/KubectlInvestigationToolkit.test.ts +70 -0
- package/Tests/Server/Utils/AI/KubectlJobRunnerFinishedJob.test.ts +58 -0
- package/Tests/Server/Utils/AI/RemediationCommandToolsKubectl.test.ts +2 -0
- package/Tests/Server/Utils/AI/RemediationCommandToolsKubectlOutcome.test.ts +2 -0
- package/Tests/Server/Utils/AI/RemediationCommandToolsLiveCluster.test.ts +2 -0
- package/Tests/Server/Utils/AI/RemediationCommandToolsRunnerScope.test.ts +125 -1
- package/Tests/Server/Utils/AI/RemediationExecutionRunner.test.ts +252 -3
- package/Tests/Server/Utils/AI/RemediationExecutionRunnerClusterMode.test.ts +2 -0
- package/Tests/Server/Utils/AI/RemediationExecutionRunnerProjectGates.test.ts +240 -0
- package/Tests/Server/Utils/AI/RemediationKubectlCopyParity.test.ts +2 -0
- package/Tests/Server/Utils/AutoRemediation/CommandPlanExecutor.test.ts +2 -0
- package/Tests/Server/Utils/AutoRemediation/CommandPlanExecutorUnknownRun.test.ts +2 -0
- package/Tests/Server/Utils/AutoRemediation/CommandPlanVerification.test.ts +2 -0
- package/Tests/Server/Utils/Telemetry/HostAssetAttributesFromOtlp.test.ts +68 -0
- package/Tests/Server/Utils/Telemetry/InventoryMirrorPostgres.test.ts +370 -0
- package/Tests/Server/Utils/Telemetry/NetworkDeviceInventoryAttributes.test.ts +522 -0
- package/Tests/Server/Utils/Telemetry/ProxmoxCephSnapshotScan.test.ts +155 -0
- package/Tests/Server/Utils/Telemetry/ProxmoxNativeNodeLiveness.test.ts +8631 -0
- package/Tests/Server/Utils/Telemetry/ProxmoxNativePush.test.ts +2852 -0
- package/Tests/Server/Utils/Telemetry/TelemetryEntity.test.ts +367 -1
- package/Tests/Server/Utils/Telemetry/TelemetryIngestionKeyGuard.test.ts +45 -3
- package/Tests/Server/Utils/Workspace/MicrosoftTeamsChats.test.ts +25 -0
- package/Tests/Server/Utils/Workspace/MicrosoftTeamsGroupChatNames.test.ts +3119 -0
- package/Tests/Server/Utils/Workspace/MicrosoftTeamsGroupChatNamesPostgres.test.ts +1721 -0
- package/Tests/Types/Kubernetes/KubernetesAgentChartAiAccess.test.ts +581 -113
- package/Tests/Types/Kubernetes/KubernetesAiAgentAccessContract.test.ts +510 -0
- package/Tests/Types/Team/ComplianceRule.test.ts +1029 -0
- package/Tests/Types/Team/TeamComplianceStatus.test.ts +176 -0
- package/Tests/UI/Components/ActionButton/RowActions.test.tsx +641 -0
- package/Tests/UI/Components/ActionButton/RowActionsAccessibility.test.tsx +414 -0
- package/Tests/UI/Components/ActionButton/SplitActionButtons.test.ts +565 -0
- package/Tests/UI/Components/Charts/BarChartDragToSelect.test.tsx +386 -0
- package/Tests/UI/Components/Charts/ChartDoubleClickResetWithoutDblclick.test.tsx +1025 -0
- package/Tests/UI/Components/Charts/ChartGroupPageZoomHint.test.tsx +196 -0
- package/Tests/UI/Components/Charts/ChartPlaceholdersTakeDoubleClick.test.tsx +1861 -0
- package/Tests/UI/Components/Charts/ChartPlotClicksWhileResetArmed.test.tsx +382 -0
- package/Tests/UI/Components/Charts/ChartRangeSelection.test.tsx +704 -0
- package/Tests/UI/Components/Charts/ChartRangeSelectionDoubleClickReset.test.tsx +1481 -0
- package/Tests/UI/Components/Charts/ChartRangeSelectionRealRecharts.test.tsx +918 -0
- package/Tests/UI/Components/Charts/ChartRowBucketStart.test.ts +350 -0
- package/Tests/UI/Components/Charts/DoubleClickReset.test.tsx +996 -0
- package/Tests/UI/Components/Charts/HistogramDoubleClickResetRealRecharts.test.tsx +965 -0
- package/Tests/UI/Components/Charts/HistogramDriftThenRefetch.test.tsx +277 -0
- package/Tests/UI/Components/Charts/HistogramRangeSelectionDataChange.test.tsx +1757 -0
- package/Tests/UI/Components/Charts/HistogramRangeSelectionRealRecharts.test.tsx +560 -0
- package/Tests/UI/Components/Charts/TimeRangeZoom/ChartPageTimeRangeZoom.test.tsx +513 -0
- package/Tests/UI/Components/Charts/TimeRangeZoom/TimeRangeZoomContext.test.tsx +845 -0
- package/Tests/UI/Components/Charts/TimeRangeZoom/TimeRangeZoomHint.test.tsx +119 -0
- package/Tests/UI/Components/Charts/TimeRangeZoom/TimeRangeZoomHintTailwind.test.tsx +274 -0
- package/Tests/UI/Components/Charts/TimeRangeZoom/TimeRangeZoomUtil.test.ts +433 -0
- package/Tests/UI/Components/Charts/TimeRangeZoom/UseTimeRangeZoom.test.tsx +863 -0
- package/Tests/UI/Components/Charts/TimeRangeZoom/ZoomDocsPromises.test.tsx +1147 -0
- package/Tests/UI/Components/DisabledButtonTooltip.test.tsx +14 -3
- package/Tests/UI/Components/EntityDropdownEscape.test.tsx +454 -0
- package/Tests/UI/Components/EntityDropdownFocusLeave.test.tsx +420 -0
- package/Tests/UI/Components/Forms/FormFieldDropdownDisabled.test.tsx +233 -0
- package/Tests/UI/Components/Input.test.tsx +80 -1
- package/Tests/UI/Components/LogsAnalyticsViewZoom.test.tsx +947 -0
- package/Tests/UI/Components/LogsHistogramDragTooltip.test.tsx +17 -5
- package/Tests/UI/Components/LogsViewerAnalyticsZoom.test.tsx +501 -0
- package/Tests/UI/Components/LogsViewerHistogramZoom.test.tsx +1 -1
- package/Tests/UI/Components/ModelTable/BaseModelTableBulkDelete.test.tsx +6 -4
- package/Tests/UI/Components/ModelTable/BaseModelTablePermissionGating.test.tsx +60 -8
- package/Tests/UI/Components/ModelTable/BaseModelTableRowActions.test.tsx +476 -0
- package/Tests/UI/Components/ModelTable/ModelTableWrapContent.test.tsx +7 -5
- package/Tests/UI/Components/MoreMenuPortal.test.tsx +623 -0
- package/Tests/UI/Components/StatusPage/ResourceGridActions.test.tsx +538 -0
- package/Tests/UI/Components/StatusPage/ResourceGridSelection.test.tsx +14 -4
- package/Tests/UI/Components/TableCellWrapping.test.tsx +3 -3
- package/Tests/UI/Components/TableRowActions.test.tsx +472 -0
- package/Tests/UI/Components/TelemetryHistogramDragTooltip.test.tsx +1 -1
- package/Tests/UI/Components/TelemetryViewer/TelemetryViewerHistogramClickZoom.test.tsx +1 -1
- package/Tests/UI/Components/TelemetryViewer/TelemetryViewerZoomScope.test.tsx +470 -0
- package/Tests/UI/Components/TelemetryViewer/UseViewerTimeRangeZoom.test.tsx +571 -0
- package/Tests/UI/Components/TelemetryViewer/ViewerDoubleClickResetUnderDataChange.test.tsx +928 -0
- package/Tests/UI/Components/TelemetryViewer/ViewerFollowsEnclosingZoom.test.tsx +1569 -0
- package/Tests/UI/Components/Toggle.test.tsx +66 -0
- package/Tests/UI/MicrosoftTeams/MicrosoftTeamsChatsCardRefreshNames.test.tsx +1023 -0
- package/Tests/UI/Rum/ChunkLoader.test.ts +201 -0
- package/Tests/UI/Rum/FidelityNotices.test.ts +54 -0
- package/Tests/UI/Rum/ReplayCorrelationPanel.test.tsx +72 -0
- package/Tests/UI/Rum/ReplayFrameCapture.test.ts +1009 -9
- package/Tests/UI/Rum/ReplayRecordedAssets.test.ts +1069 -0
- package/Tests/UI/Rum/ReplayStage.test.tsx +913 -2
- package/Tests/UI/Rum/SessionReplayTable.test.tsx +443 -8
- package/Tests/UI/Rum/SessionReplayUsersTable.test.tsx +439 -9
- package/Tests/UI/Rum/UserFlowTablesRowActions.test.tsx +501 -0
- package/Tests/Utils/AiRemediation/KubectlPolicyChartRbacParity.test.ts +47 -19
- package/Tests/Utils/AiRemediation/KubectlWriteScopeCallerParity.test.ts +2 -0
- package/Tests/Utils/AiRemediation/KubernetesAiAgentPolicyCopyParity.test.ts +396 -0
- package/Tests/Utils/Dashboard/DashboardTimeRangeZoom.test.ts +104 -1
- package/Types/AI/AIRunType.ts +9 -4
- package/Types/AI/InvestigationNotStartedReason.ts +6 -0
- package/Types/AutoRemediation/AiRemediationCommandPlan.ts +14 -16
- package/Types/AutoRemediation/AiRemediationCommandPolicyVerdict.ts +28 -0
- package/Types/Dashboard/DashboardComponents/DashboardTraceChartComponent.ts +4 -2
- package/Types/Kubernetes/KubernetesClusterAiAccess.ts +263 -32
- package/Types/Kubernetes/KubernetesClusterAiAccessPermissions.ts +20 -16
- package/Types/Kubernetes/KubernetesClusterAiInsights.ts +81 -0
- package/Types/Monitor/ProxmoxAlertTemplates.ts +4 -4
- package/Types/Team/ComplianceNotificationChannel.ts +22 -0
- package/Types/Team/ComplianceRule.ts +434 -0
- package/Types/Team/ComplianceRuleType.ts +21 -0
- package/Types/Team/TeamComplianceStatus.ts +81 -0
- package/Types/Telemetry/TelemetryIngestSurface.ts +28 -0
- package/UI/Components/ActionButton/ActionButtonSchema.ts +21 -0
- package/UI/Components/ActionButton/RowActions.tsx +238 -0
- package/UI/Components/ActionButton/SplitActionButtons.ts +173 -0
- package/UI/Components/Charts/Area/AreaChart.tsx +32 -2
- package/UI/Components/Charts/Bar/BarChart.tsx +40 -3
- package/UI/Components/Charts/ChartGroup/ChartGroup.tsx +37 -11
- package/UI/Components/Charts/ChartLibrary/AreaChart/AreaChart.tsx +80 -243
- package/UI/Components/Charts/ChartLibrary/BarChart/BarChart.tsx +122 -31
- package/UI/Components/Charts/ChartLibrary/LineChart/LineChart.tsx +95 -244
- package/UI/Components/Charts/ChartLibrary/Utils/DoubleClick.ts +214 -0
- package/UI/Components/Charts/ChartLibrary/Utils/UseChartBucketsChange.ts +32 -0
- package/UI/Components/Charts/ChartLibrary/Utils/UseChartRangeSelection.ts +599 -0
- package/UI/Components/Charts/Line/LineChart.tsx +32 -2
- package/UI/Components/Charts/TimeRangeZoom/ResetTimeRangeZoomButton.tsx +70 -0
- package/UI/Components/Charts/TimeRangeZoom/TimeRangeZoomContext.tsx +226 -0
- package/UI/Components/Charts/TimeRangeZoom/TimeRangeZoomHint.tsx +73 -0
- package/UI/Components/Charts/TimeRangeZoom/TimeRangeZoomUtil.ts +163 -0
- package/UI/Components/Charts/TimeRangeZoom/UseTimeRangeZoom.ts +215 -0
- package/UI/Components/Charts/Utils/DataPoint.ts +20 -8
- package/UI/Components/Charts/Utils/XAxis.ts +75 -0
- package/UI/Components/Charts/Utils/useHistogramRangeSelection.ts +493 -59
- package/UI/Components/EntityDropdown/EntityDropdown.tsx +107 -11
- package/UI/Components/Forms/Fields/FormField.tsx +10 -0
- package/UI/Components/Input/Input.tsx +90 -57
- package/UI/Components/List/ListRow.tsx +8 -74
- package/UI/Components/LogsViewer/LogsViewer.tsx +40 -11
- package/UI/Components/LogsViewer/components/LogTimeRangePicker.tsx +22 -6
- package/UI/Components/LogsViewer/components/LogsAnalyticsView.tsx +209 -6
- package/UI/Components/LogsViewer/components/LogsHistogram.tsx +127 -84
- package/UI/Components/LogsViewer/components/LogsViewerToolbar.tsx +3 -0
- package/UI/Components/ModelTable/BaseModelTable.tsx +12 -1
- package/UI/Components/MoreMenu/MoreMenu.tsx +365 -30
- package/UI/Components/MoreMenu/MoreMenuItem.tsx +70 -17
- package/UI/Components/OrderedStatesList/Item.tsx +7 -72
- package/UI/Components/StatusPage/ResourceGrid.tsx +126 -77
- package/UI/Components/Table/TableBody.tsx +8 -2
- package/UI/Components/Table/TableRow.tsx +20 -140
- package/UI/Components/TelemetryViewer/TelemetryViewer.tsx +41 -10
- package/UI/Components/TelemetryViewer/components/TelemetryHistogram.tsx +47 -9
- package/UI/Components/TelemetryViewer/components/TelemetryTimeRangePicker.tsx +25 -6
- package/UI/Components/TelemetryViewer/types.ts +2 -0
- package/UI/Components/TelemetryViewer/useViewerTimeRangeZoom.ts +448 -0
- package/UI/Components/Toggle/Toggle.tsx +26 -0
- package/UI/Components/Tooltip/Tooltip.tsx +8 -1
- package/UI/Styles/Theme.css +14 -2
- package/Utils/AiRemediation/CommandPolicy.ts +1 -1
- package/Utils/AiRemediation/KubectlPolicy.ts +1 -1
- package/Utils/Dashboard/DashboardTimeRangeZoom.ts +46 -2
- package/build/dist/Models/DatabaseModels/Index.js +2 -0
- package/build/dist/Models/DatabaseModels/Index.js.map +1 -1
- package/build/dist/Models/DatabaseModels/KubernetesAiAgent.js +385 -0
- package/build/dist/Models/DatabaseModels/KubernetesAiAgent.js.map +1 -0
- package/build/dist/Models/DatabaseModels/KubernetesCluster.js +3 -3
- package/build/dist/Models/DatabaseModels/KubernetesCluster.js.map +1 -1
- package/build/dist/Models/DatabaseModels/Project.js +56 -5
- package/build/dist/Models/DatabaseModels/Project.js.map +1 -1
- package/build/dist/Models/DatabaseModels/ProxmoxResource.js +51 -1
- package/build/dist/Models/DatabaseModels/ProxmoxResource.js.map +1 -1
- package/build/dist/Models/DatabaseModels/RunnerJob.js +74 -0
- package/build/dist/Models/DatabaseModels/RunnerJob.js.map +1 -1
- package/build/dist/Models/DatabaseModels/TeamComplianceSetting.js +134 -3
- package/build/dist/Models/DatabaseModels/TeamComplianceSetting.js.map +1 -1
- package/build/dist/Models/DatabaseModels/WorkspaceProjectAuthToken.js.map +1 -1
- package/build/dist/Server/API/AIChatAPI.js +6 -1
- package/build/dist/Server/API/AIChatAPI.js.map +1 -1
- package/build/dist/Server/API/AIInvestigationAPI.js +17 -10
- package/build/dist/Server/API/AIInvestigationAPI.js.map +1 -1
- package/build/dist/Server/API/AutoRemediationAPI.js +144 -24
- package/build/dist/Server/API/AutoRemediationAPI.js.map +1 -1
- package/build/dist/Server/API/KubernetesClusterAiAccessAPI.js +436 -22
- package/build/dist/Server/API/KubernetesClusterAiAccessAPI.js.map +1 -1
- package/build/dist/Server/API/MicrosoftTeamsAPI.js +69 -13
- package/build/dist/Server/API/MicrosoftTeamsAPI.js.map +1 -1
- package/build/dist/Server/API/ProxmoxResourceAPI.js +187 -14
- package/build/dist/Server/API/ProxmoxResourceAPI.js.map +1 -1
- package/build/dist/Server/Infrastructure/GlobalCache.js +27 -0
- package/build/dist/Server/Infrastructure/GlobalCache.js.map +1 -1
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1796000000000-AddTeamComplianceRuleScope.js +50 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1796000000000-AddTeamComplianceRuleScope.js.map +1 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1796100000000-AddKubernetesAiAgentAndAiDefaults.js +84 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1796100000000-AddKubernetesAiAgentAndAiDefaults.js.map +1 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1796200000000-AddProxmoxResourceNativePushColumns.js +31 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1796200000000-AddProxmoxResourceNativePushColumns.js.map +1 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js +6 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js.map +1 -1
- package/build/dist/Server/Middleware/TelemetryIngest.js +34 -19
- package/build/dist/Server/Middleware/TelemetryIngest.js.map +1 -1
- package/build/dist/Server/Services/AIService.js +59 -1
- package/build/dist/Server/Services/AIService.js.map +1 -1
- package/build/dist/Server/Services/AlertService.js +248 -13
- package/build/dist/Server/Services/AlertService.js.map +1 -1
- package/build/dist/Server/Services/AlertSeverityService.js +51 -15
- package/build/dist/Server/Services/AlertSeverityService.js.map +1 -1
- package/build/dist/Server/Services/AutoRemediationRuleService.js +1 -1
- package/build/dist/Server/Services/AutoRemediationRuleService.js.map +1 -1
- package/build/dist/Server/Services/IncidentService.js +199 -72
- package/build/dist/Server/Services/IncidentService.js.map +1 -1
- package/build/dist/Server/Services/IncidentSeverityService.js +51 -15
- package/build/dist/Server/Services/IncidentSeverityService.js.map +1 -1
- package/build/dist/Server/Services/KubernetesAiAgentJobService.js +158 -0
- package/build/dist/Server/Services/KubernetesAiAgentJobService.js.map +1 -0
- package/build/dist/Server/Services/KubernetesAiAgentService.js +1145 -0
- package/build/dist/Server/Services/KubernetesAiAgentService.js.map +1 -0
- package/build/dist/Server/Services/KubernetesClusterAiAccessService.js +842 -371
- package/build/dist/Server/Services/KubernetesClusterAiAccessService.js.map +1 -1
- package/build/dist/Server/Services/KubernetesClusterService.js +86 -17
- package/build/dist/Server/Services/KubernetesClusterService.js.map +1 -1
- package/build/dist/Server/Services/ProjectService.js +22 -0
- package/build/dist/Server/Services/ProjectService.js.map +1 -1
- package/build/dist/Server/Services/ProxmoxResourceService.js +258 -3
- package/build/dist/Server/Services/ProxmoxResourceService.js.map +1 -1
- package/build/dist/Server/Services/RunnerJobService.js +178 -92
- package/build/dist/Server/Services/RunnerJobService.js.map +1 -1
- package/build/dist/Server/Services/RunnerService.js +1 -1
- package/build/dist/Server/Services/RunnerService.js.map +1 -1
- package/build/dist/Server/Services/ScheduledMaintenanceService.js +535 -3
- package/build/dist/Server/Services/ScheduledMaintenanceService.js.map +1 -1
- package/build/dist/Server/Services/ScheduledMaintenanceStateTimelineService.js +316 -46
- package/build/dist/Server/Services/ScheduledMaintenanceStateTimelineService.js.map +1 -1
- package/build/dist/Server/Services/TeamComplianceSettingService.js +833 -7
- package/build/dist/Server/Services/TeamComplianceSettingService.js.map +1 -1
- package/build/dist/Server/Utils/AI/ClusterAccess/ClusterAccessContext.js +52 -9
- package/build/dist/Server/Utils/AI/ClusterAccess/ClusterAccessContext.js.map +1 -1
- package/build/dist/Server/Utils/AI/ClusterAccess/KubectlInvestigationToolkit.js +13 -9
- package/build/dist/Server/Utils/AI/ClusterAccess/KubectlInvestigationToolkit.js.map +1 -1
- package/build/dist/Server/Utils/AI/ClusterAccess/KubectlJobRunner.js +7 -3
- package/build/dist/Server/Utils/AI/ClusterAccess/KubectlJobRunner.js.map +1 -1
- package/build/dist/Server/Utils/AI/Remediation/RemediationCommandTools.js +29 -15
- package/build/dist/Server/Utils/AI/Remediation/RemediationCommandTools.js.map +1 -1
- package/build/dist/Server/Utils/AI/Remediation/RemediationExecutionRunner.js +56 -20
- package/build/dist/Server/Utils/AI/Remediation/RemediationExecutionRunner.js.map +1 -1
- package/build/dist/Server/Utils/AI/SRE/AIInvestigationEngine.js +39 -5
- package/build/dist/Server/Utils/AI/SRE/AIInvestigationEngine.js.map +1 -1
- package/build/dist/Server/Utils/AI/SRE/IncidentPostmortemRunner.js +30 -3
- package/build/dist/Server/Utils/AI/SRE/IncidentPostmortemRunner.js.map +1 -1
- package/build/dist/Server/Utils/AI/SRE/InvestigationEligibility.js +13 -2
- package/build/dist/Server/Utils/AI/SRE/InvestigationEligibility.js.map +1 -1
- package/build/dist/Server/Utils/AI/SRE/InvestigationGrader.js +1 -1
- package/build/dist/Server/Utils/AutoRemediation/CommandPlanExecutor.js +4 -2
- package/build/dist/Server/Utils/AutoRemediation/CommandPlanExecutor.js.map +1 -1
- package/build/dist/Server/Utils/Telemetry/InventoryEntityRegistry.js +111 -6
- package/build/dist/Server/Utils/Telemetry/InventoryEntityRegistry.js.map +1 -1
- package/build/dist/Server/Utils/Telemetry/ProxmoxCephSnapshotScan.js +40 -4
- package/build/dist/Server/Utils/Telemetry/ProxmoxCephSnapshotScan.js.map +1 -1
- package/build/dist/Server/Utils/Telemetry/ProxmoxNativeNodeLiveness.js +296 -0
- package/build/dist/Server/Utils/Telemetry/ProxmoxNativeNodeLiveness.js.map +1 -0
- package/build/dist/Server/Utils/Telemetry/ProxmoxNativePush.js +699 -0
- package/build/dist/Server/Utils/Telemetry/ProxmoxNativePush.js.map +1 -0
- package/build/dist/Server/Utils/Telemetry/TelemetryEntity.js +77 -19
- package/build/dist/Server/Utils/Telemetry/TelemetryEntity.js.map +1 -1
- package/build/dist/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeams.js +505 -42
- package/build/dist/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeams.js.map +1 -1
- package/build/dist/Types/AI/AIRunType.js +9 -4
- package/build/dist/Types/AI/AIRunType.js.map +1 -1
- package/build/dist/Types/AutoRemediation/AiRemediationCommandPlan.js +10 -16
- package/build/dist/Types/AutoRemediation/AiRemediationCommandPlan.js.map +1 -1
- package/build/dist/Types/AutoRemediation/AiRemediationCommandPolicyVerdict.js +28 -0
- package/build/dist/Types/AutoRemediation/AiRemediationCommandPolicyVerdict.js.map +1 -0
- package/build/dist/Types/Kubernetes/KubernetesClusterAiAccess.js +72 -16
- package/build/dist/Types/Kubernetes/KubernetesClusterAiAccess.js.map +1 -1
- package/build/dist/Types/Kubernetes/KubernetesClusterAiAccessPermissions.js +20 -16
- package/build/dist/Types/Kubernetes/KubernetesClusterAiAccessPermissions.js.map +1 -1
- package/build/dist/Types/Kubernetes/KubernetesClusterAiInsights.js +17 -0
- package/build/dist/Types/Kubernetes/KubernetesClusterAiInsights.js.map +1 -0
- package/build/dist/Types/Monitor/ProxmoxAlertTemplates.js +4 -4
- package/build/dist/Types/Monitor/ProxmoxAlertTemplates.js.map +1 -1
- package/build/dist/Types/Team/ComplianceNotificationChannel.js +23 -0
- package/build/dist/Types/Team/ComplianceNotificationChannel.js.map +1 -0
- package/build/dist/Types/Team/ComplianceRule.js +304 -0
- package/build/dist/Types/Team/ComplianceRule.js.map +1 -0
- package/build/dist/Types/Team/ComplianceRuleType.js +21 -0
- package/build/dist/Types/Team/ComplianceRuleType.js.map +1 -1
- package/build/dist/Types/Team/TeamComplianceStatus.js +2 -0
- package/build/dist/Types/Team/TeamComplianceStatus.js.map +1 -0
- package/build/dist/Types/Telemetry/TelemetryIngestSurface.js +25 -0
- package/build/dist/Types/Telemetry/TelemetryIngestSurface.js.map +1 -1
- package/build/dist/UI/Components/ActionButton/ActionButtonSchema.js +20 -1
- package/build/dist/UI/Components/ActionButton/ActionButtonSchema.js.map +1 -1
- package/build/dist/UI/Components/ActionButton/RowActions.js +113 -0
- package/build/dist/UI/Components/ActionButton/RowActions.js.map +1 -0
- package/build/dist/UI/Components/ActionButton/SplitActionButtons.js +71 -0
- package/build/dist/UI/Components/ActionButton/SplitActionButtons.js.map +1 -0
- package/build/dist/UI/Components/Charts/Area/AreaChart.js +16 -1
- package/build/dist/UI/Components/Charts/Area/AreaChart.js.map +1 -1
- package/build/dist/UI/Components/Charts/Bar/BarChart.js +16 -1
- package/build/dist/UI/Components/Charts/Bar/BarChart.js.map +1 -1
- package/build/dist/UI/Components/Charts/ChartGroup/ChartGroup.js +27 -5
- package/build/dist/UI/Components/Charts/ChartGroup/ChartGroup.js.map +1 -1
- package/build/dist/UI/Components/Charts/ChartLibrary/AreaChart/AreaChart.js +61 -194
- package/build/dist/UI/Components/Charts/ChartLibrary/AreaChart/AreaChart.js.map +1 -1
- package/build/dist/UI/Components/Charts/ChartLibrary/BarChart/BarChart.js +84 -26
- package/build/dist/UI/Components/Charts/ChartLibrary/BarChart/BarChart.js.map +1 -1
- package/build/dist/UI/Components/Charts/ChartLibrary/LineChart/LineChart.js +75 -196
- package/build/dist/UI/Components/Charts/ChartLibrary/LineChart/LineChart.js.map +1 -1
- package/build/dist/UI/Components/Charts/ChartLibrary/Utils/DoubleClick.js +133 -0
- package/build/dist/UI/Components/Charts/ChartLibrary/Utils/DoubleClick.js.map +1 -1
- package/build/dist/UI/Components/Charts/ChartLibrary/Utils/UseChartBucketsChange.js +26 -0
- package/build/dist/UI/Components/Charts/ChartLibrary/Utils/UseChartBucketsChange.js.map +1 -0
- package/build/dist/UI/Components/Charts/ChartLibrary/Utils/UseChartRangeSelection.js +371 -0
- package/build/dist/UI/Components/Charts/ChartLibrary/Utils/UseChartRangeSelection.js.map +1 -0
- package/build/dist/UI/Components/Charts/Line/LineChart.js +16 -1
- package/build/dist/UI/Components/Charts/Line/LineChart.js.map +1 -1
- package/build/dist/UI/Components/Charts/TimeRangeZoom/ResetTimeRangeZoomButton.js +35 -0
- package/build/dist/UI/Components/Charts/TimeRangeZoom/ResetTimeRangeZoomButton.js.map +1 -0
- package/build/dist/UI/Components/Charts/TimeRangeZoom/TimeRangeZoomContext.js +107 -0
- package/build/dist/UI/Components/Charts/TimeRangeZoom/TimeRangeZoomContext.js.map +1 -0
- package/build/dist/UI/Components/Charts/TimeRangeZoom/TimeRangeZoomHint.js +41 -0
- package/build/dist/UI/Components/Charts/TimeRangeZoom/TimeRangeZoomHint.js.map +1 -0
- package/build/dist/UI/Components/Charts/TimeRangeZoom/TimeRangeZoomUtil.js +117 -0
- package/build/dist/UI/Components/Charts/TimeRangeZoom/TimeRangeZoomUtil.js.map +1 -0
- package/build/dist/UI/Components/Charts/TimeRangeZoom/UseTimeRangeZoom.js +121 -0
- package/build/dist/UI/Components/Charts/TimeRangeZoom/UseTimeRangeZoom.js.map +1 -0
- package/build/dist/UI/Components/Charts/Utils/DataPoint.js +15 -7
- package/build/dist/UI/Components/Charts/Utils/DataPoint.js.map +1 -1
- package/build/dist/UI/Components/Charts/Utils/XAxis.js +70 -0
- package/build/dist/UI/Components/Charts/Utils/XAxis.js.map +1 -1
- package/build/dist/UI/Components/Charts/Utils/useHistogramRangeSelection.js +305 -38
- package/build/dist/UI/Components/Charts/Utils/useHistogramRangeSelection.js.map +1 -1
- package/build/dist/UI/Components/EntityDropdown/EntityDropdown.js +89 -14
- package/build/dist/UI/Components/EntityDropdown/EntityDropdown.js.map +1 -1
- package/build/dist/UI/Components/Forms/Fields/FormField.js +11 -2
- package/build/dist/UI/Components/Forms/Fields/FormField.js.map +1 -1
- package/build/dist/UI/Components/Input/Input.js +69 -54
- package/build/dist/UI/Components/Input/Input.js.map +1 -1
- package/build/dist/UI/Components/List/ListRow.js +4 -39
- package/build/dist/UI/Components/List/ListRow.js.map +1 -1
- package/build/dist/UI/Components/LogsViewer/LogsViewer.js +31 -8
- package/build/dist/UI/Components/LogsViewer/LogsViewer.js.map +1 -1
- package/build/dist/UI/Components/LogsViewer/components/LogTimeRangePicker.js +10 -1
- package/build/dist/UI/Components/LogsViewer/components/LogTimeRangePicker.js.map +1 -1
- package/build/dist/UI/Components/LogsViewer/components/LogsAnalyticsView.js +139 -21
- package/build/dist/UI/Components/LogsViewer/components/LogsAnalyticsView.js.map +1 -1
- package/build/dist/UI/Components/LogsViewer/components/LogsHistogram.js +30 -8
- package/build/dist/UI/Components/LogsViewer/components/LogsHistogram.js.map +1 -1
- package/build/dist/UI/Components/LogsViewer/components/LogsViewerToolbar.js +1 -1
- package/build/dist/UI/Components/LogsViewer/components/LogsViewerToolbar.js.map +1 -1
- package/build/dist/UI/Components/ModelTable/BaseModelTable.js +10 -0
- package/build/dist/UI/Components/ModelTable/BaseModelTable.js.map +1 -1
- package/build/dist/UI/Components/MoreMenu/MoreMenu.js +253 -14
- package/build/dist/UI/Components/MoreMenu/MoreMenu.js.map +1 -1
- package/build/dist/UI/Components/MoreMenu/MoreMenuItem.js +43 -13
- package/build/dist/UI/Components/MoreMenu/MoreMenuItem.js.map +1 -1
- package/build/dist/UI/Components/OrderedStatesList/Item.js +2 -38
- package/build/dist/UI/Components/OrderedStatesList/Item.js.map +1 -1
- package/build/dist/UI/Components/StatusPage/ResourceGrid.js +53 -15
- package/build/dist/UI/Components/StatusPage/ResourceGrid.js.map +1 -1
- package/build/dist/UI/Components/Table/TableBody.js +6 -2
- package/build/dist/UI/Components/Table/TableBody.js.map +1 -1
- package/build/dist/UI/Components/Table/TableRow.js +12 -75
- package/build/dist/UI/Components/Table/TableRow.js.map +1 -1
- package/build/dist/UI/Components/TelemetryViewer/TelemetryViewer.js +32 -7
- package/build/dist/UI/Components/TelemetryViewer/TelemetryViewer.js.map +1 -1
- package/build/dist/UI/Components/TelemetryViewer/components/TelemetryHistogram.js +27 -9
- package/build/dist/UI/Components/TelemetryViewer/components/TelemetryHistogram.js.map +1 -1
- package/build/dist/UI/Components/TelemetryViewer/components/TelemetryTimeRangePicker.js +11 -1
- package/build/dist/UI/Components/TelemetryViewer/components/TelemetryTimeRangePicker.js.map +1 -1
- package/build/dist/UI/Components/TelemetryViewer/useViewerTimeRangeZoom.js +272 -0
- package/build/dist/UI/Components/TelemetryViewer/useViewerTimeRangeZoom.js.map +1 -0
- package/build/dist/UI/Components/Toggle/Toggle.js +14 -1
- package/build/dist/UI/Components/Toggle/Toggle.js.map +1 -1
- package/build/dist/UI/Components/Tooltip/Tooltip.js +1 -1
- package/build/dist/UI/Components/Tooltip/Tooltip.js.map +1 -1
- package/build/dist/Utils/AiRemediation/CommandPolicy.js +1 -1
- package/build/dist/Utils/AiRemediation/CommandPolicy.js.map +1 -1
- package/build/dist/Utils/AiRemediation/KubectlPolicy.js +1 -1
- package/build/dist/Utils/AiRemediation/KubectlPolicy.js.map +1 -1
- package/build/dist/Utils/Dashboard/DashboardTimeRangeZoom.js +27 -1
- package/build/dist/Utils/Dashboard/DashboardTimeRangeZoom.js.map +1 -1
- package/package.json +1 -1
|
@@ -0,0 +1,1208 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @timezone UTC
|
|
3
|
+
*/
|
|
4
|
+
import "@testing-library/jest-dom";
|
|
5
|
+
import {
|
|
6
|
+
afterEach,
|
|
7
|
+
beforeEach,
|
|
8
|
+
describe,
|
|
9
|
+
expect,
|
|
10
|
+
jest,
|
|
11
|
+
test,
|
|
12
|
+
} from "@jest/globals";
|
|
13
|
+
import {
|
|
14
|
+
act,
|
|
15
|
+
cleanup,
|
|
16
|
+
fireEvent,
|
|
17
|
+
render,
|
|
18
|
+
screen,
|
|
19
|
+
} from "@testing-library/react";
|
|
20
|
+
import * as React from "react";
|
|
21
|
+
import getJestMockFunction, { MockFunction } from "../../MockType";
|
|
22
|
+
|
|
23
|
+
/*
|
|
24
|
+
* Issue #4105 on the Host pages: the overview, a process, a Windows service
|
|
25
|
+
* and a systemd unit. Each page is rendered for real with its data layer
|
|
26
|
+
* mocked and its line charts replaced by the zoom stand-in (see
|
|
27
|
+
* TimeRangeZoomPageHarness), and a customer's gestures are played against
|
|
28
|
+
* it:
|
|
29
|
+
*
|
|
30
|
+
* - every chart on the page holds the page's one zoom;
|
|
31
|
+
* - a drag on any chart retimes the whole page - every fetch behind the
|
|
32
|
+
* charts, tiles, tables and identity is re-issued for the window dragged
|
|
33
|
+
* out, and the picker reads Custom with Reset zoom beside it;
|
|
34
|
+
* - a double-click on any OTHER chart, or Reset zoom, puts the range from
|
|
35
|
+
* before the zoom back, however many times the reader drilled in;
|
|
36
|
+
* - the picker ends the zoom; auto-refresh keeps a zoom pinned;
|
|
37
|
+
* - a slow response for a window the reader left cannot repaint the page;
|
|
38
|
+
* - a zoom into a quiet stretch (no chart left to double-click) still has
|
|
39
|
+
* a way back where the reader is looking.
|
|
40
|
+
*/
|
|
41
|
+
|
|
42
|
+
const HOST_ID: string = "84858d6c-1111-4aaa-8bbb-000000000001";
|
|
43
|
+
const NOW: Date = new Date("2026-09-28T12:00:00.000Z");
|
|
44
|
+
const GIB: number = 1024 * 1024 * 1024;
|
|
45
|
+
|
|
46
|
+
// A time on the test's day, e.g. at("11:36") or at("11:46:30").
|
|
47
|
+
function at(time: string): Date {
|
|
48
|
+
const withSeconds: string = time.length === 5 ? `${time}:00` : time;
|
|
49
|
+
return new Date(`2026-09-28T${withSeconds}.000Z`);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
const getItemMock: MockFunction = getJestMockFunction();
|
|
53
|
+
const getListMock: MockFunction = getJestMockFunction();
|
|
54
|
+
const aggregateMock: MockFunction = getJestMockFunction();
|
|
55
|
+
const lastParamMock: MockFunction = getJestMockFunction();
|
|
56
|
+
|
|
57
|
+
jest.mock("../../../UI/Utils/ModelAPI/ModelAPI", () => {
|
|
58
|
+
return {
|
|
59
|
+
__esModule: true,
|
|
60
|
+
default: {
|
|
61
|
+
getItem: (...args: Array<unknown>) => {
|
|
62
|
+
return getItemMock(...args);
|
|
63
|
+
},
|
|
64
|
+
},
|
|
65
|
+
};
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
jest.mock("../../../UI/Utils/AnalyticsModelAPI/AnalyticsModelAPI", () => {
|
|
69
|
+
return {
|
|
70
|
+
__esModule: true,
|
|
71
|
+
default: {
|
|
72
|
+
getList: (...args: Array<unknown>) => {
|
|
73
|
+
return getListMock(...args);
|
|
74
|
+
},
|
|
75
|
+
aggregate: (...args: Array<unknown>) => {
|
|
76
|
+
return aggregateMock(...args);
|
|
77
|
+
},
|
|
78
|
+
},
|
|
79
|
+
};
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
jest.mock("../../../UI/Utils/API/API", () => {
|
|
83
|
+
return {
|
|
84
|
+
__esModule: true,
|
|
85
|
+
default: {
|
|
86
|
+
getFriendlyMessage: (error: unknown) => {
|
|
87
|
+
return String((error as Error)?.message || error);
|
|
88
|
+
},
|
|
89
|
+
},
|
|
90
|
+
};
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
jest.mock("../../../UI/Utils/Navigation", () => {
|
|
94
|
+
return {
|
|
95
|
+
__esModule: true,
|
|
96
|
+
default: {
|
|
97
|
+
getLastParamAsObjectID: () => {
|
|
98
|
+
const { default: ObjectIDType } = jest.requireActual(
|
|
99
|
+
"../../../Types/ObjectID",
|
|
100
|
+
) as { default: new (id: string) => unknown };
|
|
101
|
+
return new ObjectIDType("84858d6c-1111-4aaa-8bbb-000000000001");
|
|
102
|
+
},
|
|
103
|
+
getLastParamAsString: () => {
|
|
104
|
+
return lastParamMock();
|
|
105
|
+
},
|
|
106
|
+
navigate: () => {},
|
|
107
|
+
},
|
|
108
|
+
};
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
jest.mock("../../../UI/Utils/Project", () => {
|
|
112
|
+
return {
|
|
113
|
+
__esModule: true,
|
|
114
|
+
default: {
|
|
115
|
+
getCurrentProjectId: () => {
|
|
116
|
+
const { default: ObjectIDType } = jest.requireActual(
|
|
117
|
+
"../../../Types/ObjectID",
|
|
118
|
+
) as { default: new (id: string) => unknown };
|
|
119
|
+
return new ObjectIDType("10000000-0000-4000-8000-000000000001");
|
|
120
|
+
},
|
|
121
|
+
},
|
|
122
|
+
};
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
jest.mock("../../../UI/Components/Charts/Line/LineChart", () => {
|
|
126
|
+
const harness: { StandInLineChart: unknown } = jest.requireActual(
|
|
127
|
+
"./TimeRangeZoomPageHarness",
|
|
128
|
+
) as { StandInLineChart: unknown };
|
|
129
|
+
return { __esModule: true, default: harness.StandInLineChart };
|
|
130
|
+
});
|
|
131
|
+
|
|
132
|
+
jest.mock(
|
|
133
|
+
"../../../../App/FeatureSet/Dashboard/src/Components/TelemetryResource/AutoRefreshControl",
|
|
134
|
+
() => {
|
|
135
|
+
const harness: { StandInAutoRefreshControl: unknown } = jest.requireActual(
|
|
136
|
+
"./TimeRangeZoomPageHarness",
|
|
137
|
+
) as {
|
|
138
|
+
StandInAutoRefreshControl: unknown;
|
|
139
|
+
};
|
|
140
|
+
return { __esModule: true, default: harness.StandInAutoRefreshControl };
|
|
141
|
+
},
|
|
142
|
+
);
|
|
143
|
+
|
|
144
|
+
// Covered by its own suite; it fetches counts that do not follow the range.
|
|
145
|
+
jest.mock(
|
|
146
|
+
"../../../../App/FeatureSet/Dashboard/src/Components/ResourceActivity/ResourceActivityCards",
|
|
147
|
+
() => {
|
|
148
|
+
return {
|
|
149
|
+
__esModule: true,
|
|
150
|
+
default: () => {
|
|
151
|
+
return null;
|
|
152
|
+
},
|
|
153
|
+
};
|
|
154
|
+
},
|
|
155
|
+
);
|
|
156
|
+
|
|
157
|
+
import HostOverview from "../../../../App/FeatureSet/Dashboard/src/Pages/Host/View/Overview";
|
|
158
|
+
import HostProcessView from "../../../../App/FeatureSet/Dashboard/src/Pages/Host/View/ProcessView";
|
|
159
|
+
import HostServiceView from "../../../../App/FeatureSet/Dashboard/src/Pages/Host/View/ServiceView";
|
|
160
|
+
import HostSystemdUnitView from "../../../../App/FeatureSet/Dashboard/src/Pages/Host/View/SystemdUnitView";
|
|
161
|
+
import PageComponentProps from "../../../../App/FeatureSet/Dashboard/src/Pages/PageComponentProps";
|
|
162
|
+
import TimeRange from "../../../Types/Time/TimeRange";
|
|
163
|
+
import { infoButtonsFor } from "./HostTooltipHarness";
|
|
164
|
+
import {
|
|
165
|
+
chartWindows,
|
|
166
|
+
customRangeLabel,
|
|
167
|
+
deferred,
|
|
168
|
+
Deferred,
|
|
169
|
+
doubleClick,
|
|
170
|
+
dragAcross,
|
|
171
|
+
expectOneSharedZoom,
|
|
172
|
+
expectRevealedOnHoverOf,
|
|
173
|
+
flush,
|
|
174
|
+
pickPreset,
|
|
175
|
+
pickerLabel,
|
|
176
|
+
presetLabel,
|
|
177
|
+
resetZoomButtons,
|
|
178
|
+
windowOf,
|
|
179
|
+
zoomChart,
|
|
180
|
+
zoomCharts,
|
|
181
|
+
zoomHints,
|
|
182
|
+
} from "./TimeRangeZoomPageHarness";
|
|
183
|
+
|
|
184
|
+
// The Host pages read their ids from the route, not from these props.
|
|
185
|
+
const PAGE_PROPS: PageComponentProps = {} as PageComponentProps;
|
|
186
|
+
|
|
187
|
+
const PAST_30_MINUTES: string = presetLabel(TimeRange.PAST_THIRTY_MINS);
|
|
188
|
+
|
|
189
|
+
type Row = {
|
|
190
|
+
timestamp: Date;
|
|
191
|
+
value: number;
|
|
192
|
+
attributes?: Record<string, string>;
|
|
193
|
+
};
|
|
194
|
+
|
|
195
|
+
interface TimeWindow {
|
|
196
|
+
startValue: Date;
|
|
197
|
+
endValue: Date;
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
interface AggregateCall {
|
|
201
|
+
aggregateBy: {
|
|
202
|
+
query: {
|
|
203
|
+
name: string;
|
|
204
|
+
attributes: Record<string, string>;
|
|
205
|
+
time: TimeWindow;
|
|
206
|
+
};
|
|
207
|
+
startTimestamp: Date;
|
|
208
|
+
endTimestamp: Date;
|
|
209
|
+
};
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
interface ListCall {
|
|
213
|
+
query: {
|
|
214
|
+
name: string;
|
|
215
|
+
time: TimeWindow;
|
|
216
|
+
};
|
|
217
|
+
limit: number;
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
function aggregateCalls(): Array<AggregateCall> {
|
|
221
|
+
return aggregateMock.mock.calls.map((args: Array<unknown>): AggregateCall => {
|
|
222
|
+
return args[0] as AggregateCall;
|
|
223
|
+
});
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
function listCalls(): Array<ListCall> {
|
|
227
|
+
return getListMock.mock.calls.map((args: Array<unknown>): ListCall => {
|
|
228
|
+
return args[0] as ListCall;
|
|
229
|
+
});
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
// The [start, end] a set of aggregate calls asked for, as ISO strings.
|
|
233
|
+
function aggregateWindows(
|
|
234
|
+
calls: Array<AggregateCall>,
|
|
235
|
+
): Array<[string, string]> {
|
|
236
|
+
return calls.map((call: AggregateCall): [string, string] => {
|
|
237
|
+
return [
|
|
238
|
+
call.aggregateBy.startTimestamp.toISOString(),
|
|
239
|
+
call.aggregateBy.endTimestamp.toISOString(),
|
|
240
|
+
];
|
|
241
|
+
});
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
function queryWindow(window: TimeWindow): [string, string] {
|
|
245
|
+
return [window.startValue.toISOString(), window.endValue.toISOString()];
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
// One row a minute from 11:00 to 11:59 - inside every window a test asks for.
|
|
249
|
+
function everyMinute(
|
|
250
|
+
value: (time: Date) => number,
|
|
251
|
+
attributes?: Record<string, string>,
|
|
252
|
+
): Array<Row> {
|
|
253
|
+
const rows: Array<Row> = [];
|
|
254
|
+
|
|
255
|
+
for (let minute: number = 0; minute < 60; minute++) {
|
|
256
|
+
const time: Date = new Date(Date.UTC(2026, 8, 28, 11, minute));
|
|
257
|
+
rows.push({
|
|
258
|
+
timestamp: time,
|
|
259
|
+
value: value(time),
|
|
260
|
+
...(attributes ? { attributes } : {}),
|
|
261
|
+
});
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
return rows;
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
// The rows the server would return for the window an aggregate asked for.
|
|
268
|
+
function insideWindow(rows: Array<Row>, call: AggregateCall): Array<Row> {
|
|
269
|
+
const start: number = call.aggregateBy.startTimestamp.getTime();
|
|
270
|
+
const end: number = call.aggregateBy.endTimestamp.getTime();
|
|
271
|
+
|
|
272
|
+
return rows.filter((row: Row): boolean => {
|
|
273
|
+
const time: number = row.timestamp.getTime();
|
|
274
|
+
return time >= start && time <= end;
|
|
275
|
+
});
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
beforeEach(() => {
|
|
279
|
+
jest.useFakeTimers();
|
|
280
|
+
jest.setSystemTime(NOW);
|
|
281
|
+
window.localStorage.clear();
|
|
282
|
+
getItemMock.mockReset();
|
|
283
|
+
getListMock.mockReset();
|
|
284
|
+
aggregateMock.mockReset();
|
|
285
|
+
lastParamMock.mockReset();
|
|
286
|
+
getItemMock.mockResolvedValue({
|
|
287
|
+
_id: HOST_ID,
|
|
288
|
+
name: "web-01",
|
|
289
|
+
hostIdentifier: "web-01",
|
|
290
|
+
otelCollectorStatus: "connected",
|
|
291
|
+
lastSeenAt: NOW,
|
|
292
|
+
cpuCores: 4,
|
|
293
|
+
totalMemoryBytes: 16 * GIB,
|
|
294
|
+
processCount: 212,
|
|
295
|
+
osType: "linux",
|
|
296
|
+
});
|
|
297
|
+
});
|
|
298
|
+
|
|
299
|
+
afterEach(() => {
|
|
300
|
+
cleanup();
|
|
301
|
+
jest.useRealTimers();
|
|
302
|
+
});
|
|
303
|
+
|
|
304
|
+
// ------------------------------------------------------------------ overview
|
|
305
|
+
|
|
306
|
+
/*
|
|
307
|
+
* CPU is quiet (12%) until 11:45 and busy (64%) from then on, so the CPU
|
|
308
|
+
* tile - the mean of the last five minutes of the window - reads 64% on the
|
|
309
|
+
* default half hour and 12% on a window zoomed in before 11:45.
|
|
310
|
+
*/
|
|
311
|
+
function overviewRows(call: AggregateCall): Array<Row> {
|
|
312
|
+
const attributes: Record<string, string> = call.aggregateBy.query.attributes;
|
|
313
|
+
const busyFrom: number = at("11:45").getTime();
|
|
314
|
+
|
|
315
|
+
switch (call.aggregateBy.query.name) {
|
|
316
|
+
case "system.cpu.utilization":
|
|
317
|
+
return everyMinute((time: Date): number => {
|
|
318
|
+
if (attributes["state"] !== "user") {
|
|
319
|
+
return 0;
|
|
320
|
+
}
|
|
321
|
+
return time.getTime() < busyFrom ? 0.12 : 0.64;
|
|
322
|
+
});
|
|
323
|
+
case "system.memory.utilization":
|
|
324
|
+
return everyMinute(() => {
|
|
325
|
+
return 0.5;
|
|
326
|
+
});
|
|
327
|
+
case "system.cpu.load_average.1m":
|
|
328
|
+
return everyMinute(() => {
|
|
329
|
+
return 1.5;
|
|
330
|
+
});
|
|
331
|
+
case "system.processes.count":
|
|
332
|
+
return everyMinute(() => {
|
|
333
|
+
return 3;
|
|
334
|
+
});
|
|
335
|
+
case "system.filesystem.usage":
|
|
336
|
+
return [
|
|
337
|
+
...everyMinute(
|
|
338
|
+
() => {
|
|
339
|
+
return 40 * GIB;
|
|
340
|
+
},
|
|
341
|
+
{ mountpoint: "/", device: "/dev/sda1", type: "ext4", state: "used" },
|
|
342
|
+
),
|
|
343
|
+
...everyMinute(
|
|
344
|
+
() => {
|
|
345
|
+
return 60 * GIB;
|
|
346
|
+
},
|
|
347
|
+
{ mountpoint: "/", device: "/dev/sda1", type: "ext4", state: "free" },
|
|
348
|
+
),
|
|
349
|
+
];
|
|
350
|
+
case "system.network.io":
|
|
351
|
+
return everyMinute(
|
|
352
|
+
(time: Date): number => {
|
|
353
|
+
return time.getTime() / 1000;
|
|
354
|
+
},
|
|
355
|
+
{ device: "eth0", direction: "receive" },
|
|
356
|
+
);
|
|
357
|
+
case "oneuptime.host.heartbeat":
|
|
358
|
+
return everyMinute(() => {
|
|
359
|
+
return 2;
|
|
360
|
+
});
|
|
361
|
+
default:
|
|
362
|
+
return [];
|
|
363
|
+
}
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
interface HeldAggregate {
|
|
367
|
+
call: AggregateCall;
|
|
368
|
+
release: () => void;
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
let holdAggregates: boolean = false;
|
|
372
|
+
let heldAggregates: Array<HeldAggregate> = [];
|
|
373
|
+
let failAggregate: (call: AggregateCall) => boolean = (): boolean => {
|
|
374
|
+
return false;
|
|
375
|
+
};
|
|
376
|
+
|
|
377
|
+
function answerAggregatesWith(
|
|
378
|
+
rowsFor: (call: AggregateCall) => Array<Row>,
|
|
379
|
+
): void {
|
|
380
|
+
holdAggregates = false;
|
|
381
|
+
heldAggregates = [];
|
|
382
|
+
failAggregate = (): boolean => {
|
|
383
|
+
return false;
|
|
384
|
+
};
|
|
385
|
+
|
|
386
|
+
aggregateMock.mockImplementation((request: unknown) => {
|
|
387
|
+
const call: AggregateCall = request as AggregateCall;
|
|
388
|
+
const answer: { data: Array<Row> } = {
|
|
389
|
+
data: insideWindow(rowsFor(call), call),
|
|
390
|
+
};
|
|
391
|
+
|
|
392
|
+
if (failAggregate(call)) {
|
|
393
|
+
return Promise.reject(new Error("Analytics is down"));
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
if (holdAggregates) {
|
|
397
|
+
const held: Deferred<{ data: Array<Row> }> = deferred<{
|
|
398
|
+
data: Array<Row>;
|
|
399
|
+
}>();
|
|
400
|
+
heldAggregates.push({
|
|
401
|
+
call: call,
|
|
402
|
+
release: (): void => {
|
|
403
|
+
held.resolve(answer);
|
|
404
|
+
},
|
|
405
|
+
});
|
|
406
|
+
return held.promise;
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
return Promise.resolve(answer);
|
|
410
|
+
});
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
// Answer the held aggregates whose window ends at `end`.
|
|
414
|
+
async function releaseAggregatesEndingAt(end: Date): Promise<void> {
|
|
415
|
+
const releasing: Array<HeldAggregate> = heldAggregates.filter(
|
|
416
|
+
(held: HeldAggregate): boolean => {
|
|
417
|
+
return held.call.aggregateBy.endTimestamp.getTime() === end.getTime();
|
|
418
|
+
},
|
|
419
|
+
);
|
|
420
|
+
|
|
421
|
+
heldAggregates = heldAggregates.filter((held: HeldAggregate): boolean => {
|
|
422
|
+
return !releasing.includes(held);
|
|
423
|
+
});
|
|
424
|
+
|
|
425
|
+
expect(releasing.length).toBeGreaterThan(0);
|
|
426
|
+
|
|
427
|
+
for (const held of releasing) {
|
|
428
|
+
held.release();
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
await flush();
|
|
432
|
+
}
|
|
433
|
+
|
|
434
|
+
const OVERVIEW_CHARTS: number = 5; // Availability, CPU, Memory, Disk space, Network
|
|
435
|
+
const OVERVIEW_FETCHES: number = 9; // aggregates per load
|
|
436
|
+
|
|
437
|
+
function cpuTile(): HTMLElement {
|
|
438
|
+
// The first "About CPU" is the tile's; the second is the chart card's.
|
|
439
|
+
return infoButtonsFor("CPU")[0]!.closest(".rounded-xl") as HTMLElement;
|
|
440
|
+
}
|
|
441
|
+
|
|
442
|
+
async function renderOverview(): Promise<void> {
|
|
443
|
+
answerAggregatesWith(overviewRows);
|
|
444
|
+
render(<HostOverview {...PAGE_PROPS} />);
|
|
445
|
+
await flush();
|
|
446
|
+
expect(zoomCharts()).toHaveLength(OVERVIEW_CHARTS);
|
|
447
|
+
}
|
|
448
|
+
|
|
449
|
+
// The last load's aggregate windows, the tile-window process fallback aside.
|
|
450
|
+
function lastOverviewLoad(): {
|
|
451
|
+
windows: Array<[string, string]>;
|
|
452
|
+
processFallback: [string, string];
|
|
453
|
+
} {
|
|
454
|
+
const load: Array<AggregateCall> = aggregateCalls().slice(-OVERVIEW_FETCHES);
|
|
455
|
+
const fallback: AggregateCall | undefined = load.find(
|
|
456
|
+
(call: AggregateCall): boolean => {
|
|
457
|
+
return call.aggregateBy.query.name === "process.cpu.utilization";
|
|
458
|
+
},
|
|
459
|
+
);
|
|
460
|
+
|
|
461
|
+
expect(fallback).toBeDefined();
|
|
462
|
+
|
|
463
|
+
return {
|
|
464
|
+
windows: aggregateWindows(
|
|
465
|
+
load.filter((call: AggregateCall): boolean => {
|
|
466
|
+
return call !== fallback;
|
|
467
|
+
}),
|
|
468
|
+
),
|
|
469
|
+
processFallback: aggregateWindows([fallback!])[0]!,
|
|
470
|
+
};
|
|
471
|
+
}
|
|
472
|
+
|
|
473
|
+
function everyWindowIs(
|
|
474
|
+
window: [string, string],
|
|
475
|
+
count: number,
|
|
476
|
+
): Array<[string, string]> {
|
|
477
|
+
return Array.from({ length: count }, (): [string, string] => {
|
|
478
|
+
return window;
|
|
479
|
+
});
|
|
480
|
+
}
|
|
481
|
+
|
|
482
|
+
describe("Host overview: one zoom for the whole page", () => {
|
|
483
|
+
test("all five charts hold the page's one zoom, with nothing to reset yet", async () => {
|
|
484
|
+
await renderOverview();
|
|
485
|
+
|
|
486
|
+
expectOneSharedZoom({ zoomed: false });
|
|
487
|
+
expect(chartWindows()).toEqual(
|
|
488
|
+
everyWindowIs(windowOf(at("11:30"), NOW), OVERVIEW_CHARTS),
|
|
489
|
+
);
|
|
490
|
+
expect(pickerLabel()).toBe(PAST_30_MINUTES);
|
|
491
|
+
expect(resetZoomButtons()).toHaveLength(0);
|
|
492
|
+
});
|
|
493
|
+
|
|
494
|
+
test("each chart section names the gesture at its heading, revealed while hovered", async () => {
|
|
495
|
+
await renderOverview();
|
|
496
|
+
|
|
497
|
+
const hints: Array<HTMLElement> = zoomHints();
|
|
498
|
+
|
|
499
|
+
// One per section: Availability, and Resource usage's four cards.
|
|
500
|
+
expect(hints).toHaveLength(2);
|
|
501
|
+
|
|
502
|
+
for (const hint of hints) {
|
|
503
|
+
expect(hint).toHaveTextContent("Drag to zoom");
|
|
504
|
+
const section: HTMLElement = expectRevealedOnHoverOf(hint);
|
|
505
|
+
expect(
|
|
506
|
+
section.querySelectorAll('[data-testid="zoom-chart"]').length,
|
|
507
|
+
).toBeGreaterThan(0);
|
|
508
|
+
}
|
|
509
|
+
|
|
510
|
+
// The Resource usage section holds the four cards the hint speaks for.
|
|
511
|
+
const resourceSection: HTMLElement = hints[1]!.closest(
|
|
512
|
+
'[class~="group/zoomhint"]',
|
|
513
|
+
) as HTMLElement;
|
|
514
|
+
expect(resourceSection).toHaveTextContent("Resource usage");
|
|
515
|
+
expect(
|
|
516
|
+
resourceSection.querySelectorAll('[data-testid="zoom-chart"]'),
|
|
517
|
+
).toHaveLength(4);
|
|
518
|
+
});
|
|
519
|
+
|
|
520
|
+
test("a drag on the CPU chart refetches every chart, tile and table for the dragged window", async () => {
|
|
521
|
+
await renderOverview();
|
|
522
|
+
expect(cpuTile()).toHaveTextContent("64.0%");
|
|
523
|
+
const before: number = aggregateCalls().length;
|
|
524
|
+
|
|
525
|
+
await dragAcross(zoomChart(1), at("11:36"), at("11:44"));
|
|
526
|
+
|
|
527
|
+
expect(aggregateCalls().length).toBe(before + OVERVIEW_FETCHES);
|
|
528
|
+
const load: {
|
|
529
|
+
windows: Array<[string, string]>;
|
|
530
|
+
processFallback: [string, string];
|
|
531
|
+
} = lastOverviewLoad();
|
|
532
|
+
expect(load.windows).toEqual(
|
|
533
|
+
everyWindowIs(windowOf(at("11:36"), at("11:44")), OVERVIEW_FETCHES - 1),
|
|
534
|
+
);
|
|
535
|
+
// The Processes fallback reads the last five minutes of the new window.
|
|
536
|
+
expect(load.processFallback).toEqual(windowOf(at("11:39"), at("11:44")));
|
|
537
|
+
|
|
538
|
+
// Every chart is redrawn over the zoomed window...
|
|
539
|
+
expect(chartWindows()).toEqual(
|
|
540
|
+
everyWindowIs(windowOf(at("11:36"), at("11:44")), OVERVIEW_CHARTS),
|
|
541
|
+
);
|
|
542
|
+
// ...and the tiles read it too: 11:39-11:44 is before CPU got busy.
|
|
543
|
+
expect(cpuTile()).toHaveTextContent("12.0%");
|
|
544
|
+
|
|
545
|
+
expect(pickerLabel()).toBe(customRangeLabel(at("11:36"), at("11:44")));
|
|
546
|
+
expect(resetZoomButtons()).toHaveLength(1);
|
|
547
|
+
expectOneSharedZoom({ zoomed: true });
|
|
548
|
+
for (const hint of zoomHints()) {
|
|
549
|
+
expect(hint).toHaveTextContent("Double-click to reset");
|
|
550
|
+
}
|
|
551
|
+
});
|
|
552
|
+
|
|
553
|
+
test("a drag made right to left zooms to the same window", async () => {
|
|
554
|
+
await renderOverview();
|
|
555
|
+
|
|
556
|
+
await dragAcross(zoomChart(3), at("11:44"), at("11:36"));
|
|
557
|
+
|
|
558
|
+
expect(lastOverviewLoad().windows[0]).toEqual(
|
|
559
|
+
windowOf(at("11:36"), at("11:44")),
|
|
560
|
+
);
|
|
561
|
+
expect(pickerLabel()).toBe(customRangeLabel(at("11:36"), at("11:44")));
|
|
562
|
+
});
|
|
563
|
+
|
|
564
|
+
test("a double-click on a different chart puts Past 30 Minutes back, sliding with the clock", async () => {
|
|
565
|
+
await renderOverview();
|
|
566
|
+
await dragAcross(zoomChart(1), at("11:36"), at("11:44"));
|
|
567
|
+
|
|
568
|
+
await doubleClick(zoomChart(4));
|
|
569
|
+
|
|
570
|
+
expect(lastOverviewLoad().windows).toEqual(
|
|
571
|
+
everyWindowIs(windowOf(at("11:30"), NOW), OVERVIEW_FETCHES - 1),
|
|
572
|
+
);
|
|
573
|
+
expect(chartWindows()).toEqual(
|
|
574
|
+
everyWindowIs(windowOf(at("11:30"), NOW), OVERVIEW_CHARTS),
|
|
575
|
+
);
|
|
576
|
+
expect(cpuTile()).toHaveTextContent("64.0%");
|
|
577
|
+
expect(pickerLabel()).toBe(PAST_30_MINUTES);
|
|
578
|
+
expect(resetZoomButtons()).toHaveLength(0);
|
|
579
|
+
expectOneSharedZoom({ zoomed: false });
|
|
580
|
+
});
|
|
581
|
+
|
|
582
|
+
test("a double-click on the chart that was dragged resets too", async () => {
|
|
583
|
+
await renderOverview();
|
|
584
|
+
await dragAcross(zoomChart(0), at("11:31"), at("11:59"));
|
|
585
|
+
expect(pickerLabel()).toBe(customRangeLabel(at("11:31"), at("11:59")));
|
|
586
|
+
|
|
587
|
+
await doubleClick(zoomChart(0));
|
|
588
|
+
|
|
589
|
+
expect(pickerLabel()).toBe(PAST_30_MINUTES);
|
|
590
|
+
expect(lastOverviewLoad().windows[0]).toEqual(windowOf(at("11:30"), NOW));
|
|
591
|
+
});
|
|
592
|
+
|
|
593
|
+
test("Reset zoom beside the picker does what a double-click does", async () => {
|
|
594
|
+
await renderOverview();
|
|
595
|
+
await dragAcross(zoomChart(2), at("11:36"), at("11:44"));
|
|
596
|
+
|
|
597
|
+
fireEvent.click(resetZoomButtons()[0]!);
|
|
598
|
+
await flush();
|
|
599
|
+
|
|
600
|
+
expect(lastOverviewLoad().windows[0]).toEqual(windowOf(at("11:30"), NOW));
|
|
601
|
+
expect(pickerLabel()).toBe(PAST_30_MINUTES);
|
|
602
|
+
expect(resetZoomButtons()).toHaveLength(0);
|
|
603
|
+
});
|
|
604
|
+
|
|
605
|
+
test("drilling in twice, one double-click climbs all the way out", async () => {
|
|
606
|
+
await renderOverview();
|
|
607
|
+
|
|
608
|
+
await dragAcross(zoomChart(1), at("11:36"), at("11:44"));
|
|
609
|
+
await dragAcross(zoomChart(2), at("11:40"), at("11:42"));
|
|
610
|
+
|
|
611
|
+
expect(lastOverviewLoad().windows[0]).toEqual(
|
|
612
|
+
windowOf(at("11:40"), at("11:42")),
|
|
613
|
+
);
|
|
614
|
+
expect(pickerLabel()).toBe(customRangeLabel(at("11:40"), at("11:42")));
|
|
615
|
+
|
|
616
|
+
await doubleClick(zoomChart(3));
|
|
617
|
+
|
|
618
|
+
// Straight back to the half hour, not to the first zoom.
|
|
619
|
+
expect(lastOverviewLoad().windows[0]).toEqual(windowOf(at("11:30"), NOW));
|
|
620
|
+
expect(pickerLabel()).toBe(PAST_30_MINUTES);
|
|
621
|
+
});
|
|
622
|
+
|
|
623
|
+
test("a drag past the end of the window stops at the window's end", async () => {
|
|
624
|
+
await renderOverview();
|
|
625
|
+
|
|
626
|
+
// The newest bucket reaches past "now"; the zoom must not.
|
|
627
|
+
await dragAcross(zoomChart(1), at("11:50"), at("12:10"));
|
|
628
|
+
|
|
629
|
+
expect(lastOverviewLoad().windows[0]).toEqual(windowOf(at("11:50"), NOW));
|
|
630
|
+
});
|
|
631
|
+
|
|
632
|
+
test("picking a range in the picker ends the zoom, and a double-click then leaves the page alone", async () => {
|
|
633
|
+
await renderOverview();
|
|
634
|
+
await dragAcross(zoomChart(1), at("11:36"), at("11:44"));
|
|
635
|
+
|
|
636
|
+
await pickPreset("Past 1 Hour");
|
|
637
|
+
|
|
638
|
+
expect(lastOverviewLoad().windows[0]).toEqual(windowOf(at("11:00"), NOW));
|
|
639
|
+
expect(resetZoomButtons()).toHaveLength(0);
|
|
640
|
+
expectOneSharedZoom({ zoomed: false });
|
|
641
|
+
|
|
642
|
+
const before: number = aggregateCalls().length;
|
|
643
|
+
await doubleClick(zoomChart(4));
|
|
644
|
+
|
|
645
|
+
expect(aggregateCalls().length).toBe(before);
|
|
646
|
+
expect(pickerLabel()).toBe(presetLabel(TimeRange.PAST_ONE_HOUR));
|
|
647
|
+
});
|
|
648
|
+
|
|
649
|
+
test("a double-click with nothing to undo does not refetch", async () => {
|
|
650
|
+
await renderOverview();
|
|
651
|
+
const before: number = aggregateCalls().length;
|
|
652
|
+
|
|
653
|
+
await doubleClick(zoomChart(2));
|
|
654
|
+
|
|
655
|
+
expect(aggregateCalls().length).toBe(before);
|
|
656
|
+
expect(pickerLabel()).toBe(PAST_30_MINUTES);
|
|
657
|
+
});
|
|
658
|
+
|
|
659
|
+
test("auto-refresh keeps a zoom pinned to its window, and slides again after the reset", async () => {
|
|
660
|
+
await renderOverview();
|
|
661
|
+
await dragAcross(zoomChart(1), at("11:36"), at("11:44"));
|
|
662
|
+
const afterZoom: number = aggregateCalls().length;
|
|
663
|
+
|
|
664
|
+
// The overview refreshes every 30 seconds by default.
|
|
665
|
+
await act(async () => {
|
|
666
|
+
jest.advanceTimersByTime(30_000);
|
|
667
|
+
});
|
|
668
|
+
await flush();
|
|
669
|
+
|
|
670
|
+
expect(aggregateCalls().length).toBe(afterZoom + OVERVIEW_FETCHES);
|
|
671
|
+
expect(lastOverviewLoad().windows[0]).toEqual(
|
|
672
|
+
windowOf(at("11:36"), at("11:44")),
|
|
673
|
+
);
|
|
674
|
+
expect(resetZoomButtons()).toHaveLength(1);
|
|
675
|
+
|
|
676
|
+
await doubleClick(zoomChart(0));
|
|
677
|
+
expect(lastOverviewLoad().windows[0]).toEqual(
|
|
678
|
+
windowOf(at("11:30:30"), at("12:00:30")),
|
|
679
|
+
);
|
|
680
|
+
|
|
681
|
+
await act(async () => {
|
|
682
|
+
jest.advanceTimersByTime(30_000);
|
|
683
|
+
});
|
|
684
|
+
await flush();
|
|
685
|
+
|
|
686
|
+
expect(lastOverviewLoad().windows[0]).toEqual(
|
|
687
|
+
windowOf(at("11:31"), at("12:01")),
|
|
688
|
+
);
|
|
689
|
+
});
|
|
690
|
+
|
|
691
|
+
test("Refresh now while zoomed reloads the zoomed window and keeps the zoom", async () => {
|
|
692
|
+
await renderOverview();
|
|
693
|
+
await dragAcross(zoomChart(1), at("11:36"), at("11:44"));
|
|
694
|
+
const afterZoom: number = aggregateCalls().length;
|
|
695
|
+
|
|
696
|
+
fireEvent.click(screen.getByTestId("manual-refresh"));
|
|
697
|
+
await flush();
|
|
698
|
+
|
|
699
|
+
expect(aggregateCalls().length).toBe(afterZoom + OVERVIEW_FETCHES);
|
|
700
|
+
expect(lastOverviewLoad().windows[0]).toEqual(
|
|
701
|
+
windowOf(at("11:36"), at("11:44")),
|
|
702
|
+
);
|
|
703
|
+
expect(resetZoomButtons()).toHaveLength(1);
|
|
704
|
+
expectOneSharedZoom({ zoomed: true });
|
|
705
|
+
});
|
|
706
|
+
|
|
707
|
+
test("a slow response for the zoomed window cannot repaint the page after the reset", async () => {
|
|
708
|
+
await renderOverview();
|
|
709
|
+
holdAggregates = true;
|
|
710
|
+
|
|
711
|
+
await dragAcross(zoomChart(1), at("11:36"), at("11:44"));
|
|
712
|
+
await doubleClick(zoomChart(2));
|
|
713
|
+
|
|
714
|
+
// The reset's answer lands first; the zoom's, late, must be dropped.
|
|
715
|
+
await releaseAggregatesEndingAt(NOW);
|
|
716
|
+
expect(chartWindows()).toEqual(
|
|
717
|
+
everyWindowIs(windowOf(at("11:30"), NOW), OVERVIEW_CHARTS),
|
|
718
|
+
);
|
|
719
|
+
|
|
720
|
+
await releaseAggregatesEndingAt(at("11:44"));
|
|
721
|
+
expect(chartWindows()).toEqual(
|
|
722
|
+
everyWindowIs(windowOf(at("11:30"), NOW), OVERVIEW_CHARTS),
|
|
723
|
+
);
|
|
724
|
+
expect(cpuTile()).toHaveTextContent("64.0%");
|
|
725
|
+
expect(pickerLabel()).toBe(PAST_30_MINUTES);
|
|
726
|
+
});
|
|
727
|
+
|
|
728
|
+
test("a slow response for the old window cannot overwrite the zoom", async () => {
|
|
729
|
+
await renderOverview();
|
|
730
|
+
holdAggregates = true;
|
|
731
|
+
|
|
732
|
+
// A refresh of the half hour is in flight when the reader zooms.
|
|
733
|
+
fireEvent.click(screen.getByTestId("manual-refresh"));
|
|
734
|
+
await flush();
|
|
735
|
+
await dragAcross(zoomChart(1), at("11:36"), at("11:44"));
|
|
736
|
+
|
|
737
|
+
await releaseAggregatesEndingAt(at("11:44"));
|
|
738
|
+
await releaseAggregatesEndingAt(NOW);
|
|
739
|
+
|
|
740
|
+
expect(chartWindows()).toEqual(
|
|
741
|
+
everyWindowIs(windowOf(at("11:36"), at("11:44")), OVERVIEW_CHARTS),
|
|
742
|
+
);
|
|
743
|
+
expect(cpuTile()).toHaveTextContent("12.0%");
|
|
744
|
+
});
|
|
745
|
+
|
|
746
|
+
test("when the zoomed load fails, Reset zoom beside the picker is still the way back", async () => {
|
|
747
|
+
await renderOverview();
|
|
748
|
+
failAggregate = (call: AggregateCall): boolean => {
|
|
749
|
+
return call.aggregateBy.endTimestamp.getTime() === at("11:44").getTime();
|
|
750
|
+
};
|
|
751
|
+
|
|
752
|
+
await dragAcross(zoomChart(1), at("11:36"), at("11:44"));
|
|
753
|
+
|
|
754
|
+
expect(screen.getByText("Analytics is down")).toBeInTheDocument();
|
|
755
|
+
expect(zoomCharts()).toHaveLength(0);
|
|
756
|
+
expect(resetZoomButtons()).toHaveLength(1);
|
|
757
|
+
|
|
758
|
+
fireEvent.click(resetZoomButtons()[0]!);
|
|
759
|
+
await flush();
|
|
760
|
+
|
|
761
|
+
expect(screen.queryByText("Analytics is down")).toBeNull();
|
|
762
|
+
expect(zoomCharts()).toHaveLength(OVERVIEW_CHARTS);
|
|
763
|
+
expect(pickerLabel()).toBe(PAST_30_MINUTES);
|
|
764
|
+
});
|
|
765
|
+
});
|
|
766
|
+
|
|
767
|
+
// ------------------------------------------------------------------- process
|
|
768
|
+
|
|
769
|
+
const PROCESS_CHARTS: number = 3; // CPU, Memory (RSS), Disk I/O
|
|
770
|
+
const PROCESS_FETCHES: number = 6; // aggregates per load
|
|
771
|
+
|
|
772
|
+
// The process started at 11:40; before that there is nothing to show.
|
|
773
|
+
function processRows(call: AggregateCall): Array<Row> {
|
|
774
|
+
const startedAt: number = at("11:40").getTime();
|
|
775
|
+
const alive: (rows: Array<Row>) => Array<Row> = (
|
|
776
|
+
rows: Array<Row>,
|
|
777
|
+
): Array<Row> => {
|
|
778
|
+
return rows.filter((row: Row): boolean => {
|
|
779
|
+
return row.timestamp.getTime() >= startedAt;
|
|
780
|
+
});
|
|
781
|
+
};
|
|
782
|
+
|
|
783
|
+
switch (call.aggregateBy.query.name) {
|
|
784
|
+
case "process.cpu.utilization":
|
|
785
|
+
return alive(
|
|
786
|
+
everyMinute(() => {
|
|
787
|
+
return 0.3;
|
|
788
|
+
}),
|
|
789
|
+
);
|
|
790
|
+
case "process.memory.usage":
|
|
791
|
+
return alive(
|
|
792
|
+
everyMinute(() => {
|
|
793
|
+
return 2 * GIB;
|
|
794
|
+
}),
|
|
795
|
+
);
|
|
796
|
+
case "process.disk.io":
|
|
797
|
+
return alive(
|
|
798
|
+
everyMinute(
|
|
799
|
+
(time: Date): number => {
|
|
800
|
+
return time.getTime() / 100;
|
|
801
|
+
},
|
|
802
|
+
{ direction: "read" },
|
|
803
|
+
),
|
|
804
|
+
);
|
|
805
|
+
default:
|
|
806
|
+
return [];
|
|
807
|
+
}
|
|
808
|
+
}
|
|
809
|
+
|
|
810
|
+
function mockProcessIdentity(): void {
|
|
811
|
+
lastParamMock.mockReturnValue("4321");
|
|
812
|
+
getListMock.mockImplementation((request: unknown) => {
|
|
813
|
+
const call: ListCall = request as ListCall;
|
|
814
|
+
const end: Date = call.query.time.endValue;
|
|
815
|
+
|
|
816
|
+
// Samples only exist while the process runs (from 11:40).
|
|
817
|
+
if (end.getTime() <= at("11:40").getTime()) {
|
|
818
|
+
return Promise.resolve({ data: [] });
|
|
819
|
+
}
|
|
820
|
+
|
|
821
|
+
return Promise.resolve({
|
|
822
|
+
data: [
|
|
823
|
+
{
|
|
824
|
+
time: new Date(end.getTime() - 60_000),
|
|
825
|
+
attributes: {
|
|
826
|
+
"resource.process.pid": "4321",
|
|
827
|
+
"resource.process.executable.name": "postgres",
|
|
828
|
+
"resource.process.command": "postgres -D /var/lib/postgresql",
|
|
829
|
+
"resource.process.owner": "postgres",
|
|
830
|
+
},
|
|
831
|
+
},
|
|
832
|
+
],
|
|
833
|
+
});
|
|
834
|
+
});
|
|
835
|
+
}
|
|
836
|
+
|
|
837
|
+
async function renderProcess(): Promise<void> {
|
|
838
|
+
mockProcessIdentity();
|
|
839
|
+
answerAggregatesWith(processRows);
|
|
840
|
+
render(<HostProcessView {...PAGE_PROPS} />);
|
|
841
|
+
await flush();
|
|
842
|
+
expect(zoomCharts()).toHaveLength(PROCESS_CHARTS);
|
|
843
|
+
}
|
|
844
|
+
|
|
845
|
+
function lastProcessLoadWindows(): Array<[string, string]> {
|
|
846
|
+
return aggregateWindows(aggregateCalls().slice(-PROCESS_FETCHES));
|
|
847
|
+
}
|
|
848
|
+
|
|
849
|
+
describe("Host process view: one zoom for the whole page", () => {
|
|
850
|
+
test("the three charts hold the page's one zoom; the section heading names the gesture", async () => {
|
|
851
|
+
await renderProcess();
|
|
852
|
+
|
|
853
|
+
expectOneSharedZoom({ zoomed: false });
|
|
854
|
+
expect(chartWindows()).toEqual(
|
|
855
|
+
everyWindowIs(windowOf(at("11:30"), NOW), PROCESS_CHARTS),
|
|
856
|
+
);
|
|
857
|
+
|
|
858
|
+
const hints: Array<HTMLElement> = zoomHints();
|
|
859
|
+
expect(hints).toHaveLength(1);
|
|
860
|
+
expect(expectRevealedOnHoverOf(hints[0]!)).toHaveTextContent(
|
|
861
|
+
"Resource usage",
|
|
862
|
+
);
|
|
863
|
+
});
|
|
864
|
+
|
|
865
|
+
test("a drag retimes the charts, the tiles and the identity lookup", async () => {
|
|
866
|
+
await renderProcess();
|
|
867
|
+
const listsBefore: number = listCalls().length;
|
|
868
|
+
|
|
869
|
+
await dragAcross(zoomChart(0), at("11:44"), at("11:52"));
|
|
870
|
+
|
|
871
|
+
// Identity is resolved from the last 15 minutes of the zoomed window.
|
|
872
|
+
expect(listCalls().length).toBe(listsBefore + 1);
|
|
873
|
+
expect(
|
|
874
|
+
queryWindow(listCalls()[listCalls().length - 1]!.query.time),
|
|
875
|
+
).toEqual(windowOf(at("11:37"), at("11:52")));
|
|
876
|
+
// Every metric the charts and tiles read is fetched for the window.
|
|
877
|
+
expect(lastProcessLoadWindows()).toEqual(
|
|
878
|
+
everyWindowIs(windowOf(at("11:44"), at("11:52")), PROCESS_FETCHES),
|
|
879
|
+
);
|
|
880
|
+
expect(chartWindows()).toEqual(
|
|
881
|
+
everyWindowIs(windowOf(at("11:44"), at("11:52")), PROCESS_CHARTS),
|
|
882
|
+
);
|
|
883
|
+
expect(pickerLabel()).toBe(customRangeLabel(at("11:44"), at("11:52")));
|
|
884
|
+
expectOneSharedZoom({ zoomed: true });
|
|
885
|
+
expect(zoomHints()[0]).toHaveTextContent("Double-click to reset");
|
|
886
|
+
});
|
|
887
|
+
|
|
888
|
+
test("a double-click on another chart puts Past 30 Minutes back", async () => {
|
|
889
|
+
await renderProcess();
|
|
890
|
+
await dragAcross(zoomChart(0), at("11:44"), at("11:52"));
|
|
891
|
+
|
|
892
|
+
await doubleClick(zoomChart(2));
|
|
893
|
+
|
|
894
|
+
expect(chartWindows()).toEqual(
|
|
895
|
+
everyWindowIs(windowOf(at("11:30"), NOW), PROCESS_CHARTS),
|
|
896
|
+
);
|
|
897
|
+
expect(pickerLabel()).toBe(PAST_30_MINUTES);
|
|
898
|
+
expectOneSharedZoom({ zoomed: false });
|
|
899
|
+
});
|
|
900
|
+
|
|
901
|
+
test("a zoom into a stretch the process was quiet for offers Reset zoom under the no-data note", async () => {
|
|
902
|
+
await renderProcess();
|
|
903
|
+
expect(screen.queryByText("No process metrics in range")).toBeNull();
|
|
904
|
+
|
|
905
|
+
await dragAcross(zoomChart(1), at("11:31"), at("11:36"));
|
|
906
|
+
|
|
907
|
+
expect(screen.getByText("No process metrics in range")).toBeInTheDocument();
|
|
908
|
+
// The empty charts stay mounted, so a double-click still reaches them...
|
|
909
|
+
expect(zoomCharts()).toHaveLength(PROCESS_CHARTS);
|
|
910
|
+
// ...and the note carries the way back too, beside the picker's.
|
|
911
|
+
const note: HTMLElement = screen
|
|
912
|
+
.getByText("No process metrics in range")
|
|
913
|
+
.closest('[data-testid="card"]') as HTMLElement;
|
|
914
|
+
const resetInNote: Array<HTMLElement> = resetZoomButtons().filter(
|
|
915
|
+
(button: HTMLElement): boolean => {
|
|
916
|
+
return note.contains(button);
|
|
917
|
+
},
|
|
918
|
+
);
|
|
919
|
+
expect(resetZoomButtons()).toHaveLength(2);
|
|
920
|
+
expect(resetInNote).toHaveLength(1);
|
|
921
|
+
|
|
922
|
+
fireEvent.click(resetInNote[0]!);
|
|
923
|
+
await flush();
|
|
924
|
+
|
|
925
|
+
expect(screen.queryByText("No process metrics in range")).toBeNull();
|
|
926
|
+
expect(pickerLabel()).toBe(PAST_30_MINUTES);
|
|
927
|
+
expect(resetZoomButtons()).toHaveLength(0);
|
|
928
|
+
});
|
|
929
|
+
|
|
930
|
+
test("the no-data note offers no reset when the page is not zoomed", async () => {
|
|
931
|
+
await renderProcess();
|
|
932
|
+
|
|
933
|
+
await pickPreset("Past 5 Minutes");
|
|
934
|
+
answerAggregatesWith((): Array<Row> => {
|
|
935
|
+
return [];
|
|
936
|
+
});
|
|
937
|
+
fireEvent.click(screen.getByTestId("manual-refresh"));
|
|
938
|
+
await flush();
|
|
939
|
+
|
|
940
|
+
expect(screen.getByText("No process metrics in range")).toBeInTheDocument();
|
|
941
|
+
expect(resetZoomButtons()).toHaveLength(0);
|
|
942
|
+
});
|
|
943
|
+
|
|
944
|
+
test("a slow response for the zoomed window cannot repaint the page after the reset", async () => {
|
|
945
|
+
await renderProcess();
|
|
946
|
+
holdAggregates = true;
|
|
947
|
+
|
|
948
|
+
await dragAcross(zoomChart(0), at("11:44"), at("11:52"));
|
|
949
|
+
await doubleClick(zoomChart(1));
|
|
950
|
+
|
|
951
|
+
await releaseAggregatesEndingAt(NOW);
|
|
952
|
+
await releaseAggregatesEndingAt(at("11:52"));
|
|
953
|
+
|
|
954
|
+
expect(chartWindows()).toEqual(
|
|
955
|
+
everyWindowIs(windowOf(at("11:30"), NOW), PROCESS_CHARTS),
|
|
956
|
+
);
|
|
957
|
+
expect(pickerLabel()).toBe(PAST_30_MINUTES);
|
|
958
|
+
});
|
|
959
|
+
});
|
|
960
|
+
|
|
961
|
+
// ------------------------------------------------------------------- service
|
|
962
|
+
|
|
963
|
+
/*
|
|
964
|
+
* A sample every 30 seconds from 11:00, newest first as the API returns
|
|
965
|
+
* them: Running (4), except Stopped (1) from 11:45 to 11:50.
|
|
966
|
+
*/
|
|
967
|
+
function serviceRows(window: TimeWindow): Array<Record<string, unknown>> {
|
|
968
|
+
const rows: Array<Record<string, unknown>> = [];
|
|
969
|
+
|
|
970
|
+
for (let i: number = 0; i < 120; i++) {
|
|
971
|
+
const time: Date = new Date(at("11:00").getTime() + i * 30_000);
|
|
972
|
+
|
|
973
|
+
if (
|
|
974
|
+
time.getTime() < window.startValue.getTime() ||
|
|
975
|
+
time.getTime() > window.endValue.getTime()
|
|
976
|
+
) {
|
|
977
|
+
continue;
|
|
978
|
+
}
|
|
979
|
+
|
|
980
|
+
const stopped: boolean =
|
|
981
|
+
time.getTime() >= at("11:45").getTime() &&
|
|
982
|
+
time.getTime() < at("11:50").getTime();
|
|
983
|
+
|
|
984
|
+
rows.push({
|
|
985
|
+
time: time,
|
|
986
|
+
value: stopped ? 1 : 4,
|
|
987
|
+
attributes: {
|
|
988
|
+
name: "Spooler",
|
|
989
|
+
startup_mode: "auto_start",
|
|
990
|
+
"resource.host.name": "web-01",
|
|
991
|
+
},
|
|
992
|
+
});
|
|
993
|
+
}
|
|
994
|
+
|
|
995
|
+
return rows.reverse();
|
|
996
|
+
}
|
|
997
|
+
|
|
998
|
+
async function renderService(): Promise<void> {
|
|
999
|
+
lastParamMock.mockReturnValue("Spooler");
|
|
1000
|
+
getListMock.mockImplementation((request: unknown) => {
|
|
1001
|
+
return Promise.resolve({
|
|
1002
|
+
data: serviceRows((request as ListCall).query.time),
|
|
1003
|
+
});
|
|
1004
|
+
});
|
|
1005
|
+
render(<HostServiceView {...PAGE_PROPS} />);
|
|
1006
|
+
await flush();
|
|
1007
|
+
expect(zoomCharts()).toHaveLength(1);
|
|
1008
|
+
}
|
|
1009
|
+
|
|
1010
|
+
function tileOf(title: string): HTMLElement {
|
|
1011
|
+
return infoButtonsFor(title)[0]!.closest(".rounded-xl") as HTMLElement;
|
|
1012
|
+
}
|
|
1013
|
+
|
|
1014
|
+
describe("Host Windows service view: the timeline zooms the page", () => {
|
|
1015
|
+
test("the status timeline holds the page's zoom; its heading names the gesture", async () => {
|
|
1016
|
+
await renderService();
|
|
1017
|
+
|
|
1018
|
+
expectOneSharedZoom({ zoomed: false });
|
|
1019
|
+
const hints: Array<HTMLElement> = zoomHints();
|
|
1020
|
+
expect(hints).toHaveLength(1);
|
|
1021
|
+
const section: HTMLElement = expectRevealedOnHoverOf(hints[0]!);
|
|
1022
|
+
expect(section).toHaveTextContent("Status timeline");
|
|
1023
|
+
expect(section.querySelector('[data-testid="zoom-chart"]')).not.toBeNull();
|
|
1024
|
+
});
|
|
1025
|
+
|
|
1026
|
+
test("a drag narrows the timeline, the tiles and State Changes to the dragged window", async () => {
|
|
1027
|
+
await renderService();
|
|
1028
|
+
// The half hour holds the 5-minute stop: 50 of 60 samples running.
|
|
1029
|
+
expect(tileOf("Availability")).toHaveTextContent("83.3%");
|
|
1030
|
+
expect(tileOf("State Changes")).toHaveTextContent(/State Changes\s*2/);
|
|
1031
|
+
|
|
1032
|
+
await dragAcross(zoomChart(0), at("11:46"), at("11:49"));
|
|
1033
|
+
|
|
1034
|
+
expect(
|
|
1035
|
+
queryWindow(listCalls()[listCalls().length - 1]!.query.time),
|
|
1036
|
+
).toEqual(windowOf(at("11:46"), at("11:49")));
|
|
1037
|
+
expect(chartWindows()).toEqual([windowOf(at("11:46"), at("11:49"))]);
|
|
1038
|
+
expect(tileOf("Availability")).toHaveTextContent("0.0%");
|
|
1039
|
+
expect(tileOf("Current Status")).toHaveTextContent("Stopped");
|
|
1040
|
+
expect(tileOf("State Changes")).toHaveTextContent(/State Changes\s*0/);
|
|
1041
|
+
expect(pickerLabel()).toBe(customRangeLabel(at("11:46"), at("11:49")));
|
|
1042
|
+
expect(resetZoomButtons()).toHaveLength(1);
|
|
1043
|
+
});
|
|
1044
|
+
|
|
1045
|
+
test("a double-click on the timeline puts Past 30 Minutes back", async () => {
|
|
1046
|
+
await renderService();
|
|
1047
|
+
await dragAcross(zoomChart(0), at("11:46"), at("11:49"));
|
|
1048
|
+
expect(tileOf("Availability")).toHaveTextContent("0.0%");
|
|
1049
|
+
|
|
1050
|
+
await doubleClick(zoomChart(0));
|
|
1051
|
+
|
|
1052
|
+
expect(
|
|
1053
|
+
queryWindow(listCalls()[listCalls().length - 1]!.query.time),
|
|
1054
|
+
).toEqual(windowOf(at("11:30"), NOW));
|
|
1055
|
+
expect(tileOf("Availability")).toHaveTextContent("83.3%");
|
|
1056
|
+
expect(pickerLabel()).toBe(PAST_30_MINUTES);
|
|
1057
|
+
expect(resetZoomButtons()).toHaveLength(0);
|
|
1058
|
+
});
|
|
1059
|
+
|
|
1060
|
+
test("a zoom into a silent stretch hides the timeline and offers Reset zoom under the no-data note", async () => {
|
|
1061
|
+
await renderService();
|
|
1062
|
+
|
|
1063
|
+
// No scrape lands between two 30-second samples.
|
|
1064
|
+
await dragAcross(zoomChart(0), at("11:40:05"), at("11:40:25"));
|
|
1065
|
+
|
|
1066
|
+
expect(zoomCharts()).toHaveLength(0);
|
|
1067
|
+
expect(screen.getByText("No service metrics in range")).toBeInTheDocument();
|
|
1068
|
+
expect(resetZoomButtons()).toHaveLength(2);
|
|
1069
|
+
|
|
1070
|
+
const note: HTMLElement = screen
|
|
1071
|
+
.getByText("No service metrics in range")
|
|
1072
|
+
.closest('[data-testid="card"]') as HTMLElement;
|
|
1073
|
+
const inNote: HTMLElement | undefined = resetZoomButtons().find(
|
|
1074
|
+
(button: HTMLElement): boolean => {
|
|
1075
|
+
return note.contains(button);
|
|
1076
|
+
},
|
|
1077
|
+
);
|
|
1078
|
+
expect(inNote).toBeDefined();
|
|
1079
|
+
|
|
1080
|
+
fireEvent.click(inNote!);
|
|
1081
|
+
await flush();
|
|
1082
|
+
|
|
1083
|
+
expect(zoomCharts()).toHaveLength(1);
|
|
1084
|
+
expect(screen.queryByText("No service metrics in range")).toBeNull();
|
|
1085
|
+
expect(pickerLabel()).toBe(PAST_30_MINUTES);
|
|
1086
|
+
});
|
|
1087
|
+
});
|
|
1088
|
+
|
|
1089
|
+
// ------------------------------------------------------------- systemd unit
|
|
1090
|
+
|
|
1091
|
+
/*
|
|
1092
|
+
* A sample every 30 seconds from 11:00, newest first: active, except
|
|
1093
|
+
* failed from 11:45 to 11:50.
|
|
1094
|
+
*/
|
|
1095
|
+
function unitRows(window: TimeWindow): Array<Record<string, unknown>> {
|
|
1096
|
+
const rows: Array<Record<string, unknown>> = [];
|
|
1097
|
+
|
|
1098
|
+
for (let i: number = 0; i < 120; i++) {
|
|
1099
|
+
const time: Date = new Date(at("11:00").getTime() + i * 30_000);
|
|
1100
|
+
|
|
1101
|
+
if (
|
|
1102
|
+
time.getTime() < window.startValue.getTime() ||
|
|
1103
|
+
time.getTime() > window.endValue.getTime()
|
|
1104
|
+
) {
|
|
1105
|
+
continue;
|
|
1106
|
+
}
|
|
1107
|
+
|
|
1108
|
+
const failed: boolean =
|
|
1109
|
+
time.getTime() >= at("11:45").getTime() &&
|
|
1110
|
+
time.getTime() < at("11:50").getTime();
|
|
1111
|
+
|
|
1112
|
+
rows.push({
|
|
1113
|
+
time: time,
|
|
1114
|
+
value: 1,
|
|
1115
|
+
attributes: {
|
|
1116
|
+
"resource.systemd.unit.name": "nginx.service",
|
|
1117
|
+
"systemd.unit.active_state": failed ? "failed" : "active",
|
|
1118
|
+
"resource.host.name": "web-01",
|
|
1119
|
+
},
|
|
1120
|
+
});
|
|
1121
|
+
}
|
|
1122
|
+
|
|
1123
|
+
return rows.reverse();
|
|
1124
|
+
}
|
|
1125
|
+
|
|
1126
|
+
async function renderUnit(): Promise<void> {
|
|
1127
|
+
lastParamMock.mockReturnValue("nginx.service");
|
|
1128
|
+
getListMock.mockImplementation((request: unknown) => {
|
|
1129
|
+
return Promise.resolve({
|
|
1130
|
+
data: unitRows((request as ListCall).query.time),
|
|
1131
|
+
});
|
|
1132
|
+
});
|
|
1133
|
+
render(<HostSystemdUnitView {...PAGE_PROPS} />);
|
|
1134
|
+
await flush();
|
|
1135
|
+
expect(zoomCharts()).toHaveLength(1);
|
|
1136
|
+
}
|
|
1137
|
+
|
|
1138
|
+
describe("Host systemd unit view: the timeline zooms the page", () => {
|
|
1139
|
+
test("the state timeline holds the page's zoom; its heading names the gesture", async () => {
|
|
1140
|
+
await renderUnit();
|
|
1141
|
+
|
|
1142
|
+
expectOneSharedZoom({ zoomed: false });
|
|
1143
|
+
const hints: Array<HTMLElement> = zoomHints();
|
|
1144
|
+
expect(hints).toHaveLength(1);
|
|
1145
|
+
expect(expectRevealedOnHoverOf(hints[0]!)).toHaveTextContent(
|
|
1146
|
+
"State timeline",
|
|
1147
|
+
);
|
|
1148
|
+
});
|
|
1149
|
+
|
|
1150
|
+
test("a drag narrows the timeline, the tiles and State Changes to the dragged window", async () => {
|
|
1151
|
+
await renderUnit();
|
|
1152
|
+
expect(tileOf("Availability")).toHaveTextContent("83.3%");
|
|
1153
|
+
|
|
1154
|
+
await dragAcross(zoomChart(0), at("11:46"), at("11:49"));
|
|
1155
|
+
|
|
1156
|
+
expect(
|
|
1157
|
+
queryWindow(listCalls()[listCalls().length - 1]!.query.time),
|
|
1158
|
+
).toEqual(windowOf(at("11:46"), at("11:49")));
|
|
1159
|
+
expect(chartWindows()).toEqual([windowOf(at("11:46"), at("11:49"))]);
|
|
1160
|
+
expect(tileOf("Availability")).toHaveTextContent("0.0%");
|
|
1161
|
+
expect(tileOf("Current State")).toHaveTextContent("Failed");
|
|
1162
|
+
expect(tileOf("State Changes")).toHaveTextContent(/State Changes\s*0/);
|
|
1163
|
+
expect(pickerLabel()).toBe(customRangeLabel(at("11:46"), at("11:49")));
|
|
1164
|
+
expectOneSharedZoom({ zoomed: true });
|
|
1165
|
+
});
|
|
1166
|
+
|
|
1167
|
+
test("a double-click on the timeline, or Reset zoom, puts Past 30 Minutes back", async () => {
|
|
1168
|
+
await renderUnit();
|
|
1169
|
+
|
|
1170
|
+
await dragAcross(zoomChart(0), at("11:46"), at("11:49"));
|
|
1171
|
+
await doubleClick(zoomChart(0));
|
|
1172
|
+
expect(pickerLabel()).toBe(PAST_30_MINUTES);
|
|
1173
|
+
expect(tileOf("Availability")).toHaveTextContent("83.3%");
|
|
1174
|
+
|
|
1175
|
+
await dragAcross(zoomChart(0), at("11:46"), at("11:49"));
|
|
1176
|
+
fireEvent.click(resetZoomButtons()[0]!);
|
|
1177
|
+
await flush();
|
|
1178
|
+
expect(pickerLabel()).toBe(PAST_30_MINUTES);
|
|
1179
|
+
expect(
|
|
1180
|
+
queryWindow(listCalls()[listCalls().length - 1]!.query.time),
|
|
1181
|
+
).toEqual(windowOf(at("11:30"), NOW));
|
|
1182
|
+
});
|
|
1183
|
+
|
|
1184
|
+
test("a zoom into a silent stretch hides the timeline and offers Reset zoom under the no-data note", async () => {
|
|
1185
|
+
await renderUnit();
|
|
1186
|
+
|
|
1187
|
+
await dragAcross(zoomChart(0), at("11:40:05"), at("11:40:25"));
|
|
1188
|
+
|
|
1189
|
+
expect(zoomCharts()).toHaveLength(0);
|
|
1190
|
+
expect(screen.getByText("No unit metrics in range")).toBeInTheDocument();
|
|
1191
|
+
|
|
1192
|
+
const note: HTMLElement = screen
|
|
1193
|
+
.getByText("No unit metrics in range")
|
|
1194
|
+
.closest('[data-testid="card"]') as HTMLElement;
|
|
1195
|
+
const inNote: HTMLElement | undefined = resetZoomButtons().find(
|
|
1196
|
+
(button: HTMLElement): boolean => {
|
|
1197
|
+
return note.contains(button);
|
|
1198
|
+
},
|
|
1199
|
+
);
|
|
1200
|
+
expect(inNote).toBeDefined();
|
|
1201
|
+
|
|
1202
|
+
fireEvent.click(inNote!);
|
|
1203
|
+
await flush();
|
|
1204
|
+
|
|
1205
|
+
expect(zoomCharts()).toHaveLength(1);
|
|
1206
|
+
expect(pickerLabel()).toBe(PAST_30_MINUTES);
|
|
1207
|
+
});
|
|
1208
|
+
});
|