@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,1292 @@
|
|
|
1
|
+
import "@testing-library/jest-dom";
|
|
2
|
+
import {
|
|
3
|
+
afterEach,
|
|
4
|
+
beforeAll,
|
|
5
|
+
beforeEach,
|
|
6
|
+
describe,
|
|
7
|
+
expect,
|
|
8
|
+
jest,
|
|
9
|
+
test,
|
|
10
|
+
} from "@jest/globals";
|
|
11
|
+
import {
|
|
12
|
+
RenderResult,
|
|
13
|
+
act,
|
|
14
|
+
cleanup,
|
|
15
|
+
fireEvent,
|
|
16
|
+
render,
|
|
17
|
+
screen,
|
|
18
|
+
waitFor,
|
|
19
|
+
within,
|
|
20
|
+
} from "@testing-library/react";
|
|
21
|
+
import * as React from "react";
|
|
22
|
+
import getJestMockFunction, { MockFunction } from "../../MockType";
|
|
23
|
+
|
|
24
|
+
/*
|
|
25
|
+
* Issue #4105, review finding dash-1: a drag across a Value widget's
|
|
26
|
+
* sparkline crashed the whole dashboard when the zoomed line came back
|
|
27
|
+
* with fewer points.
|
|
28
|
+
*
|
|
29
|
+
* The line remembered the point under the pointer as an index into the
|
|
30
|
+
* points it was drawing. A drag always ends with the pointer resting on the
|
|
31
|
+
* line, the board reloads the widget for the dragged window, and a narrower
|
|
32
|
+
* window has fewer points: "Past 1 Hour" draws 60, ten minutes of it 11. The
|
|
33
|
+
* render that drew the new line read the remembered index past its end and
|
|
34
|
+
* threw, and the error boundary above replaced the whole page - on the
|
|
35
|
+
* Component Settings preview, taking unsaved edits with it. A refresh that
|
|
36
|
+
* lands one point fewer in the middle of a drag did the same through the
|
|
37
|
+
* drag's own ends.
|
|
38
|
+
*
|
|
39
|
+
* The first half drives the view directly and hands it the next line the
|
|
40
|
+
* way a host would; the second goes through the real widgets and the real
|
|
41
|
+
* zoom hooks both dashboard shells and the settings preview use, over a
|
|
42
|
+
* data layer that answers with one point per minute of whatever window it
|
|
43
|
+
* is asked for - so a zoom really does shrink the line.
|
|
44
|
+
*/
|
|
45
|
+
|
|
46
|
+
const MINUTE_MS: number = 60 * 1000;
|
|
47
|
+
const BOARD_START_MS: number = Date.UTC(2026, 8, 28, 10, 0, 0);
|
|
48
|
+
|
|
49
|
+
function at(minute: number): Date {
|
|
50
|
+
return new Date(BOARD_START_MS + minute * MINUTE_MS);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
const fetchResultsMock: MockFunction = getJestMockFunction();
|
|
54
|
+
const fetchTimeSeriesMock: MockFunction = getJestMockFunction();
|
|
55
|
+
|
|
56
|
+
/*
|
|
57
|
+
* The arrow wrappers are load bearing: jest.mock is hoisted above the
|
|
58
|
+
* compiled requires, so the mock variables above are still unassigned when
|
|
59
|
+
* the factory runs. Dereferencing them lazily, at call time, is what makes
|
|
60
|
+
* this work.
|
|
61
|
+
*/
|
|
62
|
+
jest.mock(
|
|
63
|
+
"../../../../App/FeatureSet/Dashboard/src/Components/Metrics/Utils/Metrics",
|
|
64
|
+
() => {
|
|
65
|
+
return {
|
|
66
|
+
__esModule: true,
|
|
67
|
+
default: {
|
|
68
|
+
fetchResults: (...args: Array<any>) => {
|
|
69
|
+
return fetchResultsMock(...args);
|
|
70
|
+
},
|
|
71
|
+
clearQueryTopNOverridesForScope: () => {
|
|
72
|
+
return undefined;
|
|
73
|
+
},
|
|
74
|
+
},
|
|
75
|
+
};
|
|
76
|
+
},
|
|
77
|
+
);
|
|
78
|
+
|
|
79
|
+
jest.mock(
|
|
80
|
+
"../../../../App/FeatureSet/Dashboard/src/Utils/DataSourceQuery",
|
|
81
|
+
() => {
|
|
82
|
+
return {
|
|
83
|
+
__esModule: true,
|
|
84
|
+
default: {
|
|
85
|
+
fetchTimeSeries: (...args: Array<any>) => {
|
|
86
|
+
return fetchTimeSeriesMock(...args);
|
|
87
|
+
},
|
|
88
|
+
},
|
|
89
|
+
};
|
|
90
|
+
},
|
|
91
|
+
);
|
|
92
|
+
|
|
93
|
+
// The settings form is not under test; the preview above it is.
|
|
94
|
+
jest.mock(
|
|
95
|
+
"../../../../App/FeatureSet/Dashboard/src/Components/Dashboard/Canvas/ArgumentsForm",
|
|
96
|
+
() => {
|
|
97
|
+
const react: typeof React = jest.requireActual("react") as typeof React;
|
|
98
|
+
return {
|
|
99
|
+
__esModule: true,
|
|
100
|
+
default: () => {
|
|
101
|
+
return react.createElement("div", { "data-testid": "arguments-form" });
|
|
102
|
+
},
|
|
103
|
+
};
|
|
104
|
+
},
|
|
105
|
+
);
|
|
106
|
+
|
|
107
|
+
import ValueWidgetView, {
|
|
108
|
+
SPARKLINE_SELECTION_TEST_ID,
|
|
109
|
+
SPARKLINE_TEST_ID,
|
|
110
|
+
Sparkline,
|
|
111
|
+
SparklinePoint,
|
|
112
|
+
ValueWidgetViewProps,
|
|
113
|
+
} from "../../../../App/FeatureSet/Dashboard/src/Components/Dashboard/Components/ValueWidgetView";
|
|
114
|
+
import DashboardValueComponentElement from "../../../../App/FeatureSet/Dashboard/src/Components/Dashboard/Components/DashboardValueComponent";
|
|
115
|
+
import DashboardDataSourceValueComponentElement from "../../../../App/FeatureSet/Dashboard/src/Components/Dashboard/Components/DashboardDataSourceValueComponent";
|
|
116
|
+
import { DashboardBaseComponentProps } from "../../../../App/FeatureSet/Dashboard/src/Components/Dashboard/Components/DashboardBaseComponent";
|
|
117
|
+
import DashboardCanvas from "../../../../App/FeatureSet/Dashboard/src/Components/Dashboard/Canvas/Index";
|
|
118
|
+
import InBetween from "../../../Types/BaseDatabase/InBetween";
|
|
119
|
+
import DashboardComponentType from "../../../Types/Dashboard/DashboardComponentType";
|
|
120
|
+
import DashboardDataSourceValueComponent, {
|
|
121
|
+
DataSourceValueReduce,
|
|
122
|
+
} from "../../../Types/Dashboard/DashboardComponents/DashboardDataSourceValueComponent";
|
|
123
|
+
import DashboardValueComponent from "../../../Types/Dashboard/DashboardComponents/DashboardValueComponent";
|
|
124
|
+
import DashboardViewConfig from "../../../Types/Dashboard/DashboardViewConfig";
|
|
125
|
+
import { ObjectType } from "../../../Types/JSON";
|
|
126
|
+
import MetricsAggregationType from "../../../Types/Metrics/MetricsAggregationType";
|
|
127
|
+
import MetricViewData from "../../../Types/Metrics/MetricViewData";
|
|
128
|
+
import ObjectID from "../../../Types/ObjectID";
|
|
129
|
+
import RangeStartAndEndDateTime from "../../../Types/Time/RangeStartAndEndDateTime";
|
|
130
|
+
import TimeRange from "../../../Types/Time/TimeRange";
|
|
131
|
+
import useDashboardTimeRangeZoom, {
|
|
132
|
+
DashboardTimeRangeZoom,
|
|
133
|
+
} from "../../../UI/Utils/UseDashboardTimeRangeZoom";
|
|
134
|
+
|
|
135
|
+
// What an error boundary above the widget shows once a render has thrown.
|
|
136
|
+
const CRASHED_TEST_ID: string = "widget-crashed";
|
|
137
|
+
|
|
138
|
+
interface CrashBoundaryProps {
|
|
139
|
+
children: React.ReactNode;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
interface CrashBoundaryState {
|
|
143
|
+
error: Error | null;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
/*
|
|
147
|
+
* Stands for the application's error boundary, which replaces the whole
|
|
148
|
+
* dashboard page with its fallback: a throw anywhere under it shows here.
|
|
149
|
+
*/
|
|
150
|
+
class CrashBoundary extends React.Component<
|
|
151
|
+
CrashBoundaryProps,
|
|
152
|
+
CrashBoundaryState
|
|
153
|
+
> {
|
|
154
|
+
public constructor(props: CrashBoundaryProps) {
|
|
155
|
+
super(props);
|
|
156
|
+
this.state = { error: null };
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
public static getDerivedStateFromError(error: Error): CrashBoundaryState {
|
|
160
|
+
return { error: error };
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
public override render(): React.ReactNode {
|
|
164
|
+
if (this.state.error) {
|
|
165
|
+
return (
|
|
166
|
+
<div data-testid={CRASHED_TEST_ID}>
|
|
167
|
+
{`${this.state.error.name}: ${this.state.error.message}`}
|
|
168
|
+
</div>
|
|
169
|
+
);
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
return this.props.children;
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
function crashed(): HTMLElement | null {
|
|
177
|
+
return screen.queryByTestId(CRASHED_TEST_ID);
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
/*
|
|
181
|
+
* `count` points a minute apart from `firstMinute`, each worth 100 plus its
|
|
182
|
+
* minute: every point reads differently, and differently from any
|
|
183
|
+
* aggregate the tests hand the view.
|
|
184
|
+
*/
|
|
185
|
+
function minutePoints(
|
|
186
|
+
firstMinute: number,
|
|
187
|
+
count: number,
|
|
188
|
+
): Array<SparklinePoint> {
|
|
189
|
+
const points: Array<SparklinePoint> = [];
|
|
190
|
+
|
|
191
|
+
for (
|
|
192
|
+
let minute: number = firstMinute;
|
|
193
|
+
minute < firstMinute + count;
|
|
194
|
+
minute++
|
|
195
|
+
) {
|
|
196
|
+
points.push({ timestamp: at(minute), value: 100 + minute });
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
return points;
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
/*
|
|
203
|
+
* jsdom lays nothing out, so the sparkline would read every pointer as
|
|
204
|
+
* being over its first point. Give it a real box, BOX_LEFT from the left
|
|
205
|
+
* and as wide as it draws, and hand back the pointer x over any point of
|
|
206
|
+
* the line as it is drawn right now.
|
|
207
|
+
*
|
|
208
|
+
* That x is rounded to a whole pixel: jsdom truncates a mouse event's
|
|
209
|
+
* clientX, and an hour of points on a 120px line sits under 2px apart, so
|
|
210
|
+
* a truncated x can land nearer the point before.
|
|
211
|
+
*/
|
|
212
|
+
const BOX_LEFT: number = 100;
|
|
213
|
+
const PADDING: number = 4;
|
|
214
|
+
|
|
215
|
+
interface LaidOutSparkline {
|
|
216
|
+
svg: SVGSVGElement;
|
|
217
|
+
xOf: (index: number) => number;
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
function sparklineIn(container: HTMLElement | null): SVGSVGElement {
|
|
221
|
+
return (container ? within(container) : screen).getByTestId(
|
|
222
|
+
SPARKLINE_TEST_ID,
|
|
223
|
+
) as unknown as SVGSVGElement;
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
function pointCountOf(svg: SVGSVGElement): number {
|
|
227
|
+
return (svg.querySelector("polyline")!.getAttribute("points") || "")
|
|
228
|
+
.trim()
|
|
229
|
+
.split(" ").length;
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
function layOut(container: HTMLElement | null = null): LaidOutSparkline {
|
|
233
|
+
const svg: SVGSVGElement = sparklineIn(container);
|
|
234
|
+
const width: number = Number(svg.getAttribute("width"));
|
|
235
|
+
|
|
236
|
+
svg.getBoundingClientRect = (): DOMRect => {
|
|
237
|
+
return {
|
|
238
|
+
left: BOX_LEFT,
|
|
239
|
+
top: 0,
|
|
240
|
+
right: BOX_LEFT + width,
|
|
241
|
+
bottom: 20,
|
|
242
|
+
width: width,
|
|
243
|
+
height: 20,
|
|
244
|
+
x: BOX_LEFT,
|
|
245
|
+
y: 0,
|
|
246
|
+
toJSON: (): string => {
|
|
247
|
+
return "";
|
|
248
|
+
},
|
|
249
|
+
} as DOMRect;
|
|
250
|
+
};
|
|
251
|
+
|
|
252
|
+
return {
|
|
253
|
+
svg: svg,
|
|
254
|
+
xOf: (index: number): number => {
|
|
255
|
+
const lastIndex: number = pointCountOf(svg) - 1;
|
|
256
|
+
return Math.round(
|
|
257
|
+
BOX_LEFT + PADDING + (index / lastIndex) * (width - PADDING * 2),
|
|
258
|
+
);
|
|
259
|
+
},
|
|
260
|
+
};
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
// The hover marker: the ring the line draws on the point it names.
|
|
264
|
+
function hoverMarkerIn(svg: SVGSVGElement): Element | null {
|
|
265
|
+
return svg.querySelector("circle");
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
function hoverOver(sparkline: LaidOutSparkline, index: number): void {
|
|
269
|
+
fireEvent.mouseMove(sparkline.svg, {
|
|
270
|
+
clientX: sparkline.xOf(index),
|
|
271
|
+
buttons: 0,
|
|
272
|
+
});
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
function pressOn(sparkline: LaidOutSparkline, index: number): void {
|
|
276
|
+
fireEvent.mouseDown(sparkline.svg, {
|
|
277
|
+
clientX: sparkline.xOf(index),
|
|
278
|
+
button: 0,
|
|
279
|
+
});
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
function dragOnTo(sparkline: LaidOutSparkline, index: number): void {
|
|
283
|
+
fireEvent.mouseMove(sparkline.svg, {
|
|
284
|
+
clientX: sparkline.xOf(index),
|
|
285
|
+
buttons: 1,
|
|
286
|
+
});
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
function releaseOn(sparkline: LaidOutSparkline, index: number): void {
|
|
290
|
+
fireEvent.mouseUp(sparkline.svg, { clientX: sparkline.xOf(index) });
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
/*
|
|
294
|
+
* A reader's drag: onto the line, press, across, let go - and the pointer
|
|
295
|
+
* stays where it was let go, on the line.
|
|
296
|
+
*/
|
|
297
|
+
function dragAcross(
|
|
298
|
+
sparkline: LaidOutSparkline,
|
|
299
|
+
fromIndex: number,
|
|
300
|
+
toIndex: number,
|
|
301
|
+
): void {
|
|
302
|
+
hoverOver(sparkline, fromIndex);
|
|
303
|
+
pressOn(sparkline, fromIndex);
|
|
304
|
+
dragOnTo(sparkline, toIndex);
|
|
305
|
+
releaseOn(sparkline, toIndex);
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
function windowsOf(mock: MockFunction): Array<[number, number]> {
|
|
309
|
+
return mock.mock.calls.map((call: Array<unknown>): [number, number] => {
|
|
310
|
+
return [(call[0] as Date).getTime(), (call[1] as Date).getTime()];
|
|
311
|
+
});
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
afterEach(() => {
|
|
315
|
+
cleanup();
|
|
316
|
+
});
|
|
317
|
+
|
|
318
|
+
/*
|
|
319
|
+
* What the line tells whoever draws the read-out: the index of the point
|
|
320
|
+
* under the pointer, and null as soon as that index names no point on the
|
|
321
|
+
* line as it is now. The view holds only that index, so these are the
|
|
322
|
+
* moments it must hear about.
|
|
323
|
+
*/
|
|
324
|
+
describe("the Sparkline's word to its host about the point under the pointer", () => {
|
|
325
|
+
let onHoverIndex: MockFunction;
|
|
326
|
+
let onSelect: MockFunction;
|
|
327
|
+
|
|
328
|
+
function line(points: Array<SparklinePoint>): React.ReactElement {
|
|
329
|
+
return (
|
|
330
|
+
<Sparkline
|
|
331
|
+
data={points}
|
|
332
|
+
width={120}
|
|
333
|
+
height={24}
|
|
334
|
+
color="#6366f1"
|
|
335
|
+
fillColor="rgba(99, 102, 241, 0.08)"
|
|
336
|
+
onHoverIndex={onHoverIndex as unknown as (index: number | null) => void}
|
|
337
|
+
onTimeRangeSelect={
|
|
338
|
+
onSelect as unknown as (startTime: Date, endTime: Date) => void
|
|
339
|
+
}
|
|
340
|
+
/>
|
|
341
|
+
);
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
// Everything the host has been told, in order.
|
|
345
|
+
function told(): Array<number | null> {
|
|
346
|
+
return onHoverIndex.mock.calls.map(
|
|
347
|
+
(call: Array<unknown>): number | null => {
|
|
348
|
+
return call[0] as number | null;
|
|
349
|
+
},
|
|
350
|
+
);
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
beforeEach(() => {
|
|
354
|
+
onHoverIndex = getJestMockFunction();
|
|
355
|
+
onSelect = getJestMockFunction();
|
|
356
|
+
});
|
|
357
|
+
|
|
358
|
+
test("a move names the point under the pointer by its index in the data", () => {
|
|
359
|
+
render(line(minutePoints(0, 6)));
|
|
360
|
+
|
|
361
|
+
hoverOver(layOut(), 4);
|
|
362
|
+
|
|
363
|
+
expect(told()).toEqual([4]);
|
|
364
|
+
});
|
|
365
|
+
|
|
366
|
+
test("a drag that zooms lets go of the hover before it hands the window up", () => {
|
|
367
|
+
render(line(minutePoints(0, 6)));
|
|
368
|
+
|
|
369
|
+
dragAcross(layOut(), 1, 3);
|
|
370
|
+
|
|
371
|
+
expect(windowsOf(onSelect)).toEqual([[at(1).getTime(), at(4).getTime()]]);
|
|
372
|
+
expect(told()[told().length - 1]).toBeNull();
|
|
373
|
+
/*
|
|
374
|
+
* Told first, so nothing the zoom renders on its way in can read the
|
|
375
|
+
* point the pointer was over in the window being left.
|
|
376
|
+
*/
|
|
377
|
+
const hoverCalls: Array<number> = onHoverIndex.mock.invocationCallOrder;
|
|
378
|
+
expect(hoverCalls[hoverCalls.length - 1]!).toBeLessThan(
|
|
379
|
+
onSelect.mock.invocationCallOrder[0]!,
|
|
380
|
+
);
|
|
381
|
+
});
|
|
382
|
+
|
|
383
|
+
test("a press and release on one point zooms nothing, so the hover stands", () => {
|
|
384
|
+
render(line(minutePoints(0, 6)));
|
|
385
|
+
const sparkline: LaidOutSparkline = layOut();
|
|
386
|
+
|
|
387
|
+
hoverOver(sparkline, 2);
|
|
388
|
+
pressOn(sparkline, 2);
|
|
389
|
+
releaseOn(sparkline, 2);
|
|
390
|
+
|
|
391
|
+
expect(onSelect).not.toHaveBeenCalled();
|
|
392
|
+
// Never told to let go: the read-out still shows the point.
|
|
393
|
+
expect(told()).toEqual([2]);
|
|
394
|
+
expect(hoverMarkerIn(sparkline.svg)).not.toBeNull();
|
|
395
|
+
});
|
|
396
|
+
|
|
397
|
+
test("redrawn with as many points the hover stands; with a different number it is let go of, once", () => {
|
|
398
|
+
const rendered: RenderResult = render(line(minutePoints(0, 6)));
|
|
399
|
+
hoverOver(layOut(), 2);
|
|
400
|
+
|
|
401
|
+
// A rolling window moved on a minute: six points again.
|
|
402
|
+
rendered.rerender(line(minutePoints(1, 6)));
|
|
403
|
+
expect(told()).toEqual([2]);
|
|
404
|
+
expect(hoverMarkerIn(sparklineIn(null))).not.toBeNull();
|
|
405
|
+
|
|
406
|
+
rendered.rerender(line(minutePoints(0, 4)));
|
|
407
|
+
expect(told()).toEqual([2, null]);
|
|
408
|
+
expect(hoverMarkerIn(sparklineIn(null))).toBeNull();
|
|
409
|
+
|
|
410
|
+
// Nothing more to say while the count holds.
|
|
411
|
+
rendered.rerender(line(minutePoints(2, 4)));
|
|
412
|
+
expect(told()).toEqual([2, null]);
|
|
413
|
+
});
|
|
414
|
+
|
|
415
|
+
test("a line cut to one point draws nothing, and lets go of the hover", () => {
|
|
416
|
+
const rendered: RenderResult = render(line(minutePoints(0, 6)));
|
|
417
|
+
hoverOver(layOut(), 5);
|
|
418
|
+
|
|
419
|
+
rendered.rerender(line(minutePoints(0, 1)));
|
|
420
|
+
|
|
421
|
+
expect(told()).toEqual([5, null]);
|
|
422
|
+
expect(screen.queryByTestId(SPARKLINE_TEST_ID)).toBeNull();
|
|
423
|
+
});
|
|
424
|
+
|
|
425
|
+
test("taken away while hovered, it lets go of the hover", () => {
|
|
426
|
+
const rendered: RenderResult = render(line(minutePoints(0, 6)));
|
|
427
|
+
hoverOver(layOut(), 3);
|
|
428
|
+
|
|
429
|
+
rendered.unmount();
|
|
430
|
+
|
|
431
|
+
expect(told()).toEqual([3, null]);
|
|
432
|
+
});
|
|
433
|
+
});
|
|
434
|
+
|
|
435
|
+
describe("the sparkline under a line that changes beneath the pointer", () => {
|
|
436
|
+
/*
|
|
437
|
+
* Tall enough that the layout admits the sparkline: it only does once the
|
|
438
|
+
* number is already at its largest size.
|
|
439
|
+
*/
|
|
440
|
+
const baseViewProps: ValueWidgetViewProps = {
|
|
441
|
+
widthInPx: 400,
|
|
442
|
+
heightInPx: 320,
|
|
443
|
+
isEditMode: false,
|
|
444
|
+
isLoading: false,
|
|
445
|
+
hasEverLoaded: true,
|
|
446
|
+
error: null,
|
|
447
|
+
value: 42,
|
|
448
|
+
points: minutePoints(0, 60),
|
|
449
|
+
noDataMessage: "No data for the selected time range",
|
|
450
|
+
isConfigured: true,
|
|
451
|
+
setupTitle: "Value",
|
|
452
|
+
setupMessage: "Click to configure",
|
|
453
|
+
title: "Requests",
|
|
454
|
+
rawUnit: "",
|
|
455
|
+
metricName: "",
|
|
456
|
+
hideUnit: false,
|
|
457
|
+
warningThreshold: undefined,
|
|
458
|
+
criticalThreshold: undefined,
|
|
459
|
+
trendDirection: undefined,
|
|
460
|
+
};
|
|
461
|
+
|
|
462
|
+
let onSelect: MockFunction;
|
|
463
|
+
let rendered: RenderResult;
|
|
464
|
+
|
|
465
|
+
function view(overrides: Partial<ValueWidgetViewProps>): React.ReactElement {
|
|
466
|
+
return (
|
|
467
|
+
<CrashBoundary>
|
|
468
|
+
<ValueWidgetView
|
|
469
|
+
{...baseViewProps}
|
|
470
|
+
onTimeRangeSelect={
|
|
471
|
+
onSelect as unknown as (startTime: Date, endTime: Date) => void
|
|
472
|
+
}
|
|
473
|
+
{...overrides}
|
|
474
|
+
/>
|
|
475
|
+
</CrashBoundary>
|
|
476
|
+
);
|
|
477
|
+
}
|
|
478
|
+
|
|
479
|
+
function renderView(overrides: Partial<ValueWidgetViewProps> = {}): void {
|
|
480
|
+
rendered = render(view(overrides));
|
|
481
|
+
}
|
|
482
|
+
|
|
483
|
+
// What the host hands down next: a zoom or a refresh landed.
|
|
484
|
+
function handDown(overrides: Partial<ValueWidgetViewProps>): void {
|
|
485
|
+
rendered.rerender(view(overrides));
|
|
486
|
+
}
|
|
487
|
+
|
|
488
|
+
beforeEach(() => {
|
|
489
|
+
onSelect = getJestMockFunction();
|
|
490
|
+
});
|
|
491
|
+
|
|
492
|
+
describe("a zoom that lands fewer points", () => {
|
|
493
|
+
test("the drag lets go of the hover as it zooms, and the zoomed line draws without throwing", () => {
|
|
494
|
+
renderView();
|
|
495
|
+
const sparkline: LaidOutSparkline = layOut();
|
|
496
|
+
|
|
497
|
+
hoverOver(sparkline, 20);
|
|
498
|
+
pressOn(sparkline, 20);
|
|
499
|
+
dragOnTo(sparkline, 30);
|
|
500
|
+
// While dragging, the read-out names the point the window ends on.
|
|
501
|
+
expect(screen.getByText("130")).toBeInTheDocument();
|
|
502
|
+
|
|
503
|
+
releaseOn(sparkline, 30);
|
|
504
|
+
|
|
505
|
+
expect(windowsOf(onSelect)).toEqual([
|
|
506
|
+
[at(20).getTime(), at(31).getTime()],
|
|
507
|
+
]);
|
|
508
|
+
/*
|
|
509
|
+
* The pointer has not moved, but the point it was naming belongs to
|
|
510
|
+
* the window being left: the aggregate is back and nothing is marked.
|
|
511
|
+
*/
|
|
512
|
+
expect(screen.getByText("42")).toBeInTheDocument();
|
|
513
|
+
expect(screen.queryByText("130")).toBeNull();
|
|
514
|
+
expect(hoverMarkerIn(sparkline.svg)).toBeNull();
|
|
515
|
+
|
|
516
|
+
// The board hands down the zoomed window: 10:20 to 10:30.
|
|
517
|
+
handDown({ points: minutePoints(20, 11), value: 7 });
|
|
518
|
+
|
|
519
|
+
expect(crashed()).toBeNull();
|
|
520
|
+
expect(pointCountOf(sparklineIn(null))).toBe(11);
|
|
521
|
+
expect(hoverMarkerIn(sparklineIn(null))).toBeNull();
|
|
522
|
+
expect(screen.getByText("7")).toBeInTheDocument();
|
|
523
|
+
});
|
|
524
|
+
|
|
525
|
+
test("a pointer moved while the zoom loads names a point of the old line, and the new line lets go of it", () => {
|
|
526
|
+
renderView();
|
|
527
|
+
const sparkline: LaidOutSparkline = layOut();
|
|
528
|
+
dragAcross(sparkline, 20, 30);
|
|
529
|
+
|
|
530
|
+
// The zoomed window is still loading; the old line is still drawn.
|
|
531
|
+
hoverOver(sparkline, 33);
|
|
532
|
+
expect(screen.getByText("133")).toBeInTheDocument();
|
|
533
|
+
expect(hoverMarkerIn(sparkline.svg)).not.toBeNull();
|
|
534
|
+
|
|
535
|
+
handDown({ points: minutePoints(20, 11), value: 7 });
|
|
536
|
+
|
|
537
|
+
expect(crashed()).toBeNull();
|
|
538
|
+
expect(pointCountOf(sparklineIn(null))).toBe(11);
|
|
539
|
+
expect(hoverMarkerIn(sparklineIn(null))).toBeNull();
|
|
540
|
+
expect(screen.getByText("7")).toBeInTheDocument();
|
|
541
|
+
expect(screen.queryByText("133")).toBeNull();
|
|
542
|
+
});
|
|
543
|
+
|
|
544
|
+
test("the hover it let go of stays gone when a reset brings the longer line back", () => {
|
|
545
|
+
renderView();
|
|
546
|
+
const sparkline: LaidOutSparkline = layOut();
|
|
547
|
+
dragAcross(sparkline, 20, 30);
|
|
548
|
+
hoverOver(sparkline, 33);
|
|
549
|
+
handDown({ points: minutePoints(20, 11), value: 7 });
|
|
550
|
+
|
|
551
|
+
/*
|
|
552
|
+
* The pointer never moved. The place it was over is a place on the
|
|
553
|
+
* old line again, but nothing has told the line the pointer is there.
|
|
554
|
+
*/
|
|
555
|
+
handDown({ points: minutePoints(0, 60), value: 42 });
|
|
556
|
+
|
|
557
|
+
expect(crashed()).toBeNull();
|
|
558
|
+
expect(hoverMarkerIn(sparklineIn(null))).toBeNull();
|
|
559
|
+
expect(screen.getByText("42")).toBeInTheDocument();
|
|
560
|
+
expect(screen.queryByText("133")).toBeNull();
|
|
561
|
+
});
|
|
562
|
+
|
|
563
|
+
test("a right-to-left drag, released past the end of the zoomed line, draws it too", () => {
|
|
564
|
+
renderView();
|
|
565
|
+
const sparkline: LaidOutSparkline = layOut();
|
|
566
|
+
|
|
567
|
+
dragAcross(sparkline, 50, 30);
|
|
568
|
+
hoverOver(sparkline, 45);
|
|
569
|
+
|
|
570
|
+
expect(windowsOf(onSelect)).toEqual([
|
|
571
|
+
[at(30).getTime(), at(51).getTime()],
|
|
572
|
+
]);
|
|
573
|
+
|
|
574
|
+
handDown({ points: minutePoints(30, 21), value: 9 });
|
|
575
|
+
|
|
576
|
+
expect(crashed()).toBeNull();
|
|
577
|
+
expect(pointCountOf(sparklineIn(null))).toBe(21);
|
|
578
|
+
expect(hoverMarkerIn(sparklineIn(null))).toBeNull();
|
|
579
|
+
expect(screen.getByText("9")).toBeInTheDocument();
|
|
580
|
+
});
|
|
581
|
+
|
|
582
|
+
test("a plain click is no zoom, so the hover stays on the point", () => {
|
|
583
|
+
renderView();
|
|
584
|
+
const sparkline: LaidOutSparkline = layOut();
|
|
585
|
+
|
|
586
|
+
hoverOver(sparkline, 12);
|
|
587
|
+
pressOn(sparkline, 12);
|
|
588
|
+
releaseOn(sparkline, 12);
|
|
589
|
+
|
|
590
|
+
expect(onSelect).not.toHaveBeenCalled();
|
|
591
|
+
expect(screen.getByText("112")).toBeInTheDocument();
|
|
592
|
+
expect(hoverMarkerIn(sparkline.svg)).not.toBeNull();
|
|
593
|
+
});
|
|
594
|
+
});
|
|
595
|
+
|
|
596
|
+
describe("a refresh that lands under a resting pointer", () => {
|
|
597
|
+
test("with as many points as before, the hover stays and reads the point now at that place", () => {
|
|
598
|
+
renderView({ points: minutePoints(0, 6) });
|
|
599
|
+
const sparkline: LaidOutSparkline = layOut();
|
|
600
|
+
|
|
601
|
+
hoverOver(sparkline, 3);
|
|
602
|
+
expect(screen.getByText("103")).toBeInTheDocument();
|
|
603
|
+
|
|
604
|
+
// The same six minutes, re-measured: every value moved.
|
|
605
|
+
handDown({
|
|
606
|
+
points: minutePoints(0, 6).map(
|
|
607
|
+
(point: SparklinePoint): SparklinePoint => {
|
|
608
|
+
return { timestamp: point.timestamp, value: point.value + 50 };
|
|
609
|
+
},
|
|
610
|
+
),
|
|
611
|
+
});
|
|
612
|
+
|
|
613
|
+
expect(crashed()).toBeNull();
|
|
614
|
+
expect(hoverMarkerIn(sparklineIn(null))).not.toBeNull();
|
|
615
|
+
// The point under the pointer as it reads now, not as it read before.
|
|
616
|
+
expect(screen.getByText("153")).toBeInTheDocument();
|
|
617
|
+
expect(screen.queryByText("103")).toBeNull();
|
|
618
|
+
});
|
|
619
|
+
|
|
620
|
+
test("with a different number of points, the pointer is over another place, so the hover is let go of", () => {
|
|
621
|
+
renderView({ points: minutePoints(0, 6) });
|
|
622
|
+
const sparkline: LaidOutSparkline = layOut();
|
|
623
|
+
|
|
624
|
+
hoverOver(sparkline, 2);
|
|
625
|
+
expect(screen.getByText("102")).toBeInTheDocument();
|
|
626
|
+
|
|
627
|
+
// A seventh minute came in: every point sits somewhere else now.
|
|
628
|
+
handDown({ points: minutePoints(0, 7) });
|
|
629
|
+
|
|
630
|
+
expect(crashed()).toBeNull();
|
|
631
|
+
expect(hoverMarkerIn(sparklineIn(null))).toBeNull();
|
|
632
|
+
expect(screen.getByText("42")).toBeInTheDocument();
|
|
633
|
+
expect(screen.queryByText("102")).toBeNull();
|
|
634
|
+
});
|
|
635
|
+
|
|
636
|
+
test("a line that goes away takes the read-out with it, so it cannot come back stale", () => {
|
|
637
|
+
renderView({ points: minutePoints(0, 6) });
|
|
638
|
+
const sparkline: LaidOutSparkline = layOut();
|
|
639
|
+
hoverOver(sparkline, 2);
|
|
640
|
+
expect(screen.getByText("102")).toBeInTheDocument();
|
|
641
|
+
|
|
642
|
+
// A zoom into a quiet stretch: nothing to draw, nothing to hover.
|
|
643
|
+
handDown({ points: [], value: null });
|
|
644
|
+
expect(
|
|
645
|
+
screen.getByText("No data for the selected time range"),
|
|
646
|
+
).toBeInTheDocument();
|
|
647
|
+
|
|
648
|
+
// And back out: the same line, with no pointer on it.
|
|
649
|
+
handDown({ points: minutePoints(0, 6), value: 42 });
|
|
650
|
+
|
|
651
|
+
expect(crashed()).toBeNull();
|
|
652
|
+
expect(screen.getByText("42")).toBeInTheDocument();
|
|
653
|
+
expect(screen.queryByText("102")).toBeNull();
|
|
654
|
+
expect(hoverMarkerIn(sparklineIn(null))).toBeNull();
|
|
655
|
+
});
|
|
656
|
+
});
|
|
657
|
+
|
|
658
|
+
describe("the empty state a zoom into a quiet stretch lands on", () => {
|
|
659
|
+
const NO_DATA: string = "No data for the selected time range";
|
|
660
|
+
|
|
661
|
+
// The box that takes the double-click: the message sits directly in it.
|
|
662
|
+
function emptyState(): HTMLElement {
|
|
663
|
+
return screen.getByText(NO_DATA).parentElement as HTMLElement;
|
|
664
|
+
}
|
|
665
|
+
|
|
666
|
+
test("while it takes the double-click that resets, the double-click selects none of its words", () => {
|
|
667
|
+
const onReset: MockFunction = getJestMockFunction();
|
|
668
|
+
renderView({
|
|
669
|
+
value: null,
|
|
670
|
+
points: [],
|
|
671
|
+
onTimeRangeReset: onReset as unknown as () => void,
|
|
672
|
+
});
|
|
673
|
+
|
|
674
|
+
expect(emptyState()).toHaveClass("select-none");
|
|
675
|
+
|
|
676
|
+
fireEvent.doubleClick(screen.getByText(NO_DATA));
|
|
677
|
+
expect(onReset).toHaveBeenCalledTimes(1);
|
|
678
|
+
});
|
|
679
|
+
|
|
680
|
+
test("with no zoom to undo, its words stay selectable", () => {
|
|
681
|
+
renderView({ value: null, points: [] });
|
|
682
|
+
|
|
683
|
+
expect(emptyState()).not.toHaveClass("select-none");
|
|
684
|
+
});
|
|
685
|
+
});
|
|
686
|
+
|
|
687
|
+
describe("a refresh that lands one point fewer in the middle of a drag", () => {
|
|
688
|
+
// Seven five-minute points, 10:00 to 10:30.
|
|
689
|
+
function fiveMinutePoints(
|
|
690
|
+
fromIndex: number,
|
|
691
|
+
toIndex: number,
|
|
692
|
+
): Array<SparklinePoint> {
|
|
693
|
+
const points: Array<SparklinePoint> = [];
|
|
694
|
+
|
|
695
|
+
for (let index: number = fromIndex; index <= toIndex; index++) {
|
|
696
|
+
points.push({ timestamp: at(index * 5), value: 10 + index });
|
|
697
|
+
}
|
|
698
|
+
|
|
699
|
+
return points;
|
|
700
|
+
}
|
|
701
|
+
|
|
702
|
+
// Where the line draws point `index` of `count`, as the band reads it.
|
|
703
|
+
function bandXOf(svg: SVGSVGElement, index: number, count: number): number {
|
|
704
|
+
const width: number = Number(svg.getAttribute("width"));
|
|
705
|
+
return PADDING + (index / (count - 1)) * (width - PADDING * 2);
|
|
706
|
+
}
|
|
707
|
+
|
|
708
|
+
function band(): Element | null {
|
|
709
|
+
return screen.queryByTestId(SPARKLINE_SELECTION_TEST_ID);
|
|
710
|
+
}
|
|
711
|
+
|
|
712
|
+
test("a drag onto the newest point keeps its band on the line and zooms what it still covers", () => {
|
|
713
|
+
renderView({ points: fiveMinutePoints(0, 6), value: 42 });
|
|
714
|
+
const sparkline: LaidOutSparkline = layOut();
|
|
715
|
+
|
|
716
|
+
pressOn(sparkline, 3);
|
|
717
|
+
dragOnTo(sparkline, 6);
|
|
718
|
+
expect(band()).not.toBeNull();
|
|
719
|
+
|
|
720
|
+
/*
|
|
721
|
+
* The rolling window moved on under the drag: the oldest bucket
|
|
722
|
+
* dropped out and the newest has no data yet.
|
|
723
|
+
*/
|
|
724
|
+
handDown({ points: fiveMinutePoints(1, 6), value: 42 });
|
|
725
|
+
|
|
726
|
+
expect(crashed()).toBeNull();
|
|
727
|
+
const svg: SVGSVGElement = sparklineIn(null);
|
|
728
|
+
expect(pointCountOf(svg)).toBe(6);
|
|
729
|
+
// The band runs from the drag's start to the newest point left.
|
|
730
|
+
expect(Number(band()!.getAttribute("x"))).toBeCloseTo(bandXOf(svg, 3, 6));
|
|
731
|
+
expect(Number(band()!.getAttribute("width"))).toBeCloseTo(
|
|
732
|
+
bandXOf(svg, 5, 6) - bandXOf(svg, 3, 6),
|
|
733
|
+
);
|
|
734
|
+
|
|
735
|
+
releaseOn(layOut(), 5);
|
|
736
|
+
|
|
737
|
+
// Places 3 to 5 of the line as it is now: 10:20 to the end of 10:30.
|
|
738
|
+
expect(windowsOf(onSelect)).toEqual([
|
|
739
|
+
[at(20).getTime(), at(35).getTime()],
|
|
740
|
+
]);
|
|
741
|
+
expect(band()).toBeNull();
|
|
742
|
+
});
|
|
743
|
+
|
|
744
|
+
test("a press on the newest point, before the pointer moves, survives the refresh", () => {
|
|
745
|
+
renderView({ points: fiveMinutePoints(0, 6), value: 42 });
|
|
746
|
+
const sparkline: LaidOutSparkline = layOut();
|
|
747
|
+
|
|
748
|
+
// No move before the press: the hover names nothing, only the drag does.
|
|
749
|
+
pressOn(sparkline, 6);
|
|
750
|
+
|
|
751
|
+
handDown({ points: fiveMinutePoints(1, 6), value: 42 });
|
|
752
|
+
|
|
753
|
+
expect(crashed()).toBeNull();
|
|
754
|
+
expect(band()).toBeNull();
|
|
755
|
+
|
|
756
|
+
const shrunk: LaidOutSparkline = layOut();
|
|
757
|
+
dragOnTo(shrunk, 2);
|
|
758
|
+
expect(band()).not.toBeNull();
|
|
759
|
+
releaseOn(shrunk, 2);
|
|
760
|
+
|
|
761
|
+
// From place 2 to the newest place the drag still reaches, 5.
|
|
762
|
+
expect(windowsOf(onSelect)).toEqual([
|
|
763
|
+
[at(15).getTime(), at(35).getTime()],
|
|
764
|
+
]);
|
|
765
|
+
});
|
|
766
|
+
|
|
767
|
+
test("a right-to-left drag that began on the newest point survives it too", () => {
|
|
768
|
+
renderView({ points: fiveMinutePoints(0, 6), value: 42 });
|
|
769
|
+
const sparkline: LaidOutSparkline = layOut();
|
|
770
|
+
|
|
771
|
+
pressOn(sparkline, 6);
|
|
772
|
+
dragOnTo(sparkline, 2);
|
|
773
|
+
|
|
774
|
+
handDown({ points: fiveMinutePoints(1, 6), value: 42 });
|
|
775
|
+
|
|
776
|
+
expect(crashed()).toBeNull();
|
|
777
|
+
const svg: SVGSVGElement = sparklineIn(null);
|
|
778
|
+
expect(Number(band()!.getAttribute("x"))).toBeCloseTo(bandXOf(svg, 2, 6));
|
|
779
|
+
expect(Number(band()!.getAttribute("width"))).toBeCloseTo(
|
|
780
|
+
bandXOf(svg, 5, 6) - bandXOf(svg, 2, 6),
|
|
781
|
+
);
|
|
782
|
+
|
|
783
|
+
releaseOn(layOut(), 2);
|
|
784
|
+
|
|
785
|
+
expect(windowsOf(onSelect)).toEqual([
|
|
786
|
+
[at(15).getTime(), at(35).getTime()],
|
|
787
|
+
]);
|
|
788
|
+
});
|
|
789
|
+
|
|
790
|
+
test("a drag whose points all dropped out zooms nothing", () => {
|
|
791
|
+
renderView({ points: fiveMinutePoints(0, 6), value: 42 });
|
|
792
|
+
const sparkline: LaidOutSparkline = layOut();
|
|
793
|
+
|
|
794
|
+
pressOn(sparkline, 5);
|
|
795
|
+
dragOnTo(sparkline, 6);
|
|
796
|
+
|
|
797
|
+
// A refresh that lost the whole end of the line.
|
|
798
|
+
handDown({ points: fiveMinutePoints(0, 3), value: 42 });
|
|
799
|
+
|
|
800
|
+
expect(crashed()).toBeNull();
|
|
801
|
+
expect(band()).toBeNull();
|
|
802
|
+
|
|
803
|
+
fireEvent.mouseUp(window);
|
|
804
|
+
|
|
805
|
+
expect(onSelect).not.toHaveBeenCalled();
|
|
806
|
+
});
|
|
807
|
+
});
|
|
808
|
+
});
|
|
809
|
+
|
|
810
|
+
/*
|
|
811
|
+
* Through the real widgets, on the real zoom hooks. The data layer answers
|
|
812
|
+
* with one point, worth 1, per minute of the window it is asked for: an
|
|
813
|
+
* hour is 60 points, and both widgets sum them, so the big number is the
|
|
814
|
+
* number of points on the line.
|
|
815
|
+
*/
|
|
816
|
+
|
|
817
|
+
const COMPONENT_ID: ObjectID = new ObjectID(
|
|
818
|
+
"5a5a5a5a-1111-4111-8111-5a5a5a5a5a5a",
|
|
819
|
+
);
|
|
820
|
+
|
|
821
|
+
const BOARD_RANGE: RangeStartAndEndDateTime = {
|
|
822
|
+
range: TimeRange.CUSTOM,
|
|
823
|
+
startAndEndDate: new InBetween<Date>(at(0), at(60)),
|
|
824
|
+
};
|
|
825
|
+
|
|
826
|
+
const DASHBOARD_VIEW_CONFIG: DashboardViewConfig = {
|
|
827
|
+
_type: ObjectType.DashboardViewConfig,
|
|
828
|
+
components: [],
|
|
829
|
+
heightInDashboardUnits: 60,
|
|
830
|
+
};
|
|
831
|
+
|
|
832
|
+
interface Point {
|
|
833
|
+
timestamp: Date;
|
|
834
|
+
value: number;
|
|
835
|
+
}
|
|
836
|
+
|
|
837
|
+
function onePerMinute(start: Date, end: Date): Array<Point> {
|
|
838
|
+
const points: Array<Point> = [];
|
|
839
|
+
|
|
840
|
+
for (let ms: number = start.getTime(); ms < end.getTime(); ms += MINUTE_MS) {
|
|
841
|
+
points.push({ timestamp: new Date(ms), value: 1 });
|
|
842
|
+
}
|
|
843
|
+
|
|
844
|
+
return points;
|
|
845
|
+
}
|
|
846
|
+
|
|
847
|
+
/*
|
|
848
|
+
* Holds every fetch made after it is called until the returned release
|
|
849
|
+
* runs: the zoomed window "still loading".
|
|
850
|
+
*/
|
|
851
|
+
let heldFetches: Promise<void> | null = null;
|
|
852
|
+
|
|
853
|
+
function holdFetches(): () => void {
|
|
854
|
+
let release: () => void = (): void => {
|
|
855
|
+
// Replaced below, synchronously, by the promise executor.
|
|
856
|
+
};
|
|
857
|
+
heldFetches = new Promise<void>((resolve: () => void) => {
|
|
858
|
+
release = resolve;
|
|
859
|
+
});
|
|
860
|
+
return (): void => {
|
|
861
|
+
heldFetches = null;
|
|
862
|
+
release();
|
|
863
|
+
};
|
|
864
|
+
}
|
|
865
|
+
|
|
866
|
+
// Drops the newest point of every answer from here on.
|
|
867
|
+
let dropNewestPoint: boolean = false;
|
|
868
|
+
|
|
869
|
+
async function answer(start: Date, end: Date): Promise<Array<Point>> {
|
|
870
|
+
const hold: Promise<void> | null = heldFetches;
|
|
871
|
+
const points: Array<Point> = onePerMinute(start, end);
|
|
872
|
+
|
|
873
|
+
if (hold) {
|
|
874
|
+
await hold;
|
|
875
|
+
}
|
|
876
|
+
|
|
877
|
+
return dropNewestPoint ? points.slice(0, -1) : points;
|
|
878
|
+
}
|
|
879
|
+
|
|
880
|
+
interface WidgetZoomProps {
|
|
881
|
+
dashboardStartAndEndDate: RangeStartAndEndDateTime;
|
|
882
|
+
refreshTick: number;
|
|
883
|
+
onDashboardTimeRangeSelect: (startTime: Date, endTime: Date) => void;
|
|
884
|
+
onDashboardTimeRangeReset: () => void;
|
|
885
|
+
isDashboardTimeRangeZoomed: boolean;
|
|
886
|
+
}
|
|
887
|
+
|
|
888
|
+
function hostProps(zoom: WidgetZoomProps): DashboardBaseComponentProps {
|
|
889
|
+
return {
|
|
890
|
+
componentId: COMPONENT_ID,
|
|
891
|
+
isEditMode: false,
|
|
892
|
+
isSelected: false,
|
|
893
|
+
key: "value-widget",
|
|
894
|
+
onComponentUpdate: (): void => {
|
|
895
|
+
// The widget never writes back through this.
|
|
896
|
+
},
|
|
897
|
+
totalCurrentDashboardWidthInPx: 1200,
|
|
898
|
+
dashboardCanvasTopInPx: 0,
|
|
899
|
+
dashboardCanvasLeftInPx: 0,
|
|
900
|
+
dashboardCanvasWidthInPx: 1200,
|
|
901
|
+
dashboardCanvasHeightInPx: 800,
|
|
902
|
+
dashboardComponentHeightInPx: 320,
|
|
903
|
+
dashboardComponentWidthInPx: 400,
|
|
904
|
+
dashboardViewConfig: DASHBOARD_VIEW_CONFIG,
|
|
905
|
+
metricTypes: [],
|
|
906
|
+
variables: undefined,
|
|
907
|
+
...zoom,
|
|
908
|
+
};
|
|
909
|
+
}
|
|
910
|
+
|
|
911
|
+
function buildValueComponent(): DashboardValueComponent {
|
|
912
|
+
return {
|
|
913
|
+
_type: ObjectType.DashboardComponent,
|
|
914
|
+
componentId: COMPONENT_ID,
|
|
915
|
+
componentType: DashboardComponentType.Value,
|
|
916
|
+
topInDashboardUnits: 0,
|
|
917
|
+
leftInDashboardUnits: 0,
|
|
918
|
+
widthInDashboardUnits: 3,
|
|
919
|
+
heightInDashboardUnits: 2,
|
|
920
|
+
minWidthInDashboardUnits: 1,
|
|
921
|
+
minHeightInDashboardUnits: 1,
|
|
922
|
+
arguments: {
|
|
923
|
+
title: "Requests",
|
|
924
|
+
metricQueryConfig: {
|
|
925
|
+
metricAliasData: { metricVariable: "a" },
|
|
926
|
+
metricQueryData: {
|
|
927
|
+
filterData: {
|
|
928
|
+
metricName: "http.requests",
|
|
929
|
+
aggegationType: MetricsAggregationType.Sum,
|
|
930
|
+
},
|
|
931
|
+
},
|
|
932
|
+
},
|
|
933
|
+
},
|
|
934
|
+
} as unknown as DashboardValueComponent;
|
|
935
|
+
}
|
|
936
|
+
|
|
937
|
+
function buildDataSourceValueComponent(): DashboardDataSourceValueComponent {
|
|
938
|
+
return {
|
|
939
|
+
_type: ObjectType.DashboardComponent,
|
|
940
|
+
componentId: COMPONENT_ID,
|
|
941
|
+
componentType: DashboardComponentType.DataSourceValue,
|
|
942
|
+
topInDashboardUnits: 0,
|
|
943
|
+
leftInDashboardUnits: 0,
|
|
944
|
+
widthInDashboardUnits: 3,
|
|
945
|
+
heightInDashboardUnits: 2,
|
|
946
|
+
minWidthInDashboardUnits: 1,
|
|
947
|
+
minHeightInDashboardUnits: 1,
|
|
948
|
+
arguments: {
|
|
949
|
+
title: "Queue depth",
|
|
950
|
+
reduce: DataSourceValueReduce.Sum,
|
|
951
|
+
query: {
|
|
952
|
+
id: "q1",
|
|
953
|
+
dataSourceId: "6b6b6b6b-1111-4111-8111-6b6b6b6b6b6b",
|
|
954
|
+
query: "sum(queue_depth)",
|
|
955
|
+
},
|
|
956
|
+
},
|
|
957
|
+
} as unknown as DashboardDataSourceValueComponent;
|
|
958
|
+
}
|
|
959
|
+
|
|
960
|
+
interface HostUnderTest {
|
|
961
|
+
name: string;
|
|
962
|
+
widget: (zoom: WidgetZoomProps) => React.ReactElement;
|
|
963
|
+
// Every window the widget has asked the data layer for, in order.
|
|
964
|
+
fetchedWindows: () => Array<[number, number]>;
|
|
965
|
+
}
|
|
966
|
+
|
|
967
|
+
const HOSTS: Array<HostUnderTest> = [
|
|
968
|
+
{
|
|
969
|
+
name: "metric Value widget",
|
|
970
|
+
widget: (zoom: WidgetZoomProps): React.ReactElement => {
|
|
971
|
+
return (
|
|
972
|
+
<DashboardValueComponentElement
|
|
973
|
+
{...hostProps(zoom)}
|
|
974
|
+
component={buildValueComponent()}
|
|
975
|
+
/>
|
|
976
|
+
);
|
|
977
|
+
},
|
|
978
|
+
fetchedWindows: (): Array<[number, number]> => {
|
|
979
|
+
return fetchResultsMock.mock.calls.map(
|
|
980
|
+
(call: Array<unknown>): [number, number] => {
|
|
981
|
+
const data: MetricViewData = (
|
|
982
|
+
call[0] as { metricViewData: MetricViewData }
|
|
983
|
+
).metricViewData;
|
|
984
|
+
return [
|
|
985
|
+
data.startAndEndDate!.startValue.getTime(),
|
|
986
|
+
data.startAndEndDate!.endValue.getTime(),
|
|
987
|
+
];
|
|
988
|
+
},
|
|
989
|
+
);
|
|
990
|
+
},
|
|
991
|
+
},
|
|
992
|
+
{
|
|
993
|
+
name: "Data Source Value widget",
|
|
994
|
+
widget: (zoom: WidgetZoomProps): React.ReactElement => {
|
|
995
|
+
return (
|
|
996
|
+
<DashboardDataSourceValueComponentElement
|
|
997
|
+
{...hostProps(zoom)}
|
|
998
|
+
component={buildDataSourceValueComponent()}
|
|
999
|
+
/>
|
|
1000
|
+
);
|
|
1001
|
+
},
|
|
1002
|
+
fetchedWindows: (): Array<[number, number]> => {
|
|
1003
|
+
return fetchTimeSeriesMock.mock.calls.map(
|
|
1004
|
+
(call: Array<unknown>): [number, number] => {
|
|
1005
|
+
const args: { startDate: Date; endDate: Date } = call[0] as {
|
|
1006
|
+
startDate: Date;
|
|
1007
|
+
endDate: Date;
|
|
1008
|
+
};
|
|
1009
|
+
return [args.startDate.getTime(), args.endDate.getTime()];
|
|
1010
|
+
},
|
|
1011
|
+
);
|
|
1012
|
+
},
|
|
1013
|
+
},
|
|
1014
|
+
];
|
|
1015
|
+
|
|
1016
|
+
interface BoardShellProps {
|
|
1017
|
+
host: HostUnderTest;
|
|
1018
|
+
refreshTick: number;
|
|
1019
|
+
}
|
|
1020
|
+
|
|
1021
|
+
/*
|
|
1022
|
+
* The board's range as both dashboard shells hold it (DashboardView and the
|
|
1023
|
+
* public DashboardViewPage): useDashboardTimeRangeZoom, its handlers handed
|
|
1024
|
+
* to the widget, the zoomed range handed back down.
|
|
1025
|
+
*/
|
|
1026
|
+
function BoardShell(props: BoardShellProps): React.ReactElement {
|
|
1027
|
+
const zoom: DashboardTimeRangeZoom = useDashboardTimeRangeZoom(BOARD_RANGE);
|
|
1028
|
+
|
|
1029
|
+
return props.host.widget({
|
|
1030
|
+
dashboardStartAndEndDate: zoom.startAndEndDate,
|
|
1031
|
+
refreshTick: props.refreshTick,
|
|
1032
|
+
onDashboardTimeRangeSelect: zoom.zoomToTimeRange,
|
|
1033
|
+
onDashboardTimeRangeReset: zoom.resetZoom,
|
|
1034
|
+
isDashboardTimeRangeZoomed: zoom.isZoomed,
|
|
1035
|
+
});
|
|
1036
|
+
}
|
|
1037
|
+
|
|
1038
|
+
function board(
|
|
1039
|
+
host: HostUnderTest,
|
|
1040
|
+
refreshTick: number = 0,
|
|
1041
|
+
): React.ReactElement {
|
|
1042
|
+
return (
|
|
1043
|
+
<CrashBoundary>
|
|
1044
|
+
<BoardShell host={host} refreshTick={refreshTick} />
|
|
1045
|
+
</CrashBoundary>
|
|
1046
|
+
);
|
|
1047
|
+
}
|
|
1048
|
+
|
|
1049
|
+
function bigNumber(text: string): HTMLElement | null {
|
|
1050
|
+
return screen.queryByText(text);
|
|
1051
|
+
}
|
|
1052
|
+
|
|
1053
|
+
// Waits for the line to be drawn with `count` points, and hands it back.
|
|
1054
|
+
async function lineOf(count: number): Promise<SVGSVGElement> {
|
|
1055
|
+
await waitFor(() => {
|
|
1056
|
+
expect(crashed()).toBeNull();
|
|
1057
|
+
expect(pointCountOf(sparklineIn(null))).toBe(count);
|
|
1058
|
+
});
|
|
1059
|
+
|
|
1060
|
+
return sparklineIn(null);
|
|
1061
|
+
}
|
|
1062
|
+
|
|
1063
|
+
beforeEach(() => {
|
|
1064
|
+
heldFetches = null;
|
|
1065
|
+
dropNewestPoint = false;
|
|
1066
|
+
fetchResultsMock.mockReset();
|
|
1067
|
+
fetchTimeSeriesMock.mockReset();
|
|
1068
|
+
fetchResultsMock.mockImplementation(async (...args: Array<unknown>) => {
|
|
1069
|
+
const data: MetricViewData = (args[0] as { metricViewData: MetricViewData })
|
|
1070
|
+
.metricViewData;
|
|
1071
|
+
return [
|
|
1072
|
+
{
|
|
1073
|
+
data: await answer(
|
|
1074
|
+
data.startAndEndDate!.startValue,
|
|
1075
|
+
data.startAndEndDate!.endValue,
|
|
1076
|
+
),
|
|
1077
|
+
},
|
|
1078
|
+
];
|
|
1079
|
+
});
|
|
1080
|
+
fetchTimeSeriesMock.mockImplementation(async (...args: Array<unknown>) => {
|
|
1081
|
+
const request: { startDate: Date; endDate: Date } = args[0] as {
|
|
1082
|
+
startDate: Date;
|
|
1083
|
+
endDate: Date;
|
|
1084
|
+
};
|
|
1085
|
+
return { data: await answer(request.startDate, request.endDate) };
|
|
1086
|
+
});
|
|
1087
|
+
});
|
|
1088
|
+
|
|
1089
|
+
describe.each(HOSTS)(
|
|
1090
|
+
"the $name on a board, zoomed from its own sparkline",
|
|
1091
|
+
(host: HostUnderTest) => {
|
|
1092
|
+
test("a drag, with the pointer left resting on the line, survives the zoomed line landing", async () => {
|
|
1093
|
+
render(board(host));
|
|
1094
|
+
await lineOf(60);
|
|
1095
|
+
expect(bigNumber("60")).not.toBeNull();
|
|
1096
|
+
|
|
1097
|
+
dragAcross(layOut(), 20, 30);
|
|
1098
|
+
|
|
1099
|
+
await waitFor(() => {
|
|
1100
|
+
const windows: Array<[number, number]> = host.fetchedWindows();
|
|
1101
|
+
expect(windows[windows.length - 1]).toEqual([
|
|
1102
|
+
at(20).getTime(),
|
|
1103
|
+
at(31).getTime(),
|
|
1104
|
+
]);
|
|
1105
|
+
});
|
|
1106
|
+
const zoomed: SVGSVGElement = await lineOf(11);
|
|
1107
|
+
|
|
1108
|
+
expect(crashed()).toBeNull();
|
|
1109
|
+
expect(hoverMarkerIn(zoomed)).toBeNull();
|
|
1110
|
+
// The zoomed window's sum, not a point left over from the hour.
|
|
1111
|
+
expect(bigNumber("11")).not.toBeNull();
|
|
1112
|
+
});
|
|
1113
|
+
|
|
1114
|
+
test("a pointer moved while the zoomed window loads is let go of when it lands", async () => {
|
|
1115
|
+
render(board(host));
|
|
1116
|
+
await lineOf(60);
|
|
1117
|
+
const sparkline: LaidOutSparkline = layOut();
|
|
1118
|
+
|
|
1119
|
+
const release: () => void = holdFetches();
|
|
1120
|
+
dragAcross(sparkline, 20, 30);
|
|
1121
|
+
await waitFor(() => {
|
|
1122
|
+
expect(host.fetchedWindows().length).toBe(2);
|
|
1123
|
+
});
|
|
1124
|
+
|
|
1125
|
+
// Still the hour on screen, dimmed: the reader nudges the pointer.
|
|
1126
|
+
hoverOver(sparkline, 33);
|
|
1127
|
+
expect(hoverMarkerIn(sparkline.svg)).not.toBeNull();
|
|
1128
|
+
expect(bigNumber("1")).not.toBeNull();
|
|
1129
|
+
|
|
1130
|
+
await act(async () => {
|
|
1131
|
+
release();
|
|
1132
|
+
});
|
|
1133
|
+
const zoomed: SVGSVGElement = await lineOf(11);
|
|
1134
|
+
|
|
1135
|
+
expect(crashed()).toBeNull();
|
|
1136
|
+
expect(hoverMarkerIn(zoomed)).toBeNull();
|
|
1137
|
+
expect(bigNumber("11")).not.toBeNull();
|
|
1138
|
+
});
|
|
1139
|
+
|
|
1140
|
+
test("a right-to-left drag survives the zoomed line landing", async () => {
|
|
1141
|
+
render(board(host));
|
|
1142
|
+
await lineOf(60);
|
|
1143
|
+
|
|
1144
|
+
dragAcross(layOut(), 50, 30);
|
|
1145
|
+
|
|
1146
|
+
const zoomed: SVGSVGElement = await lineOf(21);
|
|
1147
|
+
|
|
1148
|
+
expect(crashed()).toBeNull();
|
|
1149
|
+
expect(hoverMarkerIn(zoomed)).toBeNull();
|
|
1150
|
+
expect(bigNumber("21")).not.toBeNull();
|
|
1151
|
+
});
|
|
1152
|
+
|
|
1153
|
+
test("a zoom inside a zoom, and the reset out of both, never throw", async () => {
|
|
1154
|
+
render(board(host));
|
|
1155
|
+
await lineOf(60);
|
|
1156
|
+
|
|
1157
|
+
dragAcross(layOut(), 20, 40);
|
|
1158
|
+
await lineOf(21);
|
|
1159
|
+
|
|
1160
|
+
dragAcross(layOut(), 5, 15);
|
|
1161
|
+
await lineOf(11);
|
|
1162
|
+
expect(host.fetchedWindows()[host.fetchedWindows().length - 1]).toEqual([
|
|
1163
|
+
at(25).getTime(),
|
|
1164
|
+
at(36).getTime(),
|
|
1165
|
+
]);
|
|
1166
|
+
|
|
1167
|
+
// Back to the hour in one step, the pointer still on the line.
|
|
1168
|
+
fireEvent.doubleClick(sparklineIn(null));
|
|
1169
|
+
const hour: SVGSVGElement = await lineOf(60);
|
|
1170
|
+
|
|
1171
|
+
expect(crashed()).toBeNull();
|
|
1172
|
+
expect(hoverMarkerIn(hour)).toBeNull();
|
|
1173
|
+
expect(bigNumber("60")).not.toBeNull();
|
|
1174
|
+
});
|
|
1175
|
+
|
|
1176
|
+
test("an auto-refresh that lands one point fewer in the middle of a drag keeps the board up", async () => {
|
|
1177
|
+
const rendered: RenderResult = render(board(host, 0));
|
|
1178
|
+
await lineOf(60);
|
|
1179
|
+
|
|
1180
|
+
// Pressed on the newest point; the pointer has not moved since.
|
|
1181
|
+
pressOn(layOut(), 59);
|
|
1182
|
+
|
|
1183
|
+
dropNewestPoint = true;
|
|
1184
|
+
rendered.rerender(board(host, 1));
|
|
1185
|
+
await lineOf(59);
|
|
1186
|
+
|
|
1187
|
+
expect(crashed()).toBeNull();
|
|
1188
|
+
|
|
1189
|
+
// The drag goes on over the line as it is now.
|
|
1190
|
+
const refreshed: LaidOutSparkline = layOut();
|
|
1191
|
+
dragOnTo(refreshed, 40);
|
|
1192
|
+
releaseOn(refreshed, 40);
|
|
1193
|
+
|
|
1194
|
+
await waitFor(() => {
|
|
1195
|
+
const windows: Array<[number, number]> = host.fetchedWindows();
|
|
1196
|
+
expect(windows[windows.length - 1]).toEqual([
|
|
1197
|
+
at(40).getTime(),
|
|
1198
|
+
at(59).getTime(),
|
|
1199
|
+
]);
|
|
1200
|
+
});
|
|
1201
|
+
await lineOf(18);
|
|
1202
|
+
expect(crashed()).toBeNull();
|
|
1203
|
+
});
|
|
1204
|
+
},
|
|
1205
|
+
);
|
|
1206
|
+
|
|
1207
|
+
/*
|
|
1208
|
+
* The Component Settings preview zooms itself (useTimeRangeZoom over its
|
|
1209
|
+
* own copy of the board's range), so the same drag there landed on the
|
|
1210
|
+
* same stale index - and threw away the edits the dialog was holding.
|
|
1211
|
+
*/
|
|
1212
|
+
class FakeResizeObserver {
|
|
1213
|
+
private callback: ResizeObserverCallback;
|
|
1214
|
+
|
|
1215
|
+
public constructor(callback: ResizeObserverCallback) {
|
|
1216
|
+
this.callback = callback;
|
|
1217
|
+
}
|
|
1218
|
+
|
|
1219
|
+
public observe(): void {
|
|
1220
|
+
this.callback(
|
|
1221
|
+
[{ contentRect: { width: 800 } } as unknown as ResizeObserverEntry],
|
|
1222
|
+
this as unknown as ResizeObserver,
|
|
1223
|
+
);
|
|
1224
|
+
}
|
|
1225
|
+
|
|
1226
|
+
public unobserve(): void {
|
|
1227
|
+
// Nothing to stop observing.
|
|
1228
|
+
}
|
|
1229
|
+
|
|
1230
|
+
public disconnect(): void {
|
|
1231
|
+
// Nothing to disconnect.
|
|
1232
|
+
}
|
|
1233
|
+
}
|
|
1234
|
+
|
|
1235
|
+
describe("the Value widget in the Component Settings preview", () => {
|
|
1236
|
+
beforeAll(() => {
|
|
1237
|
+
(globalThis as unknown as { ResizeObserver: unknown }).ResizeObserver =
|
|
1238
|
+
FakeResizeObserver;
|
|
1239
|
+
});
|
|
1240
|
+
|
|
1241
|
+
function editor(): React.ReactElement {
|
|
1242
|
+
const viewConfig: DashboardViewConfig = {
|
|
1243
|
+
_type: ObjectType.DashboardViewConfig,
|
|
1244
|
+
components: [buildValueComponent()],
|
|
1245
|
+
heightInDashboardUnits: 12,
|
|
1246
|
+
};
|
|
1247
|
+
|
|
1248
|
+
return (
|
|
1249
|
+
<CrashBoundary>
|
|
1250
|
+
<DashboardCanvas
|
|
1251
|
+
dashboardViewConfig={viewConfig}
|
|
1252
|
+
onDashboardViewConfigChange={() => {}}
|
|
1253
|
+
isEditMode={true}
|
|
1254
|
+
currentTotalDashboardWidthInPx={1200}
|
|
1255
|
+
onComponentSelected={() => {}}
|
|
1256
|
+
onComponentUnselected={() => {}}
|
|
1257
|
+
selectedComponentId={COMPONENT_ID}
|
|
1258
|
+
metrics={{ metricTypes: [], telemetryAttributes: [] }}
|
|
1259
|
+
dashboardStartAndEndDate={BOARD_RANGE}
|
|
1260
|
+
refreshTick={0}
|
|
1261
|
+
onDashboardTimeRangeSelect={() => {}}
|
|
1262
|
+
onDashboardTimeRangeReset={() => {}}
|
|
1263
|
+
isDashboardTimeRangeZoomed={false}
|
|
1264
|
+
/>
|
|
1265
|
+
</CrashBoundary>
|
|
1266
|
+
);
|
|
1267
|
+
}
|
|
1268
|
+
|
|
1269
|
+
function previewLine(): SVGSVGElement {
|
|
1270
|
+
return sparklineIn(screen.getByTestId("modal"));
|
|
1271
|
+
}
|
|
1272
|
+
|
|
1273
|
+
test("a drag on the preview's sparkline zooms the preview without taking the editor down", async () => {
|
|
1274
|
+
render(editor());
|
|
1275
|
+
|
|
1276
|
+
await waitFor(() => {
|
|
1277
|
+
expect(pointCountOf(previewLine())).toBe(60);
|
|
1278
|
+
});
|
|
1279
|
+
|
|
1280
|
+
dragAcross(layOut(screen.getByTestId("modal")), 20, 30);
|
|
1281
|
+
|
|
1282
|
+
await waitFor(() => {
|
|
1283
|
+
expect(crashed()).toBeNull();
|
|
1284
|
+
expect(pointCountOf(previewLine())).toBe(11);
|
|
1285
|
+
});
|
|
1286
|
+
|
|
1287
|
+
// Still editing: the dialog, and the settings it holds, are there.
|
|
1288
|
+
expect(screen.getByTestId("modal")).toBeInTheDocument();
|
|
1289
|
+
expect(screen.getByTestId("arguments-form")).toBeInTheDocument();
|
|
1290
|
+
expect(hoverMarkerIn(previewLine())).toBeNull();
|
|
1291
|
+
});
|
|
1292
|
+
});
|