@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,1481 @@
|
|
|
1
|
+
import { afterEach, beforeEach, describe, expect, test } from "@jest/globals";
|
|
2
|
+
import {
|
|
3
|
+
RenderHookResult,
|
|
4
|
+
act,
|
|
5
|
+
cleanup,
|
|
6
|
+
fireEvent,
|
|
7
|
+
renderHook,
|
|
8
|
+
} from "@testing-library/react";
|
|
9
|
+
import React from "react";
|
|
10
|
+
import getJestMockFunction, { MockFunction } from "../../../MockType";
|
|
11
|
+
import useChartRangeSelection, {
|
|
12
|
+
CHART_PRESS_STILLNESS_PX,
|
|
13
|
+
ChartRangeSelection,
|
|
14
|
+
ChartRangeSelectionRootProps,
|
|
15
|
+
RANGE_SELECTION_THROTTLED_EVENTS,
|
|
16
|
+
RangeSelectionChartState,
|
|
17
|
+
UseChartRangeSelectionOptions,
|
|
18
|
+
} from "../../../../UI/Components/Charts/ChartLibrary/Utils/UseChartRangeSelection";
|
|
19
|
+
import {
|
|
20
|
+
CHART_DATA_POINT_DATE_KEY,
|
|
21
|
+
CHART_DATA_POINT_X_AXIS_KEY,
|
|
22
|
+
} from "../../../../UI/Components/Charts/ChartLibrary/Types/ChartDataPoint";
|
|
23
|
+
|
|
24
|
+
/*
|
|
25
|
+
* Issue #4116: right after a drag zooms a page, the page refetches, and the
|
|
26
|
+
* reader double-clicks to undo the zoom at exactly that moment. When the new
|
|
27
|
+
* rows landed during the double-click's second press, recharts re-keyed the
|
|
28
|
+
* node under the press, and Chrome sent that press no click and no dblclick:
|
|
29
|
+
* the chart stayed zoomed, and the press's release - read against the new
|
|
30
|
+
* rows - could even zoom it further, to a window made of old and new
|
|
31
|
+
* buckets.
|
|
32
|
+
*
|
|
33
|
+
* useChartRangeSelection (the line, area and bar charts) now takes the
|
|
34
|
+
* chart's onTimeRangeReset and no longer depends on the dblclick arriving.
|
|
35
|
+
* The second press of a double-click (MouseEvent.detail 2, the browser's own
|
|
36
|
+
* click count) IS the reset: it starts no selection and paints nothing, and
|
|
37
|
+
* its release arms the reset for the next task. The browser's dblclick, when
|
|
38
|
+
* it does arrive, comes before that task, resets, and stands the fallback
|
|
39
|
+
* down - so a double-click resets exactly once either way.
|
|
40
|
+
*
|
|
41
|
+
* These drive the hook's chart-level handlers the way recharts 3 calls them,
|
|
42
|
+
* handler(state, event), with rows shaped like the real charts' (the x-axis
|
|
43
|
+
* label under the index key, the bucket start as epoch ms), and pin:
|
|
44
|
+
*
|
|
45
|
+
* - what chartEventProps hands the chart root with and without a select
|
|
46
|
+
* handler and a reset - including a chart that only resets (a vertical
|
|
47
|
+
* bar chart), which still has to see presses and releases;
|
|
48
|
+
* - that the reset press never selects, paints or renders, wherever the
|
|
49
|
+
* pointer goes before its release - on the chart or off it - and leaves
|
|
50
|
+
* nothing armed for a release it did not begin;
|
|
51
|
+
* - that a double-click resets exactly once whether or not its dblclick
|
|
52
|
+
* arrives, with the reset the chart handed in last;
|
|
53
|
+
* - that everything that is not a main-button second press - a drag, a
|
|
54
|
+
* click, a right-click, a triple-click's third press - behaves as it
|
|
55
|
+
* did before, but for one rule: a drag needs the POINTER to move, by
|
|
56
|
+
* more than CHART_PRESS_STILLNESS_PX. A press whose row changes under a
|
|
57
|
+
* still pointer - new rows landing mid-press, a hand's drift across a
|
|
58
|
+
* row's edge - is a plain click, not a two-bucket zoom. A caller that
|
|
59
|
+
* hands no mouse events still drags by the change of row alone;
|
|
60
|
+
* - that a release only the page hears is read from the native event the
|
|
61
|
+
* page-wide listener hands on: a press let go where it went down selects
|
|
62
|
+
* nothing, a drag let go far off still selects, and the count tells a
|
|
63
|
+
* later click's release (1), which ends a reset press whose own release
|
|
64
|
+
* was lost without resetting, from the reset press's own (2).
|
|
65
|
+
*/
|
|
66
|
+
|
|
67
|
+
type Rows = Array<Record<string, unknown>>;
|
|
68
|
+
type SelectHandler = (startTime: Date, endTime: Date) => void;
|
|
69
|
+
type ResetHandler = () => void;
|
|
70
|
+
type Band = [string | null, string | null];
|
|
71
|
+
|
|
72
|
+
const MINUTE_MS: number = 60 * 1000;
|
|
73
|
+
const HALF_MINUTE_MS: number = 30 * 1000;
|
|
74
|
+
const FIRST_BUCKET_MS: number = Date.parse("2026-09-28T10:00:00.000Z");
|
|
75
|
+
|
|
76
|
+
const NO_BAND: Band = [null, null];
|
|
77
|
+
|
|
78
|
+
function buildRows(
|
|
79
|
+
firstBucketMs: number,
|
|
80
|
+
bucketMs: number,
|
|
81
|
+
count: number,
|
|
82
|
+
): Rows {
|
|
83
|
+
return Array.from(
|
|
84
|
+
{ length: count },
|
|
85
|
+
(_: unknown, rowIndex: number): Record<string, unknown> => {
|
|
86
|
+
const bucketStartMs: number = firstBucketMs + rowIndex * bucketMs;
|
|
87
|
+
return {
|
|
88
|
+
[CHART_DATA_POINT_X_AXIS_KEY]: new Date(bucketStartMs)
|
|
89
|
+
.toISOString()
|
|
90
|
+
.slice(11, 19),
|
|
91
|
+
[CHART_DATA_POINT_DATE_KEY]: bucketStartMs,
|
|
92
|
+
Requests: 100 + rowIndex,
|
|
93
|
+
};
|
|
94
|
+
},
|
|
95
|
+
);
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
// The rows on screen: ten one-minute buckets from 10:00.
|
|
99
|
+
const ROWS: Rows = buildRows(FIRST_BUCKET_MS, MINUTE_MS, 10);
|
|
100
|
+
|
|
101
|
+
/*
|
|
102
|
+
* What the refetch after a zoom brings in: the same ten minutes in twice as
|
|
103
|
+
* many buckets. A pointer that has not moved from 10:04 - the fifth old
|
|
104
|
+
* bucket - is over the ninth new one.
|
|
105
|
+
*/
|
|
106
|
+
const REFETCHED_ROWS: Rows = buildRows(FIRST_BUCKET_MS, HALF_MINUTE_MS, 20);
|
|
107
|
+
|
|
108
|
+
// Where recharts has no bucket under the pointer: an axis, a margin.
|
|
109
|
+
const OFF_THE_ROWS: RangeSelectionChartState = {
|
|
110
|
+
activeTooltipIndex: null,
|
|
111
|
+
activeLabel: undefined,
|
|
112
|
+
};
|
|
113
|
+
|
|
114
|
+
// What recharts reports with the pointer over a row.
|
|
115
|
+
function at(rowIndex: number, rows: Rows = ROWS): RangeSelectionChartState {
|
|
116
|
+
const label: unknown = rows[rowIndex]?.[CHART_DATA_POINT_X_AXIS_KEY];
|
|
117
|
+
return {
|
|
118
|
+
// recharts 3 reports the tooltip index as a string.
|
|
119
|
+
activeTooltipIndex: String(rowIndex),
|
|
120
|
+
activeLabel: typeof label === "string" ? label : undefined,
|
|
121
|
+
};
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
interface ChartSetup {
|
|
125
|
+
onTimeRangeSelect?: SelectHandler | undefined;
|
|
126
|
+
onTimeRangeReset?: ResetHandler | undefined;
|
|
127
|
+
enabled?: boolean | undefined;
|
|
128
|
+
data?: Rows | undefined;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
interface ChartHarness {
|
|
132
|
+
result: { current: ChartRangeSelection };
|
|
133
|
+
rerender: (setup: ChartSetup) => void;
|
|
134
|
+
unmount: () => void;
|
|
135
|
+
// How many times the chart has rendered so far.
|
|
136
|
+
renders: () => number;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
function hookOptions(setup: ChartSetup): UseChartRangeSelectionOptions {
|
|
140
|
+
return {
|
|
141
|
+
data: setup.data ?? ROWS,
|
|
142
|
+
index: CHART_DATA_POINT_X_AXIS_KEY,
|
|
143
|
+
onTimeRangeSelect: setup.onTimeRangeSelect,
|
|
144
|
+
enabled: setup.enabled,
|
|
145
|
+
onTimeRangeReset: setup.onTimeRangeReset,
|
|
146
|
+
};
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
function renderChart(setup: ChartSetup): ChartHarness {
|
|
150
|
+
let renderCount: number = 0;
|
|
151
|
+
const hook: RenderHookResult<
|
|
152
|
+
ChartRangeSelection,
|
|
153
|
+
UseChartRangeSelectionOptions
|
|
154
|
+
> = renderHook(
|
|
155
|
+
(options: UseChartRangeSelectionOptions): ChartRangeSelection => {
|
|
156
|
+
renderCount += 1;
|
|
157
|
+
return useChartRangeSelection(options);
|
|
158
|
+
},
|
|
159
|
+
{ initialProps: hookOptions(setup) },
|
|
160
|
+
);
|
|
161
|
+
|
|
162
|
+
return {
|
|
163
|
+
result: hook.result,
|
|
164
|
+
rerender: (next: ChartSetup): void => {
|
|
165
|
+
hook.rerender(hookOptions(next));
|
|
166
|
+
},
|
|
167
|
+
unmount: hook.unmount,
|
|
168
|
+
renders: (): number => {
|
|
169
|
+
return renderCount;
|
|
170
|
+
},
|
|
171
|
+
};
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
interface ClickInit {
|
|
175
|
+
// The browser's click count: 2 on a double-click's second press and release.
|
|
176
|
+
detail?: number;
|
|
177
|
+
// The button pressed or let go: 0 main, 1 middle, 2 secondary.
|
|
178
|
+
button?: number;
|
|
179
|
+
// Where the pointer is, if not where clientXOf puts the row reported.
|
|
180
|
+
clientX?: number;
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
// MouseEvent.buttons while each button is held.
|
|
184
|
+
const HELD_BUTTONS: Record<number, number> = { 0: 1, 1: 4, 2: 2 };
|
|
185
|
+
|
|
186
|
+
// How far apart the rows sit on screen, as a drawn chart would place them.
|
|
187
|
+
const ROW_WIDTH_PX: number = 20;
|
|
188
|
+
|
|
189
|
+
// Where the pointer is when recharts reports this row under it.
|
|
190
|
+
function clientXOf(state: RangeSelectionChartState): number {
|
|
191
|
+
const index: number = Number(state.activeTooltipIndex);
|
|
192
|
+
return Number.isFinite(index) ? index * ROW_WIDTH_PX : 0;
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
// The part of the React event recharts hands the handlers that they read.
|
|
196
|
+
function mouseEvent(
|
|
197
|
+
detail: number,
|
|
198
|
+
button: number,
|
|
199
|
+
buttons: number,
|
|
200
|
+
clientX: number,
|
|
201
|
+
): React.MouseEvent<SVGGraphicsElement> {
|
|
202
|
+
return {
|
|
203
|
+
detail: detail,
|
|
204
|
+
button: button,
|
|
205
|
+
buttons: buttons,
|
|
206
|
+
clientX: clientX,
|
|
207
|
+
} as unknown as React.MouseEvent<SVGGraphicsElement>;
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
function press(
|
|
211
|
+
chart: ChartHarness,
|
|
212
|
+
state: RangeSelectionChartState,
|
|
213
|
+
init: ClickInit = {},
|
|
214
|
+
): void {
|
|
215
|
+
const onMouseDown: ChartRangeSelectionRootProps["onMouseDown"] =
|
|
216
|
+
chart.result.current.chartEventProps.onMouseDown;
|
|
217
|
+
if (!onMouseDown) {
|
|
218
|
+
throw new Error("The chart root was handed no onMouseDown: no press.");
|
|
219
|
+
}
|
|
220
|
+
const button: number = init.button ?? 0;
|
|
221
|
+
act(() => {
|
|
222
|
+
onMouseDown(
|
|
223
|
+
state,
|
|
224
|
+
mouseEvent(
|
|
225
|
+
init.detail ?? 1,
|
|
226
|
+
button,
|
|
227
|
+
HELD_BUTTONS[button] ?? 1,
|
|
228
|
+
init.clientX ?? clientXOf(state),
|
|
229
|
+
),
|
|
230
|
+
);
|
|
231
|
+
});
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
function move(
|
|
235
|
+
chart: ChartHarness,
|
|
236
|
+
state: RangeSelectionChartState,
|
|
237
|
+
buttons: number = 1,
|
|
238
|
+
clientX: number = clientXOf(state),
|
|
239
|
+
): void {
|
|
240
|
+
const onMouseMove: ChartRangeSelectionRootProps["onMouseMove"] =
|
|
241
|
+
chart.result.current.chartEventProps.onMouseMove;
|
|
242
|
+
if (!onMouseMove) {
|
|
243
|
+
throw new Error("The chart root was handed no onMouseMove: no drag.");
|
|
244
|
+
}
|
|
245
|
+
act(() => {
|
|
246
|
+
onMouseMove(state, mouseEvent(0, 0, buttons, clientX));
|
|
247
|
+
});
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
function release(
|
|
251
|
+
chart: ChartHarness,
|
|
252
|
+
state: RangeSelectionChartState,
|
|
253
|
+
init: ClickInit = {},
|
|
254
|
+
): void {
|
|
255
|
+
const onMouseUp: ChartRangeSelectionRootProps["onMouseUp"] =
|
|
256
|
+
chart.result.current.chartEventProps.onMouseUp;
|
|
257
|
+
if (!onMouseUp) {
|
|
258
|
+
throw new Error("The chart root was handed no onMouseUp: no release.");
|
|
259
|
+
}
|
|
260
|
+
act(() => {
|
|
261
|
+
onMouseUp(
|
|
262
|
+
state,
|
|
263
|
+
mouseEvent(
|
|
264
|
+
init.detail ?? 1,
|
|
265
|
+
init.button ?? 0,
|
|
266
|
+
0,
|
|
267
|
+
init.clientX ?? clientXOf(state),
|
|
268
|
+
),
|
|
269
|
+
);
|
|
270
|
+
});
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
// The browser's dblclick, when it reaches the chart root.
|
|
274
|
+
function doubleClick(chart: ChartHarness): void {
|
|
275
|
+
const onDoubleClick: ChartRangeSelectionRootProps["onDoubleClick"] =
|
|
276
|
+
chart.result.current.chartEventProps.onDoubleClick;
|
|
277
|
+
if (!onDoubleClick) {
|
|
278
|
+
throw new Error("The chart root was handed no onDoubleClick.");
|
|
279
|
+
}
|
|
280
|
+
act(() => {
|
|
281
|
+
onDoubleClick();
|
|
282
|
+
});
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
// A whole click: press and release on one row, with the browser's count.
|
|
286
|
+
function click(
|
|
287
|
+
chart: ChartHarness,
|
|
288
|
+
state: RangeSelectionChartState,
|
|
289
|
+
detail: number,
|
|
290
|
+
): void {
|
|
291
|
+
press(chart, state, { detail: detail });
|
|
292
|
+
release(chart, state, { detail: detail });
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
/*
|
|
296
|
+
* The handlers as a caller that hands them recharts' state alone - no
|
|
297
|
+
* mouse event - calls them: the shape they had before they read the event.
|
|
298
|
+
*/
|
|
299
|
+
function pressWithoutEvent(
|
|
300
|
+
chart: ChartHarness,
|
|
301
|
+
state: RangeSelectionChartState,
|
|
302
|
+
): void {
|
|
303
|
+
const onMouseDown: ChartRangeSelectionRootProps["onMouseDown"] =
|
|
304
|
+
chart.result.current.chartEventProps.onMouseDown;
|
|
305
|
+
if (!onMouseDown) {
|
|
306
|
+
throw new Error("The chart root was handed no onMouseDown: no press.");
|
|
307
|
+
}
|
|
308
|
+
act(() => {
|
|
309
|
+
onMouseDown(state);
|
|
310
|
+
});
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
function moveWithoutEvent(
|
|
314
|
+
chart: ChartHarness,
|
|
315
|
+
state: RangeSelectionChartState,
|
|
316
|
+
): void {
|
|
317
|
+
const onMouseMove:
|
|
318
|
+
| ((chartState: RangeSelectionChartState) => void)
|
|
319
|
+
| undefined = chart.result.current.chartEventProps.onMouseMove as
|
|
320
|
+
| ((chartState: RangeSelectionChartState) => void)
|
|
321
|
+
| undefined;
|
|
322
|
+
if (!onMouseMove) {
|
|
323
|
+
throw new Error("The chart root was handed no onMouseMove: no drag.");
|
|
324
|
+
}
|
|
325
|
+
act(() => {
|
|
326
|
+
onMouseMove(state);
|
|
327
|
+
});
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
function releaseWithoutEvent(
|
|
331
|
+
chart: ChartHarness,
|
|
332
|
+
state: RangeSelectionChartState,
|
|
333
|
+
): void {
|
|
334
|
+
const onMouseUp: ChartRangeSelectionRootProps["onMouseUp"] =
|
|
335
|
+
chart.result.current.chartEventProps.onMouseUp;
|
|
336
|
+
if (!onMouseUp) {
|
|
337
|
+
throw new Error("The chart root was handed no onMouseUp: no release.");
|
|
338
|
+
}
|
|
339
|
+
act(() => {
|
|
340
|
+
onMouseUp(state);
|
|
341
|
+
});
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
/*
|
|
345
|
+
* What Chrome runs between a release and its click: the microtasks, where
|
|
346
|
+
* React commits. The dblclick comes after them, the next task after that.
|
|
347
|
+
*/
|
|
348
|
+
async function flushMicrotasks(): Promise<void> {
|
|
349
|
+
await act(async (): Promise<void> => {
|
|
350
|
+
await Promise.resolve();
|
|
351
|
+
});
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
// The next task: what a setTimeout(0) armed during a release waits for.
|
|
355
|
+
function nextTask(): void {
|
|
356
|
+
act(() => {
|
|
357
|
+
jest.advanceTimersByTime(0);
|
|
358
|
+
});
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
function allTimers(): void {
|
|
362
|
+
act(() => {
|
|
363
|
+
jest.runAllTimers();
|
|
364
|
+
});
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
function band(chart: ChartHarness): Band {
|
|
368
|
+
return [
|
|
369
|
+
chart.result.current.selectionStartLabel,
|
|
370
|
+
chart.result.current.selectionEndLabel,
|
|
371
|
+
];
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
function selections(select: MockFunction): Array<[string, string]> {
|
|
375
|
+
return select.mock.calls.map((call: Array<unknown>): [string, string] => {
|
|
376
|
+
return [(call[0] as Date).toISOString(), (call[1] as Date).toISOString()];
|
|
377
|
+
});
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
function keysOf(props: ChartRangeSelectionRootProps): Array<string> {
|
|
381
|
+
return Object.keys(props).sort();
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
// The page-wide mouseup listeners a press adds to hear a release off the chart.
|
|
385
|
+
interface PageListenerSpies {
|
|
386
|
+
add: jest.SpiedFunction<typeof window.addEventListener>;
|
|
387
|
+
remove: jest.SpiedFunction<typeof window.removeEventListener>;
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
function spyOnPageListeners(): PageListenerSpies {
|
|
391
|
+
return {
|
|
392
|
+
add: jest.spyOn(window, "addEventListener"),
|
|
393
|
+
remove: jest.spyOn(window, "removeEventListener"),
|
|
394
|
+
};
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
function mouseUpListeners(calls: Array<Array<unknown>>): Array<unknown> {
|
|
398
|
+
return calls
|
|
399
|
+
.filter((call: Array<unknown>): boolean => {
|
|
400
|
+
return call[0] === "mouseup";
|
|
401
|
+
})
|
|
402
|
+
.map((call: Array<unknown>): unknown => {
|
|
403
|
+
return call[1];
|
|
404
|
+
});
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
function expectNoMouseUpListenerLeft(spies: PageListenerSpies): void {
|
|
408
|
+
const removed: Array<unknown> = mouseUpListeners(spies.remove.mock.calls);
|
|
409
|
+
for (const added of mouseUpListeners(spies.add.mock.calls)) {
|
|
410
|
+
expect(removed).toContain(added);
|
|
411
|
+
}
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
// The page-wide mouseup listener added last: the press in progress's own.
|
|
415
|
+
function lastMouseUpListener(
|
|
416
|
+
spies: PageListenerSpies,
|
|
417
|
+
): (event: MouseEvent) => void {
|
|
418
|
+
const listeners: Array<unknown> = mouseUpListeners(spies.add.mock.calls);
|
|
419
|
+
const listener: unknown = listeners[listeners.length - 1];
|
|
420
|
+
if (typeof listener !== "function") {
|
|
421
|
+
throw new Error("No press added a page-wide mouseup listener.");
|
|
422
|
+
}
|
|
423
|
+
return listener as (event: MouseEvent) => void;
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
interface ResetOnlyCase {
|
|
427
|
+
name: string;
|
|
428
|
+
setup: (select: MockFunction, reset: MockFunction) => ChartSetup;
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
/*
|
|
432
|
+
* The two ways a chart ends up offering a double-click but no drag: no
|
|
433
|
+
* select handler at all, or one it cannot use (a vertical bar chart, whose
|
|
434
|
+
* time runs down the y-axis).
|
|
435
|
+
*/
|
|
436
|
+
const RESET_ONLY_CASES: Array<ResetOnlyCase> = [
|
|
437
|
+
{
|
|
438
|
+
name: "no select handler",
|
|
439
|
+
setup: (_select: MockFunction, reset: MockFunction): ChartSetup => {
|
|
440
|
+
return { onTimeRangeReset: reset };
|
|
441
|
+
},
|
|
442
|
+
},
|
|
443
|
+
{
|
|
444
|
+
name: "selection turned off, as on a vertical bar chart",
|
|
445
|
+
setup: (select: MockFunction, reset: MockFunction): ChartSetup => {
|
|
446
|
+
return {
|
|
447
|
+
onTimeRangeSelect: select,
|
|
448
|
+
enabled: false,
|
|
449
|
+
onTimeRangeReset: reset,
|
|
450
|
+
};
|
|
451
|
+
},
|
|
452
|
+
},
|
|
453
|
+
];
|
|
454
|
+
|
|
455
|
+
beforeEach(() => {
|
|
456
|
+
jest.useFakeTimers();
|
|
457
|
+
});
|
|
458
|
+
|
|
459
|
+
afterEach(() => {
|
|
460
|
+
cleanup();
|
|
461
|
+
jest.restoreAllMocks();
|
|
462
|
+
jest.useRealTimers();
|
|
463
|
+
});
|
|
464
|
+
|
|
465
|
+
describe("what the chart root is handed", () => {
|
|
466
|
+
test("a chart that drags and resets takes presses, moves, releases and the dblclick, with the crosshair and unthrottled moves", () => {
|
|
467
|
+
const chart: ChartHarness = renderChart({
|
|
468
|
+
onTimeRangeSelect: getJestMockFunction(),
|
|
469
|
+
onTimeRangeReset: getJestMockFunction(),
|
|
470
|
+
});
|
|
471
|
+
const props: ChartRangeSelectionRootProps =
|
|
472
|
+
chart.result.current.chartEventProps;
|
|
473
|
+
|
|
474
|
+
expect(keysOf(props)).toEqual([
|
|
475
|
+
"onDoubleClick",
|
|
476
|
+
"onMouseDown",
|
|
477
|
+
"onMouseMove",
|
|
478
|
+
"onMouseUp",
|
|
479
|
+
"style",
|
|
480
|
+
"throttledEvents",
|
|
481
|
+
]);
|
|
482
|
+
expect(typeof props.onMouseDown).toBe("function");
|
|
483
|
+
expect(typeof props.onMouseMove).toBe("function");
|
|
484
|
+
expect(typeof props.onMouseUp).toBe("function");
|
|
485
|
+
expect(typeof props.onDoubleClick).toBe("function");
|
|
486
|
+
expect(props.style).toEqual({ cursor: "crosshair" });
|
|
487
|
+
expect(props.throttledEvents).toEqual(RANGE_SELECTION_THROTTLED_EVENTS);
|
|
488
|
+
expect(props.throttledEvents).not.toContain("mousemove");
|
|
489
|
+
expect(chart.result.current.canSelect).toBe(true);
|
|
490
|
+
});
|
|
491
|
+
|
|
492
|
+
test("a chart that drags with no zoom to undo gets no dblclick handler", () => {
|
|
493
|
+
const chart: ChartHarness = renderChart({
|
|
494
|
+
onTimeRangeSelect: getJestMockFunction(),
|
|
495
|
+
});
|
|
496
|
+
|
|
497
|
+
expect(keysOf(chart.result.current.chartEventProps)).toEqual([
|
|
498
|
+
"onMouseDown",
|
|
499
|
+
"onMouseMove",
|
|
500
|
+
"onMouseUp",
|
|
501
|
+
"style",
|
|
502
|
+
"throttledEvents",
|
|
503
|
+
]);
|
|
504
|
+
expect(chart.result.current.canSelect).toBe(true);
|
|
505
|
+
});
|
|
506
|
+
|
|
507
|
+
for (const resetOnly of RESET_ONLY_CASES) {
|
|
508
|
+
test(`a chart that only resets (${resetOnly.name}) takes presses, releases and the dblclick - no moves, no crosshair, recharts' own throttling`, () => {
|
|
509
|
+
const chart: ChartHarness = renderChart(
|
|
510
|
+
resetOnly.setup(getJestMockFunction(), getJestMockFunction()),
|
|
511
|
+
);
|
|
512
|
+
const props: ChartRangeSelectionRootProps =
|
|
513
|
+
chart.result.current.chartEventProps;
|
|
514
|
+
|
|
515
|
+
expect(keysOf(props)).toEqual([
|
|
516
|
+
"onDoubleClick",
|
|
517
|
+
"onMouseDown",
|
|
518
|
+
"onMouseUp",
|
|
519
|
+
]);
|
|
520
|
+
expect(typeof props.onMouseDown).toBe("function");
|
|
521
|
+
expect(typeof props.onMouseUp).toBe("function");
|
|
522
|
+
expect(typeof props.onDoubleClick).toBe("function");
|
|
523
|
+
expect(chart.result.current.canSelect).toBe(false);
|
|
524
|
+
});
|
|
525
|
+
}
|
|
526
|
+
|
|
527
|
+
test("a chart that neither drags nor resets is handed nothing", () => {
|
|
528
|
+
const plain: ChartHarness = renderChart({});
|
|
529
|
+
const verticalBars: ChartHarness = renderChart({
|
|
530
|
+
onTimeRangeSelect: getJestMockFunction(),
|
|
531
|
+
enabled: false,
|
|
532
|
+
});
|
|
533
|
+
|
|
534
|
+
expect(keysOf(plain.result.current.chartEventProps)).toEqual([]);
|
|
535
|
+
expect(keysOf(verticalBars.result.current.chartEventProps)).toEqual([]);
|
|
536
|
+
expect(plain.result.current.canSelect).toBe(false);
|
|
537
|
+
expect(verticalBars.result.current.canSelect).toBe(false);
|
|
538
|
+
});
|
|
539
|
+
});
|
|
540
|
+
|
|
541
|
+
describe("the second press of a double-click is the reset", () => {
|
|
542
|
+
test("dragged across the rows, it selects nothing and paints no band", () => {
|
|
543
|
+
const select: MockFunction = getJestMockFunction();
|
|
544
|
+
const chart: ChartHarness = renderChart({
|
|
545
|
+
onTimeRangeSelect: select,
|
|
546
|
+
onTimeRangeReset: getJestMockFunction(),
|
|
547
|
+
});
|
|
548
|
+
|
|
549
|
+
press(chart, at(2), { detail: 2 });
|
|
550
|
+
for (const rowIndex of [3, 4, 5, 6]) {
|
|
551
|
+
move(chart, at(rowIndex));
|
|
552
|
+
expect(band(chart)).toEqual(NO_BAND);
|
|
553
|
+
}
|
|
554
|
+
release(chart, at(6), { detail: 2 });
|
|
555
|
+
allTimers();
|
|
556
|
+
|
|
557
|
+
expect(select).not.toHaveBeenCalled();
|
|
558
|
+
expect(band(chart)).toEqual(NO_BAND);
|
|
559
|
+
});
|
|
560
|
+
|
|
561
|
+
test("its release resets once, on the next task - not during the release", async () => {
|
|
562
|
+
const reset: MockFunction = getJestMockFunction();
|
|
563
|
+
const chart: ChartHarness = renderChart({
|
|
564
|
+
onTimeRangeSelect: getJestMockFunction(),
|
|
565
|
+
onTimeRangeReset: reset,
|
|
566
|
+
});
|
|
567
|
+
|
|
568
|
+
press(chart, at(2), { detail: 2 });
|
|
569
|
+
move(chart, at(4));
|
|
570
|
+
release(chart, at(4), { detail: 2 });
|
|
571
|
+
expect(reset).not.toHaveBeenCalled();
|
|
572
|
+
|
|
573
|
+
// The dblclick has its chance first: it comes before the next task.
|
|
574
|
+
await flushMicrotasks();
|
|
575
|
+
expect(reset).not.toHaveBeenCalled();
|
|
576
|
+
|
|
577
|
+
nextTask();
|
|
578
|
+
expect(reset).toHaveBeenCalledTimes(1);
|
|
579
|
+
expect(reset).toHaveBeenCalledWith();
|
|
580
|
+
|
|
581
|
+
allTimers();
|
|
582
|
+
expect(reset).toHaveBeenCalledTimes(1);
|
|
583
|
+
});
|
|
584
|
+
|
|
585
|
+
test("it renders nothing, wherever the pointer goes, so the node it landed on stays for its click and dblclick", () => {
|
|
586
|
+
const chart: ChartHarness = renderChart({
|
|
587
|
+
onTimeRangeSelect: getJestMockFunction(),
|
|
588
|
+
onTimeRangeReset: getJestMockFunction(),
|
|
589
|
+
});
|
|
590
|
+
const rendersBefore: number = chart.renders();
|
|
591
|
+
|
|
592
|
+
press(chart, at(3), { detail: 2 });
|
|
593
|
+
move(chart, at(4));
|
|
594
|
+
move(chart, at(7));
|
|
595
|
+
release(chart, at(7), { detail: 2 });
|
|
596
|
+
|
|
597
|
+
expect(chart.renders()).toBe(rendersBefore);
|
|
598
|
+
});
|
|
599
|
+
|
|
600
|
+
test("whatever it listens for on the page, it stops listening once released", () => {
|
|
601
|
+
const chart: ChartHarness = renderChart({
|
|
602
|
+
onTimeRangeSelect: getJestMockFunction(),
|
|
603
|
+
onTimeRangeReset: getJestMockFunction(),
|
|
604
|
+
});
|
|
605
|
+
const pageListeners: PageListenerSpies = spyOnPageListeners();
|
|
606
|
+
|
|
607
|
+
press(chart, at(3), { detail: 2 });
|
|
608
|
+
move(chart, at(6));
|
|
609
|
+
release(chart, at(6), { detail: 2 });
|
|
610
|
+
|
|
611
|
+
expectNoMouseUpListenerLeft(pageListeners);
|
|
612
|
+
});
|
|
613
|
+
|
|
614
|
+
/*
|
|
615
|
+
* The hook's own design: the reset press ends on its release wherever
|
|
616
|
+
* that is, heard by the window as a drag's is. (The browser would send
|
|
617
|
+
* the chart no dblclick there; the reset does not wait for one.)
|
|
618
|
+
*/
|
|
619
|
+
test("let go off the chart, it still resets, once: only the window hears that release", () => {
|
|
620
|
+
const select: MockFunction = getJestMockFunction();
|
|
621
|
+
const reset: MockFunction = getJestMockFunction();
|
|
622
|
+
const chart: ChartHarness = renderChart({
|
|
623
|
+
onTimeRangeSelect: select,
|
|
624
|
+
onTimeRangeReset: reset,
|
|
625
|
+
});
|
|
626
|
+
|
|
627
|
+
click(chart, at(4), 1);
|
|
628
|
+
press(chart, at(4), { detail: 2 });
|
|
629
|
+
move(chart, at(9));
|
|
630
|
+
fireEvent.mouseUp(window);
|
|
631
|
+
expect(reset).not.toHaveBeenCalled();
|
|
632
|
+
|
|
633
|
+
nextTask();
|
|
634
|
+
expect(reset).toHaveBeenCalledTimes(1);
|
|
635
|
+
allTimers();
|
|
636
|
+
expect(reset).toHaveBeenCalledTimes(1);
|
|
637
|
+
expect(select).not.toHaveBeenCalled();
|
|
638
|
+
expect(band(chart)).toEqual(NO_BAND);
|
|
639
|
+
});
|
|
640
|
+
|
|
641
|
+
/*
|
|
642
|
+
* While the reset press listened for no release off the chart, one let go
|
|
643
|
+
* there stayed "the reset press", and the next mouseup the chart heard -
|
|
644
|
+
* from a press that began anywhere else on the page - reset the zoom.
|
|
645
|
+
*/
|
|
646
|
+
test("let go off the chart, it leaves nothing armed for a later release over the chart that it did not begin", () => {
|
|
647
|
+
const select: MockFunction = getJestMockFunction();
|
|
648
|
+
const reset: MockFunction = getJestMockFunction();
|
|
649
|
+
const chart: ChartHarness = renderChart({
|
|
650
|
+
onTimeRangeSelect: select,
|
|
651
|
+
onTimeRangeReset: reset,
|
|
652
|
+
});
|
|
653
|
+
|
|
654
|
+
click(chart, at(4), 1);
|
|
655
|
+
press(chart, at(4), { detail: 2 });
|
|
656
|
+
fireEvent.mouseUp(window);
|
|
657
|
+
allTimers();
|
|
658
|
+
const resetsSoFar: number = reset.mock.calls.length;
|
|
659
|
+
|
|
660
|
+
/*
|
|
661
|
+
* Later, a press somewhere else on the page - a text selection in the
|
|
662
|
+
* table under the chart, say - is let go over the chart. No press on
|
|
663
|
+
* the chart began it, so it ends no double-click.
|
|
664
|
+
*/
|
|
665
|
+
release(chart, at(6), { detail: 1 });
|
|
666
|
+
allTimers();
|
|
667
|
+
|
|
668
|
+
expect(reset).toHaveBeenCalledTimes(resetsSoFar);
|
|
669
|
+
expect(select).not.toHaveBeenCalled();
|
|
670
|
+
});
|
|
671
|
+
|
|
672
|
+
test("a chart unmounted during it stops listening, and resets nothing when the button comes up", () => {
|
|
673
|
+
const reset: MockFunction = getJestMockFunction();
|
|
674
|
+
const chart: ChartHarness = renderChart({
|
|
675
|
+
onTimeRangeSelect: getJestMockFunction(),
|
|
676
|
+
onTimeRangeReset: reset,
|
|
677
|
+
});
|
|
678
|
+
const pageListeners: PageListenerSpies = spyOnPageListeners();
|
|
679
|
+
|
|
680
|
+
click(chart, at(4), 1);
|
|
681
|
+
press(chart, at(4), { detail: 2 });
|
|
682
|
+
chart.unmount();
|
|
683
|
+
expectNoMouseUpListenerLeft(pageListeners);
|
|
684
|
+
|
|
685
|
+
fireEvent.mouseUp(window);
|
|
686
|
+
allTimers();
|
|
687
|
+
expect(reset).not.toHaveBeenCalled();
|
|
688
|
+
});
|
|
689
|
+
|
|
690
|
+
test("a whole double-click whose dblclick never arrives resets once, and selects nothing", async () => {
|
|
691
|
+
const select: MockFunction = getJestMockFunction();
|
|
692
|
+
const reset: MockFunction = getJestMockFunction();
|
|
693
|
+
const chart: ChartHarness = renderChart({
|
|
694
|
+
onTimeRangeSelect: select,
|
|
695
|
+
onTimeRangeReset: reset,
|
|
696
|
+
});
|
|
697
|
+
|
|
698
|
+
click(chart, at(4), 1);
|
|
699
|
+
click(chart, at(4), 2);
|
|
700
|
+
await flushMicrotasks();
|
|
701
|
+
// No click and no dblclick: the node under the press was re-keyed.
|
|
702
|
+
nextTask();
|
|
703
|
+
|
|
704
|
+
expect(reset).toHaveBeenCalledTimes(1);
|
|
705
|
+
allTimers();
|
|
706
|
+
expect(reset).toHaveBeenCalledTimes(1);
|
|
707
|
+
expect(select).not.toHaveBeenCalled();
|
|
708
|
+
});
|
|
709
|
+
|
|
710
|
+
test("a whole double-click whose dblclick arrives resets exactly once, from the dblclick", async () => {
|
|
711
|
+
const select: MockFunction = getJestMockFunction();
|
|
712
|
+
const reset: MockFunction = getJestMockFunction();
|
|
713
|
+
const chart: ChartHarness = renderChart({
|
|
714
|
+
onTimeRangeSelect: select,
|
|
715
|
+
onTimeRangeReset: reset,
|
|
716
|
+
});
|
|
717
|
+
|
|
718
|
+
click(chart, at(4), 1);
|
|
719
|
+
click(chart, at(4), 2);
|
|
720
|
+
// Chrome's order: mouseup, microtasks, click, dblclick, then the next task.
|
|
721
|
+
await flushMicrotasks();
|
|
722
|
+
expect(reset).not.toHaveBeenCalled();
|
|
723
|
+
doubleClick(chart);
|
|
724
|
+
expect(reset).toHaveBeenCalledTimes(1);
|
|
725
|
+
|
|
726
|
+
// The fallback the release armed has stood down.
|
|
727
|
+
allTimers();
|
|
728
|
+
expect(reset).toHaveBeenCalledTimes(1);
|
|
729
|
+
expect(select).not.toHaveBeenCalled();
|
|
730
|
+
});
|
|
731
|
+
|
|
732
|
+
test("a dblclick on its own resets at once, and only once", () => {
|
|
733
|
+
const select: MockFunction = getJestMockFunction();
|
|
734
|
+
const reset: MockFunction = getJestMockFunction();
|
|
735
|
+
const chart: ChartHarness = renderChart({
|
|
736
|
+
onTimeRangeSelect: select,
|
|
737
|
+
onTimeRangeReset: reset,
|
|
738
|
+
});
|
|
739
|
+
|
|
740
|
+
doubleClick(chart);
|
|
741
|
+
expect(reset).toHaveBeenCalledTimes(1);
|
|
742
|
+
|
|
743
|
+
allTimers();
|
|
744
|
+
expect(reset).toHaveBeenCalledTimes(1);
|
|
745
|
+
expect(select).not.toHaveBeenCalled();
|
|
746
|
+
});
|
|
747
|
+
|
|
748
|
+
test("new rows landing during the second press make no window of old and new buckets, and the double-click still resets once", () => {
|
|
749
|
+
const select: MockFunction = getJestMockFunction();
|
|
750
|
+
const reset: MockFunction = getJestMockFunction();
|
|
751
|
+
const chart: ChartHarness = renderChart({
|
|
752
|
+
onTimeRangeSelect: select,
|
|
753
|
+
onTimeRangeReset: reset,
|
|
754
|
+
});
|
|
755
|
+
|
|
756
|
+
click(chart, at(4), 1);
|
|
757
|
+
press(chart, at(4), { detail: 2 });
|
|
758
|
+
// The refetch lands under the press and the chart redraws its rows.
|
|
759
|
+
chart.rerender({
|
|
760
|
+
onTimeRangeSelect: select,
|
|
761
|
+
onTimeRangeReset: reset,
|
|
762
|
+
data: REFETCHED_ROWS,
|
|
763
|
+
});
|
|
764
|
+
/*
|
|
765
|
+
* The pointer has not moved, but recharts now reports a bucket of the
|
|
766
|
+
* new rows - and Chrome sends this press no click and no dblclick.
|
|
767
|
+
*/
|
|
768
|
+
release(chart, at(8, REFETCHED_ROWS), { detail: 2 });
|
|
769
|
+
|
|
770
|
+
expect(select).not.toHaveBeenCalled();
|
|
771
|
+
expect(band(chart)).toEqual(NO_BAND);
|
|
772
|
+
|
|
773
|
+
nextTask();
|
|
774
|
+
expect(reset).toHaveBeenCalledTimes(1);
|
|
775
|
+
allTimers();
|
|
776
|
+
expect(reset).toHaveBeenCalledTimes(1);
|
|
777
|
+
expect(select).not.toHaveBeenCalled();
|
|
778
|
+
});
|
|
779
|
+
|
|
780
|
+
test("a double-click off the rows - on an axis or a margin - resets too", () => {
|
|
781
|
+
const select: MockFunction = getJestMockFunction();
|
|
782
|
+
const reset: MockFunction = getJestMockFunction();
|
|
783
|
+
const chart: ChartHarness = renderChart({
|
|
784
|
+
onTimeRangeSelect: select,
|
|
785
|
+
onTimeRangeReset: reset,
|
|
786
|
+
});
|
|
787
|
+
|
|
788
|
+
click(chart, OFF_THE_ROWS, 1);
|
|
789
|
+
click(chart, OFF_THE_ROWS, 2);
|
|
790
|
+
nextTask();
|
|
791
|
+
|
|
792
|
+
expect(reset).toHaveBeenCalledTimes(1);
|
|
793
|
+
expect(select).not.toHaveBeenCalled();
|
|
794
|
+
});
|
|
795
|
+
|
|
796
|
+
test("it drops a drag whose release was lost, band and page-wide listener included", () => {
|
|
797
|
+
const select: MockFunction = getJestMockFunction();
|
|
798
|
+
const reset: MockFunction = getJestMockFunction();
|
|
799
|
+
const chart: ChartHarness = renderChart({
|
|
800
|
+
onTimeRangeSelect: select,
|
|
801
|
+
onTimeRangeReset: reset,
|
|
802
|
+
});
|
|
803
|
+
|
|
804
|
+
press(chart, at(1));
|
|
805
|
+
move(chart, at(4));
|
|
806
|
+
expect(band(chart)).toEqual(["10:01:00", "10:04:00"]);
|
|
807
|
+
|
|
808
|
+
/*
|
|
809
|
+
* That drag's release never reached the page, and this press is the
|
|
810
|
+
* second of a double-click.
|
|
811
|
+
*/
|
|
812
|
+
press(chart, at(4), { detail: 2 });
|
|
813
|
+
expect(band(chart)).toEqual(NO_BAND);
|
|
814
|
+
release(chart, at(4), { detail: 2 });
|
|
815
|
+
// The same release reaching the window: no drag is listening any more.
|
|
816
|
+
fireEvent.mouseUp(window);
|
|
817
|
+
nextTask();
|
|
818
|
+
|
|
819
|
+
expect(select).not.toHaveBeenCalled();
|
|
820
|
+
expect(reset).toHaveBeenCalledTimes(1);
|
|
821
|
+
});
|
|
822
|
+
|
|
823
|
+
test("it does not suppress the click that follows it, even after crossing rows", () => {
|
|
824
|
+
const chart: ChartHarness = renderChart({
|
|
825
|
+
onTimeRangeSelect: getJestMockFunction(),
|
|
826
|
+
onTimeRangeReset: getJestMockFunction(),
|
|
827
|
+
});
|
|
828
|
+
|
|
829
|
+
press(chart, at(2), { detail: 2 });
|
|
830
|
+
move(chart, at(3));
|
|
831
|
+
move(chart, at(6));
|
|
832
|
+
release(chart, at(6), { detail: 2 });
|
|
833
|
+
expect(chart.result.current.isClickSuppressed()).toBe(false);
|
|
834
|
+
|
|
835
|
+
nextTask();
|
|
836
|
+
expect(chart.result.current.isClickSuppressed()).toBe(false);
|
|
837
|
+
});
|
|
838
|
+
});
|
|
839
|
+
|
|
840
|
+
describe("presses that are not the reset", () => {
|
|
841
|
+
interface DragTrace {
|
|
842
|
+
bands: Array<Band>;
|
|
843
|
+
selected: Array<[string, string]>;
|
|
844
|
+
suppressedAfterRelease: boolean;
|
|
845
|
+
suppressedAfterNextTask: boolean;
|
|
846
|
+
renders: number;
|
|
847
|
+
}
|
|
848
|
+
|
|
849
|
+
function plainDrag(chart: ChartHarness, select: MockFunction): DragTrace {
|
|
850
|
+
const rendersBefore: number = chart.renders();
|
|
851
|
+
const bands: Array<Band> = [];
|
|
852
|
+
press(chart, at(2));
|
|
853
|
+
bands.push(band(chart));
|
|
854
|
+
move(chart, at(3));
|
|
855
|
+
bands.push(band(chart));
|
|
856
|
+
move(chart, at(5));
|
|
857
|
+
bands.push(band(chart));
|
|
858
|
+
release(chart, at(5));
|
|
859
|
+
bands.push(band(chart));
|
|
860
|
+
const suppressedAfterRelease: boolean =
|
|
861
|
+
chart.result.current.isClickSuppressed();
|
|
862
|
+
nextTask();
|
|
863
|
+
|
|
864
|
+
return {
|
|
865
|
+
bands: bands,
|
|
866
|
+
selected: selections(select),
|
|
867
|
+
suppressedAfterRelease: suppressedAfterRelease,
|
|
868
|
+
suppressedAfterNextTask: chart.result.current.isClickSuppressed(),
|
|
869
|
+
renders: chart.renders() - rendersBefore,
|
|
870
|
+
};
|
|
871
|
+
}
|
|
872
|
+
|
|
873
|
+
test("a plain drag selects exactly as it does on a chart with no reset", () => {
|
|
874
|
+
const selectWithoutReset: MockFunction = getJestMockFunction();
|
|
875
|
+
const selectWithReset: MockFunction = getJestMockFunction();
|
|
876
|
+
const reset: MockFunction = getJestMockFunction();
|
|
877
|
+
|
|
878
|
+
const withoutReset: DragTrace = plainDrag(
|
|
879
|
+
renderChart({ onTimeRangeSelect: selectWithoutReset }),
|
|
880
|
+
selectWithoutReset,
|
|
881
|
+
);
|
|
882
|
+
const withReset: DragTrace = plainDrag(
|
|
883
|
+
renderChart({
|
|
884
|
+
onTimeRangeSelect: selectWithReset,
|
|
885
|
+
onTimeRangeReset: reset,
|
|
886
|
+
}),
|
|
887
|
+
selectWithReset,
|
|
888
|
+
);
|
|
889
|
+
|
|
890
|
+
expect(withReset).toEqual(withoutReset);
|
|
891
|
+
expect(withReset.bands).toEqual([
|
|
892
|
+
NO_BAND,
|
|
893
|
+
["10:02:00", "10:03:00"],
|
|
894
|
+
["10:02:00", "10:05:00"],
|
|
895
|
+
NO_BAND,
|
|
896
|
+
]);
|
|
897
|
+
// From the start of the first bucket to the END of the last.
|
|
898
|
+
expect(withReset.selected).toEqual([
|
|
899
|
+
["2026-09-28T10:02:00.000Z", "2026-09-28T10:06:00.000Z"],
|
|
900
|
+
]);
|
|
901
|
+
expect(withReset.suppressedAfterRelease).toBe(true);
|
|
902
|
+
expect(withReset.suppressedAfterNextTask).toBe(false);
|
|
903
|
+
|
|
904
|
+
allTimers();
|
|
905
|
+
expect(reset).not.toHaveBeenCalled();
|
|
906
|
+
});
|
|
907
|
+
|
|
908
|
+
test("a plain click selects nothing and resets nothing", () => {
|
|
909
|
+
const select: MockFunction = getJestMockFunction();
|
|
910
|
+
const reset: MockFunction = getJestMockFunction();
|
|
911
|
+
const chart: ChartHarness = renderChart({
|
|
912
|
+
onTimeRangeSelect: select,
|
|
913
|
+
onTimeRangeReset: reset,
|
|
914
|
+
});
|
|
915
|
+
|
|
916
|
+
click(chart, at(4), 1);
|
|
917
|
+
expect(band(chart)).toEqual(NO_BAND);
|
|
918
|
+
expect(chart.result.current.isClickSuppressed()).toBe(false);
|
|
919
|
+
|
|
920
|
+
allTimers();
|
|
921
|
+
expect(select).not.toHaveBeenCalled();
|
|
922
|
+
expect(reset).not.toHaveBeenCalled();
|
|
923
|
+
});
|
|
924
|
+
|
|
925
|
+
for (const otherButton of [
|
|
926
|
+
{ name: "right", button: 2 },
|
|
927
|
+
{ name: "middle", button: 1 },
|
|
928
|
+
]) {
|
|
929
|
+
test(`a ${otherButton.name}-button second press is not the reset, and does not drag`, () => {
|
|
930
|
+
const select: MockFunction = getJestMockFunction();
|
|
931
|
+
const reset: MockFunction = getJestMockFunction();
|
|
932
|
+
const chart: ChartHarness = renderChart({
|
|
933
|
+
onTimeRangeSelect: select,
|
|
934
|
+
onTimeRangeReset: reset,
|
|
935
|
+
});
|
|
936
|
+
|
|
937
|
+
press(chart, at(4), { detail: 1, button: otherButton.button });
|
|
938
|
+
release(chart, at(4), { detail: 1, button: otherButton.button });
|
|
939
|
+
press(chart, at(4), { detail: 2, button: otherButton.button });
|
|
940
|
+
move(chart, at(7), HELD_BUTTONS[otherButton.button]);
|
|
941
|
+
release(chart, at(7), { detail: 2, button: otherButton.button });
|
|
942
|
+
allTimers();
|
|
943
|
+
|
|
944
|
+
expect(reset).not.toHaveBeenCalled();
|
|
945
|
+
expect(select).not.toHaveBeenCalled();
|
|
946
|
+
expect(band(chart)).toEqual(NO_BAND);
|
|
947
|
+
});
|
|
948
|
+
}
|
|
949
|
+
|
|
950
|
+
test("with no zoom to undo, a second press drags as it always did", () => {
|
|
951
|
+
const select: MockFunction = getJestMockFunction();
|
|
952
|
+
const chart: ChartHarness = renderChart({ onTimeRangeSelect: select });
|
|
953
|
+
|
|
954
|
+
click(chart, at(2), 1);
|
|
955
|
+
press(chart, at(2), { detail: 2 });
|
|
956
|
+
move(chart, at(4));
|
|
957
|
+
expect(band(chart)).toEqual(["10:02:00", "10:04:00"]);
|
|
958
|
+
release(chart, at(4), { detail: 2 });
|
|
959
|
+
|
|
960
|
+
expect(selections(select)).toEqual([
|
|
961
|
+
["2026-09-28T10:02:00.000Z", "2026-09-28T10:05:00.000Z"],
|
|
962
|
+
]);
|
|
963
|
+
});
|
|
964
|
+
|
|
965
|
+
test("a press handed no event is never the reset: it drags as it always did", () => {
|
|
966
|
+
const select: MockFunction = getJestMockFunction();
|
|
967
|
+
const reset: MockFunction = getJestMockFunction();
|
|
968
|
+
const chart: ChartHarness = renderChart({
|
|
969
|
+
onTimeRangeSelect: select,
|
|
970
|
+
onTimeRangeReset: reset,
|
|
971
|
+
});
|
|
972
|
+
|
|
973
|
+
act(() => {
|
|
974
|
+
chart.result.current.chartEventProps.onMouseDown?.(at(3));
|
|
975
|
+
});
|
|
976
|
+
move(chart, at(5));
|
|
977
|
+
act(() => {
|
|
978
|
+
chart.result.current.chartEventProps.onMouseUp?.(at(5));
|
|
979
|
+
});
|
|
980
|
+
allTimers();
|
|
981
|
+
|
|
982
|
+
expect(selections(select)).toEqual([
|
|
983
|
+
["2026-09-28T10:03:00.000Z", "2026-09-28T10:06:00.000Z"],
|
|
984
|
+
]);
|
|
985
|
+
expect(reset).not.toHaveBeenCalled();
|
|
986
|
+
});
|
|
987
|
+
|
|
988
|
+
/*
|
|
989
|
+
* The browser sends dblclick at a click count of 2 only, so the fallback
|
|
990
|
+
* stands in for it at 2 only: a triple-click's third press is a press.
|
|
991
|
+
*/
|
|
992
|
+
test("a triple-click's third press, on a chart with a zoom to undo, is an ordinary press: it arms no reset of its own", () => {
|
|
993
|
+
const select: MockFunction = getJestMockFunction();
|
|
994
|
+
const reset: MockFunction = getJestMockFunction();
|
|
995
|
+
const chart: ChartHarness = renderChart({
|
|
996
|
+
onTimeRangeSelect: select,
|
|
997
|
+
onTimeRangeReset: reset,
|
|
998
|
+
});
|
|
999
|
+
|
|
1000
|
+
click(chart, at(4), 1);
|
|
1001
|
+
click(chart, at(4), 2);
|
|
1002
|
+
doubleClick(chart);
|
|
1003
|
+
|
|
1004
|
+
expect(reset).toHaveBeenCalledTimes(1);
|
|
1005
|
+
expect(jest.getTimerCount()).toBe(0);
|
|
1006
|
+
|
|
1007
|
+
// The third press and its release, which the browser counts 3.
|
|
1008
|
+
click(chart, at(4), 3);
|
|
1009
|
+
|
|
1010
|
+
expect(jest.getTimerCount()).toBe(0);
|
|
1011
|
+
expect(chart.result.current.isClickSuppressed()).toBe(false);
|
|
1012
|
+
|
|
1013
|
+
allTimers();
|
|
1014
|
+
|
|
1015
|
+
expect(reset).toHaveBeenCalledTimes(1);
|
|
1016
|
+
expect(select).not.toHaveBeenCalled();
|
|
1017
|
+
});
|
|
1018
|
+
|
|
1019
|
+
test("a drag from a press the browser counts 3 selects, as any drag does, and resets nothing", () => {
|
|
1020
|
+
const select: MockFunction = getJestMockFunction();
|
|
1021
|
+
const reset: MockFunction = getJestMockFunction();
|
|
1022
|
+
const chart: ChartHarness = renderChart({
|
|
1023
|
+
onTimeRangeSelect: select,
|
|
1024
|
+
onTimeRangeReset: reset,
|
|
1025
|
+
});
|
|
1026
|
+
|
|
1027
|
+
press(chart, at(2), { detail: 3 });
|
|
1028
|
+
move(chart, at(3));
|
|
1029
|
+
move(chart, at(5));
|
|
1030
|
+
|
|
1031
|
+
expect(band(chart)).toEqual(["10:02:00", "10:05:00"]);
|
|
1032
|
+
|
|
1033
|
+
release(chart, at(5), { detail: 3 });
|
|
1034
|
+
allTimers();
|
|
1035
|
+
|
|
1036
|
+
expect(selections(select)).toEqual([
|
|
1037
|
+
["2026-09-28T10:02:00.000Z", "2026-09-28T10:06:00.000Z"],
|
|
1038
|
+
]);
|
|
1039
|
+
expect(reset).not.toHaveBeenCalled();
|
|
1040
|
+
});
|
|
1041
|
+
});
|
|
1042
|
+
|
|
1043
|
+
/*
|
|
1044
|
+
* A drag needs the pointer itself to move, by more than
|
|
1045
|
+
* CHART_PRESS_STILLNESS_PX. The row recharts reports under the pointer can
|
|
1046
|
+
* change while the pointer stays put: new rows land mid-press (the refetch
|
|
1047
|
+
* after a zoom), or a hand's drift crosses a row's edge. Either way the
|
|
1048
|
+
* press is a plain click, which keeps its meaning (a bucket click, a legend
|
|
1049
|
+
* toggle) - never a zoom into two buckets the reader did not drag across.
|
|
1050
|
+
*/
|
|
1051
|
+
describe("a press becomes a drag only when the pointer moves", () => {
|
|
1052
|
+
const CHART_KINDS: Array<{
|
|
1053
|
+
name: string;
|
|
1054
|
+
setup: (select: MockFunction, reset: MockFunction) => ChartSetup;
|
|
1055
|
+
}> = [
|
|
1056
|
+
{
|
|
1057
|
+
name: "a chart with a zoom to undo",
|
|
1058
|
+
setup: (select: MockFunction, reset: MockFunction): ChartSetup => {
|
|
1059
|
+
return { onTimeRangeSelect: select, onTimeRangeReset: reset };
|
|
1060
|
+
},
|
|
1061
|
+
},
|
|
1062
|
+
{
|
|
1063
|
+
name: "a chart with none",
|
|
1064
|
+
setup: (select: MockFunction): ChartSetup => {
|
|
1065
|
+
return { onTimeRangeSelect: select };
|
|
1066
|
+
},
|
|
1067
|
+
},
|
|
1068
|
+
];
|
|
1069
|
+
|
|
1070
|
+
for (const kind of CHART_KINDS) {
|
|
1071
|
+
test(`on ${kind.name}, a press at a row's edge whose pointer drifts 2px onto the next row paints no band and renders nothing, and its release there is a plain click`, () => {
|
|
1072
|
+
const select: MockFunction = getJestMockFunction();
|
|
1073
|
+
const reset: MockFunction = getJestMockFunction();
|
|
1074
|
+
const chart: ChartHarness = renderChart(kind.setup(select, reset));
|
|
1075
|
+
const pressX: number = clientXOf(at(2));
|
|
1076
|
+
const rendersBefore: number = chart.renders();
|
|
1077
|
+
|
|
1078
|
+
press(chart, at(2), { clientX: pressX });
|
|
1079
|
+
// The hand is never quite still: recharts reports the next row now.
|
|
1080
|
+
move(chart, at(3), 1, pressX + 2);
|
|
1081
|
+
|
|
1082
|
+
expect(band(chart)).toEqual(NO_BAND);
|
|
1083
|
+
|
|
1084
|
+
release(chart, at(3), { clientX: pressX + 2 });
|
|
1085
|
+
|
|
1086
|
+
expect(select).not.toHaveBeenCalled();
|
|
1087
|
+
expect(band(chart)).toEqual(NO_BAND);
|
|
1088
|
+
expect(chart.result.current.isClickSuppressed()).toBe(false);
|
|
1089
|
+
expect(chart.renders()).toBe(rendersBefore);
|
|
1090
|
+
|
|
1091
|
+
allTimers();
|
|
1092
|
+
|
|
1093
|
+
expect(select).not.toHaveBeenCalled();
|
|
1094
|
+
expect(reset).not.toHaveBeenCalled();
|
|
1095
|
+
});
|
|
1096
|
+
|
|
1097
|
+
test(`on ${kind.name}, new rows landing under a still press put another row under it: no band, and its release selects nothing`, () => {
|
|
1098
|
+
const select: MockFunction = getJestMockFunction();
|
|
1099
|
+
const reset: MockFunction = getJestMockFunction();
|
|
1100
|
+
const chart: ChartHarness = renderChart(kind.setup(select, reset));
|
|
1101
|
+
const pressX: number = clientXOf(at(4));
|
|
1102
|
+
|
|
1103
|
+
press(chart, at(4), { clientX: pressX });
|
|
1104
|
+
// The refetch lands mid-press, and the chart redraws its rows.
|
|
1105
|
+
chart.rerender({ ...kind.setup(select, reset), data: REFETCHED_ROWS });
|
|
1106
|
+
/*
|
|
1107
|
+
* The pointer has not moved from 10:04, the fifth old row; recharts
|
|
1108
|
+
* reports the row now under it, the ninth new one.
|
|
1109
|
+
*/
|
|
1110
|
+
move(chart, at(8, REFETCHED_ROWS), 1, pressX);
|
|
1111
|
+
|
|
1112
|
+
expect(band(chart)).toEqual(NO_BAND);
|
|
1113
|
+
|
|
1114
|
+
release(chart, at(8, REFETCHED_ROWS), { clientX: pressX });
|
|
1115
|
+
|
|
1116
|
+
expect(select).not.toHaveBeenCalled();
|
|
1117
|
+
expect(band(chart)).toEqual(NO_BAND);
|
|
1118
|
+
expect(chart.result.current.isClickSuppressed()).toBe(false);
|
|
1119
|
+
|
|
1120
|
+
allTimers();
|
|
1121
|
+
|
|
1122
|
+
expect(select).not.toHaveBeenCalled();
|
|
1123
|
+
expect(reset).not.toHaveBeenCalled();
|
|
1124
|
+
});
|
|
1125
|
+
}
|
|
1126
|
+
|
|
1127
|
+
// Pressed at the very edge of its row, so a few pixels reach the next one.
|
|
1128
|
+
const DIRECTIONS: Array<{
|
|
1129
|
+
name: string;
|
|
1130
|
+
pressedRow: number;
|
|
1131
|
+
otherRow: number;
|
|
1132
|
+
sign: number;
|
|
1133
|
+
}> = [
|
|
1134
|
+
{ name: "rightwards", pressedRow: 2, otherRow: 3, sign: 1 },
|
|
1135
|
+
{ name: "leftwards", pressedRow: 3, otherRow: 2, sign: -1 },
|
|
1136
|
+
];
|
|
1137
|
+
|
|
1138
|
+
for (const direction of DIRECTIONS) {
|
|
1139
|
+
test(`a pointer moved ${direction.name} by CHART_PRESS_STILLNESS_PX onto the next row has not moved; one pixel further, it is a drag across both rows`, () => {
|
|
1140
|
+
const select: MockFunction = getJestMockFunction();
|
|
1141
|
+
const chart: ChartHarness = renderChart({
|
|
1142
|
+
onTimeRangeSelect: select,
|
|
1143
|
+
onTimeRangeReset: getJestMockFunction(),
|
|
1144
|
+
});
|
|
1145
|
+
const pressX: number = clientXOf(at(direction.pressedRow));
|
|
1146
|
+
|
|
1147
|
+
press(chart, at(direction.pressedRow), { clientX: pressX });
|
|
1148
|
+
move(
|
|
1149
|
+
chart,
|
|
1150
|
+
at(direction.otherRow),
|
|
1151
|
+
1,
|
|
1152
|
+
pressX + direction.sign * CHART_PRESS_STILLNESS_PX,
|
|
1153
|
+
);
|
|
1154
|
+
|
|
1155
|
+
expect(band(chart)).toEqual(NO_BAND);
|
|
1156
|
+
|
|
1157
|
+
const draggedX: number =
|
|
1158
|
+
pressX + direction.sign * (CHART_PRESS_STILLNESS_PX + 1);
|
|
1159
|
+
move(chart, at(direction.otherRow), 1, draggedX);
|
|
1160
|
+
|
|
1161
|
+
expect(band(chart)).toEqual(["10:02:00", "10:03:00"]);
|
|
1162
|
+
|
|
1163
|
+
release(chart, at(direction.otherRow), { clientX: draggedX });
|
|
1164
|
+
|
|
1165
|
+
expect(selections(select)).toEqual([
|
|
1166
|
+
["2026-09-28T10:02:00.000Z", "2026-09-28T10:04:00.000Z"],
|
|
1167
|
+
]);
|
|
1168
|
+
});
|
|
1169
|
+
}
|
|
1170
|
+
|
|
1171
|
+
test("a real drag - the pointer crossing a row's width at a time - paints its band as it goes and selects exactly", () => {
|
|
1172
|
+
const select: MockFunction = getJestMockFunction();
|
|
1173
|
+
const chart: ChartHarness = renderChart({
|
|
1174
|
+
onTimeRangeSelect: select,
|
|
1175
|
+
onTimeRangeReset: getJestMockFunction(),
|
|
1176
|
+
});
|
|
1177
|
+
|
|
1178
|
+
// Rows sit ROW_WIDTH_PX (20px) apart: each move is a real one.
|
|
1179
|
+
press(chart, at(2));
|
|
1180
|
+
move(chart, at(3));
|
|
1181
|
+
|
|
1182
|
+
expect(band(chart)).toEqual(["10:02:00", "10:03:00"]);
|
|
1183
|
+
|
|
1184
|
+
move(chart, at(5));
|
|
1185
|
+
|
|
1186
|
+
expect(band(chart)).toEqual(["10:02:00", "10:05:00"]);
|
|
1187
|
+
|
|
1188
|
+
release(chart, at(5));
|
|
1189
|
+
|
|
1190
|
+
expect(selections(select)).toEqual([
|
|
1191
|
+
["2026-09-28T10:02:00.000Z", "2026-09-28T10:06:00.000Z"],
|
|
1192
|
+
]);
|
|
1193
|
+
expect(band(chart)).toEqual(NO_BAND);
|
|
1194
|
+
expect(chart.result.current.isClickSuppressed()).toBe(true);
|
|
1195
|
+
|
|
1196
|
+
nextTask();
|
|
1197
|
+
|
|
1198
|
+
expect(chart.result.current.isClickSuppressed()).toBe(false);
|
|
1199
|
+
});
|
|
1200
|
+
|
|
1201
|
+
test("a flick - let go well away, over another row, before any move came in - selects from the one to the other", () => {
|
|
1202
|
+
const select: MockFunction = getJestMockFunction();
|
|
1203
|
+
const chart: ChartHarness = renderChart({
|
|
1204
|
+
onTimeRangeSelect: select,
|
|
1205
|
+
onTimeRangeReset: getJestMockFunction(),
|
|
1206
|
+
});
|
|
1207
|
+
|
|
1208
|
+
press(chart, at(2));
|
|
1209
|
+
release(chart, at(6));
|
|
1210
|
+
|
|
1211
|
+
expect(selections(select)).toEqual([
|
|
1212
|
+
["2026-09-28T10:02:00.000Z", "2026-09-28T10:07:00.000Z"],
|
|
1213
|
+
]);
|
|
1214
|
+
});
|
|
1215
|
+
|
|
1216
|
+
test("a caller that hands the handlers no mouse events drags by the change of row alone, as it always did", () => {
|
|
1217
|
+
const select: MockFunction = getJestMockFunction();
|
|
1218
|
+
const chart: ChartHarness = renderChart({
|
|
1219
|
+
onTimeRangeSelect: select,
|
|
1220
|
+
onTimeRangeReset: getJestMockFunction(),
|
|
1221
|
+
});
|
|
1222
|
+
|
|
1223
|
+
pressWithoutEvent(chart, at(2));
|
|
1224
|
+
moveWithoutEvent(chart, at(4));
|
|
1225
|
+
|
|
1226
|
+
expect(band(chart)).toEqual(["10:02:00", "10:04:00"]);
|
|
1227
|
+
|
|
1228
|
+
releaseWithoutEvent(chart, at(4));
|
|
1229
|
+
|
|
1230
|
+
expect(selections(select)).toEqual([
|
|
1231
|
+
["2026-09-28T10:02:00.000Z", "2026-09-28T10:05:00.000Z"],
|
|
1232
|
+
]);
|
|
1233
|
+
expect(band(chart)).toEqual(NO_BAND);
|
|
1234
|
+
});
|
|
1235
|
+
|
|
1236
|
+
test("a caller that hands no mouse events, releasing over another row with no move between, selects from the one to the other", () => {
|
|
1237
|
+
const select: MockFunction = getJestMockFunction();
|
|
1238
|
+
const chart: ChartHarness = renderChart({
|
|
1239
|
+
onTimeRangeSelect: select,
|
|
1240
|
+
onTimeRangeReset: getJestMockFunction(),
|
|
1241
|
+
});
|
|
1242
|
+
|
|
1243
|
+
pressWithoutEvent(chart, at(2));
|
|
1244
|
+
releaseWithoutEvent(chart, at(5));
|
|
1245
|
+
|
|
1246
|
+
expect(selections(select)).toEqual([
|
|
1247
|
+
["2026-09-28T10:02:00.000Z", "2026-09-28T10:06:00.000Z"],
|
|
1248
|
+
]);
|
|
1249
|
+
});
|
|
1250
|
+
});
|
|
1251
|
+
|
|
1252
|
+
/*
|
|
1253
|
+
* A release off the chart never reaches the chart; the page-wide listener
|
|
1254
|
+
* the press added hears it, and hands the hook the native event: where
|
|
1255
|
+
* the pointer is, and the browser's click count.
|
|
1256
|
+
*/
|
|
1257
|
+
describe("a release only the page hears", () => {
|
|
1258
|
+
test("a press let go where it went down - dragged straight off the chart, no move between - selects nothing, and the page stops listening", () => {
|
|
1259
|
+
const select: MockFunction = getJestMockFunction();
|
|
1260
|
+
const reset: MockFunction = getJestMockFunction();
|
|
1261
|
+
const chart: ChartHarness = renderChart({
|
|
1262
|
+
onTimeRangeSelect: select,
|
|
1263
|
+
onTimeRangeReset: reset,
|
|
1264
|
+
});
|
|
1265
|
+
const pageListeners: PageListenerSpies = spyOnPageListeners();
|
|
1266
|
+
const pressX: number = clientXOf(at(4));
|
|
1267
|
+
|
|
1268
|
+
press(chart, at(4), { clientX: pressX });
|
|
1269
|
+
fireEvent.mouseUp(window, { clientX: pressX, detail: 1, button: 0 });
|
|
1270
|
+
allTimers();
|
|
1271
|
+
|
|
1272
|
+
expect(select).not.toHaveBeenCalled();
|
|
1273
|
+
expect(reset).not.toHaveBeenCalled();
|
|
1274
|
+
expect(band(chart)).toEqual(NO_BAND);
|
|
1275
|
+
expect(chart.result.current.isClickSuppressed()).toBe(false);
|
|
1276
|
+
expectNoMouseUpListenerLeft(pageListeners);
|
|
1277
|
+
});
|
|
1278
|
+
|
|
1279
|
+
test("a real drag let go far off the chart still selects up to the last row it crossed", () => {
|
|
1280
|
+
const select: MockFunction = getJestMockFunction();
|
|
1281
|
+
const chart: ChartHarness = renderChart({
|
|
1282
|
+
onTimeRangeSelect: select,
|
|
1283
|
+
onTimeRangeReset: getJestMockFunction(),
|
|
1284
|
+
});
|
|
1285
|
+
|
|
1286
|
+
press(chart, at(2));
|
|
1287
|
+
move(chart, at(3));
|
|
1288
|
+
move(chart, at(5));
|
|
1289
|
+
fireEvent.mouseUp(window, {
|
|
1290
|
+
clientX: clientXOf(at(5)) + 400,
|
|
1291
|
+
detail: 1,
|
|
1292
|
+
button: 0,
|
|
1293
|
+
});
|
|
1294
|
+
|
|
1295
|
+
expect(selections(select)).toEqual([
|
|
1296
|
+
["2026-09-28T10:02:00.000Z", "2026-09-28T10:06:00.000Z"],
|
|
1297
|
+
]);
|
|
1298
|
+
expect(band(chart)).toEqual(NO_BAND);
|
|
1299
|
+
});
|
|
1300
|
+
|
|
1301
|
+
/*
|
|
1302
|
+
* The count is what tells a later click's release (1) - the reset
|
|
1303
|
+
* press's own was lost, the window having lost focus mid-press - from
|
|
1304
|
+
* the reset press's own (2). A listener that handed the hook no event
|
|
1305
|
+
* would reset for both.
|
|
1306
|
+
*/
|
|
1307
|
+
const PAGE_RELEASES: Array<{
|
|
1308
|
+
name: string;
|
|
1309
|
+
detail: number;
|
|
1310
|
+
resets: number;
|
|
1311
|
+
}> = [
|
|
1312
|
+
{
|
|
1313
|
+
name: "a later click's release, counted 1, ends the reset press and resets nothing",
|
|
1314
|
+
detail: 1,
|
|
1315
|
+
resets: 0,
|
|
1316
|
+
},
|
|
1317
|
+
{
|
|
1318
|
+
name: "the reset press's own release, counted 2, resets once",
|
|
1319
|
+
detail: 2,
|
|
1320
|
+
resets: 1,
|
|
1321
|
+
},
|
|
1322
|
+
];
|
|
1323
|
+
|
|
1324
|
+
for (const pageRelease of PAGE_RELEASES) {
|
|
1325
|
+
test(`the listener a reset press adds is handed the native MouseEvent: ${pageRelease.name}`, () => {
|
|
1326
|
+
const select: MockFunction = getJestMockFunction();
|
|
1327
|
+
const reset: MockFunction = getJestMockFunction();
|
|
1328
|
+
const chart: ChartHarness = renderChart({
|
|
1329
|
+
onTimeRangeSelect: select,
|
|
1330
|
+
onTimeRangeReset: reset,
|
|
1331
|
+
});
|
|
1332
|
+
const pageListeners: PageListenerSpies = spyOnPageListeners();
|
|
1333
|
+
|
|
1334
|
+
click(chart, at(4), 1);
|
|
1335
|
+
press(chart, at(4), { detail: 2 });
|
|
1336
|
+
const listener: (event: MouseEvent) => void =
|
|
1337
|
+
lastMouseUpListener(pageListeners);
|
|
1338
|
+
|
|
1339
|
+
act(() => {
|
|
1340
|
+
listener(
|
|
1341
|
+
new MouseEvent("mouseup", {
|
|
1342
|
+
detail: pageRelease.detail,
|
|
1343
|
+
button: 0,
|
|
1344
|
+
clientX: 300,
|
|
1345
|
+
}),
|
|
1346
|
+
);
|
|
1347
|
+
});
|
|
1348
|
+
|
|
1349
|
+
expectNoMouseUpListenerLeft(pageListeners);
|
|
1350
|
+
|
|
1351
|
+
nextTask();
|
|
1352
|
+
|
|
1353
|
+
expect(reset).toHaveBeenCalledTimes(pageRelease.resets);
|
|
1354
|
+
|
|
1355
|
+
allTimers();
|
|
1356
|
+
|
|
1357
|
+
expect(reset).toHaveBeenCalledTimes(pageRelease.resets);
|
|
1358
|
+
expect(select).not.toHaveBeenCalled();
|
|
1359
|
+
expect(band(chart)).toEqual(NO_BAND);
|
|
1360
|
+
});
|
|
1361
|
+
}
|
|
1362
|
+
});
|
|
1363
|
+
|
|
1364
|
+
describe("a chart that only resets", () => {
|
|
1365
|
+
for (const resetOnly of RESET_ONLY_CASES) {
|
|
1366
|
+
describe(resetOnly.name, () => {
|
|
1367
|
+
test("a single press starts nothing: no page-wide release listener, no selection", () => {
|
|
1368
|
+
const select: MockFunction = getJestMockFunction();
|
|
1369
|
+
const reset: MockFunction = getJestMockFunction();
|
|
1370
|
+
const chart: ChartHarness = renderChart(resetOnly.setup(select, reset));
|
|
1371
|
+
const pageListeners: PageListenerSpies = spyOnPageListeners();
|
|
1372
|
+
const rendersBefore: number = chart.renders();
|
|
1373
|
+
|
|
1374
|
+
press(chart, at(2), { detail: 1 });
|
|
1375
|
+
expect(mouseUpListeners(pageListeners.add.mock.calls)).toEqual([]);
|
|
1376
|
+
|
|
1377
|
+
release(chart, at(6), { detail: 1 });
|
|
1378
|
+
fireEvent.mouseUp(window);
|
|
1379
|
+
allTimers();
|
|
1380
|
+
|
|
1381
|
+
expect(select).not.toHaveBeenCalled();
|
|
1382
|
+
expect(reset).not.toHaveBeenCalled();
|
|
1383
|
+
expect(band(chart)).toEqual(NO_BAND);
|
|
1384
|
+
expect(chart.renders()).toBe(rendersBefore);
|
|
1385
|
+
});
|
|
1386
|
+
|
|
1387
|
+
test("a double-click whose dblclick never arrives resets once", () => {
|
|
1388
|
+
const select: MockFunction = getJestMockFunction();
|
|
1389
|
+
const reset: MockFunction = getJestMockFunction();
|
|
1390
|
+
const chart: ChartHarness = renderChart(resetOnly.setup(select, reset));
|
|
1391
|
+
|
|
1392
|
+
click(chart, at(5), 1);
|
|
1393
|
+
click(chart, at(5), 2);
|
|
1394
|
+
expect(reset).not.toHaveBeenCalled();
|
|
1395
|
+
|
|
1396
|
+
nextTask();
|
|
1397
|
+
expect(reset).toHaveBeenCalledTimes(1);
|
|
1398
|
+
allTimers();
|
|
1399
|
+
expect(reset).toHaveBeenCalledTimes(1);
|
|
1400
|
+
expect(select).not.toHaveBeenCalled();
|
|
1401
|
+
});
|
|
1402
|
+
|
|
1403
|
+
test("a double-click whose dblclick arrives resets exactly once", () => {
|
|
1404
|
+
const select: MockFunction = getJestMockFunction();
|
|
1405
|
+
const reset: MockFunction = getJestMockFunction();
|
|
1406
|
+
const chart: ChartHarness = renderChart(resetOnly.setup(select, reset));
|
|
1407
|
+
|
|
1408
|
+
click(chart, at(5), 1);
|
|
1409
|
+
click(chart, at(5), 2);
|
|
1410
|
+
doubleClick(chart);
|
|
1411
|
+
expect(reset).toHaveBeenCalledTimes(1);
|
|
1412
|
+
|
|
1413
|
+
allTimers();
|
|
1414
|
+
expect(reset).toHaveBeenCalledTimes(1);
|
|
1415
|
+
expect(select).not.toHaveBeenCalled();
|
|
1416
|
+
});
|
|
1417
|
+
});
|
|
1418
|
+
}
|
|
1419
|
+
});
|
|
1420
|
+
|
|
1421
|
+
describe("the reset is read when it happens", () => {
|
|
1422
|
+
test("the fallback runs the reset the chart handed in last, not the one it had at the press", () => {
|
|
1423
|
+
const select: MockFunction = getJestMockFunction();
|
|
1424
|
+
const resetAtThePress: MockFunction = getJestMockFunction();
|
|
1425
|
+
const resetNow: MockFunction = getJestMockFunction();
|
|
1426
|
+
const chart: ChartHarness = renderChart({
|
|
1427
|
+
onTimeRangeSelect: select,
|
|
1428
|
+
onTimeRangeReset: resetAtThePress,
|
|
1429
|
+
});
|
|
1430
|
+
|
|
1431
|
+
click(chart, at(4), 1);
|
|
1432
|
+
click(chart, at(4), 2);
|
|
1433
|
+
// The chart re-renders before the next task, with a new reset.
|
|
1434
|
+
chart.rerender({ onTimeRangeSelect: select, onTimeRangeReset: resetNow });
|
|
1435
|
+
nextTask();
|
|
1436
|
+
|
|
1437
|
+
expect(resetNow).toHaveBeenCalledTimes(1);
|
|
1438
|
+
expect(resetAtThePress).not.toHaveBeenCalled();
|
|
1439
|
+
});
|
|
1440
|
+
|
|
1441
|
+
test("a dblclick handler taken before a re-render runs the reset handed in last", () => {
|
|
1442
|
+
const select: MockFunction = getJestMockFunction();
|
|
1443
|
+
const resetBefore: MockFunction = getJestMockFunction();
|
|
1444
|
+
const resetNow: MockFunction = getJestMockFunction();
|
|
1445
|
+
const chart: ChartHarness = renderChart({
|
|
1446
|
+
onTimeRangeSelect: select,
|
|
1447
|
+
onTimeRangeReset: resetBefore,
|
|
1448
|
+
});
|
|
1449
|
+
const onDoubleClick: ChartRangeSelectionRootProps["onDoubleClick"] =
|
|
1450
|
+
chart.result.current.chartEventProps.onDoubleClick;
|
|
1451
|
+
expect(typeof onDoubleClick).toBe("function");
|
|
1452
|
+
|
|
1453
|
+
chart.rerender({ onTimeRangeSelect: select, onTimeRangeReset: resetNow });
|
|
1454
|
+
act(() => {
|
|
1455
|
+
onDoubleClick?.();
|
|
1456
|
+
});
|
|
1457
|
+
|
|
1458
|
+
expect(resetNow).toHaveBeenCalledTimes(1);
|
|
1459
|
+
expect(resetBefore).not.toHaveBeenCalled();
|
|
1460
|
+
});
|
|
1461
|
+
|
|
1462
|
+
test("a reset withdrawn before the next task - the zoom already undone - runs nothing", () => {
|
|
1463
|
+
const select: MockFunction = getJestMockFunction();
|
|
1464
|
+
const reset: MockFunction = getJestMockFunction();
|
|
1465
|
+
const chart: ChartHarness = renderChart({
|
|
1466
|
+
onTimeRangeSelect: select,
|
|
1467
|
+
onTimeRangeReset: reset,
|
|
1468
|
+
});
|
|
1469
|
+
|
|
1470
|
+
click(chart, at(4), 1);
|
|
1471
|
+
click(chart, at(4), 2);
|
|
1472
|
+
chart.rerender({ onTimeRangeSelect: select });
|
|
1473
|
+
allTimers();
|
|
1474
|
+
|
|
1475
|
+
expect(reset).not.toHaveBeenCalled();
|
|
1476
|
+
expect(select).not.toHaveBeenCalled();
|
|
1477
|
+
expect(keysOf(chart.result.current.chartEventProps)).not.toContain(
|
|
1478
|
+
"onDoubleClick",
|
|
1479
|
+
);
|
|
1480
|
+
});
|
|
1481
|
+
});
|