@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,1147 @@
|
|
|
1
|
+
/** @timezone UTC */
|
|
2
|
+
|
|
3
|
+
import "@testing-library/jest-dom";
|
|
4
|
+
import {
|
|
5
|
+
afterEach,
|
|
6
|
+
beforeEach,
|
|
7
|
+
describe,
|
|
8
|
+
expect,
|
|
9
|
+
jest,
|
|
10
|
+
test,
|
|
11
|
+
} from "@jest/globals";
|
|
12
|
+
import {
|
|
13
|
+
act,
|
|
14
|
+
cleanup,
|
|
15
|
+
fireEvent,
|
|
16
|
+
render,
|
|
17
|
+
screen,
|
|
18
|
+
} from "@testing-library/react";
|
|
19
|
+
import React, { FunctionComponent, ReactElement, useState } from "react";
|
|
20
|
+
import fs from "fs";
|
|
21
|
+
import path from "path";
|
|
22
|
+
import getJestMockFunction, { MockFunction } from "../../../../MockType";
|
|
23
|
+
|
|
24
|
+
/*
|
|
25
|
+
* The promises of the "Zooming Into a Time Range" docs page (issue #4105),
|
|
26
|
+
* each checked against the rendered product: the page says it, and the
|
|
27
|
+
* charts do it. A behaviour change that leaves the page behind - a reset
|
|
28
|
+
* that climbs out one level at a time, a click on a line chart that zooms,
|
|
29
|
+
* a hint in new words - fails here, next to the sentence it broke.
|
|
30
|
+
*
|
|
31
|
+
* The words on screen and the pure zoom rules are also checked, statically,
|
|
32
|
+
* in App's Tests/FeatureSet/Docs/ChartTimeRangeZoomDocs.test.ts.
|
|
33
|
+
*
|
|
34
|
+
* Only recharts' chart ROOTS are stood in for: jsdom has no layout, so a
|
|
35
|
+
* real pointer could never be resolved to a bucket. The stand-in records
|
|
36
|
+
* the chart-level handlers, and the tests call them with the chart state
|
|
37
|
+
* recharts would pass (activeTooltipIndex on the line, area and bar
|
|
38
|
+
* charts; activeLabel, the bucket's label, on the volume histograms).
|
|
39
|
+
* Everything above that - the chart wrappers, both selection hooks, the
|
|
40
|
+
* page zoom, the picker and its Reset zoom, the hints - is the product.
|
|
41
|
+
*/
|
|
42
|
+
|
|
43
|
+
type ChartRootHandlers = {
|
|
44
|
+
data?: Array<Record<string, unknown>>;
|
|
45
|
+
onMouseDown?: (
|
|
46
|
+
state: Record<string, unknown>,
|
|
47
|
+
event?: Record<string, unknown>,
|
|
48
|
+
) => void;
|
|
49
|
+
onMouseMove?: (
|
|
50
|
+
state: Record<string, unknown>,
|
|
51
|
+
event: Record<string, unknown>,
|
|
52
|
+
) => void;
|
|
53
|
+
onMouseUp?: (state?: Record<string, unknown> | null) => void;
|
|
54
|
+
onClick?: (state: Record<string, unknown>) => void;
|
|
55
|
+
onDoubleClick?: () => void;
|
|
56
|
+
};
|
|
57
|
+
|
|
58
|
+
// Keyed by syncId, or "<kind>-unsynced" for a chart that has none.
|
|
59
|
+
const mockChartRoots: Map<string, ChartRootHandlers> = new Map();
|
|
60
|
+
|
|
61
|
+
jest.mock("recharts", () => {
|
|
62
|
+
const actual: Record<string, unknown> = jest.requireActual(
|
|
63
|
+
"recharts",
|
|
64
|
+
) as Record<string, unknown>;
|
|
65
|
+
const react: typeof React = jest.requireActual("react") as typeof React;
|
|
66
|
+
|
|
67
|
+
const makeRoot: (
|
|
68
|
+
kind: string,
|
|
69
|
+
) => (props: Record<string, unknown>) => ReactElement | null = (
|
|
70
|
+
kind: string,
|
|
71
|
+
) => {
|
|
72
|
+
return (props: Record<string, unknown>): ReactElement | null => {
|
|
73
|
+
/*
|
|
74
|
+
* React calls a component bare, with no props, to describe the
|
|
75
|
+
* component stack for a dev warning. That is not a render.
|
|
76
|
+
*/
|
|
77
|
+
if (!props) {
|
|
78
|
+
return null;
|
|
79
|
+
}
|
|
80
|
+
const key: string =
|
|
81
|
+
props["syncId"] !== undefined
|
|
82
|
+
? String(props["syncId"])
|
|
83
|
+
: `${kind}-unsynced`;
|
|
84
|
+
mockChartRoots.set(key, props as ChartRootHandlers);
|
|
85
|
+
return react.createElement("div", {
|
|
86
|
+
"data-testid": `chart-root-${key}`,
|
|
87
|
+
});
|
|
88
|
+
};
|
|
89
|
+
};
|
|
90
|
+
|
|
91
|
+
return {
|
|
92
|
+
...actual,
|
|
93
|
+
ResponsiveContainer: (props: { children: React.ReactNode }) => {
|
|
94
|
+
return props.children;
|
|
95
|
+
},
|
|
96
|
+
LineChart: makeRoot("line"),
|
|
97
|
+
AreaChart: makeRoot("area"),
|
|
98
|
+
BarChart: makeRoot("bar"),
|
|
99
|
+
};
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
// Imported after the mock so the charts pick the stand-in up.
|
|
103
|
+
import LineChartElement from "../../../../../UI/Components/Charts/Line/LineChart";
|
|
104
|
+
import AreaChartElement from "../../../../../UI/Components/Charts/Area/AreaChart";
|
|
105
|
+
import BarChartElement from "../../../../../UI/Components/Charts/Bar/BarChart";
|
|
106
|
+
import ChartGroup, {
|
|
107
|
+
Chart,
|
|
108
|
+
ChartType,
|
|
109
|
+
} from "../../../../../UI/Components/Charts/ChartGroup/ChartGroup";
|
|
110
|
+
import { SparkAreaChart } from "../../../../../UI/Components/Charts/ChartLibrary/SparkChart/SparkChart";
|
|
111
|
+
import { CHART_DATA_POINT_DATE_KEY } from "../../../../../UI/Components/Charts/ChartLibrary/Types/ChartDataPoint";
|
|
112
|
+
import { DOUBLE_CLICK_DISAMBIGUATION_MS } from "../../../../../UI/Components/Charts/ChartLibrary/Utils/DoubleClick";
|
|
113
|
+
import ChartCurve from "../../../../../UI/Components/Charts/Types/ChartCurve";
|
|
114
|
+
import DataPoint from "../../../../../UI/Components/Charts/Types/DataPoint";
|
|
115
|
+
import SeriesPoint from "../../../../../UI/Components/Charts/Types/SeriesPoints";
|
|
116
|
+
import {
|
|
117
|
+
XAxis as ChartXAxis,
|
|
118
|
+
XAxisAggregateType,
|
|
119
|
+
} from "../../../../../UI/Components/Charts/Types/XAxis/XAxis";
|
|
120
|
+
import XAxisPrecision from "../../../../../UI/Components/Charts/Types/XAxis/XAxisPrecision";
|
|
121
|
+
import XAxisType from "../../../../../UI/Components/Charts/Types/XAxis/XAxisType";
|
|
122
|
+
import YAxis, {
|
|
123
|
+
YAxisPrecision,
|
|
124
|
+
} from "../../../../../UI/Components/Charts/Types/YAxis/YAxis";
|
|
125
|
+
import YAxisType from "../../../../../UI/Components/Charts/Types/YAxis/YAxisType";
|
|
126
|
+
import {
|
|
127
|
+
TimeRangeZoomProvider,
|
|
128
|
+
TimeRangeZoomScope,
|
|
129
|
+
} from "../../../../../UI/Components/Charts/TimeRangeZoom/TimeRangeZoomContext";
|
|
130
|
+
import { TimeRangeZoom } from "../../../../../UI/Components/Charts/TimeRangeZoom/UseTimeRangeZoom";
|
|
131
|
+
import TimeRangeZoomHint, {
|
|
132
|
+
TIME_RANGE_ZOOM_HINT_GROUP_CLASS,
|
|
133
|
+
TIME_RANGE_ZOOM_HINT_TEST_ID,
|
|
134
|
+
} from "../../../../../UI/Components/Charts/TimeRangeZoom/TimeRangeZoomHint";
|
|
135
|
+
import { RESET_TIME_RANGE_ZOOM_BUTTON_TEST_ID } from "../../../../../UI/Components/Charts/TimeRangeZoom/ResetTimeRangeZoomButton";
|
|
136
|
+
import TelemetryTimeRangePicker from "../../../../../UI/Components/TelemetryViewer/components/TelemetryTimeRangePicker";
|
|
137
|
+
import LogsHistogram from "../../../../../UI/Components/LogsViewer/components/LogsHistogram";
|
|
138
|
+
import TelemetryHistogram from "../../../../../UI/Components/TelemetryViewer/components/TelemetryHistogram";
|
|
139
|
+
import { HistogramBucket as LogsHistogramBucket } from "../../../../../UI/Components/LogsViewer/types";
|
|
140
|
+
import {
|
|
141
|
+
HistogramBucket as TelemetryHistogramBucket,
|
|
142
|
+
HistogramSeriesOption,
|
|
143
|
+
} from "../../../../../UI/Components/TelemetryViewer/types";
|
|
144
|
+
import LogSeverity from "../../../../../Types/Log/LogSeverity";
|
|
145
|
+
import InBetween from "../../../../../Types/BaseDatabase/InBetween";
|
|
146
|
+
import RangeStartAndEndDateTime, {
|
|
147
|
+
RangeStartAndEndDateTimeUtil,
|
|
148
|
+
} from "../../../../../Types/Time/RangeStartAndEndDateTime";
|
|
149
|
+
import TimeRange from "../../../../../Types/Time/TimeRange";
|
|
150
|
+
|
|
151
|
+
const ZOOM_DOCS_PATH: string = path.resolve(
|
|
152
|
+
__dirname,
|
|
153
|
+
"../../../../../../App/FeatureSet/Docs/Content/en/telemetry/charts-and-time-ranges.md",
|
|
154
|
+
);
|
|
155
|
+
|
|
156
|
+
/*
|
|
157
|
+
* The page as a reader takes it in: a phrase the markdown wraps over two
|
|
158
|
+
* lines still reads as one.
|
|
159
|
+
*/
|
|
160
|
+
function docsProse(): string {
|
|
161
|
+
return fs.readFileSync(ZOOM_DOCS_PATH, "utf8").replace(/\s+/g, " ");
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
// What the page quotes in bold, lower-cased: how it names words on screen.
|
|
165
|
+
function docsBoldPhrases(): Array<string> {
|
|
166
|
+
return Array.from(
|
|
167
|
+
docsProse().matchAll(/\*\*([^*]+)\*\*/g),
|
|
168
|
+
(match: RegExpMatchArray): string => {
|
|
169
|
+
return (match[1] || "").trim().toLowerCase();
|
|
170
|
+
},
|
|
171
|
+
);
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
function expectDocsToSay(sentence: string): void {
|
|
175
|
+
expect(docsProse()).toContain(sentence);
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
function unique(values: Array<string>): Array<string> {
|
|
179
|
+
return Array.from(new Set(values));
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
const MINUTE_MS: number = 60 * 1000;
|
|
183
|
+
|
|
184
|
+
// Half a minute into a bucket: the newest bucket is still filling up.
|
|
185
|
+
const NOW: Date = new Date("2026-09-28T12:00:30.000Z");
|
|
186
|
+
|
|
187
|
+
const PAST_HOUR: RangeStartAndEndDateTime = { range: TimeRange.PAST_ONE_HOUR };
|
|
188
|
+
|
|
189
|
+
const Y_AXIS: YAxis = {
|
|
190
|
+
legend: "%",
|
|
191
|
+
options: {
|
|
192
|
+
type: YAxisType.Number,
|
|
193
|
+
min: 0,
|
|
194
|
+
max: 100,
|
|
195
|
+
precision: YAxisPrecision.NoDecimals,
|
|
196
|
+
formatter: (value: number): string => {
|
|
197
|
+
return `${value}%`;
|
|
198
|
+
},
|
|
199
|
+
},
|
|
200
|
+
};
|
|
201
|
+
|
|
202
|
+
// One point on every minute of the window, the newest bucket included.
|
|
203
|
+
function seriesFor(window: InBetween<Date>, name: string): Array<SeriesPoint> {
|
|
204
|
+
const points: Array<DataPoint> = [];
|
|
205
|
+
for (
|
|
206
|
+
let at: number =
|
|
207
|
+
Math.ceil(window.startValue.getTime() / MINUTE_MS) * MINUTE_MS;
|
|
208
|
+
at <= window.endValue.getTime();
|
|
209
|
+
at += MINUTE_MS
|
|
210
|
+
) {
|
|
211
|
+
points.push({ x: new Date(at), y: 40 + ((at / MINUTE_MS) % 20) });
|
|
212
|
+
}
|
|
213
|
+
return [{ seriesName: name, data: points }];
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
function xAxisFor(window: InBetween<Date>): ChartXAxis {
|
|
217
|
+
return {
|
|
218
|
+
legend: "Time",
|
|
219
|
+
options: {
|
|
220
|
+
type: XAxisType.Time,
|
|
221
|
+
min: window.startValue,
|
|
222
|
+
max: window.endValue,
|
|
223
|
+
aggregateType: XAxisAggregateType.Average,
|
|
224
|
+
precision: XAxisPrecision.EVERY_MINUTE,
|
|
225
|
+
},
|
|
226
|
+
};
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
const rangeChanges: MockFunction = getJestMockFunction();
|
|
230
|
+
const fetchWindow: MockFunction = getJestMockFunction();
|
|
231
|
+
const bucketClick: MockFunction = getJestMockFunction();
|
|
232
|
+
|
|
233
|
+
interface ZoomablePageProps {
|
|
234
|
+
initialRange?: RangeStartAndEndDateTime | undefined;
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
/*
|
|
238
|
+
* A page shaped like the resource overviews: it owns its range, hands the
|
|
239
|
+
* same setter to its picker and to its zoom, re-queries whenever the
|
|
240
|
+
* resolved window moves, and draws a line, an area and a bar chart from
|
|
241
|
+
* it, plus a chart-card hint. The tick button stands for an auto-refresh
|
|
242
|
+
* tick: a render that resolves the range against the clock again.
|
|
243
|
+
*/
|
|
244
|
+
const ZoomablePage: FunctionComponent<ZoomablePageProps> = (
|
|
245
|
+
props: ZoomablePageProps,
|
|
246
|
+
): ReactElement => {
|
|
247
|
+
const [timeRange, setTimeRange] = useState<RangeStartAndEndDateTime>(
|
|
248
|
+
props.initialRange || PAST_HOUR,
|
|
249
|
+
);
|
|
250
|
+
const [ticks, setTicks] = useState<number>(0);
|
|
251
|
+
const window: InBetween<Date> =
|
|
252
|
+
RangeStartAndEndDateTimeUtil.getStartAndEndDate(timeRange);
|
|
253
|
+
|
|
254
|
+
React.useEffect(() => {
|
|
255
|
+
fetchWindow(window.startValue.toISOString(), window.endValue.toISOString());
|
|
256
|
+
}, [window.startValue.getTime(), window.endValue.getTime()]);
|
|
257
|
+
|
|
258
|
+
const changeRange: (next: RangeStartAndEndDateTime) => void = (
|
|
259
|
+
next: RangeStartAndEndDateTime,
|
|
260
|
+
): void => {
|
|
261
|
+
rangeChanges(next);
|
|
262
|
+
setTimeRange(next);
|
|
263
|
+
};
|
|
264
|
+
|
|
265
|
+
return (
|
|
266
|
+
<TimeRangeZoomScope timeRange={timeRange} onTimeRangeChange={changeRange}>
|
|
267
|
+
<TelemetryTimeRangePicker value={timeRange} onChange={changeRange} />
|
|
268
|
+
<button
|
|
269
|
+
type="button"
|
|
270
|
+
data-testid="auto-refresh-tick"
|
|
271
|
+
onClick={() => {
|
|
272
|
+
setTicks(ticks + 1);
|
|
273
|
+
}}
|
|
274
|
+
>
|
|
275
|
+
Tick
|
|
276
|
+
</button>
|
|
277
|
+
<span data-testid="window">
|
|
278
|
+
{`${window.startValue.toISOString()}/${window.endValue.toISOString()}`}
|
|
279
|
+
</span>
|
|
280
|
+
<span data-testid="range-kind">{timeRange.range}</span>
|
|
281
|
+
<div className={TIME_RANGE_ZOOM_HINT_GROUP_CLASS}>
|
|
282
|
+
<TimeRangeZoomHint revealOnHover={true} />
|
|
283
|
+
<LineChartElement
|
|
284
|
+
data={seriesFor(window, "CPU")}
|
|
285
|
+
xAxis={xAxisFor(window)}
|
|
286
|
+
yAxis={Y_AXIS}
|
|
287
|
+
curve={ChartCurve.MONOTONE}
|
|
288
|
+
sync={true}
|
|
289
|
+
syncid="cpu"
|
|
290
|
+
heightInPx={180}
|
|
291
|
+
onBucketClick={
|
|
292
|
+
bucketClick as unknown as (
|
|
293
|
+
bucketStart: Date,
|
|
294
|
+
bucketEnd: Date,
|
|
295
|
+
valuesAtBucket: Record<string, number | string>,
|
|
296
|
+
) => void
|
|
297
|
+
}
|
|
298
|
+
/>
|
|
299
|
+
</div>
|
|
300
|
+
<AreaChartElement
|
|
301
|
+
data={seriesFor(window, "Memory")}
|
|
302
|
+
xAxis={xAxisFor(window)}
|
|
303
|
+
yAxis={Y_AXIS}
|
|
304
|
+
curve={ChartCurve.MONOTONE}
|
|
305
|
+
sync={true}
|
|
306
|
+
syncid="memory"
|
|
307
|
+
heightInPx={180}
|
|
308
|
+
/>
|
|
309
|
+
<BarChartElement
|
|
310
|
+
data={seriesFor(window, "Requests")}
|
|
311
|
+
xAxis={xAxisFor(window)}
|
|
312
|
+
yAxis={Y_AXIS}
|
|
313
|
+
sync={true}
|
|
314
|
+
syncid="requests"
|
|
315
|
+
heightInPx={180}
|
|
316
|
+
/>
|
|
317
|
+
</TimeRangeZoomScope>
|
|
318
|
+
);
|
|
319
|
+
};
|
|
320
|
+
|
|
321
|
+
const CHARTS: Array<string> = ["cpu", "memory", "requests"];
|
|
322
|
+
|
|
323
|
+
function root(key: string): ChartRootHandlers {
|
|
324
|
+
const handlers: ChartRootHandlers | undefined = mockChartRoots.get(key);
|
|
325
|
+
if (!handlers) {
|
|
326
|
+
throw new Error(`chart ${key} has not rendered`);
|
|
327
|
+
}
|
|
328
|
+
return handlers;
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
// The row of a chart that draws the bucket starting at `iso`.
|
|
332
|
+
function rowAt(key: string, iso: string): number {
|
|
333
|
+
const rows: Array<Record<string, unknown>> = root(key).data || [];
|
|
334
|
+
const index: number = rows.findIndex(
|
|
335
|
+
(row: Record<string, unknown>): boolean => {
|
|
336
|
+
return row[CHART_DATA_POINT_DATE_KEY] === new Date(iso).getTime();
|
|
337
|
+
},
|
|
338
|
+
);
|
|
339
|
+
if (index < 0) {
|
|
340
|
+
throw new Error(`chart ${key} draws no bucket at ${iso}`);
|
|
341
|
+
}
|
|
342
|
+
return index;
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
// Press on one bucket, sweep to another, let go: a reader's drag.
|
|
346
|
+
function drag(key: string, fromIso: string, toIso: string): void {
|
|
347
|
+
const from: number = rowAt(key, fromIso);
|
|
348
|
+
const to: number = rowAt(key, toIso);
|
|
349
|
+
act(() => {
|
|
350
|
+
root(key).onMouseDown?.({ activeTooltipIndex: from });
|
|
351
|
+
});
|
|
352
|
+
act(() => {
|
|
353
|
+
root(key).onMouseMove?.({ activeTooltipIndex: to }, { buttons: 1 });
|
|
354
|
+
});
|
|
355
|
+
act(() => {
|
|
356
|
+
root(key).onMouseUp?.({ activeTooltipIndex: to });
|
|
357
|
+
});
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
// A press and release on one bucket, then the click the browser sends.
|
|
361
|
+
function plainClick(key: string, iso: string): void {
|
|
362
|
+
const at: number = rowAt(key, iso);
|
|
363
|
+
act(() => {
|
|
364
|
+
root(key).onMouseDown?.({ activeTooltipIndex: at });
|
|
365
|
+
});
|
|
366
|
+
act(() => {
|
|
367
|
+
root(key).onMouseUp?.({ activeTooltipIndex: at });
|
|
368
|
+
});
|
|
369
|
+
act(() => {
|
|
370
|
+
root(key).onClick?.({ activeTooltipIndex: at });
|
|
371
|
+
});
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
function doubleClick(key: string): void {
|
|
375
|
+
act(() => {
|
|
376
|
+
root(key).onDoubleClick?.();
|
|
377
|
+
});
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
function windowText(): string {
|
|
381
|
+
return screen.getByTestId("window").textContent || "";
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
function windowOf(start: string, end: string): string {
|
|
385
|
+
return `${new Date(start).toISOString()}/${new Date(end).toISOString()}`;
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
function pastHourAt(now: Date): string {
|
|
389
|
+
return `${new Date(now.getTime() - 60 * MINUTE_MS).toISOString()}/${now.toISOString()}`;
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
function resetButton(): HTMLElement | null {
|
|
393
|
+
return screen.queryByTestId(RESET_TIME_RANGE_ZOOM_BUTTON_TEST_ID);
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
function hintText(): string {
|
|
397
|
+
return screen.getByTestId(TIME_RANGE_ZOOM_HINT_TEST_ID).textContent || "";
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
function advanceClock(ms: number): void {
|
|
401
|
+
act(() => {
|
|
402
|
+
jest.setSystemTime(new Date(Date.now() + ms));
|
|
403
|
+
});
|
|
404
|
+
fireEvent.click(screen.getByTestId("auto-refresh-tick"));
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
beforeEach(() => {
|
|
408
|
+
jest.useFakeTimers();
|
|
409
|
+
jest.setSystemTime(NOW);
|
|
410
|
+
mockChartRoots.clear();
|
|
411
|
+
rangeChanges.mockReset();
|
|
412
|
+
fetchWindow.mockReset();
|
|
413
|
+
bucketClick.mockReset();
|
|
414
|
+
});
|
|
415
|
+
|
|
416
|
+
afterEach(() => {
|
|
417
|
+
cleanup();
|
|
418
|
+
jest.useRealTimers();
|
|
419
|
+
});
|
|
420
|
+
|
|
421
|
+
describe("the page's two gestures, on a page that zooms", () => {
|
|
422
|
+
test("a drag moves the page's range exactly as picking that window in the picker would", () => {
|
|
423
|
+
expectDocsToSay(
|
|
424
|
+
"**Drag across the spike** on any chart. The page's time range moves to the window you dragged out, exactly as if you had picked it in the time-range picker. Every chart, and every tile or table worked out from the page's time range, re-queries for it, so you read one moment across all of them.",
|
|
425
|
+
);
|
|
426
|
+
|
|
427
|
+
render(<ZoomablePage />);
|
|
428
|
+
fetchWindow.mockReset();
|
|
429
|
+
|
|
430
|
+
drag("cpu", "2026-09-28T11:10:00.000Z", "2026-09-28T11:19:00.000Z");
|
|
431
|
+
|
|
432
|
+
// The page's own setter got a custom range, as a custom pick hands it.
|
|
433
|
+
expect(rangeChanges).toHaveBeenCalledTimes(1);
|
|
434
|
+
const zoomed: RangeStartAndEndDateTime = rangeChanges.mock
|
|
435
|
+
.calls[0]![0] as RangeStartAndEndDateTime;
|
|
436
|
+
expect(zoomed.range).toBe(TimeRange.CUSTOM);
|
|
437
|
+
expect(windowText()).toBe(
|
|
438
|
+
windowOf("2026-09-28T11:10:00.000Z", "2026-09-28T11:20:00.000Z"),
|
|
439
|
+
);
|
|
440
|
+
|
|
441
|
+
// Everything worked out from the range re-queried once, for that window.
|
|
442
|
+
expect(fetchWindow).toHaveBeenCalledTimes(1);
|
|
443
|
+
for (const chart of CHARTS) {
|
|
444
|
+
expect(root(chart).data?.[0]?.[CHART_DATA_POINT_DATE_KEY]).toBe(
|
|
445
|
+
new Date("2026-09-28T11:10:00.000Z").getTime(),
|
|
446
|
+
);
|
|
447
|
+
}
|
|
448
|
+
|
|
449
|
+
// The picker reads exactly as it does when that window is picked in it.
|
|
450
|
+
const pickerLabel: string =
|
|
451
|
+
screen.getByTestId("telemetry-time-range-picker-button").textContent ||
|
|
452
|
+
"";
|
|
453
|
+
cleanup();
|
|
454
|
+
render(<ZoomablePage initialRange={zoomed} />);
|
|
455
|
+
expect(
|
|
456
|
+
screen.getByTestId("telemetry-time-range-picker-button").textContent,
|
|
457
|
+
).toBe(pickerLabel);
|
|
458
|
+
});
|
|
459
|
+
|
|
460
|
+
test("a double-click on any chart returns the range from before the zoom", () => {
|
|
461
|
+
expectDocsToSay(
|
|
462
|
+
"**Double-click any chart** to go back. The page returns to the time range it had before you started zooming.",
|
|
463
|
+
);
|
|
464
|
+
|
|
465
|
+
for (const target of CHARTS) {
|
|
466
|
+
cleanup();
|
|
467
|
+
mockChartRoots.clear();
|
|
468
|
+
render(<ZoomablePage />);
|
|
469
|
+
|
|
470
|
+
drag("cpu", "2026-09-28T11:30:00.000Z", "2026-09-28T11:40:00.000Z");
|
|
471
|
+
doubleClick(target);
|
|
472
|
+
|
|
473
|
+
expect(windowText()).toBe(pastHourAt(NOW));
|
|
474
|
+
expect(screen.getByTestId("range-kind")).toHaveTextContent(
|
|
475
|
+
TimeRange.PAST_ONE_HOUR,
|
|
476
|
+
);
|
|
477
|
+
}
|
|
478
|
+
});
|
|
479
|
+
|
|
480
|
+
test("Reset zoom shows beside the picker only while zoomed, and does what a double-click does", () => {
|
|
481
|
+
expectDocsToSay(
|
|
482
|
+
"While a zoom is active, a **Reset zoom** button appears next to the page's time-range picker. It does the same thing as a double-click, and it is the way back for keyboard users and on touch screens.",
|
|
483
|
+
);
|
|
484
|
+
|
|
485
|
+
render(<ZoomablePage />);
|
|
486
|
+
expect(resetButton()).toBeNull();
|
|
487
|
+
|
|
488
|
+
drag("memory", "2026-09-28T11:05:00.000Z", "2026-09-28T11:15:00.000Z");
|
|
489
|
+
|
|
490
|
+
const button: HTMLElement = screen.getByRole("button", {
|
|
491
|
+
name: "Reset zoom",
|
|
492
|
+
});
|
|
493
|
+
expect(button).toBe(resetButton());
|
|
494
|
+
// Beside the picker, in the picker's own row.
|
|
495
|
+
expect(button.parentElement).toContainElement(
|
|
496
|
+
screen.getByTestId("telemetry-time-range-picker-button"),
|
|
497
|
+
);
|
|
498
|
+
|
|
499
|
+
fireEvent.click(button);
|
|
500
|
+
|
|
501
|
+
expect(windowText()).toBe(pastHourAt(NOW));
|
|
502
|
+
expect(resetButton()).toBeNull();
|
|
503
|
+
});
|
|
504
|
+
});
|
|
505
|
+
|
|
506
|
+
describe("How zooming behaves", () => {
|
|
507
|
+
test("zoom as deep as you like; one reset climbs all the way out", () => {
|
|
508
|
+
expectDocsToSay(
|
|
509
|
+
'**Zoom as deep as you like; one reset climbs all the way out.** After drilling from "Past 1 Hour" into ten minutes and then into one, a single double-click (or **Reset zoom**) returns the whole hour rather than one level at a time.',
|
|
510
|
+
);
|
|
511
|
+
|
|
512
|
+
render(<ZoomablePage />);
|
|
513
|
+
|
|
514
|
+
drag("cpu", "2026-09-28T11:30:00.000Z", "2026-09-28T11:39:00.000Z");
|
|
515
|
+
drag("memory", "2026-09-28T11:34:00.000Z", "2026-09-28T11:34:00.000Z");
|
|
516
|
+
// A press that never left its bucket is not a zoom: still ten minutes.
|
|
517
|
+
expect(windowText()).toBe(
|
|
518
|
+
windowOf("2026-09-28T11:30:00.000Z", "2026-09-28T11:40:00.000Z"),
|
|
519
|
+
);
|
|
520
|
+
drag("memory", "2026-09-28T11:34:00.000Z", "2026-09-28T11:35:00.000Z");
|
|
521
|
+
expect(windowText()).toBe(
|
|
522
|
+
windowOf("2026-09-28T11:34:00.000Z", "2026-09-28T11:36:00.000Z"),
|
|
523
|
+
);
|
|
524
|
+
|
|
525
|
+
doubleClick("requests");
|
|
526
|
+
|
|
527
|
+
expect(windowText()).toBe(pastHourAt(NOW));
|
|
528
|
+
expect(screen.getByTestId("range-kind")).toHaveTextContent(
|
|
529
|
+
TimeRange.PAST_ONE_HOUR,
|
|
530
|
+
);
|
|
531
|
+
expect(resetButton()).toBeNull();
|
|
532
|
+
});
|
|
533
|
+
|
|
534
|
+
test("any chart can reset any zoom: it is the page that is zoomed", () => {
|
|
535
|
+
expectDocsToSay(
|
|
536
|
+
"**Any chart can reset any zoom.** Drag on the CPU chart, double-click the memory chart — it is the page that is zoomed, not the chart.",
|
|
537
|
+
);
|
|
538
|
+
|
|
539
|
+
render(<ZoomablePage />);
|
|
540
|
+
|
|
541
|
+
drag("cpu", "2026-09-28T11:10:00.000Z", "2026-09-28T11:20:00.000Z");
|
|
542
|
+
for (const chart of CHARTS) {
|
|
543
|
+
expect(root(chart).onDoubleClick).toBeInstanceOf(Function);
|
|
544
|
+
}
|
|
545
|
+
|
|
546
|
+
doubleClick("memory");
|
|
547
|
+
|
|
548
|
+
expect(windowText()).toBe(pastHourAt(NOW));
|
|
549
|
+
});
|
|
550
|
+
|
|
551
|
+
test("picking a range yourself starts over", () => {
|
|
552
|
+
expectDocsToSay(
|
|
553
|
+
"**Picking a range yourself starts over.** Choosing a preset or a custom range in the picker is a new starting point: the zoom is over and **Reset zoom** goes away.",
|
|
554
|
+
);
|
|
555
|
+
|
|
556
|
+
render(<ZoomablePage />);
|
|
557
|
+
|
|
558
|
+
drag("cpu", "2026-09-28T11:10:00.000Z", "2026-09-28T11:20:00.000Z");
|
|
559
|
+
fireEvent.click(screen.getByTestId("telemetry-time-range-picker-button"));
|
|
560
|
+
fireEvent.click(screen.getByText("Past 1 Day"));
|
|
561
|
+
|
|
562
|
+
expect(resetButton()).toBeNull();
|
|
563
|
+
for (const chart of CHARTS) {
|
|
564
|
+
expect(root(chart).onDoubleClick).toBeUndefined();
|
|
565
|
+
}
|
|
566
|
+
expect(hintText()).toBe("Drag to zoom");
|
|
567
|
+
|
|
568
|
+
// The next zoom goes back to the day, not to the hour it started from.
|
|
569
|
+
drag("cpu", "2026-09-28T10:00:00.000Z", "2026-09-28T11:00:00.000Z");
|
|
570
|
+
doubleClick("cpu");
|
|
571
|
+
|
|
572
|
+
expect(screen.getByTestId("range-kind")).toHaveTextContent(
|
|
573
|
+
TimeRange.PAST_ONE_DAY,
|
|
574
|
+
);
|
|
575
|
+
});
|
|
576
|
+
|
|
577
|
+
test("a zoomed window is fixed: it stops rolling, and a reset rolls again", () => {
|
|
578
|
+
expectDocsToSay(
|
|
579
|
+
'**A zoomed window is fixed.** "Past 30 Minutes" rolls forward with the clock; a zoom is a fixed window, so it stops rolling while auto-refresh is on. Reset the zoom to start rolling again.',
|
|
580
|
+
);
|
|
581
|
+
|
|
582
|
+
render(<ZoomablePage />);
|
|
583
|
+
|
|
584
|
+
// Before any zoom, a tick five minutes on moves the window along.
|
|
585
|
+
advanceClock(5 * MINUTE_MS);
|
|
586
|
+
expect(windowText()).toBe(
|
|
587
|
+
pastHourAt(new Date(NOW.getTime() + 5 * MINUTE_MS)),
|
|
588
|
+
);
|
|
589
|
+
|
|
590
|
+
drag("cpu", "2026-09-28T11:20:00.000Z", "2026-09-28T11:29:00.000Z");
|
|
591
|
+
const zoomedWindow: string = windowText();
|
|
592
|
+
|
|
593
|
+
advanceClock(5 * MINUTE_MS);
|
|
594
|
+
expect(windowText()).toBe(zoomedWindow);
|
|
595
|
+
|
|
596
|
+
doubleClick("cpu");
|
|
597
|
+
expect(windowText()).toBe(
|
|
598
|
+
pastHourAt(new Date(NOW.getTime() + 10 * MINUTE_MS)),
|
|
599
|
+
);
|
|
600
|
+
});
|
|
601
|
+
|
|
602
|
+
test("a zoom never runs past now: a drag onto the newest bucket is cut at the current time", () => {
|
|
603
|
+
expectDocsToSay(
|
|
604
|
+
"**A zoom never runs past now.** The newest bucket of a chart is usually still filling up; a drag that ends on it is cut at the current time.",
|
|
605
|
+
);
|
|
606
|
+
|
|
607
|
+
render(<ZoomablePage />);
|
|
608
|
+
|
|
609
|
+
// The 12:00 bucket runs to 12:01, but it is only 12:00:30.
|
|
610
|
+
drag("requests", "2026-09-28T11:55:00.000Z", "2026-09-28T12:00:00.000Z");
|
|
611
|
+
|
|
612
|
+
expect(windowText()).toBe(
|
|
613
|
+
windowOf("2026-09-28T11:55:00.000Z", NOW.toISOString()),
|
|
614
|
+
);
|
|
615
|
+
});
|
|
616
|
+
|
|
617
|
+
test("you can let go of the mouse outside the chart: the drag still counts", () => {
|
|
618
|
+
expectDocsToSay(
|
|
619
|
+
"**You can let go of the mouse outside the chart** — the drag still counts.",
|
|
620
|
+
);
|
|
621
|
+
|
|
622
|
+
render(<ZoomablePage />);
|
|
623
|
+
const from: number = rowAt("memory", "2026-09-28T11:40:00.000Z");
|
|
624
|
+
const to: number = rowAt("memory", "2026-09-28T11:44:00.000Z");
|
|
625
|
+
|
|
626
|
+
act(() => {
|
|
627
|
+
root("memory").onMouseDown?.({ activeTooltipIndex: from });
|
|
628
|
+
});
|
|
629
|
+
act(() => {
|
|
630
|
+
root("memory").onMouseMove?.({ activeTooltipIndex: to }, { buttons: 1 });
|
|
631
|
+
});
|
|
632
|
+
// Released past the chart's edge: only the window hears it.
|
|
633
|
+
act(() => {
|
|
634
|
+
fireEvent.mouseUp(window);
|
|
635
|
+
});
|
|
636
|
+
|
|
637
|
+
expect(windowText()).toBe(
|
|
638
|
+
windowOf("2026-09-28T11:40:00.000Z", "2026-09-28T11:45:00.000Z"),
|
|
639
|
+
);
|
|
640
|
+
});
|
|
641
|
+
|
|
642
|
+
test("double-clicking a page that isn't zoomed does nothing", () => {
|
|
643
|
+
expectDocsToSay(
|
|
644
|
+
"**Double-clicking a page that isn't zoomed does nothing.**",
|
|
645
|
+
);
|
|
646
|
+
|
|
647
|
+
render(<ZoomablePage />);
|
|
648
|
+
|
|
649
|
+
// No chart even takes a double-click while there is nothing to undo.
|
|
650
|
+
for (const chart of CHARTS) {
|
|
651
|
+
expect(root(chart).onDoubleClick).toBeUndefined();
|
|
652
|
+
fireEvent.doubleClick(screen.getByTestId(`chart-root-${chart}`));
|
|
653
|
+
}
|
|
654
|
+
|
|
655
|
+
// And once a zoom is undone, a second double-click finds nothing to do.
|
|
656
|
+
drag("cpu", "2026-09-28T11:10:00.000Z", "2026-09-28T11:20:00.000Z");
|
|
657
|
+
doubleClick("cpu");
|
|
658
|
+
rangeChanges.mockReset();
|
|
659
|
+
for (const chart of CHARTS) {
|
|
660
|
+
doubleClick(chart);
|
|
661
|
+
}
|
|
662
|
+
|
|
663
|
+
expect(rangeChanges).not.toHaveBeenCalled();
|
|
664
|
+
expect(windowText()).toBe(pastHourAt(NOW));
|
|
665
|
+
});
|
|
666
|
+
});
|
|
667
|
+
|
|
668
|
+
describe("Clicks and drags", () => {
|
|
669
|
+
test("on line, area and bar charts, a plain click is not a zoom", () => {
|
|
670
|
+
expectDocsToSay(
|
|
671
|
+
"**On line, area and bar charts, a plain click is not a zoom.** That covers most charts: metric cards and the metric explorer, resource overviews, SLOs, monitors and every chart on a dashboard. Only a drag across buckets zooms, so clicking a point, a bar or a legend entry keeps doing what it did before.",
|
|
672
|
+
);
|
|
673
|
+
|
|
674
|
+
render(<ZoomablePage />);
|
|
675
|
+
fetchWindow.mockReset();
|
|
676
|
+
|
|
677
|
+
for (const chart of CHARTS) {
|
|
678
|
+
plainClick(chart, "2026-09-28T11:30:00.000Z");
|
|
679
|
+
}
|
|
680
|
+
act(() => {
|
|
681
|
+
jest.advanceTimersByTime(DOUBLE_CLICK_DISAMBIGUATION_MS);
|
|
682
|
+
});
|
|
683
|
+
|
|
684
|
+
expect(rangeChanges).not.toHaveBeenCalled();
|
|
685
|
+
expect(fetchWindow).not.toHaveBeenCalled();
|
|
686
|
+
expect(windowText()).toBe(pastHourAt(NOW));
|
|
687
|
+
|
|
688
|
+
// The click kept its meaning: the line chart's bucket click, at once.
|
|
689
|
+
expect(bucketClick).toHaveBeenCalledTimes(1);
|
|
690
|
+
expect((bucketClick.mock.calls[0]![0] as Date).toISOString()).toBe(
|
|
691
|
+
"2026-09-28T11:30:00.000Z",
|
|
692
|
+
);
|
|
693
|
+
});
|
|
694
|
+
|
|
695
|
+
test("while a zoom is active, a click on the plot takes effect a moment later, so a double-click is not two clicks", () => {
|
|
696
|
+
expectDocsToSay(
|
|
697
|
+
"While a zoom is active, a click on a chart's plot takes effect a moment later, so that it can be told apart from the double-click that resets.",
|
|
698
|
+
);
|
|
699
|
+
|
|
700
|
+
render(<ZoomablePage />);
|
|
701
|
+
drag("cpu", "2026-09-28T11:10:00.000Z", "2026-09-28T11:20:00.000Z");
|
|
702
|
+
/*
|
|
703
|
+
* The drag's own trailing click is swallowed until the moment passes;
|
|
704
|
+
* the reader's next click comes after it.
|
|
705
|
+
*/
|
|
706
|
+
act(() => {
|
|
707
|
+
jest.advanceTimersByTime(0);
|
|
708
|
+
});
|
|
709
|
+
|
|
710
|
+
plainClick("cpu", "2026-09-28T11:12:00.000Z");
|
|
711
|
+
expect(bucketClick).not.toHaveBeenCalled();
|
|
712
|
+
|
|
713
|
+
act(() => {
|
|
714
|
+
jest.advanceTimersByTime(DOUBLE_CLICK_DISAMBIGUATION_MS);
|
|
715
|
+
});
|
|
716
|
+
expect(bucketClick).toHaveBeenCalledTimes(1);
|
|
717
|
+
// Still zoomed: the click was a click.
|
|
718
|
+
expect(windowText()).toBe(
|
|
719
|
+
windowOf("2026-09-28T11:10:00.000Z", "2026-09-28T11:21:00.000Z"),
|
|
720
|
+
);
|
|
721
|
+
|
|
722
|
+
// Both clicks of a double-click are dropped; the reset is all it does.
|
|
723
|
+
bucketClick.mockReset();
|
|
724
|
+
plainClick("cpu", "2026-09-28T11:12:00.000Z");
|
|
725
|
+
plainClick("cpu", "2026-09-28T11:12:00.000Z");
|
|
726
|
+
doubleClick("cpu");
|
|
727
|
+
act(() => {
|
|
728
|
+
jest.advanceTimersByTime(DOUBLE_CLICK_DISAMBIGUATION_MS);
|
|
729
|
+
});
|
|
730
|
+
|
|
731
|
+
expect(bucketClick).not.toHaveBeenCalled();
|
|
732
|
+
// Back on the rolling hour: the double-click reset the zoom.
|
|
733
|
+
expect(screen.getByTestId("range-kind")).toHaveTextContent(
|
|
734
|
+
TimeRange.PAST_ONE_HOUR,
|
|
735
|
+
);
|
|
736
|
+
expect(resetButton()).toBeNull();
|
|
737
|
+
});
|
|
738
|
+
|
|
739
|
+
test("a small sparkline takes no part, even on a zoomed page", () => {
|
|
740
|
+
expectDocsToSay(
|
|
741
|
+
"the small trend sparklines in metric lists, where a click opens the metric",
|
|
742
|
+
);
|
|
743
|
+
expectDocsToSay(
|
|
744
|
+
"small sparklines with a fixed window of their own, such as a network device's round-trip time over the past hour",
|
|
745
|
+
);
|
|
746
|
+
|
|
747
|
+
const zoomedPage: TimeRangeZoom = {
|
|
748
|
+
isZoomed: true,
|
|
749
|
+
rangeBeforeZoom: PAST_HOUR,
|
|
750
|
+
zoomToTimeRange: getJestMockFunction() as unknown as (
|
|
751
|
+
startTime: Date,
|
|
752
|
+
endTime: Date,
|
|
753
|
+
) => void,
|
|
754
|
+
resetZoom: getJestMockFunction() as unknown as () => void,
|
|
755
|
+
};
|
|
756
|
+
|
|
757
|
+
render(
|
|
758
|
+
<TimeRangeZoomProvider zoom={zoomedPage}>
|
|
759
|
+
<SparkAreaChart
|
|
760
|
+
data={[
|
|
761
|
+
{ time: "11:00", value: 3 },
|
|
762
|
+
{ time: "11:01", value: 5 },
|
|
763
|
+
]}
|
|
764
|
+
index="time"
|
|
765
|
+
categories={["value"]}
|
|
766
|
+
/>
|
|
767
|
+
</TimeRangeZoomProvider>,
|
|
768
|
+
);
|
|
769
|
+
|
|
770
|
+
const sparkline: ChartRootHandlers = root("area-unsynced");
|
|
771
|
+
expect(sparkline.onMouseDown).toBeUndefined();
|
|
772
|
+
expect(sparkline.onMouseUp).toBeUndefined();
|
|
773
|
+
expect(sparkline.onDoubleClick).toBeUndefined();
|
|
774
|
+
});
|
|
775
|
+
});
|
|
776
|
+
|
|
777
|
+
// Three ClickHouse-style bucket labels, one minute apart (UTC).
|
|
778
|
+
const BAR_A: string = "2026-09-28 11:15:00";
|
|
779
|
+
const BAR_B: string = "2026-09-28 11:16:00";
|
|
780
|
+
const BAR_C: string = "2026-09-28 11:17:00";
|
|
781
|
+
|
|
782
|
+
const LOG_BUCKETS: Array<LogsHistogramBucket> = [
|
|
783
|
+
{ time: BAR_A, severity: LogSeverity.Error, count: 3 },
|
|
784
|
+
{ time: BAR_B, severity: LogSeverity.Warning, count: 5 },
|
|
785
|
+
{ time: BAR_C, severity: LogSeverity.Error, count: 7 },
|
|
786
|
+
];
|
|
787
|
+
|
|
788
|
+
const SERIES: Array<HistogramSeriesOption> = [
|
|
789
|
+
{ key: "ok", label: "OK", color: "#34d399" },
|
|
790
|
+
{ key: "error", label: "Error", color: "#f87171" },
|
|
791
|
+
];
|
|
792
|
+
|
|
793
|
+
const TELEMETRY_BUCKETS: Array<TelemetryHistogramBucket> = [
|
|
794
|
+
{ time: BAR_A, series: "ok", count: 3 },
|
|
795
|
+
{ time: BAR_B, series: "error", count: 5 },
|
|
796
|
+
{ time: BAR_C, series: "ok", count: 7 },
|
|
797
|
+
];
|
|
798
|
+
|
|
799
|
+
interface VolumeChartProps {
|
|
800
|
+
onTimeRangeSelect: (startTime: Date, endTime: Date) => void;
|
|
801
|
+
onZoomOut?: (() => void) | undefined;
|
|
802
|
+
bucketIntervalMs?: number | undefined;
|
|
803
|
+
}
|
|
804
|
+
|
|
805
|
+
interface VolumeChart {
|
|
806
|
+
name: string;
|
|
807
|
+
render: (props: VolumeChartProps) => ReactElement;
|
|
808
|
+
}
|
|
809
|
+
|
|
810
|
+
// The explorers' two volume charts, which share the click-or-drag hook.
|
|
811
|
+
const VOLUME_CHARTS: Array<VolumeChart> = [
|
|
812
|
+
{
|
|
813
|
+
name: "the logs explorer's volume chart",
|
|
814
|
+
render: (props: VolumeChartProps): ReactElement => {
|
|
815
|
+
return (
|
|
816
|
+
<LogsHistogram buckets={LOG_BUCKETS} isLoading={false} {...props} />
|
|
817
|
+
);
|
|
818
|
+
},
|
|
819
|
+
},
|
|
820
|
+
{
|
|
821
|
+
name: "the trace, exception and security-event explorers' volume chart",
|
|
822
|
+
render: (props: VolumeChartProps): ReactElement => {
|
|
823
|
+
return (
|
|
824
|
+
<TelemetryHistogram
|
|
825
|
+
buckets={TELEMETRY_BUCKETS}
|
|
826
|
+
series={SERIES}
|
|
827
|
+
isLoading={false}
|
|
828
|
+
{...props}
|
|
829
|
+
/>
|
|
830
|
+
);
|
|
831
|
+
},
|
|
832
|
+
},
|
|
833
|
+
];
|
|
834
|
+
|
|
835
|
+
function pressBar(label: string): void {
|
|
836
|
+
act(() => {
|
|
837
|
+
root("bar-unsynced").onMouseDown?.({ activeLabel: label });
|
|
838
|
+
});
|
|
839
|
+
}
|
|
840
|
+
|
|
841
|
+
function releaseBar(label: string): void {
|
|
842
|
+
act(() => {
|
|
843
|
+
root("bar-unsynced").onMouseUp?.({ activeLabel: label });
|
|
844
|
+
});
|
|
845
|
+
}
|
|
846
|
+
|
|
847
|
+
function selectedWindow(select: MockFunction): string {
|
|
848
|
+
expect(select).toHaveBeenCalledTimes(1);
|
|
849
|
+
const [start, end] = select.mock.calls[0] as [Date, Date];
|
|
850
|
+
return `${start.toISOString()}/${end.toISOString()}`;
|
|
851
|
+
}
|
|
852
|
+
|
|
853
|
+
describe("the explorers' volume charts: a click on one bar zooms into it", () => {
|
|
854
|
+
for (const chart of VOLUME_CHARTS) {
|
|
855
|
+
test(`${chart.name}: a click on one bar zooms into that bar, and the chart says so`, () => {
|
|
856
|
+
expectDocsToSay(
|
|
857
|
+
"**On the explorers' volume charts, a click on one bar zooms into that bar.** The volume charts of the log, trace, exception and security-event explorers, and the log and trace analytics charts, zoom into the bars you drag across, or into the single bar you click. These charts say **Click or drag to zoom**.",
|
|
858
|
+
);
|
|
859
|
+
|
|
860
|
+
const select: MockFunction = getJestMockFunction();
|
|
861
|
+
render(
|
|
862
|
+
chart.render({
|
|
863
|
+
onTimeRangeSelect: select as unknown as (
|
|
864
|
+
startTime: Date,
|
|
865
|
+
endTime: Date,
|
|
866
|
+
) => void,
|
|
867
|
+
bucketIntervalMs: MINUTE_MS,
|
|
868
|
+
}),
|
|
869
|
+
);
|
|
870
|
+
|
|
871
|
+
expect(screen.getByText("Click or drag to zoom")).toBeVisible();
|
|
872
|
+
|
|
873
|
+
pressBar(BAR_B);
|
|
874
|
+
releaseBar(BAR_B);
|
|
875
|
+
|
|
876
|
+
expect(selectedWindow(select)).toBe(
|
|
877
|
+
windowOf("2026-09-28T11:16:00.000Z", "2026-09-28T11:17:00.000Z"),
|
|
878
|
+
);
|
|
879
|
+
});
|
|
880
|
+
|
|
881
|
+
test(`${chart.name}: a drag zooms into the bars dragged across, the last one whole`, () => {
|
|
882
|
+
const select: MockFunction = getJestMockFunction();
|
|
883
|
+
render(
|
|
884
|
+
chart.render({
|
|
885
|
+
onTimeRangeSelect: select as unknown as (
|
|
886
|
+
startTime: Date,
|
|
887
|
+
endTime: Date,
|
|
888
|
+
) => void,
|
|
889
|
+
bucketIntervalMs: MINUTE_MS,
|
|
890
|
+
}),
|
|
891
|
+
);
|
|
892
|
+
|
|
893
|
+
pressBar(BAR_A);
|
|
894
|
+
act(() => {
|
|
895
|
+
root("bar-unsynced").onMouseMove?.({ activeLabel: BAR_C }, {});
|
|
896
|
+
});
|
|
897
|
+
// Let go outside the chart.
|
|
898
|
+
act(() => {
|
|
899
|
+
fireEvent.mouseUp(window);
|
|
900
|
+
});
|
|
901
|
+
|
|
902
|
+
expect(selectedWindow(select)).toBe(
|
|
903
|
+
windowOf("2026-09-28T11:15:00.000Z", "2026-09-28T11:18:00.000Z"),
|
|
904
|
+
);
|
|
905
|
+
});
|
|
906
|
+
|
|
907
|
+
test(`${chart.name}: while zoomed, the click waits out the double-click, which resets instead`, () => {
|
|
908
|
+
const select: MockFunction = getJestMockFunction();
|
|
909
|
+
const zoomOut: MockFunction = getJestMockFunction();
|
|
910
|
+
render(
|
|
911
|
+
chart.render({
|
|
912
|
+
onTimeRangeSelect: select as unknown as (
|
|
913
|
+
startTime: Date,
|
|
914
|
+
endTime: Date,
|
|
915
|
+
) => void,
|
|
916
|
+
onZoomOut: zoomOut as unknown as () => void,
|
|
917
|
+
bucketIntervalMs: MINUTE_MS,
|
|
918
|
+
}),
|
|
919
|
+
);
|
|
920
|
+
|
|
921
|
+
expect(screen.getByText("Double-click to reset")).toBeVisible();
|
|
922
|
+
|
|
923
|
+
pressBar(BAR_C);
|
|
924
|
+
releaseBar(BAR_C);
|
|
925
|
+
expect(select).not.toHaveBeenCalled();
|
|
926
|
+
act(() => {
|
|
927
|
+
jest.advanceTimersByTime(DOUBLE_CLICK_DISAMBIGUATION_MS);
|
|
928
|
+
});
|
|
929
|
+
expect(selectedWindow(select)).toBe(
|
|
930
|
+
windowOf("2026-09-28T11:17:00.000Z", "2026-09-28T11:18:00.000Z"),
|
|
931
|
+
);
|
|
932
|
+
|
|
933
|
+
select.mockReset();
|
|
934
|
+
pressBar(BAR_A);
|
|
935
|
+
releaseBar(BAR_A);
|
|
936
|
+
fireEvent.doubleClick(screen.getByTestId("chart-root-bar-unsynced"));
|
|
937
|
+
act(() => {
|
|
938
|
+
jest.advanceTimersByTime(DOUBLE_CLICK_DISAMBIGUATION_MS);
|
|
939
|
+
});
|
|
940
|
+
|
|
941
|
+
expect(select).not.toHaveBeenCalled();
|
|
942
|
+
expect(zoomOut).toHaveBeenCalledTimes(1);
|
|
943
|
+
});
|
|
944
|
+
|
|
945
|
+
test(`${chart.name}: without the bucket width - how the drag-only charts use the hook - a click is not a zoom`, () => {
|
|
946
|
+
expectDocsToSay(
|
|
947
|
+
"The error-pattern timeline in Logs Insights and an exception's Occurrence Trend also zoom on a drag only.",
|
|
948
|
+
);
|
|
949
|
+
|
|
950
|
+
const select: MockFunction = getJestMockFunction();
|
|
951
|
+
render(
|
|
952
|
+
chart.render({
|
|
953
|
+
onTimeRangeSelect: select as unknown as (
|
|
954
|
+
startTime: Date,
|
|
955
|
+
endTime: Date,
|
|
956
|
+
) => void,
|
|
957
|
+
}),
|
|
958
|
+
);
|
|
959
|
+
|
|
960
|
+
expect(screen.getByText("Drag to zoom")).toBeVisible();
|
|
961
|
+
expect(screen.queryByText("Click or drag to zoom")).toBeNull();
|
|
962
|
+
|
|
963
|
+
pressBar(BAR_B);
|
|
964
|
+
releaseBar(BAR_B);
|
|
965
|
+
act(() => {
|
|
966
|
+
jest.advanceTimersByTime(DOUBLE_CLICK_DISAMBIGUATION_MS);
|
|
967
|
+
});
|
|
968
|
+
expect(select).not.toHaveBeenCalled();
|
|
969
|
+
|
|
970
|
+
pressBar(BAR_A);
|
|
971
|
+
releaseBar(BAR_C);
|
|
972
|
+
expect(select).toHaveBeenCalledTimes(1);
|
|
973
|
+
});
|
|
974
|
+
}
|
|
975
|
+
});
|
|
976
|
+
|
|
977
|
+
function splitHint(text: string): Array<string> {
|
|
978
|
+
return text
|
|
979
|
+
.split("·")
|
|
980
|
+
.map((part: string): string => {
|
|
981
|
+
return part.trim();
|
|
982
|
+
})
|
|
983
|
+
.filter((part: string): boolean => {
|
|
984
|
+
return part.length > 0;
|
|
985
|
+
});
|
|
986
|
+
}
|
|
987
|
+
|
|
988
|
+
const NOT_ZOOMED: TimeRangeZoom = {
|
|
989
|
+
isZoomed: false,
|
|
990
|
+
rangeBeforeZoom: null,
|
|
991
|
+
zoomToTimeRange: (): void => {},
|
|
992
|
+
resetZoom: (): void => {},
|
|
993
|
+
};
|
|
994
|
+
|
|
995
|
+
const ZOOMED: TimeRangeZoom = {
|
|
996
|
+
isZoomed: true,
|
|
997
|
+
rangeBeforeZoom: PAST_HOUR,
|
|
998
|
+
zoomToTimeRange: (): void => {},
|
|
999
|
+
resetZoom: (): void => {},
|
|
1000
|
+
};
|
|
1001
|
+
|
|
1002
|
+
function metricChart(): Chart {
|
|
1003
|
+
const window: InBetween<Date> = new InBetween<Date>(
|
|
1004
|
+
new Date("2026-09-28T11:00:00.000Z"),
|
|
1005
|
+
new Date("2026-09-28T11:30:00.000Z"),
|
|
1006
|
+
);
|
|
1007
|
+
return {
|
|
1008
|
+
id: "cpu-chart",
|
|
1009
|
+
title: "CPU",
|
|
1010
|
+
type: ChartType.LINE,
|
|
1011
|
+
props: {
|
|
1012
|
+
data: seriesFor(window, "CPU"),
|
|
1013
|
+
xAxis: xAxisFor(window),
|
|
1014
|
+
yAxis: Y_AXIS,
|
|
1015
|
+
curve: ChartCurve.MONOTONE,
|
|
1016
|
+
sync: false,
|
|
1017
|
+
} as unknown as Chart["props"],
|
|
1018
|
+
};
|
|
1019
|
+
}
|
|
1020
|
+
|
|
1021
|
+
// The header hint of a metric chart card (ChartGroup): the span naming the drag.
|
|
1022
|
+
function metricChartHint(): HTMLElement {
|
|
1023
|
+
return screen.getByText((content: string, element: Element | null) => {
|
|
1024
|
+
return element?.tagName === "SPAN" && content.startsWith("Drag to zoom");
|
|
1025
|
+
});
|
|
1026
|
+
}
|
|
1027
|
+
|
|
1028
|
+
describe("the hints the charts show", () => {
|
|
1029
|
+
test("every hint a chart renders is quoted on the page", () => {
|
|
1030
|
+
const shown: Array<string> = [];
|
|
1031
|
+
const collect: (text: string | null | undefined) => void = (
|
|
1032
|
+
text: string | null | undefined,
|
|
1033
|
+
): void => {
|
|
1034
|
+
shown.push(...splitHint(text || ""));
|
|
1035
|
+
};
|
|
1036
|
+
|
|
1037
|
+
// A chart card's hint, before and during a zoom.
|
|
1038
|
+
for (const zoom of [NOT_ZOOMED, ZOOMED]) {
|
|
1039
|
+
render(
|
|
1040
|
+
<TimeRangeZoomProvider zoom={zoom}>
|
|
1041
|
+
<TimeRangeZoomHint />
|
|
1042
|
+
</TimeRangeZoomProvider>,
|
|
1043
|
+
);
|
|
1044
|
+
collect(hintText());
|
|
1045
|
+
cleanup();
|
|
1046
|
+
}
|
|
1047
|
+
|
|
1048
|
+
// A metric chart card's header hint, before and during a zoom.
|
|
1049
|
+
for (const zoom of [NOT_ZOOMED, ZOOMED]) {
|
|
1050
|
+
render(
|
|
1051
|
+
<TimeRangeZoomProvider zoom={zoom}>
|
|
1052
|
+
<ChartGroup charts={[metricChart()]} />
|
|
1053
|
+
</TimeRangeZoomProvider>,
|
|
1054
|
+
);
|
|
1055
|
+
collect(metricChartHint().textContent);
|
|
1056
|
+
cleanup();
|
|
1057
|
+
}
|
|
1058
|
+
|
|
1059
|
+
// The volume charts, with and without the width, before and during a zoom.
|
|
1060
|
+
for (const chart of VOLUME_CHARTS) {
|
|
1061
|
+
for (const bucketIntervalMs of [MINUTE_MS, undefined]) {
|
|
1062
|
+
const { container } = render(
|
|
1063
|
+
chart.render({
|
|
1064
|
+
onTimeRangeSelect: (): void => {},
|
|
1065
|
+
onZoomOut: (): void => {},
|
|
1066
|
+
bucketIntervalMs: bucketIntervalMs,
|
|
1067
|
+
}),
|
|
1068
|
+
);
|
|
1069
|
+
container
|
|
1070
|
+
.querySelectorAll("span.text-\\[10px\\]")
|
|
1071
|
+
.forEach((span: Element) => {
|
|
1072
|
+
collect(span.textContent);
|
|
1073
|
+
});
|
|
1074
|
+
cleanup();
|
|
1075
|
+
}
|
|
1076
|
+
}
|
|
1077
|
+
|
|
1078
|
+
expect(unique(shown).sort()).toEqual(
|
|
1079
|
+
[
|
|
1080
|
+
"Click or drag to zoom",
|
|
1081
|
+
"Double-click to reset",
|
|
1082
|
+
"Drag to zoom",
|
|
1083
|
+
"double-click to reset",
|
|
1084
|
+
].sort(),
|
|
1085
|
+
);
|
|
1086
|
+
|
|
1087
|
+
const quoted: Array<string> = docsBoldPhrases();
|
|
1088
|
+
for (const hint of unique(shown)) {
|
|
1089
|
+
expect({ hint, quoted: quoted.includes(hint.toLowerCase()) }).toEqual({
|
|
1090
|
+
hint,
|
|
1091
|
+
quoted: true,
|
|
1092
|
+
});
|
|
1093
|
+
}
|
|
1094
|
+
});
|
|
1095
|
+
|
|
1096
|
+
test("the metric charts and the volume charts always show theirs; a chart card's waits to be pointed at", () => {
|
|
1097
|
+
expectDocsToSay(
|
|
1098
|
+
"Metric cards, the metric explorer and the explorers' volume charts always show the hint. On the chart cards of resource overviews and SLOs, and on some dashboard widgets, it appears only while you point at the card or tab into it.",
|
|
1099
|
+
);
|
|
1100
|
+
|
|
1101
|
+
render(
|
|
1102
|
+
<TimeRangeZoomProvider zoom={NOT_ZOOMED}>
|
|
1103
|
+
<ChartGroup charts={[metricChart()]} />
|
|
1104
|
+
</TimeRangeZoomProvider>,
|
|
1105
|
+
);
|
|
1106
|
+
expect(metricChartHint()).not.toHaveClass("opacity-0");
|
|
1107
|
+
cleanup();
|
|
1108
|
+
|
|
1109
|
+
render(
|
|
1110
|
+
<LogsHistogram
|
|
1111
|
+
buckets={LOG_BUCKETS}
|
|
1112
|
+
isLoading={false}
|
|
1113
|
+
onTimeRangeSelect={(): void => {}}
|
|
1114
|
+
bucketIntervalMs={MINUTE_MS}
|
|
1115
|
+
/>,
|
|
1116
|
+
);
|
|
1117
|
+
expect(screen.getByText("Click or drag to zoom")).not.toHaveClass(
|
|
1118
|
+
"opacity-0",
|
|
1119
|
+
);
|
|
1120
|
+
cleanup();
|
|
1121
|
+
|
|
1122
|
+
render(<ZoomablePage />);
|
|
1123
|
+
const cardHint: HTMLElement = screen.getByTestId(
|
|
1124
|
+
TIME_RANGE_ZOOM_HINT_TEST_ID,
|
|
1125
|
+
);
|
|
1126
|
+
expect(cardHint).toHaveClass("opacity-0");
|
|
1127
|
+
expect(cardHint).toHaveClass("group-hover/zoomhint:opacity-100");
|
|
1128
|
+
expect(cardHint).toHaveClass(
|
|
1129
|
+
"group-has-[:focus-visible]/zoomhint:opacity-100",
|
|
1130
|
+
);
|
|
1131
|
+
});
|
|
1132
|
+
|
|
1133
|
+
test("a chart card's hint names the double-click once there is a zoom to undo", () => {
|
|
1134
|
+
expectDocsToSay(
|
|
1135
|
+
"Most charts that zoom name the gesture above the plot — **Drag to zoom**, or **Click or drag to zoom** — and, while a zoom is active, remind you to **double-click to reset**.",
|
|
1136
|
+
);
|
|
1137
|
+
|
|
1138
|
+
render(<ZoomablePage />);
|
|
1139
|
+
expect(hintText()).toBe("Drag to zoom");
|
|
1140
|
+
|
|
1141
|
+
drag("cpu", "2026-09-28T11:10:00.000Z", "2026-09-28T11:20:00.000Z");
|
|
1142
|
+
expect(hintText()).toBe("Double-click to reset");
|
|
1143
|
+
|
|
1144
|
+
doubleClick("cpu");
|
|
1145
|
+
expect(hintText()).toBe("Drag to zoom");
|
|
1146
|
+
});
|
|
1147
|
+
});
|