@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
|
@@ -17,12 +17,29 @@ import {
|
|
|
17
17
|
} from "@testing-library/react";
|
|
18
18
|
import * as React from "react";
|
|
19
19
|
import { MemoryRouter } from "react-router-dom";
|
|
20
|
+
import {
|
|
21
|
+
Edge,
|
|
22
|
+
FitViewOptions,
|
|
23
|
+
Node,
|
|
24
|
+
NodeChange,
|
|
25
|
+
ReactFlowInstance,
|
|
26
|
+
Viewport,
|
|
27
|
+
} from "reactflow";
|
|
20
28
|
import EntityType from "../../../Types/Telemetry/EntityType";
|
|
21
29
|
import EntityRelationshipType from "../../../Types/Telemetry/EntityRelationshipType";
|
|
22
30
|
import EntitySource from "../../../Types/Telemetry/EntitySource";
|
|
23
31
|
import TimeRange from "../../../Types/Time/TimeRange";
|
|
32
|
+
import { ComponentProps as FlowViewportGuardProps } from "../../../../App/FeatureSet/Dashboard/src/Components/Topology/FlowViewportGuard";
|
|
33
|
+
import {
|
|
34
|
+
FlowExtent,
|
|
35
|
+
UNBOUNDED_FLOW_EXTENT,
|
|
36
|
+
noticeOffsetInCanvas,
|
|
37
|
+
} from "../../../../App/FeatureSet/Dashboard/src/Components/Topology/FlowViewport";
|
|
24
38
|
import { ServiceOperationalStatus } from "../../../../App/FeatureSet/Dashboard/src/Components/Topology/OperationalOverlay";
|
|
25
|
-
import ServiceMapGraph
|
|
39
|
+
import ServiceMapGraph, {
|
|
40
|
+
OUT_OF_VIEW_NOTICE_DELAY_MS,
|
|
41
|
+
SERVICE_MAP_PAN_MARGIN,
|
|
42
|
+
} from "../../../../App/FeatureSet/Dashboard/src/Components/Topology/ServiceMapGraph";
|
|
26
43
|
import {
|
|
27
44
|
EntityDetailTarget,
|
|
28
45
|
TopologyEntity,
|
|
@@ -34,15 +51,28 @@ import getJestMockFunction, { MockFunction } from "../../MockType";
|
|
|
34
51
|
|
|
35
52
|
/*
|
|
36
53
|
* The Service Map component: what a person sees first, how they narrow it
|
|
37
|
-
* down,
|
|
38
|
-
*
|
|
39
|
-
*
|
|
40
|
-
*
|
|
41
|
-
*
|
|
54
|
+
* down, how they get to details, and how the map stays on its canvas.
|
|
55
|
+
*
|
|
56
|
+
* Graph drawing itself (React Flow) is replaced by a stand-in that renders
|
|
57
|
+
* nodes and edges as buttons, renders its children and records every props
|
|
58
|
+
* object it is given, so a test can tell a rebuilt nodes array from the one
|
|
59
|
+
* handed back unchanged; its Controls record their props too. The viewport
|
|
60
|
+
* guard (which reads React Flow's store) is replaced by a stand-in that keeps
|
|
61
|
+
* the real guard's side of the contract with the map: it reports a React
|
|
62
|
+
* Flow instance once the viewport is ready and, on unmount, withdraws it and
|
|
63
|
+
* reports the drawing in view and the pan extent unbounded. What the real
|
|
64
|
+
* guard measures on a canvas (the pan extent, whether the drawing is in
|
|
65
|
+
* view) the tests report through the props it was last given, and a pan or
|
|
66
|
+
* zoom by hand through React Flow's recorded onMoveStart and onMoveEnd, the
|
|
67
|
+
* way React Flow reports one. The detail drawer (which fetches its own
|
|
68
|
+
* connections) is a stand-in that records the props it was given, and a
|
|
69
|
+
* background tab is stood in for by shadowing document.visibilityState.
|
|
70
|
+
* jsdom lays nothing out, so where the canvas is on screen, the window's
|
|
71
|
+
* height and the out-of-view notice's height are stood in for as well (see
|
|
72
|
+
* standInForLayout). So these tests exercise the component's decisions, not
|
|
73
|
+
* the canvas, the guard or the drawer.
|
|
42
74
|
*/
|
|
43
75
|
|
|
44
|
-
const mockFitView: MockFunction = getJestMockFunction();
|
|
45
|
-
mockFitView.mockReturnValue(true);
|
|
46
76
|
const mockSetQuery: MockFunction = getJestMockFunction();
|
|
47
77
|
mockSetQuery.mockImplementation((values: Record<string, string | null>) => {
|
|
48
78
|
const url: URL = new URL(window.location.href);
|
|
@@ -59,6 +89,12 @@ let mockStatuses: Map<string, ServiceOperationalStatus> = new Map<
|
|
|
59
89
|
string,
|
|
60
90
|
ServiceOperationalStatus
|
|
61
91
|
>();
|
|
92
|
+
/*
|
|
93
|
+
* While set, the operational overlay answers only once this settles, so a
|
|
94
|
+
* test can have the cards drawn (and measured) before the overlay arrives
|
|
95
|
+
* and draws them again.
|
|
96
|
+
*/
|
|
97
|
+
let mockStatusesReady: Promise<void> | null = null;
|
|
62
98
|
/*
|
|
63
99
|
* Every drawer instance, by the entity it was mounted for. The map keeps one
|
|
64
100
|
* drawer while the user moves between entities (a remount would replay the
|
|
@@ -68,6 +104,78 @@ const mockDrawerMounts: Array<string> = [];
|
|
|
68
104
|
/* The props of the drawer as last rendered. */
|
|
69
105
|
let mockDrawerProps: Record<string, unknown> | null = null;
|
|
70
106
|
|
|
107
|
+
/* What the map hands React Flow, as far as these tests look at it. */
|
|
108
|
+
interface MockFlowProps {
|
|
109
|
+
nodes: Array<Node>;
|
|
110
|
+
edges: Array<Edge>;
|
|
111
|
+
children?: React.ReactNode;
|
|
112
|
+
translateExtent?: FlowExtent;
|
|
113
|
+
minZoom?: number;
|
|
114
|
+
maxZoom?: number;
|
|
115
|
+
zoomOnScroll?: boolean;
|
|
116
|
+
zoomOnPinch?: boolean;
|
|
117
|
+
panOnScroll?: boolean;
|
|
118
|
+
preventScrolling?: boolean;
|
|
119
|
+
zoomOnDoubleClick?: boolean;
|
|
120
|
+
nodesDraggable?: boolean;
|
|
121
|
+
fitView?: boolean;
|
|
122
|
+
onInit?: (instance: ReactFlowInstance) => void;
|
|
123
|
+
onNodesChange: (changes: Array<NodeChange>) => void;
|
|
124
|
+
/*
|
|
125
|
+
* React Flow reports a pan or zoom by hand (one with a source event) as it
|
|
126
|
+
* starts, and again as it ends if the transform changed at all. A move it
|
|
127
|
+
* makes itself (a fit, a zoom button, setViewport) reports neither.
|
|
128
|
+
*/
|
|
129
|
+
onMoveStart: (event: MouseEvent | TouchEvent, viewport: Viewport) => void;
|
|
130
|
+
onMoveEnd: (event: MouseEvent | TouchEvent, viewport: Viewport) => void;
|
|
131
|
+
onNodeClick: (event: React.MouseEvent, node: Node) => void;
|
|
132
|
+
onEdgeClick: (event: React.MouseEvent, edge: Edge) => void;
|
|
133
|
+
onEdgeMouseEnter: (event: React.MouseEvent, edge: Edge) => void;
|
|
134
|
+
onEdgeMouseLeave: (event: React.MouseEvent, edge: Edge) => void;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
/* What the map hands React Flow's zoom and fit buttons. */
|
|
138
|
+
interface MockControlsProps {
|
|
139
|
+
showInteractive?: boolean;
|
|
140
|
+
fitViewOptions?: FitViewOptions;
|
|
141
|
+
onZoomIn: () => void;
|
|
142
|
+
onZoomOut: () => void;
|
|
143
|
+
onFitView: () => void;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
/* The part of a React Flow instance the map uses. */
|
|
147
|
+
interface MockFlowInstance {
|
|
148
|
+
fitView: MockFunction;
|
|
149
|
+
viewportInitialized: boolean;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
/* Every props object React Flow was rendered with, oldest first. */
|
|
153
|
+
const mockFlowRenders: Array<MockFlowProps> = [];
|
|
154
|
+
/* How many times React Flow was mounted: each one draws the canvas afresh. */
|
|
155
|
+
let mockFlowMounts: number = 0;
|
|
156
|
+
/* The props of React Flow's controls as last rendered. */
|
|
157
|
+
let mockControlsProps: MockControlsProps | null = null;
|
|
158
|
+
/* The props of the viewport guard as last rendered. */
|
|
159
|
+
let mockGuardProps: FlowViewportGuardProps | null = null;
|
|
160
|
+
/* One instance per mount of the viewport guard, oldest first. */
|
|
161
|
+
const mockFlowInstances: Array<MockFlowInstance> = [];
|
|
162
|
+
/*
|
|
163
|
+
* Whether a newly mounted canvas has a usable viewport. The real guard
|
|
164
|
+
* reports no instance until it has one.
|
|
165
|
+
*/
|
|
166
|
+
let mockViewportInitialized: boolean = true;
|
|
167
|
+
/*
|
|
168
|
+
* What document.visibilityState says while a test stands in for the tab
|
|
169
|
+
* (see setTabVisibility), or null while jsdom's own getter answers: jsdom's
|
|
170
|
+
* tab is always visible.
|
|
171
|
+
*/
|
|
172
|
+
let mockTabVisibility: DocumentVisibilityState | null = null;
|
|
173
|
+
/*
|
|
174
|
+
* Hands jsdom back what a test stood in for with standInForLayout, run by
|
|
175
|
+
* afterEach, last first.
|
|
176
|
+
*/
|
|
177
|
+
const mockLayoutRestores: Array<() => void> = [];
|
|
178
|
+
|
|
71
179
|
jest.mock("../../../UI/Utils/Translation", () => {
|
|
72
180
|
return {
|
|
73
181
|
__esModule: true,
|
|
@@ -104,6 +212,9 @@ jest.mock(
|
|
|
104
212
|
() => {
|
|
105
213
|
return {
|
|
106
214
|
fetchServiceOperationalStatuses: async () => {
|
|
215
|
+
if (mockStatusesReady) {
|
|
216
|
+
await mockStatusesReady;
|
|
217
|
+
}
|
|
107
218
|
return mockStatuses;
|
|
108
219
|
},
|
|
109
220
|
};
|
|
@@ -278,6 +389,46 @@ jest.mock(
|
|
|
278
389
|
};
|
|
279
390
|
},
|
|
280
391
|
);
|
|
392
|
+
/*
|
|
393
|
+
* The viewport guard, keeping the real guard's side of its contract with
|
|
394
|
+
* the map: it reports the live React Flow instance once the viewport is
|
|
395
|
+
* ready (none until then) and, on unmount, withdraws it and reports the
|
|
396
|
+
* drawing in view and the pan extent unbounded. Each mount reports an
|
|
397
|
+
* instance of its own, so a test can tell the canvas on screen from one
|
|
398
|
+
* that is gone.
|
|
399
|
+
*/
|
|
400
|
+
jest.mock(
|
|
401
|
+
"../../../../App/FeatureSet/Dashboard/src/Components/Topology/FlowViewportGuard",
|
|
402
|
+
() => {
|
|
403
|
+
return {
|
|
404
|
+
__esModule: true,
|
|
405
|
+
default: (props: FlowViewportGuardProps) => {
|
|
406
|
+
mockGuardProps = props;
|
|
407
|
+
const latest: React.MutableRefObject<FlowViewportGuardProps> =
|
|
408
|
+
React.useRef<FlowViewportGuardProps>(props);
|
|
409
|
+
latest.current = props;
|
|
410
|
+
React.useEffect(() => {
|
|
411
|
+
const instance: MockFlowInstance = {
|
|
412
|
+
fitView: getJestMockFunction().mockReturnValue(true),
|
|
413
|
+
viewportInitialized: mockViewportInitialized,
|
|
414
|
+
};
|
|
415
|
+
mockFlowInstances.push(instance);
|
|
416
|
+
latest.current.onInstance(
|
|
417
|
+
instance.viewportInitialized
|
|
418
|
+
? (instance as unknown as ReactFlowInstance)
|
|
419
|
+
: null,
|
|
420
|
+
);
|
|
421
|
+
return () => {
|
|
422
|
+
latest.current.onInstance(null);
|
|
423
|
+
latest.current.onDrawingInViewChange(true);
|
|
424
|
+
latest.current.onExtentChange(UNBOUNDED_FLOW_EXTENT);
|
|
425
|
+
};
|
|
426
|
+
}, []);
|
|
427
|
+
return <></>;
|
|
428
|
+
},
|
|
429
|
+
};
|
|
430
|
+
},
|
|
431
|
+
);
|
|
281
432
|
jest.mock("reactflow", () => {
|
|
282
433
|
return {
|
|
283
434
|
__esModule: true,
|
|
@@ -286,107 +437,72 @@ jest.mock("reactflow", () => {
|
|
|
286
437
|
Background: () => {
|
|
287
438
|
return null;
|
|
288
439
|
},
|
|
289
|
-
Controls: () => {
|
|
440
|
+
Controls: (props: MockControlsProps) => {
|
|
441
|
+
mockControlsProps = props;
|
|
290
442
|
return null;
|
|
291
443
|
},
|
|
292
444
|
Handle: () => {
|
|
293
445
|
return null;
|
|
294
446
|
},
|
|
295
447
|
Position: { Left: "left", Right: "right", Top: "top", Bottom: "bottom" },
|
|
296
|
-
default: (props: {
|
|
297
|
-
|
|
298
|
-
id: string;
|
|
299
|
-
position: { x: number; y: number };
|
|
300
|
-
data: { label: string; dimmed: boolean; footer?: string };
|
|
301
|
-
}>;
|
|
302
|
-
edges: Array<{
|
|
303
|
-
id: string;
|
|
304
|
-
label?: string;
|
|
305
|
-
animated?: boolean;
|
|
306
|
-
labelShowBg?: boolean;
|
|
307
|
-
labelStyle?: React.CSSProperties;
|
|
308
|
-
labelBgStyle?: React.CSSProperties;
|
|
309
|
-
labelBgPadding?: [number, number];
|
|
310
|
-
labelBgBorderRadius?: number;
|
|
311
|
-
}>;
|
|
312
|
-
onInit: (value: unknown) => void;
|
|
313
|
-
onNodeClick: (event: React.MouseEvent, node: unknown) => void;
|
|
314
|
-
onEdgeClick: (event: React.MouseEvent, edge: unknown) => void;
|
|
315
|
-
onEdgeMouseEnter: (event: React.MouseEvent, edge: unknown) => void;
|
|
316
|
-
onEdgeMouseLeave: () => void;
|
|
317
|
-
}) => {
|
|
448
|
+
default: (props: MockFlowProps) => {
|
|
449
|
+
mockFlowRenders.push(props);
|
|
318
450
|
React.useEffect(() => {
|
|
319
|
-
|
|
451
|
+
mockFlowMounts += 1;
|
|
320
452
|
}, []);
|
|
321
453
|
return (
|
|
322
454
|
<div data-testid="rendered-graph">
|
|
323
|
-
{props.nodes.map(
|
|
324
|
-
(
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
)
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
)}
|
|
375
|
-
onClick={(event: React.MouseEvent) => {
|
|
376
|
-
props.onEdgeClick(event, edge);
|
|
377
|
-
}}
|
|
378
|
-
onMouseEnter={(event: React.MouseEvent) => {
|
|
379
|
-
props.onEdgeMouseEnter(event, edge);
|
|
380
|
-
}}
|
|
381
|
-
onMouseLeave={() => {
|
|
382
|
-
props.onEdgeMouseLeave();
|
|
383
|
-
}}
|
|
384
|
-
>
|
|
385
|
-
{edge.label || "Connection"}
|
|
386
|
-
</button>
|
|
387
|
-
);
|
|
388
|
-
},
|
|
389
|
-
)}
|
|
455
|
+
{props.nodes.map((node: Node) => {
|
|
456
|
+
return (
|
|
457
|
+
<button
|
|
458
|
+
key={node.id}
|
|
459
|
+
data-testid={`map-node-${node.id}`}
|
|
460
|
+
data-context={String(node.data.dimmed)}
|
|
461
|
+
data-x={String(node.position.x)}
|
|
462
|
+
data-footer={node.data.footer || ""}
|
|
463
|
+
onClick={(event: React.MouseEvent) => {
|
|
464
|
+
props.onNodeClick(event, node);
|
|
465
|
+
}}
|
|
466
|
+
>
|
|
467
|
+
{node.data.label}
|
|
468
|
+
</button>
|
|
469
|
+
);
|
|
470
|
+
})}
|
|
471
|
+
{props.edges.map((edge: Edge) => {
|
|
472
|
+
return (
|
|
473
|
+
<button
|
|
474
|
+
key={edge.id}
|
|
475
|
+
data-testid={`map-edge-${edge.id}`}
|
|
476
|
+
data-animated={String(edge.animated)}
|
|
477
|
+
data-label-show-background={String(edge.labelShowBg)}
|
|
478
|
+
data-label-color={String(edge.labelStyle?.fill || "")}
|
|
479
|
+
data-label-background={String(edge.labelBgStyle?.fill || "")}
|
|
480
|
+
data-label-background-opacity={String(
|
|
481
|
+
edge.labelBgStyle?.fillOpacity || "",
|
|
482
|
+
)}
|
|
483
|
+
data-label-border={String(edge.labelBgStyle?.stroke || "")}
|
|
484
|
+
data-label-border-width={String(
|
|
485
|
+
edge.labelBgStyle?.strokeWidth || "",
|
|
486
|
+
)}
|
|
487
|
+
data-label-padding={edge.labelBgPadding?.join(",") || ""}
|
|
488
|
+
data-label-border-radius={String(
|
|
489
|
+
edge.labelBgBorderRadius || "",
|
|
490
|
+
)}
|
|
491
|
+
onClick={(event: React.MouseEvent) => {
|
|
492
|
+
props.onEdgeClick(event, edge);
|
|
493
|
+
}}
|
|
494
|
+
onMouseEnter={(event: React.MouseEvent) => {
|
|
495
|
+
props.onEdgeMouseEnter(event, edge);
|
|
496
|
+
}}
|
|
497
|
+
onMouseLeave={(event: React.MouseEvent) => {
|
|
498
|
+
props.onEdgeMouseLeave(event, edge);
|
|
499
|
+
}}
|
|
500
|
+
>
|
|
501
|
+
{edge.label || "Connection"}
|
|
502
|
+
</button>
|
|
503
|
+
);
|
|
504
|
+
})}
|
|
505
|
+
{props.children}
|
|
390
506
|
</div>
|
|
391
507
|
);
|
|
392
508
|
},
|
|
@@ -507,17 +623,309 @@ function drawerTarget(): EntityDetailTarget {
|
|
|
507
623
|
return mockDrawerProps!["entity"] as EntityDetailTarget;
|
|
508
624
|
}
|
|
509
625
|
|
|
626
|
+
/* How every fit of the map frames it: the guard, the toolbar, the controls. */
|
|
627
|
+
const MAP_FRAMING: FitViewOptions = { padding: 0.18, maxZoom: 1 };
|
|
628
|
+
/* Fit to screen: the same framing, animated. */
|
|
629
|
+
const ANIMATED_FIT: FitViewOptions = {
|
|
630
|
+
padding: 0.18,
|
|
631
|
+
maxZoom: 1,
|
|
632
|
+
duration: 300,
|
|
633
|
+
};
|
|
634
|
+
|
|
635
|
+
/* The props React Flow was last rendered with. */
|
|
636
|
+
function flowProps(): MockFlowProps {
|
|
637
|
+
const latest: MockFlowProps | undefined =
|
|
638
|
+
mockFlowRenders[mockFlowRenders.length - 1];
|
|
639
|
+
if (!latest) {
|
|
640
|
+
throw new Error("React Flow was never rendered");
|
|
641
|
+
}
|
|
642
|
+
return latest;
|
|
643
|
+
}
|
|
644
|
+
|
|
645
|
+
/* The props the viewport guard was last rendered with. */
|
|
646
|
+
function guardProps(): FlowViewportGuardProps {
|
|
647
|
+
if (!mockGuardProps) {
|
|
648
|
+
throw new Error("The viewport guard was never rendered");
|
|
649
|
+
}
|
|
650
|
+
return mockGuardProps;
|
|
651
|
+
}
|
|
652
|
+
|
|
653
|
+
/* The props React Flow's controls were last rendered with. */
|
|
654
|
+
function controlsProps(): MockControlsProps {
|
|
655
|
+
if (!mockControlsProps) {
|
|
656
|
+
throw new Error("React Flow's controls were never rendered");
|
|
657
|
+
}
|
|
658
|
+
return mockControlsProps;
|
|
659
|
+
}
|
|
660
|
+
|
|
661
|
+
/* The instance of the guard mounted `index`-th, reported or withheld. */
|
|
662
|
+
function flowInstance(index: number): MockFlowInstance {
|
|
663
|
+
const instance: MockFlowInstance | undefined = mockFlowInstances[index];
|
|
664
|
+
if (!instance) {
|
|
665
|
+
throw new Error(`No viewport guard was mounted at #${index}`);
|
|
666
|
+
}
|
|
667
|
+
return instance;
|
|
668
|
+
}
|
|
669
|
+
|
|
670
|
+
/* The instance the guard of the canvas on screen reported. */
|
|
671
|
+
function liveInstance(): MockFlowInstance {
|
|
672
|
+
const instance: MockFlowInstance = flowInstance(mockFlowInstances.length - 1);
|
|
673
|
+
expect(instance.viewportInitialized).toBe(true);
|
|
674
|
+
return instance;
|
|
675
|
+
}
|
|
676
|
+
|
|
677
|
+
function nodeById(nodes: Array<Node>, id: string): Node {
|
|
678
|
+
const node: Node | undefined = nodes.find((candidate: Node): boolean => {
|
|
679
|
+
return candidate.id === id;
|
|
680
|
+
});
|
|
681
|
+
if (!node) {
|
|
682
|
+
throw new Error(`No node ${id} was drawn`);
|
|
683
|
+
}
|
|
684
|
+
return node;
|
|
685
|
+
}
|
|
686
|
+
|
|
687
|
+
function edgeById(edges: Array<Edge>, id: string): Edge {
|
|
688
|
+
const found: Edge | undefined = edges.find((candidate: Edge): boolean => {
|
|
689
|
+
return candidate.id === id;
|
|
690
|
+
});
|
|
691
|
+
if (!found) {
|
|
692
|
+
throw new Error(`No edge ${id} was drawn`);
|
|
693
|
+
}
|
|
694
|
+
return found;
|
|
695
|
+
}
|
|
696
|
+
|
|
697
|
+
function idsOf(nodes: Array<Node>): Array<string> {
|
|
698
|
+
return nodes.map((node: Node): string => {
|
|
699
|
+
return node.id;
|
|
700
|
+
});
|
|
701
|
+
}
|
|
702
|
+
|
|
703
|
+
/* The guard's report of whether any of the drawing is on the canvas. */
|
|
704
|
+
function reportDrawingInView(inView: boolean): void {
|
|
705
|
+
act(() => {
|
|
706
|
+
guardProps().onDrawingInViewChange(inView);
|
|
707
|
+
});
|
|
708
|
+
}
|
|
709
|
+
|
|
710
|
+
/* Let time pass on the fake clock. */
|
|
711
|
+
function advance(ms: number): void {
|
|
712
|
+
act(() => {
|
|
713
|
+
jest.advanceTimersByTime(ms);
|
|
714
|
+
});
|
|
715
|
+
}
|
|
716
|
+
|
|
717
|
+
function outOfViewNotice(): HTMLElement | null {
|
|
718
|
+
return screen.queryByTestId("service-map-out-of-view");
|
|
719
|
+
}
|
|
720
|
+
|
|
721
|
+
/* The out-of-view notice's own box: its message and its button. */
|
|
722
|
+
function outOfViewNoticeBox(): HTMLElement {
|
|
723
|
+
return within(screen.getByTestId("service-map-out-of-view")).getByRole(
|
|
724
|
+
"status",
|
|
725
|
+
);
|
|
726
|
+
}
|
|
727
|
+
|
|
728
|
+
/*
|
|
729
|
+
* The page, as far as placing the out-of-view notice goes. jsdom lays
|
|
730
|
+
* nothing out: every box it measures is empty, and its window is always 768
|
|
731
|
+
* pixels high.
|
|
732
|
+
*/
|
|
733
|
+
interface PageLayout {
|
|
734
|
+
/* The canvas's top, down from the window's: negative once scrolled past. */
|
|
735
|
+
canvasTop: number;
|
|
736
|
+
canvasHeight: number;
|
|
737
|
+
/* The window's height (window.innerHeight). */
|
|
738
|
+
windowHeight: number;
|
|
739
|
+
/* The notice's own height, once it is drawn. */
|
|
740
|
+
noticeHeight: number;
|
|
741
|
+
}
|
|
742
|
+
|
|
743
|
+
/* The notice's box, for the stand-in for its height. */
|
|
744
|
+
const OUT_OF_VIEW_NOTICE_BOX: string =
|
|
745
|
+
'[data-testid="service-map-out-of-view"] > [role="status"]';
|
|
746
|
+
|
|
747
|
+
/*
|
|
748
|
+
* Lay the page out as `page` says, for the rest of the test. The canvas's
|
|
749
|
+
* box, the window's height and the notice's height are read from it each
|
|
750
|
+
* time they are measured, so changing it lays the page out anew. Returns
|
|
751
|
+
* the canvas's measurements, one call each.
|
|
752
|
+
*/
|
|
753
|
+
function standInForLayout(page: PageLayout): MockFunction {
|
|
754
|
+
const measureCanvas: MockFunction = getJestMockFunction().mockImplementation(
|
|
755
|
+
(): DOMRect => {
|
|
756
|
+
const box: Omit<DOMRect, "toJSON"> = {
|
|
757
|
+
x: 0,
|
|
758
|
+
y: page.canvasTop,
|
|
759
|
+
left: 0,
|
|
760
|
+
top: page.canvasTop,
|
|
761
|
+
right: 1024,
|
|
762
|
+
bottom: page.canvasTop + page.canvasHeight,
|
|
763
|
+
width: 1024,
|
|
764
|
+
height: page.canvasHeight,
|
|
765
|
+
};
|
|
766
|
+
return {
|
|
767
|
+
...box,
|
|
768
|
+
toJSON: (): Omit<DOMRect, "toJSON"> => {
|
|
769
|
+
return box;
|
|
770
|
+
},
|
|
771
|
+
};
|
|
772
|
+
},
|
|
773
|
+
);
|
|
774
|
+
// The canvas goes at cleanup, and its stand-in with it.
|
|
775
|
+
screen.getByTestId("service-map-canvas").getBoundingClientRect =
|
|
776
|
+
measureCanvas;
|
|
777
|
+
|
|
778
|
+
const jsdomWindowHeight: PropertyDescriptor | undefined =
|
|
779
|
+
Object.getOwnPropertyDescriptor(window, "innerHeight");
|
|
780
|
+
Object.defineProperty(window, "innerHeight", {
|
|
781
|
+
configurable: true,
|
|
782
|
+
get: (): number => {
|
|
783
|
+
return page.windowHeight;
|
|
784
|
+
},
|
|
785
|
+
});
|
|
786
|
+
const jsdomOffsetHeight: PropertyDescriptor | undefined =
|
|
787
|
+
Object.getOwnPropertyDescriptor(HTMLElement.prototype, "offsetHeight");
|
|
788
|
+
Object.defineProperty(HTMLElement.prototype, "offsetHeight", {
|
|
789
|
+
configurable: true,
|
|
790
|
+
enumerable: true,
|
|
791
|
+
get(this: HTMLElement): number {
|
|
792
|
+
if (this.matches(OUT_OF_VIEW_NOTICE_BOX)) {
|
|
793
|
+
return page.noticeHeight;
|
|
794
|
+
}
|
|
795
|
+
return jsdomOffsetHeight?.get
|
|
796
|
+
? Number(jsdomOffsetHeight.get.call(this))
|
|
797
|
+
: 0;
|
|
798
|
+
},
|
|
799
|
+
});
|
|
800
|
+
|
|
801
|
+
mockLayoutRestores.push((): void => {
|
|
802
|
+
if (jsdomWindowHeight) {
|
|
803
|
+
Object.defineProperty(window, "innerHeight", jsdomWindowHeight);
|
|
804
|
+
} else {
|
|
805
|
+
Reflect.deleteProperty(window, "innerHeight");
|
|
806
|
+
}
|
|
807
|
+
if (jsdomOffsetHeight) {
|
|
808
|
+
Object.defineProperty(
|
|
809
|
+
HTMLElement.prototype,
|
|
810
|
+
"offsetHeight",
|
|
811
|
+
jsdomOffsetHeight,
|
|
812
|
+
);
|
|
813
|
+
} else {
|
|
814
|
+
Reflect.deleteProperty(HTMLElement.prototype, "offsetHeight");
|
|
815
|
+
}
|
|
816
|
+
});
|
|
817
|
+
return measureCanvas;
|
|
818
|
+
}
|
|
819
|
+
|
|
820
|
+
/* The page scrolling: the document, or an element the page scrolls in. */
|
|
821
|
+
function scrollPage(scroller: Document | HTMLElement = document): void {
|
|
822
|
+
fireEvent.scroll(scroller);
|
|
823
|
+
}
|
|
824
|
+
|
|
825
|
+
/* The browser window being resized. */
|
|
826
|
+
function resizeWindow(): void {
|
|
827
|
+
fireEvent(window, new Event("resize"));
|
|
828
|
+
}
|
|
829
|
+
|
|
830
|
+
/* The browser drawing its next frame: one every 16 ms on the fake clock. */
|
|
831
|
+
function nextFrame(): void {
|
|
832
|
+
advance(16);
|
|
833
|
+
}
|
|
834
|
+
|
|
835
|
+
/*
|
|
836
|
+
* A view the automatic framing left a large map in, as React Flow reported
|
|
837
|
+
* it in a browser: the whole drawing fitted at an eighth of its size, where
|
|
838
|
+
* the pan extent pins it.
|
|
839
|
+
*/
|
|
840
|
+
const FRAMED_VIEW: Viewport = { x: 720.3138, y: 67.1186, zoom: 0.1269 };
|
|
841
|
+
/*
|
|
842
|
+
* The same view as React Flow reports it after a press on it: d3-zoom
|
|
843
|
+
* re-applies the pan extent on every pointer move, and the view comes back
|
|
844
|
+
* a rounding error from where it was.
|
|
845
|
+
*/
|
|
846
|
+
const FRAMED_VIEW_AFTER_PRESS: Viewport = {
|
|
847
|
+
x: 720.3138,
|
|
848
|
+
y: 67.11864406779664,
|
|
849
|
+
zoom: 0.1269,
|
|
850
|
+
};
|
|
851
|
+
/*
|
|
852
|
+
* A view the automatic framing left a smaller map in: drawn at full size,
|
|
853
|
+
* where the pan extent leaves the view a little room, so the pointer's slip
|
|
854
|
+
* during a click pans it. (Its coordinates are exact in binary, so a slip of
|
|
855
|
+
* exactly 4 px is 4 px, not a rounding error either side of it.)
|
|
856
|
+
*/
|
|
857
|
+
const SMALL_MAP_VIEW: Viewport = { x: 296.5, y: 104.25, zoom: 1 };
|
|
858
|
+
|
|
859
|
+
/* React Flow reporting that a pan or zoom by hand began, and where. */
|
|
860
|
+
function startGesture(at: Viewport): void {
|
|
861
|
+
act(() => {
|
|
862
|
+
flowProps().onMoveStart(new MouseEvent("mousedown"), at);
|
|
863
|
+
});
|
|
864
|
+
}
|
|
865
|
+
|
|
866
|
+
/* React Flow reporting where a pan or zoom by hand left the view. */
|
|
867
|
+
function endGesture(at: Viewport): void {
|
|
868
|
+
act(() => {
|
|
869
|
+
flowProps().onMoveEnd(new MouseEvent("mouseup"), at);
|
|
870
|
+
});
|
|
871
|
+
}
|
|
872
|
+
|
|
873
|
+
/* A whole pan or zoom by hand: where it began, then where it came to rest. */
|
|
874
|
+
function gesture(from: Viewport, to: Viewport): void {
|
|
875
|
+
startGesture(from);
|
|
876
|
+
endGesture(to);
|
|
877
|
+
}
|
|
878
|
+
|
|
879
|
+
/*
|
|
880
|
+
* Put the page in a tab that is hidden or seen, without telling it so. The
|
|
881
|
+
* getter shadows jsdom's (on Document.prototype) until afterEach hands the
|
|
882
|
+
* document its own back.
|
|
883
|
+
*/
|
|
884
|
+
function setTabVisibility(state: DocumentVisibilityState): void {
|
|
885
|
+
if (mockTabVisibility === null) {
|
|
886
|
+
Object.defineProperty(document, "visibilityState", {
|
|
887
|
+
configurable: true,
|
|
888
|
+
get: (): DocumentVisibilityState => {
|
|
889
|
+
return mockTabVisibility || "visible";
|
|
890
|
+
},
|
|
891
|
+
});
|
|
892
|
+
}
|
|
893
|
+
mockTabVisibility = state;
|
|
894
|
+
}
|
|
895
|
+
|
|
896
|
+
/* The browser hiding or showing the tab, and telling the page so. */
|
|
897
|
+
function changeTabVisibility(state: DocumentVisibilityState): void {
|
|
898
|
+
setTabVisibility(state);
|
|
899
|
+
act(() => {
|
|
900
|
+
document.dispatchEvent(new Event("visibilitychange"));
|
|
901
|
+
});
|
|
902
|
+
}
|
|
903
|
+
|
|
510
904
|
beforeEach(() => {
|
|
511
905
|
window.history.replaceState({}, "", "/");
|
|
512
906
|
mockStatuses = new Map<string, ServiceOperationalStatus>();
|
|
907
|
+
mockStatusesReady = null;
|
|
513
908
|
mockSetQuery.mockClear();
|
|
514
|
-
mockFitView.mockClear();
|
|
515
909
|
mockDrawerMounts.length = 0;
|
|
516
910
|
mockDrawerProps = null;
|
|
911
|
+
mockFlowRenders.length = 0;
|
|
912
|
+
mockFlowMounts = 0;
|
|
913
|
+
mockControlsProps = null;
|
|
914
|
+
mockGuardProps = null;
|
|
915
|
+
mockFlowInstances.length = 0;
|
|
916
|
+
mockViewportInitialized = true;
|
|
517
917
|
});
|
|
518
918
|
afterEach(() => {
|
|
519
919
|
cleanup();
|
|
520
920
|
jest.useRealTimers();
|
|
921
|
+
if (mockTabVisibility !== null) {
|
|
922
|
+
// Drop the shadowing getter: jsdom's own, on the prototype, answers again.
|
|
923
|
+
Reflect.deleteProperty(document, "visibilityState");
|
|
924
|
+
mockTabVisibility = null;
|
|
925
|
+
}
|
|
926
|
+
for (const restore of mockLayoutRestores.splice(0).reverse()) {
|
|
927
|
+
restore();
|
|
928
|
+
}
|
|
521
929
|
});
|
|
522
930
|
|
|
523
931
|
describe("first impression", () => {
|
|
@@ -828,22 +1236,39 @@ describe("narrowing the map", () => {
|
|
|
828
1236
|
fireEvent.change(screen.getByRole("textbox", { name: "Search services" }), {
|
|
829
1237
|
target: { value: "web" },
|
|
830
1238
|
});
|
|
831
|
-
|
|
1239
|
+
const webDrawing: string = guardProps().drawingKey;
|
|
1240
|
+
expect(webDrawing.split("|").sort()).toEqual(["api", "web"]);
|
|
832
1241
|
fireEvent.change(screen.getByRole("textbox", { name: "Search services" }), {
|
|
833
1242
|
target: { value: "postgres" },
|
|
834
1243
|
});
|
|
835
|
-
|
|
1244
|
+
/*
|
|
1245
|
+
* The guard frames the drawing again whenever the one it is handed
|
|
1246
|
+
* changes, once React Flow holds it: the key names exactly the cards
|
|
1247
|
+
* React Flow was given, in order.
|
|
1248
|
+
*/
|
|
1249
|
+
expect(guardProps().drawingKey).not.toBe(webDrawing);
|
|
1250
|
+
expect(guardProps().drawingKey).toBe(idsOf(flowProps().nodes).join("|"));
|
|
1251
|
+
expect(guardProps().drawingKey.split("|").sort()).toEqual([
|
|
1252
|
+
"api",
|
|
1253
|
+
"postgres",
|
|
1254
|
+
]);
|
|
1255
|
+
expect(guardProps().fitViewOptions).toEqual(MAP_FRAMING);
|
|
1256
|
+
// The canvas on screen re-frames; it is not drawn again from scratch.
|
|
1257
|
+
expect(mockFlowMounts).toBe(1);
|
|
836
1258
|
});
|
|
837
1259
|
|
|
838
1260
|
test("fit to screen recovers the viewport", async () => {
|
|
839
1261
|
await renderGraph();
|
|
840
|
-
|
|
1262
|
+
const instance: MockFlowInstance = liveInstance();
|
|
841
1263
|
fireEvent.click(screen.getByRole("button", { name: "Fit to screen" }));
|
|
842
|
-
expect(
|
|
1264
|
+
expect(instance.fitView).toHaveBeenCalledTimes(1);
|
|
1265
|
+
expect(instance.fitView).toHaveBeenCalledWith({
|
|
843
1266
|
padding: 0.18,
|
|
844
1267
|
maxZoom: 1,
|
|
845
1268
|
duration: 300,
|
|
846
1269
|
});
|
|
1270
|
+
// It fitted, so the canvas is left as it is.
|
|
1271
|
+
expect(mockFlowMounts).toBe(1);
|
|
847
1272
|
});
|
|
848
1273
|
});
|
|
849
1274
|
|
|
@@ -1269,3 +1694,717 @@ describe("table", () => {
|
|
|
1269
1694
|
);
|
|
1270
1695
|
});
|
|
1271
1696
|
});
|
|
1697
|
+
|
|
1698
|
+
/*
|
|
1699
|
+
* Issue #4117: the map went blank at random and only a page reload brought
|
|
1700
|
+
* it back. Hovering a connection handed React Flow a new array of cards
|
|
1701
|
+
* without their sizes, which could leave every card unmeasured (hidden) for
|
|
1702
|
+
* good; and the wheel, a double-click or a dragged card could carry the
|
|
1703
|
+
* drawing off the canvas with nothing to bring it back. The map now builds
|
|
1704
|
+
* cards only when they change and hands back the sizes React Flow measured,
|
|
1705
|
+
* leaves the wheel to the page, holds the view to the drawing the guard
|
|
1706
|
+
* measured, says when none of the drawing is in view, and draws the canvas
|
|
1707
|
+
* again when a fit cannot bring it back.
|
|
1708
|
+
*
|
|
1709
|
+
* The view stays the automatic framing, which the guard fits again when the
|
|
1710
|
+
* canvas is resized, until a gesture really moves it: a press on a map the
|
|
1711
|
+
* pan extent pins in place comes back a rounding error away, and React Flow
|
|
1712
|
+
* reports that as a move. The out-of-view notice sits at the canvas's
|
|
1713
|
+
* top-left, and a map drawn in a background tab (where nothing is measured)
|
|
1714
|
+
* is judged only once the tab is seen.
|
|
1715
|
+
*/
|
|
1716
|
+
describe("keeping the map on screen (issue #4117)", () => {
|
|
1717
|
+
test("the wheel scrolls the page, a double-click does not zoom and cards cannot be dragged away", async () => {
|
|
1718
|
+
await renderGraph();
|
|
1719
|
+
const flow: MockFlowProps = flowProps();
|
|
1720
|
+
expect(flow.zoomOnScroll).toBe(false);
|
|
1721
|
+
expect(flow.preventScrolling).toBe(false);
|
|
1722
|
+
expect(flow.panOnScroll).not.toBe(true);
|
|
1723
|
+
// React Flow reads Ctrl + wheel as a pinch, so both still zoom.
|
|
1724
|
+
expect(flow.zoomOnPinch).not.toBe(false);
|
|
1725
|
+
expect(flow.zoomOnDoubleClick).toBe(false);
|
|
1726
|
+
// Holding a card past the edge used to pan the whole map away.
|
|
1727
|
+
expect(flow.nodesDraggable).toBe(false);
|
|
1728
|
+
// Framing is the guard's, once React Flow has measured the drawing.
|
|
1729
|
+
expect(flow.fitView).toBeUndefined();
|
|
1730
|
+
expect(flow.onInit).toBeUndefined();
|
|
1731
|
+
});
|
|
1732
|
+
|
|
1733
|
+
test("hovering or selecting a connection redraws connections, never the cards", async () => {
|
|
1734
|
+
await renderGraph();
|
|
1735
|
+
const cards: Array<Node> = flowProps().nodes;
|
|
1736
|
+
const connections: Array<Edge> = flowProps().edges;
|
|
1737
|
+
const rendersBefore: number = mockFlowRenders.length;
|
|
1738
|
+
|
|
1739
|
+
fireEvent.mouseEnter(screen.getByTestId("map-edge-web->api"));
|
|
1740
|
+
expect(screen.getByTestId("map-edge-web->api")).toHaveTextContent(
|
|
1741
|
+
"600/min",
|
|
1742
|
+
);
|
|
1743
|
+
expect(flowProps().edges).not.toBe(connections);
|
|
1744
|
+
fireEvent.mouseLeave(screen.getByTestId("map-edge-web->api"));
|
|
1745
|
+
expect(screen.getByTestId("map-edge-web->api")).toHaveTextContent(
|
|
1746
|
+
"Connection",
|
|
1747
|
+
);
|
|
1748
|
+
|
|
1749
|
+
fireEvent.click(screen.getByTestId("map-edge-api->postgres"));
|
|
1750
|
+
expect(
|
|
1751
|
+
screen.getByRole("dialog", { name: "Connection details" }),
|
|
1752
|
+
).toBeInTheDocument();
|
|
1753
|
+
// The selected connection is drawn thicker...
|
|
1754
|
+
expect(
|
|
1755
|
+
Number(edgeById(flowProps().edges, "api->postgres").style?.strokeWidth),
|
|
1756
|
+
).toBeGreaterThan(
|
|
1757
|
+
Number(edgeById(connections, "api->postgres").style?.strokeWidth),
|
|
1758
|
+
);
|
|
1759
|
+
|
|
1760
|
+
fireEvent.change(
|
|
1761
|
+
screen.getByRole("combobox", { name: "Connection labels" }),
|
|
1762
|
+
{ target: { value: "latency" } },
|
|
1763
|
+
);
|
|
1764
|
+
expect(screen.getByTestId("map-edge-api->postgres")).toHaveTextContent(
|
|
1765
|
+
"25ms",
|
|
1766
|
+
);
|
|
1767
|
+
|
|
1768
|
+
// ...but not one of these renders handed React Flow new cards.
|
|
1769
|
+
const renders: Array<MockFlowProps> = mockFlowRenders.slice(rendersBefore);
|
|
1770
|
+
expect(renders.length).toBeGreaterThanOrEqual(4);
|
|
1771
|
+
for (const flowRender of renders) {
|
|
1772
|
+
expect(flowRender.nodes).toBe(cards);
|
|
1773
|
+
}
|
|
1774
|
+
});
|
|
1775
|
+
|
|
1776
|
+
test("sizes React Flow measured are handed back on every card drawn again", async () => {
|
|
1777
|
+
await renderGraph();
|
|
1778
|
+
const unmeasured: Array<Node> = flowProps().nodes;
|
|
1779
|
+
expect(nodeById(unmeasured, "web")).not.toHaveProperty("width");
|
|
1780
|
+
act(() => {
|
|
1781
|
+
flowProps().onNodesChange([
|
|
1782
|
+
{
|
|
1783
|
+
id: "web",
|
|
1784
|
+
type: "dimensions",
|
|
1785
|
+
dimensions: { width: 256, height: 110 },
|
|
1786
|
+
},
|
|
1787
|
+
{
|
|
1788
|
+
id: "api",
|
|
1789
|
+
type: "dimensions",
|
|
1790
|
+
dimensions: { width: 256, height: 128 },
|
|
1791
|
+
},
|
|
1792
|
+
// A card that grew is measured again; the latest size is the one kept.
|
|
1793
|
+
{
|
|
1794
|
+
id: "api",
|
|
1795
|
+
type: "dimensions",
|
|
1796
|
+
dimensions: { width: 256, height: 146 },
|
|
1797
|
+
},
|
|
1798
|
+
]);
|
|
1799
|
+
});
|
|
1800
|
+
|
|
1801
|
+
// Selecting a card draws every card again.
|
|
1802
|
+
fireEvent.click(screen.getByTestId("map-node-api"));
|
|
1803
|
+
const redrawn: Array<Node> = flowProps().nodes;
|
|
1804
|
+
expect(redrawn).not.toBe(unmeasured);
|
|
1805
|
+
expect(nodeById(redrawn, "api").data.selected).toBe(true);
|
|
1806
|
+
expect(nodeById(redrawn, "web")).toMatchObject({ width: 256, height: 110 });
|
|
1807
|
+
expect(nodeById(redrawn, "api")).toMatchObject({ width: 256, height: 146 });
|
|
1808
|
+
// A card React Flow has not measured yet is left for it to measure.
|
|
1809
|
+
expect(nodeById(redrawn, "postgres")).not.toHaveProperty("width");
|
|
1810
|
+
expect(nodeById(redrawn, "postgres")).not.toHaveProperty("height");
|
|
1811
|
+
});
|
|
1812
|
+
|
|
1813
|
+
test("cards drawn again when the incident overlay arrives keep their measured sizes", async () => {
|
|
1814
|
+
let releaseOverlay: () => void = (): void => {};
|
|
1815
|
+
mockStatusesReady = new Promise<void>((resolve: () => void): void => {
|
|
1816
|
+
releaseOverlay = resolve;
|
|
1817
|
+
});
|
|
1818
|
+
mockStatuses.set("api", {
|
|
1819
|
+
serviceId: "api-id",
|
|
1820
|
+
activeIncidentCount: 2,
|
|
1821
|
+
worstIncidentSeverityName: "Critical",
|
|
1822
|
+
worstIncidentSeverityColor: "#dc2626",
|
|
1823
|
+
incidents: [],
|
|
1824
|
+
activeAlertCount: 0,
|
|
1825
|
+
worstAlertSeverityName: null,
|
|
1826
|
+
worstAlertSeverityColor: null,
|
|
1827
|
+
alerts: [],
|
|
1828
|
+
});
|
|
1829
|
+
await renderGraph();
|
|
1830
|
+
const drawn: Array<Node> = flowProps().nodes;
|
|
1831
|
+
expect(nodeById(drawn, "api").data.entry.incidentCount).toBe(0);
|
|
1832
|
+
act(() => {
|
|
1833
|
+
flowProps().onNodesChange(
|
|
1834
|
+
drawn.map((node: Node): NodeChange => {
|
|
1835
|
+
return {
|
|
1836
|
+
id: node.id,
|
|
1837
|
+
type: "dimensions",
|
|
1838
|
+
dimensions: { width: 256, height: 128 },
|
|
1839
|
+
};
|
|
1840
|
+
}),
|
|
1841
|
+
);
|
|
1842
|
+
});
|
|
1843
|
+
|
|
1844
|
+
// The overlay lands just after React Flow measured the cards.
|
|
1845
|
+
await act(async () => {
|
|
1846
|
+
releaseOverlay();
|
|
1847
|
+
});
|
|
1848
|
+
const redrawn: Array<Node> = flowProps().nodes;
|
|
1849
|
+
expect(redrawn).not.toBe(drawn);
|
|
1850
|
+
expect(nodeById(redrawn, "api").data.entry.incidentCount).toBe(2);
|
|
1851
|
+
expect(idsOf(redrawn)).toEqual(idsOf(drawn));
|
|
1852
|
+
for (const node of redrawn) {
|
|
1853
|
+
expect(node).toMatchObject({ width: 256, height: 128 });
|
|
1854
|
+
}
|
|
1855
|
+
});
|
|
1856
|
+
|
|
1857
|
+
test("only a real measured size is kept: other changes and empty sizes are not", async () => {
|
|
1858
|
+
await renderGraph();
|
|
1859
|
+
const before: Array<Node> = flowProps().nodes;
|
|
1860
|
+
act(() => {
|
|
1861
|
+
flowProps().onNodesChange([
|
|
1862
|
+
{ id: "web", type: "position", position: { x: 40, y: 40 } },
|
|
1863
|
+
{ id: "web", type: "select", selected: true },
|
|
1864
|
+
{ id: "api", type: "dimensions" },
|
|
1865
|
+
{
|
|
1866
|
+
id: "api",
|
|
1867
|
+
type: "dimensions",
|
|
1868
|
+
dimensions: { width: 0, height: 0 },
|
|
1869
|
+
},
|
|
1870
|
+
{
|
|
1871
|
+
id: "postgres",
|
|
1872
|
+
type: "dimensions",
|
|
1873
|
+
dimensions: { width: 256, height: 0 },
|
|
1874
|
+
},
|
|
1875
|
+
{ id: "postgres", type: "remove" },
|
|
1876
|
+
]);
|
|
1877
|
+
});
|
|
1878
|
+
fireEvent.click(screen.getByTestId("map-node-web"));
|
|
1879
|
+
expect(flowProps().nodes).not.toBe(before);
|
|
1880
|
+
for (const node of flowProps().nodes) {
|
|
1881
|
+
expect(node).not.toHaveProperty("width");
|
|
1882
|
+
expect(node).not.toHaveProperty("height");
|
|
1883
|
+
}
|
|
1884
|
+
});
|
|
1885
|
+
|
|
1886
|
+
test("the view pans within the drawing the guard measured, and a new canvas starts unbounded", async () => {
|
|
1887
|
+
await renderGraph();
|
|
1888
|
+
// Until the guard has measured the drawing there is nothing to hold to.
|
|
1889
|
+
expect(flowProps().translateExtent).toEqual(UNBOUNDED_FLOW_EXTENT);
|
|
1890
|
+
expect(guardProps().panMargin).toBe(SERVICE_MAP_PAN_MARGIN);
|
|
1891
|
+
/*
|
|
1892
|
+
* The slack around the drawing is less than the least the canvas ever
|
|
1893
|
+
* shows, so the view cannot come to rest in it with no card in sight.
|
|
1894
|
+
*/
|
|
1895
|
+
const canvasHeight: number = parseFloat(
|
|
1896
|
+
screen.getByTestId("service-map-canvas").style.height,
|
|
1897
|
+
);
|
|
1898
|
+
expect(SERVICE_MAP_PAN_MARGIN).toBeGreaterThan(0);
|
|
1899
|
+
expect(SERVICE_MAP_PAN_MARGIN).toBeLessThan(
|
|
1900
|
+
canvasHeight / (flowProps().maxZoom || 1),
|
|
1901
|
+
);
|
|
1902
|
+
|
|
1903
|
+
const measured: FlowExtent = [
|
|
1904
|
+
[-128, -128],
|
|
1905
|
+
[1060, 404],
|
|
1906
|
+
];
|
|
1907
|
+
act(() => {
|
|
1908
|
+
guardProps().onExtentChange(measured);
|
|
1909
|
+
});
|
|
1910
|
+
expect(flowProps().translateExtent).toBe(measured);
|
|
1911
|
+
|
|
1912
|
+
fireEvent.click(screen.getByTestId("service-map-view-list"));
|
|
1913
|
+
const rendersBefore: number = mockFlowRenders.length;
|
|
1914
|
+
fireEvent.click(screen.getByTestId("service-map-view-map"));
|
|
1915
|
+
expect(mockFlowMounts).toBe(2);
|
|
1916
|
+
/*
|
|
1917
|
+
* The old drawing's extent must not hold the new canvas before its
|
|
1918
|
+
* guard has measured the new drawing.
|
|
1919
|
+
*/
|
|
1920
|
+
expect(mockFlowRenders[rendersBefore]?.translateExtent).toEqual(
|
|
1921
|
+
UNBOUNDED_FLOW_EXTENT,
|
|
1922
|
+
);
|
|
1923
|
+
expect(flowProps().translateExtent).toEqual(UNBOUNDED_FLOW_EXTENT);
|
|
1924
|
+
});
|
|
1925
|
+
|
|
1926
|
+
test("moving the view by hand ends the automatic framing; fitting restores it", async () => {
|
|
1927
|
+
await renderGraph();
|
|
1928
|
+
const autoFrame: React.MutableRefObject<boolean> = guardProps().autoFrame;
|
|
1929
|
+
expect(autoFrame.current).toBe(true);
|
|
1930
|
+
|
|
1931
|
+
// A pan or zoom by hand: a resized canvas no longer re-frames the map.
|
|
1932
|
+
gesture(FRAMED_VIEW, { x: 120, y: -40, zoom: 0.6 });
|
|
1933
|
+
expect(autoFrame.current).toBe(false);
|
|
1934
|
+
fireEvent.click(screen.getByTestId("service-map-fit"));
|
|
1935
|
+
expect(autoFrame.current).toBe(true);
|
|
1936
|
+
|
|
1937
|
+
act(() => {
|
|
1938
|
+
controlsProps().onZoomIn();
|
|
1939
|
+
});
|
|
1940
|
+
expect(autoFrame.current).toBe(false);
|
|
1941
|
+
act(() => {
|
|
1942
|
+
controlsProps().onFitView();
|
|
1943
|
+
});
|
|
1944
|
+
expect(autoFrame.current).toBe(true);
|
|
1945
|
+
act(() => {
|
|
1946
|
+
controlsProps().onZoomOut();
|
|
1947
|
+
});
|
|
1948
|
+
expect(autoFrame.current).toBe(false);
|
|
1949
|
+
|
|
1950
|
+
// Every fit frames the map the same way, whichever button asked.
|
|
1951
|
+
expect(controlsProps().fitViewOptions).toEqual(MAP_FRAMING);
|
|
1952
|
+
expect(controlsProps().showInteractive).toBe(false);
|
|
1953
|
+
expect(guardProps().fitViewOptions).toEqual(MAP_FRAMING);
|
|
1954
|
+
expect(guardProps().autoFrame).toBe(autoFrame);
|
|
1955
|
+
});
|
|
1956
|
+
|
|
1957
|
+
test("a press that leaves the view where it was keeps the automatic framing", async () => {
|
|
1958
|
+
await renderGraph();
|
|
1959
|
+
const autoFrame: React.MutableRefObject<boolean> = guardProps().autoFrame;
|
|
1960
|
+
|
|
1961
|
+
/*
|
|
1962
|
+
* A press, or a drag attempt, on a map the pan extent pins in place:
|
|
1963
|
+
* nothing moved, but React Flow reports the view a rounding error away.
|
|
1964
|
+
* Taking that for a move left the next narrower window with the drawing
|
|
1965
|
+
* off the canvas.
|
|
1966
|
+
*/
|
|
1967
|
+
gesture(FRAMED_VIEW, FRAMED_VIEW_AFTER_PRESS);
|
|
1968
|
+
expect(autoFrame.current).toBe(true);
|
|
1969
|
+
|
|
1970
|
+
// Nor does a zoom change of 0.05%.
|
|
1971
|
+
gesture(FRAMED_VIEW, { ...FRAMED_VIEW, zoom: FRAMED_VIEW.zoom * 1.0005 });
|
|
1972
|
+
expect(autoFrame.current).toBe(true);
|
|
1973
|
+
});
|
|
1974
|
+
|
|
1975
|
+
test("the slip of a click, up to 4 px each way, keeps the automatic framing", async () => {
|
|
1976
|
+
await renderGraph();
|
|
1977
|
+
const autoFrame: React.MutableRefObject<boolean> = guardProps().autoFrame;
|
|
1978
|
+
/*
|
|
1979
|
+
* The pointer slips a pixel or two during a click, and where the pan
|
|
1980
|
+
* extent leaves the view room, the view slips with it. Taking that for
|
|
1981
|
+
* the user's move left the drawing clipped by the next narrower window.
|
|
1982
|
+
* The slop allowed is 4 px along each axis, the drag threshold operating
|
|
1983
|
+
* systems use.
|
|
1984
|
+
*/
|
|
1985
|
+
const slips: Array<[string, Viewport]> = [
|
|
1986
|
+
["a pixel right", { ...SMALL_MAP_VIEW, x: SMALL_MAP_VIEW.x + 1 }],
|
|
1987
|
+
["a pixel up", { ...SMALL_MAP_VIEW, y: SMALL_MAP_VIEW.y - 1 }],
|
|
1988
|
+
["4 px left", { ...SMALL_MAP_VIEW, x: SMALL_MAP_VIEW.x - 4 }],
|
|
1989
|
+
["4 px down", { ...SMALL_MAP_VIEW, y: SMALL_MAP_VIEW.y + 4 }],
|
|
1990
|
+
[
|
|
1991
|
+
"4 px right and 4 px up",
|
|
1992
|
+
{ ...SMALL_MAP_VIEW, x: SMALL_MAP_VIEW.x + 4, y: SMALL_MAP_VIEW.y - 4 },
|
|
1993
|
+
],
|
|
1994
|
+
[
|
|
1995
|
+
"4 px left and 0.05% closer",
|
|
1996
|
+
{
|
|
1997
|
+
x: SMALL_MAP_VIEW.x - 4,
|
|
1998
|
+
y: SMALL_MAP_VIEW.y,
|
|
1999
|
+
zoom: SMALL_MAP_VIEW.zoom * 1.0005,
|
|
2000
|
+
},
|
|
2001
|
+
],
|
|
2002
|
+
];
|
|
2003
|
+
for (const [slip, to] of slips) {
|
|
2004
|
+
gesture(SMALL_MAP_VIEW, to);
|
|
2005
|
+
expect([slip, autoFrame.current]).toEqual([slip, true]);
|
|
2006
|
+
}
|
|
2007
|
+
});
|
|
2008
|
+
|
|
2009
|
+
test("a pan of more than 4 px or a zoom of 0.2% ends the automatic framing", async () => {
|
|
2010
|
+
await renderGraph();
|
|
2011
|
+
const autoFrame: React.MutableRefObject<boolean> = guardProps().autoFrame;
|
|
2012
|
+
const moves: Array<[string, Viewport]> = [
|
|
2013
|
+
["4.1 px right", { ...SMALL_MAP_VIEW, x: SMALL_MAP_VIEW.x + 4.1 }],
|
|
2014
|
+
["4.1 px left", { ...SMALL_MAP_VIEW, x: SMALL_MAP_VIEW.x - 4.1 }],
|
|
2015
|
+
["4.1 px up", { ...SMALL_MAP_VIEW, y: SMALL_MAP_VIEW.y - 4.1 }],
|
|
2016
|
+
["4.1 px down", { ...SMALL_MAP_VIEW, y: SMALL_MAP_VIEW.y + 4.1 }],
|
|
2017
|
+
["0.2% closer", { ...SMALL_MAP_VIEW, zoom: SMALL_MAP_VIEW.zoom * 1.002 }],
|
|
2018
|
+
[
|
|
2019
|
+
"0.2% further",
|
|
2020
|
+
{ ...SMALL_MAP_VIEW, zoom: SMALL_MAP_VIEW.zoom / 1.002 },
|
|
2021
|
+
],
|
|
2022
|
+
];
|
|
2023
|
+
for (const [move, to] of moves) {
|
|
2024
|
+
// Fit to screen hands the view back to the automatic framing.
|
|
2025
|
+
fireEvent.click(screen.getByTestId("service-map-fit"));
|
|
2026
|
+
expect([move, autoFrame.current]).toEqual([move, true]);
|
|
2027
|
+
gesture(SMALL_MAP_VIEW, to);
|
|
2028
|
+
expect([move, autoFrame.current]).toEqual([move, false]);
|
|
2029
|
+
}
|
|
2030
|
+
});
|
|
2031
|
+
|
|
2032
|
+
test("a move reported without its start ends the automatic framing", async () => {
|
|
2033
|
+
await renderGraph();
|
|
2034
|
+
const autoFrame: React.MutableRefObject<boolean> = guardProps().autoFrame;
|
|
2035
|
+
// With nothing to compare it with, a move counts as a move, never as noise.
|
|
2036
|
+
endGesture(FRAMED_VIEW_AFTER_PRESS);
|
|
2037
|
+
expect(autoFrame.current).toBe(false);
|
|
2038
|
+
});
|
|
2039
|
+
|
|
2040
|
+
test("each gesture is judged from its own start, which is forgotten once it ends", async () => {
|
|
2041
|
+
await renderGraph();
|
|
2042
|
+
const autoFrame: React.MutableRefObject<boolean> = guardProps().autoFrame;
|
|
2043
|
+
|
|
2044
|
+
// React Flow reports no end for a press that changed nothing at all...
|
|
2045
|
+
startGesture({ x: 64, y: 32, zoom: 1 });
|
|
2046
|
+
// ...and none for a move of its own, such as a fit.
|
|
2047
|
+
fireEvent.click(screen.getByTestId("service-map-fit"));
|
|
2048
|
+
expect(autoFrame.current).toBe(true);
|
|
2049
|
+
|
|
2050
|
+
// The next press is judged from where it began, not where the last did.
|
|
2051
|
+
gesture(FRAMED_VIEW, FRAMED_VIEW_AFTER_PRESS);
|
|
2052
|
+
expect(autoFrame.current).toBe(true);
|
|
2053
|
+
|
|
2054
|
+
// Its start went with it: a later end on its own is a move.
|
|
2055
|
+
endGesture(FRAMED_VIEW_AFTER_PRESS);
|
|
2056
|
+
expect(autoFrame.current).toBe(false);
|
|
2057
|
+
});
|
|
2058
|
+
|
|
2059
|
+
test("an empty canvas is announced only once it has stayed empty for a moment", async () => {
|
|
2060
|
+
jest.useFakeTimers();
|
|
2061
|
+
await renderGraph();
|
|
2062
|
+
// A new drawing is hidden for a frame or two while React Flow measures it.
|
|
2063
|
+
reportDrawingInView(false);
|
|
2064
|
+
advance(OUT_OF_VIEW_NOTICE_DELAY_MS - 1);
|
|
2065
|
+
reportDrawingInView(true);
|
|
2066
|
+
advance(OUT_OF_VIEW_NOTICE_DELAY_MS * 2);
|
|
2067
|
+
expect(outOfViewNotice()).not.toBeInTheDocument();
|
|
2068
|
+
|
|
2069
|
+
reportDrawingInView(false);
|
|
2070
|
+
advance(OUT_OF_VIEW_NOTICE_DELAY_MS - 1);
|
|
2071
|
+
expect(outOfViewNotice()).not.toBeInTheDocument();
|
|
2072
|
+
advance(1);
|
|
2073
|
+
const notice: HTMLElement = screen.getByTestId("service-map-out-of-view");
|
|
2074
|
+
expect(within(notice).getByRole("status")).toHaveTextContent(
|
|
2075
|
+
"The map is out of view.",
|
|
2076
|
+
);
|
|
2077
|
+
expect(
|
|
2078
|
+
within(notice).getByRole("button", { name: "Fit to screen" }),
|
|
2079
|
+
).toBeInTheDocument();
|
|
2080
|
+
|
|
2081
|
+
// As soon as any of the drawing is back in view, the notice goes.
|
|
2082
|
+
reportDrawingInView(true);
|
|
2083
|
+
expect(outOfViewNotice()).not.toBeInTheDocument();
|
|
2084
|
+
});
|
|
2085
|
+
|
|
2086
|
+
test("the notice sits on the canvas's left, clear of a drawer, and still announces itself", async () => {
|
|
2087
|
+
jest.useFakeTimers();
|
|
2088
|
+
await renderGraph();
|
|
2089
|
+
reportDrawingInView(false);
|
|
2090
|
+
advance(OUT_OF_VIEW_NOTICE_DELAY_MS);
|
|
2091
|
+
|
|
2092
|
+
const notice: HTMLElement = screen.getByTestId("service-map-out-of-view");
|
|
2093
|
+
// Over the whole canvas, and cut off at its edges...
|
|
2094
|
+
expect(screen.getByTestId("service-map-canvas")).toContainElement(notice);
|
|
2095
|
+
expect(notice).toHaveClass("absolute", "inset-0", "overflow-hidden");
|
|
2096
|
+
// ...yet only the notice itself takes the pointer; the rest is the map's.
|
|
2097
|
+
expect(notice).toHaveClass("pointer-events-none");
|
|
2098
|
+
|
|
2099
|
+
const box: HTMLElement = outOfViewNoticeBox();
|
|
2100
|
+
expect(box).toHaveClass("pointer-events-auto", "absolute");
|
|
2101
|
+
/*
|
|
2102
|
+
* On the canvas's left, clear of a drawer open on the right, and never
|
|
2103
|
+
* wider than the canvas less an inset on each side. How far down is for
|
|
2104
|
+
* the part of the canvas on screen to say (see noticeOffsetInCanvas); a
|
|
2105
|
+
* canvas with no height, which is all jsdom lays out, keeps the notice
|
|
2106
|
+
* at the inset.
|
|
2107
|
+
*/
|
|
2108
|
+
expect(box.style.left).toBe("16px");
|
|
2109
|
+
expect(box.style.maxWidth).toBe("calc(100% - 32px)");
|
|
2110
|
+
expect(box.style.top).toBe("16px");
|
|
2111
|
+
expect(box).toHaveTextContent("The map is out of view.");
|
|
2112
|
+
expect(
|
|
2113
|
+
within(box).getByRole("button", { name: "Fit to screen" }),
|
|
2114
|
+
).toBeInTheDocument();
|
|
2115
|
+
});
|
|
2116
|
+
|
|
2117
|
+
test("the notice follows the part of the canvas on screen as the page scrolls and the window resizes", async () => {
|
|
2118
|
+
jest.useFakeTimers();
|
|
2119
|
+
await renderGraph();
|
|
2120
|
+
/*
|
|
2121
|
+
* A canvas 880 px tall whose top the page has scrolled 280 px past, in a
|
|
2122
|
+
* window 600 px high: the notice goes in the middle of the part of the
|
|
2123
|
+
* canvas on screen, less half its own height.
|
|
2124
|
+
*/
|
|
2125
|
+
const page: PageLayout = {
|
|
2126
|
+
canvasTop: -280,
|
|
2127
|
+
canvasHeight: 880,
|
|
2128
|
+
windowHeight: 600,
|
|
2129
|
+
noticeHeight: 54,
|
|
2130
|
+
};
|
|
2131
|
+
const measureCanvas: MockFunction = standInForLayout(page);
|
|
2132
|
+
reportDrawingInView(false);
|
|
2133
|
+
advance(OUT_OF_VIEW_NOTICE_DELAY_MS);
|
|
2134
|
+
expect(outOfViewNoticeBox().style.top).toBe(
|
|
2135
|
+
`${noticeOffsetInCanvas(-280, 880, 600, 54, 16)}px`,
|
|
2136
|
+
);
|
|
2137
|
+
|
|
2138
|
+
/*
|
|
2139
|
+
* The page scrolls 200 px further: the notice follows in the next frame,
|
|
2140
|
+
* measuring the canvas once however many scroll events the frame brings.
|
|
2141
|
+
*/
|
|
2142
|
+
page.canvasTop = -480;
|
|
2143
|
+
const measured: number = measureCanvas.mock.calls.length;
|
|
2144
|
+
scrollPage();
|
|
2145
|
+
scrollPage();
|
|
2146
|
+
nextFrame();
|
|
2147
|
+
expect(measureCanvas).toHaveBeenCalledTimes(measured + 1);
|
|
2148
|
+
expect(outOfViewNoticeBox().style.top).toBe(
|
|
2149
|
+
`${noticeOffsetInCanvas(-480, 880, 600, 54, 16)}px`,
|
|
2150
|
+
);
|
|
2151
|
+
|
|
2152
|
+
// The window is made shorter.
|
|
2153
|
+
page.windowHeight = 300;
|
|
2154
|
+
resizeWindow();
|
|
2155
|
+
nextFrame();
|
|
2156
|
+
expect(outOfViewNoticeBox().style.top).toBe(
|
|
2157
|
+
`${noticeOffsetInCanvas(-480, 880, 300, 54, 16)}px`,
|
|
2158
|
+
);
|
|
2159
|
+
|
|
2160
|
+
/*
|
|
2161
|
+
* A page that scrolls in an element rather than the document: the
|
|
2162
|
+
* element's scroll does not bubble up to the document, and still counts.
|
|
2163
|
+
*/
|
|
2164
|
+
page.canvasTop = -380;
|
|
2165
|
+
scrollPage(document.body);
|
|
2166
|
+
nextFrame();
|
|
2167
|
+
expect(outOfViewNoticeBox().style.top).toBe(
|
|
2168
|
+
`${noticeOffsetInCanvas(-380, 880, 300, 54, 16)}px`,
|
|
2169
|
+
);
|
|
2170
|
+
});
|
|
2171
|
+
|
|
2172
|
+
test("once the notice goes, the page's scrolls and resizes are no longer followed", async () => {
|
|
2173
|
+
jest.useFakeTimers();
|
|
2174
|
+
await renderGraph();
|
|
2175
|
+
const page: PageLayout = {
|
|
2176
|
+
canvasTop: -280,
|
|
2177
|
+
canvasHeight: 880,
|
|
2178
|
+
windowHeight: 600,
|
|
2179
|
+
noticeHeight: 54,
|
|
2180
|
+
};
|
|
2181
|
+
const measureCanvas: MockFunction = standInForLayout(page);
|
|
2182
|
+
reportDrawingInView(false);
|
|
2183
|
+
advance(OUT_OF_VIEW_NOTICE_DELAY_MS);
|
|
2184
|
+
expect(outOfViewNotice()).toBeInTheDocument();
|
|
2185
|
+
|
|
2186
|
+
/*
|
|
2187
|
+
* The drawing is back in view: the notice goes, and stops listening. No
|
|
2188
|
+
* frame is pending as it goes, so a listener left behind would ask for
|
|
2189
|
+
* one, and be caught measuring the canvas.
|
|
2190
|
+
*/
|
|
2191
|
+
reportDrawingInView(true);
|
|
2192
|
+
expect(outOfViewNotice()).not.toBeInTheDocument();
|
|
2193
|
+
const measuredAtHiding: number = measureCanvas.mock.calls.length;
|
|
2194
|
+
page.canvasTop = -480;
|
|
2195
|
+
page.windowHeight = 300;
|
|
2196
|
+
scrollPage();
|
|
2197
|
+
scrollPage(document.body);
|
|
2198
|
+
resizeWindow();
|
|
2199
|
+
nextFrame();
|
|
2200
|
+
expect(measureCanvas).toHaveBeenCalledTimes(measuredAtHiding);
|
|
2201
|
+
|
|
2202
|
+
// Shown again, the notice is placed for the page as it is now...
|
|
2203
|
+
reportDrawingInView(false);
|
|
2204
|
+
advance(OUT_OF_VIEW_NOTICE_DELAY_MS);
|
|
2205
|
+
expect(outOfViewNoticeBox().style.top).toBe(
|
|
2206
|
+
`${noticeOffsetInCanvas(-480, 880, 300, 54, 16)}px`,
|
|
2207
|
+
);
|
|
2208
|
+
// ...and follows it again.
|
|
2209
|
+
page.canvasTop = -380;
|
|
2210
|
+
scrollPage();
|
|
2211
|
+
nextFrame();
|
|
2212
|
+
expect(outOfViewNoticeBox().style.top).toBe(
|
|
2213
|
+
`${noticeOffsetInCanvas(-380, 880, 300, 54, 16)}px`,
|
|
2214
|
+
);
|
|
2215
|
+
|
|
2216
|
+
// A frame a scroll asked for is dropped if the notice goes before it.
|
|
2217
|
+
page.canvasTop = -280;
|
|
2218
|
+
scrollPage();
|
|
2219
|
+
reportDrawingInView(true);
|
|
2220
|
+
expect(outOfViewNotice()).not.toBeInTheDocument();
|
|
2221
|
+
const measuredAtSecondHiding: number = measureCanvas.mock.calls.length;
|
|
2222
|
+
nextFrame();
|
|
2223
|
+
expect(measureCanvas).toHaveBeenCalledTimes(measuredAtSecondHiding);
|
|
2224
|
+
});
|
|
2225
|
+
|
|
2226
|
+
test("a map drawn in a background tab is judged only once the tab is seen", async () => {
|
|
2227
|
+
jest.useFakeTimers();
|
|
2228
|
+
setTabVisibility("hidden");
|
|
2229
|
+
await renderGraph();
|
|
2230
|
+
// A hidden tab measures nothing, so no card is reported in view...
|
|
2231
|
+
reportDrawingInView(false);
|
|
2232
|
+
advance(OUT_OF_VIEW_NOTICE_DELAY_MS * 5);
|
|
2233
|
+
// ...and nothing is said about a map no one can see.
|
|
2234
|
+
expect(outOfViewNotice()).not.toBeInTheDocument();
|
|
2235
|
+
// A visibilitychange that leaves the tab hidden changes nothing.
|
|
2236
|
+
changeTabVisibility("hidden");
|
|
2237
|
+
advance(OUT_OF_VIEW_NOTICE_DELAY_MS * 2);
|
|
2238
|
+
expect(outOfViewNotice()).not.toBeInTheDocument();
|
|
2239
|
+
|
|
2240
|
+
// Once the tab is seen the map has the usual moment to be measured.
|
|
2241
|
+
changeTabVisibility("visible");
|
|
2242
|
+
advance(OUT_OF_VIEW_NOTICE_DELAY_MS - 1);
|
|
2243
|
+
expect(outOfViewNotice()).not.toBeInTheDocument();
|
|
2244
|
+
advance(1);
|
|
2245
|
+
const notice: HTMLElement = screen.getByTestId("service-map-out-of-view");
|
|
2246
|
+
expect(within(notice).getByRole("status")).toHaveTextContent(
|
|
2247
|
+
"The map is out of view.",
|
|
2248
|
+
);
|
|
2249
|
+
});
|
|
2250
|
+
|
|
2251
|
+
test("a map measured in view once its tab is seen never shows the notice", async () => {
|
|
2252
|
+
jest.useFakeTimers();
|
|
2253
|
+
setTabVisibility("hidden");
|
|
2254
|
+
await renderGraph();
|
|
2255
|
+
reportDrawingInView(false);
|
|
2256
|
+
advance(OUT_OF_VIEW_NOTICE_DELAY_MS * 5);
|
|
2257
|
+
|
|
2258
|
+
changeTabVisibility("visible");
|
|
2259
|
+
advance(OUT_OF_VIEW_NOTICE_DELAY_MS - 1);
|
|
2260
|
+
// React Flow measures the cards in the first frames back: they are in view.
|
|
2261
|
+
reportDrawingInView(true);
|
|
2262
|
+
advance(OUT_OF_VIEW_NOTICE_DELAY_MS * 5);
|
|
2263
|
+
expect(outOfViewNotice()).not.toBeInTheDocument();
|
|
2264
|
+
});
|
|
2265
|
+
|
|
2266
|
+
test("a map back in view before its tab is seen leaves nothing waiting for the tab", async () => {
|
|
2267
|
+
jest.useFakeTimers();
|
|
2268
|
+
setTabVisibility("hidden");
|
|
2269
|
+
await renderGraph();
|
|
2270
|
+
reportDrawingInView(false);
|
|
2271
|
+
advance(OUT_OF_VIEW_NOTICE_DELAY_MS);
|
|
2272
|
+
/*
|
|
2273
|
+
* The guard needs no measuring to say the drawing is in view again: it
|
|
2274
|
+
* does for a canvas with no cards, and for one that goes.
|
|
2275
|
+
*/
|
|
2276
|
+
reportDrawingInView(true);
|
|
2277
|
+
|
|
2278
|
+
changeTabVisibility("visible");
|
|
2279
|
+
advance(OUT_OF_VIEW_NOTICE_DELAY_MS * 5);
|
|
2280
|
+
expect(outOfViewNotice()).not.toBeInTheDocument();
|
|
2281
|
+
});
|
|
2282
|
+
|
|
2283
|
+
test("the notice's Fit to screen frames the map with the canvas on screen", async () => {
|
|
2284
|
+
jest.useFakeTimers();
|
|
2285
|
+
await renderGraph();
|
|
2286
|
+
const instance: MockFlowInstance = liveInstance();
|
|
2287
|
+
gesture(FRAMED_VIEW, { x: -4000, y: 0, zoom: 0.4 });
|
|
2288
|
+
expect(guardProps().autoFrame.current).toBe(false);
|
|
2289
|
+
reportDrawingInView(false);
|
|
2290
|
+
advance(OUT_OF_VIEW_NOTICE_DELAY_MS);
|
|
2291
|
+
|
|
2292
|
+
fireEvent.click(
|
|
2293
|
+
within(screen.getByTestId("service-map-out-of-view")).getByRole(
|
|
2294
|
+
"button",
|
|
2295
|
+
{ name: "Fit to screen" },
|
|
2296
|
+
),
|
|
2297
|
+
);
|
|
2298
|
+
expect(instance.fitView).toHaveBeenCalledTimes(1);
|
|
2299
|
+
expect(instance.fitView).toHaveBeenCalledWith(ANIMATED_FIT);
|
|
2300
|
+
expect(guardProps().autoFrame.current).toBe(true);
|
|
2301
|
+
expect(mockFlowMounts).toBe(1);
|
|
2302
|
+
// The fit brings the drawing back into view, and the notice goes.
|
|
2303
|
+
reportDrawingInView(true);
|
|
2304
|
+
expect(outOfViewNotice()).not.toBeInTheDocument();
|
|
2305
|
+
});
|
|
2306
|
+
|
|
2307
|
+
test("a canvas that cannot fit is drawn again from scratch", async () => {
|
|
2308
|
+
jest.useFakeTimers();
|
|
2309
|
+
await renderGraph();
|
|
2310
|
+
const stuck: MockFlowInstance = liveInstance();
|
|
2311
|
+
// React Flow has no measured card to frame: fitView does nothing.
|
|
2312
|
+
stuck.fitView.mockReturnValue(false);
|
|
2313
|
+
reportDrawingInView(false);
|
|
2314
|
+
advance(OUT_OF_VIEW_NOTICE_DELAY_MS);
|
|
2315
|
+
|
|
2316
|
+
fireEvent.click(
|
|
2317
|
+
within(screen.getByTestId("service-map-out-of-view")).getByRole(
|
|
2318
|
+
"button",
|
|
2319
|
+
{ name: "Fit to screen" },
|
|
2320
|
+
),
|
|
2321
|
+
);
|
|
2322
|
+
expect(stuck.fitView).toHaveBeenCalledWith(ANIMATED_FIT);
|
|
2323
|
+
expect(mockFlowMounts).toBe(2);
|
|
2324
|
+
expect(outOfViewNotice()).not.toBeInTheDocument();
|
|
2325
|
+
advance(OUT_OF_VIEW_NOTICE_DELAY_MS * 2);
|
|
2326
|
+
expect(outOfViewNotice()).not.toBeInTheDocument();
|
|
2327
|
+
|
|
2328
|
+
// The fresh canvas draws the same map, and is the one fitted from now on.
|
|
2329
|
+
expect(guardProps().drawingKey).toBe(idsOf(flowProps().nodes).join("|"));
|
|
2330
|
+
const fresh: MockFlowInstance = liveInstance();
|
|
2331
|
+
expect(fresh).not.toBe(stuck);
|
|
2332
|
+
fireEvent.click(screen.getByTestId("service-map-fit"));
|
|
2333
|
+
expect(fresh.fitView).toHaveBeenCalledWith(ANIMATED_FIT);
|
|
2334
|
+
expect(stuck.fitView).toHaveBeenCalledTimes(1);
|
|
2335
|
+
expect(mockFlowMounts).toBe(2);
|
|
2336
|
+
});
|
|
2337
|
+
|
|
2338
|
+
test("with no live canvas, Fit to screen draws it again rather than doing nothing", async () => {
|
|
2339
|
+
// The first canvas never reports a usable viewport.
|
|
2340
|
+
mockViewportInitialized = false;
|
|
2341
|
+
await renderGraph();
|
|
2342
|
+
const unready: MockFlowInstance = flowInstance(0);
|
|
2343
|
+
mockViewportInitialized = true;
|
|
2344
|
+
|
|
2345
|
+
fireEvent.click(screen.getByTestId("service-map-fit"));
|
|
2346
|
+
expect(unready.fitView).not.toHaveBeenCalled();
|
|
2347
|
+
expect(mockFlowMounts).toBe(2);
|
|
2348
|
+
|
|
2349
|
+
// The fresh canvas is ready, and Fit to screen now frames the map with it.
|
|
2350
|
+
fireEvent.click(screen.getByTestId("service-map-fit"));
|
|
2351
|
+
expect(liveInstance().fitView).toHaveBeenCalledWith(ANIMATED_FIT);
|
|
2352
|
+
expect(mockFlowMounts).toBe(2);
|
|
2353
|
+
});
|
|
2354
|
+
|
|
2355
|
+
test("after Map, Table, Map, Fit to screen fits the new canvas, never the one that is gone", async () => {
|
|
2356
|
+
await renderGraph();
|
|
2357
|
+
const first: MockFlowInstance = liveInstance();
|
|
2358
|
+
fireEvent.click(screen.getByTestId("service-map-view-list"));
|
|
2359
|
+
expect(screen.queryByTestId("rendered-graph")).not.toBeInTheDocument();
|
|
2360
|
+
fireEvent.click(screen.getByTestId("service-map-view-map"));
|
|
2361
|
+
const second: MockFlowInstance = liveInstance();
|
|
2362
|
+
expect(second).not.toBe(first);
|
|
2363
|
+
|
|
2364
|
+
fireEvent.click(screen.getByTestId("service-map-fit"));
|
|
2365
|
+
expect(second.fitView).toHaveBeenCalledTimes(1);
|
|
2366
|
+
expect(second.fitView).toHaveBeenCalledWith(ANIMATED_FIT);
|
|
2367
|
+
expect(first.fitView).not.toHaveBeenCalled();
|
|
2368
|
+
expect(mockFlowMounts).toBe(2);
|
|
2369
|
+
});
|
|
2370
|
+
|
|
2371
|
+
test("a canvas that is gone is never fitted, even before the new one is ready", async () => {
|
|
2372
|
+
await renderGraph();
|
|
2373
|
+
const gone: MockFlowInstance = liveInstance();
|
|
2374
|
+
fireEvent.click(screen.getByTestId("service-map-view-list"));
|
|
2375
|
+
mockViewportInitialized = false;
|
|
2376
|
+
fireEvent.click(screen.getByTestId("service-map-view-map"));
|
|
2377
|
+
|
|
2378
|
+
fireEvent.click(screen.getByTestId("service-map-fit"));
|
|
2379
|
+
expect(gone.fitView).not.toHaveBeenCalled();
|
|
2380
|
+
// With nothing live to fit, the canvas is drawn again instead.
|
|
2381
|
+
expect(mockFlowMounts).toBe(3);
|
|
2382
|
+
});
|
|
2383
|
+
|
|
2384
|
+
test("leaving the map while it is out of view brings no stale notice back", async () => {
|
|
2385
|
+
jest.useFakeTimers();
|
|
2386
|
+
await renderGraph();
|
|
2387
|
+
reportDrawingInView(false);
|
|
2388
|
+
advance(OUT_OF_VIEW_NOTICE_DELAY_MS);
|
|
2389
|
+
expect(outOfViewNotice()).toBeInTheDocument();
|
|
2390
|
+
|
|
2391
|
+
fireEvent.click(screen.getByTestId("service-map-view-list"));
|
|
2392
|
+
expect(outOfViewNotice()).not.toBeInTheDocument();
|
|
2393
|
+
fireEvent.click(screen.getByTestId("service-map-view-map"));
|
|
2394
|
+
advance(OUT_OF_VIEW_NOTICE_DELAY_MS * 2);
|
|
2395
|
+
expect(outOfViewNotice()).not.toBeInTheDocument();
|
|
2396
|
+
});
|
|
2397
|
+
|
|
2398
|
+
test("the zoom hint shows with the map, not the table", async () => {
|
|
2399
|
+
await renderGraph();
|
|
2400
|
+
expect(screen.getByTestId("service-map-zoom-hint")).toHaveTextContent(
|
|
2401
|
+
"Ctrl + scroll or pinch to zoom",
|
|
2402
|
+
);
|
|
2403
|
+
fireEvent.click(screen.getByTestId("service-map-view-list"));
|
|
2404
|
+
expect(
|
|
2405
|
+
screen.queryByTestId("service-map-zoom-hint"),
|
|
2406
|
+
).not.toBeInTheDocument();
|
|
2407
|
+
fireEvent.click(screen.getByTestId("service-map-view-map"));
|
|
2408
|
+
expect(screen.getByTestId("service-map-zoom-hint")).toBeInTheDocument();
|
|
2409
|
+
});
|
|
2410
|
+
});
|