@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.
Files changed (639) hide show
  1. package/Models/DatabaseModels/Index.ts +2 -0
  2. package/Models/DatabaseModels/KubernetesAiAgent.ts +364 -0
  3. package/Models/DatabaseModels/KubernetesCluster.ts +3 -3
  4. package/Models/DatabaseModels/Project.ts +55 -5
  5. package/Models/DatabaseModels/ProxmoxResource.ts +49 -1
  6. package/Models/DatabaseModels/RunnerJob.ts +75 -0
  7. package/Models/DatabaseModels/TeamComplianceSetting.ts +143 -2
  8. package/Models/DatabaseModels/WorkspaceProjectAuthToken.ts +17 -0
  9. package/Server/API/AIChatAPI.ts +6 -3
  10. package/Server/API/AIInvestigationAPI.ts +17 -10
  11. package/Server/API/AutoRemediationAPI.ts +207 -25
  12. package/Server/API/KubernetesClusterAiAccessAPI.ts +623 -21
  13. package/Server/API/MicrosoftTeamsAPI.ts +93 -14
  14. package/Server/API/ProxmoxResourceAPI.ts +244 -14
  15. package/Server/Infrastructure/GlobalCache.ts +32 -0
  16. package/Server/Infrastructure/Postgres/SchemaMigrations/1796000000000-AddTeamComplianceRuleScope.ts +99 -0
  17. package/Server/Infrastructure/Postgres/SchemaMigrations/1796100000000-AddKubernetesAiAgentAndAiDefaults.ts +137 -0
  18. package/Server/Infrastructure/Postgres/SchemaMigrations/1796200000000-AddProxmoxResourceNativePushColumns.ts +42 -0
  19. package/Server/Infrastructure/Postgres/SchemaMigrations/Index.ts +6 -0
  20. package/Server/Middleware/TelemetryIngest.ts +41 -18
  21. package/Server/Services/AIService.ts +70 -1
  22. package/Server/Services/AlertService.ts +352 -11
  23. package/Server/Services/AlertSeverityService.ts +65 -15
  24. package/Server/Services/AutoRemediationRuleService.ts +1 -1
  25. package/Server/Services/IncidentService.ts +280 -104
  26. package/Server/Services/IncidentSeverityService.ts +66 -15
  27. package/Server/Services/KubernetesAiAgentJobService.ts +185 -0
  28. package/Server/Services/KubernetesAiAgentService.ts +1634 -0
  29. package/Server/Services/KubernetesClusterAiAccessService.ts +1118 -451
  30. package/Server/Services/KubernetesClusterService.ts +129 -21
  31. package/Server/Services/ProjectService.ts +32 -0
  32. package/Server/Services/ProxmoxResourceService.ts +322 -5
  33. package/Server/Services/RunnerJobService.ts +241 -104
  34. package/Server/Services/RunnerService.ts +1 -1
  35. package/Server/Services/ScheduledMaintenanceService.ts +737 -1
  36. package/Server/Services/ScheduledMaintenanceStateTimelineService.ts +414 -56
  37. package/Server/Services/TeamComplianceSettingService.ts +1125 -9
  38. package/Server/Utils/AI/ClusterAccess/ClusterAccessContext.ts +67 -9
  39. package/Server/Utils/AI/ClusterAccess/KubectlInvestigationToolkit.ts +39 -10
  40. package/Server/Utils/AI/ClusterAccess/KubectlJobRunner.ts +14 -3
  41. package/Server/Utils/AI/Remediation/RemediationCommandTools.ts +37 -15
  42. package/Server/Utils/AI/Remediation/RemediationExecutionRunner.ts +67 -24
  43. package/Server/Utils/AI/SRE/AIInvestigationEngine.ts +48 -5
  44. package/Server/Utils/AI/SRE/IncidentPostmortemRunner.ts +40 -8
  45. package/Server/Utils/AI/SRE/InvestigationEligibility.ts +13 -2
  46. package/Server/Utils/AI/SRE/InvestigationGrader.ts +1 -1
  47. package/Server/Utils/AutoRemediation/CommandPlanExecutor.ts +6 -1
  48. package/Server/Utils/Telemetry/InventoryEntityRegistry.ts +133 -7
  49. package/Server/Utils/Telemetry/ProxmoxCephSnapshotScan.ts +58 -4
  50. package/Server/Utils/Telemetry/ProxmoxNativeNodeLiveness.ts +447 -0
  51. package/Server/Utils/Telemetry/ProxmoxNativePush.ts +903 -0
  52. package/Server/Utils/Telemetry/TelemetryEntity.ts +87 -27
  53. package/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeams.ts +684 -47
  54. package/Tests/App/AdminDashboard/RowActionPlacementOverrides.test.tsx +278 -0
  55. package/Tests/App/Dashboard/AIChatChartWidgetZoom.test.tsx +738 -0
  56. package/Tests/App/Dashboard/AIFeaturesSettingsPointers.test.tsx +147 -0
  57. package/Tests/App/Dashboard/AIInsightsKubernetesClusterPage.test.tsx +1523 -0
  58. package/Tests/App/Dashboard/AdminUserNotificationMethodsPage.test.tsx +463 -3
  59. package/Tests/App/Dashboard/AffectedResourcesPickerDropdownPlacement.test.tsx +958 -0
  60. package/Tests/App/Dashboard/AffectedResourcesPickerFocusLeave.test.tsx +595 -0
  61. package/Tests/App/Dashboard/AskAiAvailability.test.ts +20 -3
  62. package/Tests/App/Dashboard/AskAiDisabled.test.tsx +8 -3
  63. package/Tests/App/Dashboard/CephRateChartReload.test.tsx +1456 -0
  64. package/Tests/App/Dashboard/CephTimeRangeZoom.test.tsx +1229 -0
  65. package/Tests/App/Dashboard/ChartCardTimeRangeZoom.test.tsx +151 -0
  66. package/Tests/App/Dashboard/ChartCardZoomAffordances.test.tsx +257 -0
  67. package/Tests/App/Dashboard/ChartRefetchFrame.test.tsx +141 -0
  68. package/Tests/App/Dashboard/ChartZoomHarness.tsx +249 -0
  69. package/Tests/App/Dashboard/ChartZoomStandIn.tsx +131 -0
  70. package/Tests/App/Dashboard/CloudServerlessSlowLoads.test.tsx +628 -0
  71. package/Tests/App/Dashboard/CloudServerlessTimeRangeZoom.test.tsx +607 -0
  72. package/Tests/App/Dashboard/ClusterAccessNotice.test.tsx +91 -19
  73. package/Tests/App/Dashboard/CompanionMetricsTabZoom.test.tsx +632 -0
  74. package/Tests/App/Dashboard/ContainerChartZoomStandIn.tsx +195 -0
  75. package/Tests/App/Dashboard/ContainerHostOverviewSlowLoads.test.tsx +542 -0
  76. package/Tests/App/Dashboard/ContainerHostOverviewTimeRangeZoom.test.tsx +848 -0
  77. package/Tests/App/Dashboard/ContainerMetricsTimeRangeZoom.test.tsx +561 -0
  78. package/Tests/App/Dashboard/DashboardBoardWideZoomShell.test.tsx +1147 -0
  79. package/Tests/App/Dashboard/DashboardEditPermissions.test.tsx +3 -2
  80. package/Tests/App/Dashboard/DashboardHistogramWidgetDragCursor.test.tsx +399 -0
  81. package/Tests/App/Dashboard/DashboardHistogramWidgetZoom.test.tsx +985 -0
  82. package/Tests/App/Dashboard/DashboardHistogramWidgetZoomHint.test.tsx +803 -0
  83. package/Tests/App/Dashboard/DashboardLazyPages.test.tsx +1 -0
  84. package/Tests/App/Dashboard/DashboardSettingsPreviewZoom.test.tsx +679 -0
  85. package/Tests/App/Dashboard/DashboardSloAndDataSourceChartZoom.test.tsx +881 -0
  86. package/Tests/App/Dashboard/DashboardTraceStatusWidgets.test.tsx +878 -0
  87. package/Tests/App/Dashboard/DatabaseMetricChartModalTimeRangeZoom.test.tsx +442 -0
  88. package/Tests/App/Dashboard/DatabaseOverviewTimeRangeZoom.test.tsx +831 -0
  89. package/Tests/App/Dashboard/DatabaseServerComponents.test.tsx +43 -0
  90. package/Tests/App/Dashboard/DockerSwarmInsightsTimeRangeZoom.test.tsx +524 -0
  91. package/Tests/App/Dashboard/DockerSwarmRateChartRefresh.test.tsx +259 -0
  92. package/Tests/App/Dashboard/EdgeDetailPanelHistory.test.tsx +763 -0
  93. package/Tests/App/Dashboard/EdgeDetailPanelTimeRangeZoom.test.tsx +547 -0
  94. package/Tests/App/Dashboard/EdgeHistoryBucketsCases.tsx +565 -0
  95. package/Tests/App/Dashboard/EdgeHistoryBucketsKathmandu.test.tsx +73 -0
  96. package/Tests/App/Dashboard/EdgeHistoryBucketsKolkata.test.tsx +74 -0
  97. package/Tests/App/Dashboard/EdgeHistoryBucketsNewYork.test.tsx +72 -0
  98. package/Tests/App/Dashboard/EdgeHistoryBucketsUtc.test.tsx +72 -0
  99. package/Tests/App/Dashboard/EmbeddedMetricCardRefresh.test.tsx +310 -0
  100. package/Tests/App/Dashboard/EmbeddedMetricCardTimeRangeZoom.test.tsx +536 -0
  101. package/Tests/App/Dashboard/EscalationRuleReadiness.test.tsx +3 -1
  102. package/Tests/App/Dashboard/EscalationRuleRowActions.test.tsx +660 -0
  103. package/Tests/App/Dashboard/EventNotes.test.tsx +2 -2
  104. package/Tests/App/Dashboard/EventOverviewPages.test.tsx +587 -13
  105. package/Tests/App/Dashboard/EventOverviewSnapshotExplorerZoom.test.tsx +1139 -0
  106. package/Tests/App/Dashboard/EventOverviewSnapshotZoomAcrossTabs.test.tsx +1057 -0
  107. package/Tests/App/Dashboard/EventOverviewTelemetrySnapshotZoom.test.tsx +757 -0
  108. package/Tests/App/Dashboard/ExceptionOccurrenceTrendZoom.test.tsx +675 -0
  109. package/Tests/App/Dashboard/ExceptionOccurrencesZoomSurvivesViewSwitch.test.tsx +405 -0
  110. package/Tests/App/Dashboard/ExceptionSignalPages.test.tsx +67 -3
  111. package/Tests/App/Dashboard/ExceptionTablesSpanStatus.test.tsx +515 -0
  112. package/Tests/App/Dashboard/ExplorerOwnZoomRegression.test.tsx +739 -0
  113. package/Tests/App/Dashboard/FlowBandwidthAxisCases.tsx +461 -0
  114. package/Tests/App/Dashboard/FlowBandwidthAxisKathmandu.test.tsx +25 -0
  115. package/Tests/App/Dashboard/FlowBandwidthAxisKolkata.test.tsx +25 -0
  116. package/Tests/App/Dashboard/FlowBandwidthAxisNewYork.test.tsx +25 -0
  117. package/Tests/App/Dashboard/FlowBandwidthAxisUtc.test.tsx +24 -0
  118. package/Tests/App/Dashboard/FlowTopTalkersZoomRefetch.test.tsx +722 -0
  119. package/Tests/App/Dashboard/FlowViewportGuard.test.tsx +2526 -0
  120. package/Tests/App/Dashboard/HostPagesSlowLoads.test.tsx +872 -0
  121. package/Tests/App/Dashboard/HostPagesTimeRangeZoom.test.tsx +1208 -0
  122. package/Tests/App/Dashboard/IncidentRootCauseTimeRangeZoom.test.tsx +358 -0
  123. package/Tests/App/Dashboard/InfrastructureGraph.test.tsx +100 -9
  124. package/Tests/App/Dashboard/InfrastructureMetricsTabTimeRangeZoom.test.tsx +485 -0
  125. package/Tests/App/Dashboard/InvestigationDrawerCompanionZoom.test.tsx +655 -0
  126. package/Tests/App/Dashboard/InvestigationDrawerZoom.test.tsx +830 -0
  127. package/Tests/App/Dashboard/InvestigationNotStarted.test.tsx +338 -2
  128. package/Tests/App/Dashboard/InvoicesPayInvoice.test.tsx +454 -16
  129. package/Tests/App/Dashboard/KubernetesClusterAiAccessSettings.test.ts +799 -819
  130. package/Tests/App/Dashboard/KubernetesClusterAiAgentOverviewCard.test.tsx +367 -0
  131. package/Tests/App/Dashboard/KubernetesClusterAiAgentStatus.test.ts +1202 -0
  132. package/Tests/App/Dashboard/KubernetesClusterAiDocumentationMarkdown.test.ts +106 -0
  133. package/Tests/App/Dashboard/KubernetesClusterAiNavigation.test.tsx +354 -0
  134. package/Tests/App/Dashboard/KubernetesClusterAiPage.test.tsx +1884 -2054
  135. package/Tests/App/Dashboard/KubernetesClusterAiPageServerParity.test.ts +478 -179
  136. package/Tests/App/Dashboard/KubernetesClusterMetricTooltips.test.ts +50 -0
  137. package/Tests/App/Dashboard/KubernetesClusterOverviewTooltips.test.tsx +2 -1
  138. package/Tests/App/Dashboard/KubernetesClusterOverviewZoom.test.tsx +1217 -0
  139. package/Tests/App/Dashboard/KubernetesClusterPagesZoom.test.tsx +731 -0
  140. package/Tests/App/Dashboard/KubernetesCostsLoadRecovery.test.tsx +869 -0
  141. package/Tests/App/Dashboard/KubernetesCostsZoom.test.tsx +1214 -0
  142. package/Tests/App/Dashboard/KubernetesCostsZoomExactHours.test.tsx +692 -0
  143. package/Tests/App/Dashboard/KubernetesNetworkThroughputReload.test.tsx +902 -0
  144. package/Tests/App/Dashboard/KubernetesNetworkZoomBucketEdges.test.tsx +571 -0
  145. package/Tests/App/Dashboard/KubernetesOverviewZoomBucketEdges.test.tsx +697 -0
  146. package/Tests/App/Dashboard/KubernetesResourceMetricsTabZoom.test.tsx +633 -0
  147. package/Tests/App/Dashboard/LogsExplorerTimeRangeZoom.test.tsx +470 -0
  148. package/Tests/App/Dashboard/LogsInsightsTimeRangeZoom.test.tsx +948 -0
  149. package/Tests/App/Dashboard/LogsViewerPinnedWindowZoom.test.tsx +486 -0
  150. package/Tests/App/Dashboard/MetricChartsBucketInspectorDoubleClick.test.tsx +1001 -0
  151. package/Tests/App/Dashboard/MetricExplorerInvestigationAutoRefresh.test.tsx +514 -0
  152. package/Tests/App/Dashboard/MetricExplorerSavedViewZoomRoundTrip.test.tsx +581 -0
  153. package/Tests/App/Dashboard/MetricExplorerTimeRangeZoom.test.tsx +1162 -0
  154. package/Tests/App/Dashboard/MetricMonitorPreviewZoomLabel.test.ts +72 -0
  155. package/Tests/App/Dashboard/MetricRowSparklineTimeRangeZoom.test.tsx +204 -0
  156. package/Tests/App/Dashboard/MetricViewFirstResultLands.test.tsx +408 -0
  157. package/Tests/App/Dashboard/MetricViewOwnZoomResetPlacement.test.tsx +952 -0
  158. package/Tests/App/Dashboard/MetricViewTimeRange.test.ts +139 -0
  159. package/Tests/App/Dashboard/MetricViewTimeRangeZoom.test.tsx +556 -0
  160. package/Tests/App/Dashboard/MonitorMetricsChartWrappersZoom.test.tsx +552 -0
  161. package/Tests/App/Dashboard/MonitorMetricsTabsTimeRangeZoom.test.tsx +740 -0
  162. package/Tests/App/Dashboard/MonitorOverviewChartsTimeRangeZoom.test.tsx +735 -0
  163. package/Tests/App/Dashboard/MonitorStepPreviewsTimeRangeZoom.test.tsx +835 -0
  164. package/Tests/App/Dashboard/NetworkDeviceTimeRangeZoom.test.tsx +1115 -0
  165. package/Tests/App/Dashboard/OnCallPreventionGuards.test.tsx +186 -17
  166. package/Tests/App/Dashboard/OnCallRulesDeleteGuard.test.tsx +155 -46
  167. package/Tests/App/Dashboard/PasskeySettings.test.tsx +87 -2
  168. package/Tests/App/Dashboard/ProjectAISettingsPages.test.tsx +884 -0
  169. package/Tests/App/Dashboard/ProxmoxNodeRemove.test.tsx +621 -0
  170. package/Tests/App/Dashboard/ProxmoxOverviewSlowLoads.test.tsx +584 -0
  171. package/Tests/App/Dashboard/ProxmoxPagesTimeRangeZoom.test.tsx +823 -0
  172. package/Tests/App/Dashboard/ProxmoxRateChartReload.test.tsx +1253 -0
  173. package/Tests/App/Dashboard/PublicDashboardBoardWideZoom.test.tsx +605 -0
  174. package/Tests/App/Dashboard/RealRowsLineChart.tsx +250 -0
  175. package/Tests/App/Dashboard/RechartsHostsPlotCursor.test.tsx +513 -0
  176. package/Tests/App/Dashboard/RowActionPlacementOverrides.test.tsx +865 -0
  177. package/Tests/App/Dashboard/RumOverviewTimeRangeZoom.test.tsx +877 -0
  178. package/Tests/App/Dashboard/RunnerKubernetesAgentForm.test.tsx +20 -4
  179. package/Tests/App/Dashboard/RunnerSupersededByAiAgentNote.test.tsx +296 -0
  180. package/Tests/App/Dashboard/SecurityEventConnectionsTable.test.tsx +146 -36
  181. package/Tests/App/Dashboard/ServiceDatabaseSlowLoads.test.tsx +1166 -0
  182. package/Tests/App/Dashboard/ServiceMapGraph.test.tsx +1243 -104
  183. package/Tests/App/Dashboard/ServiceOverviewTimeRangeZoom.test.tsx +792 -0
  184. package/Tests/App/Dashboard/SloBudgetBurnDownZoom.test.tsx +771 -0
  185. package/Tests/App/Dashboard/SloBudgetBurnDownZoomAffordances.test.tsx +380 -0
  186. package/Tests/App/Dashboard/SloEventMetricsBarChartZoom.test.tsx +369 -0
  187. package/Tests/App/Dashboard/SloHistoryChartsTimeRangeZoom.test.tsx +694 -0
  188. package/Tests/App/Dashboard/SloHistoryChartsZoomAffordances.test.tsx +497 -0
  189. package/Tests/App/Dashboard/SloMetricsTimeRangeZoom.test.tsx +541 -0
  190. package/Tests/App/Dashboard/SlowLoadHarness.ts +123 -0
  191. package/Tests/App/Dashboard/SnapshotExplorerPrimaryZoom.test.tsx +1194 -0
  192. package/Tests/App/Dashboard/SnapshotViewerPinnedWindow.test.tsx +620 -0
  193. package/Tests/App/Dashboard/TelemetrySnapshotPanelTimeRangeZoom.test.tsx +765 -0
  194. package/Tests/App/Dashboard/TelemetrySnapshotZoom.test.tsx +516 -0
  195. package/Tests/App/Dashboard/TelemetrySnapshotZoomAcrossTabs.test.tsx +827 -0
  196. package/Tests/App/Dashboard/TimeRangeZoomPageHarness.tsx +463 -0
  197. package/Tests/App/Dashboard/TraceChartEditorStatusSplit.test.tsx +543 -0
  198. package/Tests/App/Dashboard/TraceStatusPresentation.test.tsx +967 -0
  199. package/Tests/App/Dashboard/TraceTimelineMinimapZoom.test.tsx +315 -0
  200. package/Tests/App/Dashboard/TracesAnalyticsStatusSplit.test.tsx +778 -0
  201. package/Tests/App/Dashboard/TracesAnalyticsViewZoom.test.tsx +349 -0
  202. package/Tests/App/Dashboard/TracesExplorerTimeRangeZoom.test.tsx +551 -0
  203. package/Tests/App/Dashboard/TracesStatusChartAndFacet.test.tsx +687 -0
  204. package/Tests/App/Dashboard/TracesViewerStaleResponses.test.tsx +458 -0
  205. package/Tests/App/Dashboard/UptimeStripsExcludedFromZoom.test.tsx +406 -0
  206. package/Tests/App/Dashboard/VMwareOverviewSlowLoads.test.tsx +535 -0
  207. package/Tests/App/Dashboard/VMwarePagesTimeRangeZoom.test.tsx +606 -0
  208. package/Tests/App/Dashboard/ValueWidgetSparklineStaleHover.test.tsx +1292 -0
  209. package/Tests/App/Dashboard/ValueWidgetSparklineZoom.test.tsx +765 -0
  210. package/Tests/App/StatusPage/StatusPageResourceExplorer.test.tsx +98 -0
  211. package/Tests/Models/DatabaseModels/KubernetesAiAgentModel.test.ts +295 -0
  212. package/Tests/Models/DatabaseModels/TeamComplianceSettingModel.test.ts +347 -0
  213. package/Tests/Server/API/AIChatRouteAuthorization.test.ts +58 -0
  214. package/Tests/Server/API/AIGenerationProjectAIToggle.test.ts +1 -1
  215. package/Tests/Server/API/AIInvestigationAPI.test.ts +123 -45
  216. package/Tests/Server/API/AutoRemediationApproveClusterRound.test.ts +1329 -0
  217. package/Tests/Server/API/AutoRemediationApproveKubectlRecheck.test.ts +25 -2
  218. package/Tests/Server/API/KubernetesClusterAiAccessAPI.test.ts +2 -0
  219. package/Tests/Server/API/KubernetesClusterAiAccessAPIAgent.test.ts +903 -0
  220. package/Tests/Server/API/MicrosoftTeamsChatsRefreshRoute.test.ts +667 -0
  221. package/Tests/Server/API/MicrosoftTeamsManifest.test.ts +294 -6
  222. package/Tests/Server/API/ProxmoxResourceAPIRemoveNode.test.ts +1932 -0
  223. package/Tests/Server/Infrastructure/GlobalCache.test.ts +195 -0
  224. package/Tests/Server/Infrastructure/Postgres/AddKubernetesAiAgentAndAiDefaultsMigration.test.ts +623 -0
  225. package/Tests/Server/Infrastructure/Postgres/AddKubernetesClusterAiAccessMigration.test.ts +87 -10
  226. package/Tests/Server/Infrastructure/Postgres/AddTeamComplianceRuleScopeMigration.test.ts +622 -0
  227. package/Tests/Server/Middleware/TelemetryIngestBrowserKey.test.ts +61 -4
  228. package/Tests/Server/Middleware/TelemetryIngestKubernetesAgentRunnerPinnedKey.test.ts +98 -2
  229. package/Tests/Server/Middleware/TelemetryIngestKubernetesAgentRunnerRateLimit.test.ts +105 -2
  230. package/Tests/Server/Services/AIServiceBalanceBlocker.test.ts +282 -0
  231. package/Tests/Server/Services/AlertMonitorClearWritesNull.test.ts +409 -0
  232. package/Tests/Server/Services/AlertMonitorEditFeed.test.ts +474 -0
  233. package/Tests/Server/Services/AlertMonitorEditGuard.test.ts +610 -0
  234. package/Tests/Server/Services/AutoRemediationClusterRuleEngine.test.ts +2 -0
  235. package/Tests/Server/Services/AutoRemediationRuleCommandRunners.test.ts +2 -1
  236. package/Tests/Server/Services/IncidentAffectedMonitorsEdit.test.ts +1360 -0
  237. package/Tests/Server/Services/IncidentAlertSloLinkSurvivesEdit.test.ts +3 -1
  238. package/Tests/Server/Services/IncidentSeverityDeletePausesComplianceRules.test.ts +488 -0
  239. package/Tests/Server/Services/KubernetesAiAgentJobService.test.ts +329 -0
  240. package/Tests/Server/Services/KubernetesAiAgentServiceHelpers.test.ts +618 -0
  241. package/Tests/Server/Services/KubernetesAiAgentServiceLifecycle.test.ts +1444 -0
  242. package/Tests/Server/Services/KubernetesAiAgentServiceRegister.test.ts +1666 -0
  243. package/Tests/Server/Services/KubernetesClusterAiAccessDeletedRunnerRemedy.test.ts +140 -111
  244. package/Tests/Server/Services/KubernetesClusterAiAccessGapNextSteps.test.ts +267 -105
  245. package/Tests/Server/Services/KubernetesClusterAiAccessLegacySuperseded.test.ts +440 -0
  246. package/Tests/Server/Services/KubernetesClusterAiAccessService.test.ts +567 -411
  247. package/Tests/Server/Services/KubernetesClusterAiAccessTarget.test.ts +1168 -0
  248. package/Tests/Server/Services/KubernetesClusterAiSettingsPermission.test.ts +410 -12
  249. package/Tests/Server/Services/KubernetesClusterServiceAiAccessFeed.test.ts +55 -0
  250. package/Tests/Server/Services/ProjectServiceNewProjectAiDefaults.test.ts +288 -0
  251. package/Tests/Server/Services/ProxmoxResourcePostgres.test.ts +5633 -0
  252. package/Tests/Server/Services/ProxmoxResourceService.test.ts +6458 -5
  253. package/Tests/Server/Services/RunnerJobEnqueueKubectl.test.ts +48 -15
  254. package/Tests/Server/Services/RunnerJobEnqueueKubectlAiAgent.test.ts +620 -0
  255. package/Tests/Server/Services/RunnerJobKubectlWriteScope.test.ts +170 -3
  256. package/Tests/Server/Services/RunnerJobTimeoutReason.test.ts +72 -0
  257. package/Tests/Server/Services/RunnerServiceAgentName.test.ts +6 -4
  258. package/Tests/Server/Services/ScheduledMaintenanceAttachmentEdit.test.ts +2113 -0
  259. package/Tests/Server/Services/ScheduledMaintenanceMonitorRelease.test.ts +972 -0
  260. package/Tests/Server/Services/TeamComplianceSettingPostgres.test.ts +2237 -0
  261. package/Tests/Server/Services/TeamComplianceSettingService.test.ts +3988 -138
  262. package/Tests/Server/Utils/AI/ClusterAccessContext.test.ts +107 -1
  263. package/Tests/Server/Utils/AI/IncidentPostmortemRunner.test.ts +351 -0
  264. package/Tests/Server/Utils/AI/InvestigationClusterAccessWiring.test.ts +2 -0
  265. package/Tests/Server/Utils/AI/InvestigationEligibility.test.ts +310 -0
  266. package/Tests/Server/Utils/AI/KubectlInvestigationToolkit.test.ts +70 -0
  267. package/Tests/Server/Utils/AI/KubectlJobRunnerFinishedJob.test.ts +58 -0
  268. package/Tests/Server/Utils/AI/RemediationCommandToolsKubectl.test.ts +2 -0
  269. package/Tests/Server/Utils/AI/RemediationCommandToolsKubectlOutcome.test.ts +2 -0
  270. package/Tests/Server/Utils/AI/RemediationCommandToolsLiveCluster.test.ts +2 -0
  271. package/Tests/Server/Utils/AI/RemediationCommandToolsRunnerScope.test.ts +125 -1
  272. package/Tests/Server/Utils/AI/RemediationExecutionRunner.test.ts +252 -3
  273. package/Tests/Server/Utils/AI/RemediationExecutionRunnerClusterMode.test.ts +2 -0
  274. package/Tests/Server/Utils/AI/RemediationExecutionRunnerProjectGates.test.ts +240 -0
  275. package/Tests/Server/Utils/AI/RemediationKubectlCopyParity.test.ts +2 -0
  276. package/Tests/Server/Utils/AutoRemediation/CommandPlanExecutor.test.ts +2 -0
  277. package/Tests/Server/Utils/AutoRemediation/CommandPlanExecutorUnknownRun.test.ts +2 -0
  278. package/Tests/Server/Utils/AutoRemediation/CommandPlanVerification.test.ts +2 -0
  279. package/Tests/Server/Utils/Telemetry/HostAssetAttributesFromOtlp.test.ts +68 -0
  280. package/Tests/Server/Utils/Telemetry/InventoryMirrorPostgres.test.ts +370 -0
  281. package/Tests/Server/Utils/Telemetry/NetworkDeviceInventoryAttributes.test.ts +522 -0
  282. package/Tests/Server/Utils/Telemetry/ProxmoxCephSnapshotScan.test.ts +155 -0
  283. package/Tests/Server/Utils/Telemetry/ProxmoxNativeNodeLiveness.test.ts +8631 -0
  284. package/Tests/Server/Utils/Telemetry/ProxmoxNativePush.test.ts +2852 -0
  285. package/Tests/Server/Utils/Telemetry/TelemetryEntity.test.ts +367 -1
  286. package/Tests/Server/Utils/Telemetry/TelemetryIngestionKeyGuard.test.ts +45 -3
  287. package/Tests/Server/Utils/Workspace/MicrosoftTeamsChats.test.ts +25 -0
  288. package/Tests/Server/Utils/Workspace/MicrosoftTeamsGroupChatNames.test.ts +3119 -0
  289. package/Tests/Server/Utils/Workspace/MicrosoftTeamsGroupChatNamesPostgres.test.ts +1721 -0
  290. package/Tests/Types/Kubernetes/KubernetesAgentChartAiAccess.test.ts +581 -113
  291. package/Tests/Types/Kubernetes/KubernetesAiAgentAccessContract.test.ts +510 -0
  292. package/Tests/Types/Team/ComplianceRule.test.ts +1029 -0
  293. package/Tests/Types/Team/TeamComplianceStatus.test.ts +176 -0
  294. package/Tests/UI/Components/ActionButton/RowActions.test.tsx +641 -0
  295. package/Tests/UI/Components/ActionButton/RowActionsAccessibility.test.tsx +414 -0
  296. package/Tests/UI/Components/ActionButton/SplitActionButtons.test.ts +565 -0
  297. package/Tests/UI/Components/Charts/BarChartDragToSelect.test.tsx +386 -0
  298. package/Tests/UI/Components/Charts/ChartDoubleClickResetWithoutDblclick.test.tsx +1025 -0
  299. package/Tests/UI/Components/Charts/ChartGroupPageZoomHint.test.tsx +196 -0
  300. package/Tests/UI/Components/Charts/ChartPlaceholdersTakeDoubleClick.test.tsx +1861 -0
  301. package/Tests/UI/Components/Charts/ChartPlotClicksWhileResetArmed.test.tsx +382 -0
  302. package/Tests/UI/Components/Charts/ChartRangeSelection.test.tsx +704 -0
  303. package/Tests/UI/Components/Charts/ChartRangeSelectionDoubleClickReset.test.tsx +1481 -0
  304. package/Tests/UI/Components/Charts/ChartRangeSelectionRealRecharts.test.tsx +918 -0
  305. package/Tests/UI/Components/Charts/ChartRowBucketStart.test.ts +350 -0
  306. package/Tests/UI/Components/Charts/DoubleClickReset.test.tsx +996 -0
  307. package/Tests/UI/Components/Charts/HistogramDoubleClickResetRealRecharts.test.tsx +965 -0
  308. package/Tests/UI/Components/Charts/HistogramDriftThenRefetch.test.tsx +277 -0
  309. package/Tests/UI/Components/Charts/HistogramRangeSelectionDataChange.test.tsx +1757 -0
  310. package/Tests/UI/Components/Charts/HistogramRangeSelectionRealRecharts.test.tsx +560 -0
  311. package/Tests/UI/Components/Charts/TimeRangeZoom/ChartPageTimeRangeZoom.test.tsx +513 -0
  312. package/Tests/UI/Components/Charts/TimeRangeZoom/TimeRangeZoomContext.test.tsx +845 -0
  313. package/Tests/UI/Components/Charts/TimeRangeZoom/TimeRangeZoomHint.test.tsx +119 -0
  314. package/Tests/UI/Components/Charts/TimeRangeZoom/TimeRangeZoomHintTailwind.test.tsx +274 -0
  315. package/Tests/UI/Components/Charts/TimeRangeZoom/TimeRangeZoomUtil.test.ts +433 -0
  316. package/Tests/UI/Components/Charts/TimeRangeZoom/UseTimeRangeZoom.test.tsx +863 -0
  317. package/Tests/UI/Components/Charts/TimeRangeZoom/ZoomDocsPromises.test.tsx +1147 -0
  318. package/Tests/UI/Components/DisabledButtonTooltip.test.tsx +14 -3
  319. package/Tests/UI/Components/EntityDropdownEscape.test.tsx +454 -0
  320. package/Tests/UI/Components/EntityDropdownFocusLeave.test.tsx +420 -0
  321. package/Tests/UI/Components/Forms/FormFieldDropdownDisabled.test.tsx +233 -0
  322. package/Tests/UI/Components/Input.test.tsx +80 -1
  323. package/Tests/UI/Components/LogsAnalyticsViewZoom.test.tsx +947 -0
  324. package/Tests/UI/Components/LogsHistogramDragTooltip.test.tsx +17 -5
  325. package/Tests/UI/Components/LogsViewerAnalyticsZoom.test.tsx +501 -0
  326. package/Tests/UI/Components/LogsViewerHistogramZoom.test.tsx +1 -1
  327. package/Tests/UI/Components/ModelTable/BaseModelTableBulkDelete.test.tsx +6 -4
  328. package/Tests/UI/Components/ModelTable/BaseModelTablePermissionGating.test.tsx +60 -8
  329. package/Tests/UI/Components/ModelTable/BaseModelTableRowActions.test.tsx +476 -0
  330. package/Tests/UI/Components/ModelTable/ModelTableWrapContent.test.tsx +7 -5
  331. package/Tests/UI/Components/MoreMenuPortal.test.tsx +623 -0
  332. package/Tests/UI/Components/StatusPage/ResourceGridActions.test.tsx +538 -0
  333. package/Tests/UI/Components/StatusPage/ResourceGridSelection.test.tsx +14 -4
  334. package/Tests/UI/Components/TableCellWrapping.test.tsx +3 -3
  335. package/Tests/UI/Components/TableRowActions.test.tsx +472 -0
  336. package/Tests/UI/Components/TelemetryHistogramDragTooltip.test.tsx +1 -1
  337. package/Tests/UI/Components/TelemetryViewer/TelemetryViewerHistogramClickZoom.test.tsx +1 -1
  338. package/Tests/UI/Components/TelemetryViewer/TelemetryViewerZoomScope.test.tsx +470 -0
  339. package/Tests/UI/Components/TelemetryViewer/UseViewerTimeRangeZoom.test.tsx +571 -0
  340. package/Tests/UI/Components/TelemetryViewer/ViewerDoubleClickResetUnderDataChange.test.tsx +928 -0
  341. package/Tests/UI/Components/TelemetryViewer/ViewerFollowsEnclosingZoom.test.tsx +1569 -0
  342. package/Tests/UI/Components/Toggle.test.tsx +66 -0
  343. package/Tests/UI/MicrosoftTeams/MicrosoftTeamsChatsCardRefreshNames.test.tsx +1023 -0
  344. package/Tests/UI/Rum/ChunkLoader.test.ts +201 -0
  345. package/Tests/UI/Rum/FidelityNotices.test.ts +54 -0
  346. package/Tests/UI/Rum/ReplayCorrelationPanel.test.tsx +72 -0
  347. package/Tests/UI/Rum/ReplayFrameCapture.test.ts +1009 -9
  348. package/Tests/UI/Rum/ReplayRecordedAssets.test.ts +1069 -0
  349. package/Tests/UI/Rum/ReplayStage.test.tsx +913 -2
  350. package/Tests/UI/Rum/SessionReplayTable.test.tsx +443 -8
  351. package/Tests/UI/Rum/SessionReplayUsersTable.test.tsx +439 -9
  352. package/Tests/UI/Rum/UserFlowTablesRowActions.test.tsx +501 -0
  353. package/Tests/Utils/AiRemediation/KubectlPolicyChartRbacParity.test.ts +47 -19
  354. package/Tests/Utils/AiRemediation/KubectlWriteScopeCallerParity.test.ts +2 -0
  355. package/Tests/Utils/AiRemediation/KubernetesAiAgentPolicyCopyParity.test.ts +396 -0
  356. package/Tests/Utils/Dashboard/DashboardTimeRangeZoom.test.ts +104 -1
  357. package/Types/AI/AIRunType.ts +9 -4
  358. package/Types/AI/InvestigationNotStartedReason.ts +6 -0
  359. package/Types/AutoRemediation/AiRemediationCommandPlan.ts +14 -16
  360. package/Types/AutoRemediation/AiRemediationCommandPolicyVerdict.ts +28 -0
  361. package/Types/Dashboard/DashboardComponents/DashboardTraceChartComponent.ts +4 -2
  362. package/Types/Kubernetes/KubernetesClusterAiAccess.ts +263 -32
  363. package/Types/Kubernetes/KubernetesClusterAiAccessPermissions.ts +20 -16
  364. package/Types/Kubernetes/KubernetesClusterAiInsights.ts +81 -0
  365. package/Types/Monitor/ProxmoxAlertTemplates.ts +4 -4
  366. package/Types/Team/ComplianceNotificationChannel.ts +22 -0
  367. package/Types/Team/ComplianceRule.ts +434 -0
  368. package/Types/Team/ComplianceRuleType.ts +21 -0
  369. package/Types/Team/TeamComplianceStatus.ts +81 -0
  370. package/Types/Telemetry/TelemetryIngestSurface.ts +28 -0
  371. package/UI/Components/ActionButton/ActionButtonSchema.ts +21 -0
  372. package/UI/Components/ActionButton/RowActions.tsx +238 -0
  373. package/UI/Components/ActionButton/SplitActionButtons.ts +173 -0
  374. package/UI/Components/Charts/Area/AreaChart.tsx +32 -2
  375. package/UI/Components/Charts/Bar/BarChart.tsx +40 -3
  376. package/UI/Components/Charts/ChartGroup/ChartGroup.tsx +37 -11
  377. package/UI/Components/Charts/ChartLibrary/AreaChart/AreaChart.tsx +80 -243
  378. package/UI/Components/Charts/ChartLibrary/BarChart/BarChart.tsx +122 -31
  379. package/UI/Components/Charts/ChartLibrary/LineChart/LineChart.tsx +95 -244
  380. package/UI/Components/Charts/ChartLibrary/Utils/DoubleClick.ts +214 -0
  381. package/UI/Components/Charts/ChartLibrary/Utils/UseChartBucketsChange.ts +32 -0
  382. package/UI/Components/Charts/ChartLibrary/Utils/UseChartRangeSelection.ts +599 -0
  383. package/UI/Components/Charts/Line/LineChart.tsx +32 -2
  384. package/UI/Components/Charts/TimeRangeZoom/ResetTimeRangeZoomButton.tsx +70 -0
  385. package/UI/Components/Charts/TimeRangeZoom/TimeRangeZoomContext.tsx +226 -0
  386. package/UI/Components/Charts/TimeRangeZoom/TimeRangeZoomHint.tsx +73 -0
  387. package/UI/Components/Charts/TimeRangeZoom/TimeRangeZoomUtil.ts +163 -0
  388. package/UI/Components/Charts/TimeRangeZoom/UseTimeRangeZoom.ts +215 -0
  389. package/UI/Components/Charts/Utils/DataPoint.ts +20 -8
  390. package/UI/Components/Charts/Utils/XAxis.ts +75 -0
  391. package/UI/Components/Charts/Utils/useHistogramRangeSelection.ts +493 -59
  392. package/UI/Components/EntityDropdown/EntityDropdown.tsx +107 -11
  393. package/UI/Components/Forms/Fields/FormField.tsx +10 -0
  394. package/UI/Components/Input/Input.tsx +90 -57
  395. package/UI/Components/List/ListRow.tsx +8 -74
  396. package/UI/Components/LogsViewer/LogsViewer.tsx +40 -11
  397. package/UI/Components/LogsViewer/components/LogTimeRangePicker.tsx +22 -6
  398. package/UI/Components/LogsViewer/components/LogsAnalyticsView.tsx +209 -6
  399. package/UI/Components/LogsViewer/components/LogsHistogram.tsx +127 -84
  400. package/UI/Components/LogsViewer/components/LogsViewerToolbar.tsx +3 -0
  401. package/UI/Components/ModelTable/BaseModelTable.tsx +12 -1
  402. package/UI/Components/MoreMenu/MoreMenu.tsx +365 -30
  403. package/UI/Components/MoreMenu/MoreMenuItem.tsx +70 -17
  404. package/UI/Components/OrderedStatesList/Item.tsx +7 -72
  405. package/UI/Components/StatusPage/ResourceGrid.tsx +126 -77
  406. package/UI/Components/Table/TableBody.tsx +8 -2
  407. package/UI/Components/Table/TableRow.tsx +20 -140
  408. package/UI/Components/TelemetryViewer/TelemetryViewer.tsx +41 -10
  409. package/UI/Components/TelemetryViewer/components/TelemetryHistogram.tsx +47 -9
  410. package/UI/Components/TelemetryViewer/components/TelemetryTimeRangePicker.tsx +25 -6
  411. package/UI/Components/TelemetryViewer/types.ts +2 -0
  412. package/UI/Components/TelemetryViewer/useViewerTimeRangeZoom.ts +448 -0
  413. package/UI/Components/Toggle/Toggle.tsx +26 -0
  414. package/UI/Components/Tooltip/Tooltip.tsx +8 -1
  415. package/UI/Styles/Theme.css +14 -2
  416. package/Utils/AiRemediation/CommandPolicy.ts +1 -1
  417. package/Utils/AiRemediation/KubectlPolicy.ts +1 -1
  418. package/Utils/Dashboard/DashboardTimeRangeZoom.ts +46 -2
  419. package/build/dist/Models/DatabaseModels/Index.js +2 -0
  420. package/build/dist/Models/DatabaseModels/Index.js.map +1 -1
  421. package/build/dist/Models/DatabaseModels/KubernetesAiAgent.js +385 -0
  422. package/build/dist/Models/DatabaseModels/KubernetesAiAgent.js.map +1 -0
  423. package/build/dist/Models/DatabaseModels/KubernetesCluster.js +3 -3
  424. package/build/dist/Models/DatabaseModels/KubernetesCluster.js.map +1 -1
  425. package/build/dist/Models/DatabaseModels/Project.js +56 -5
  426. package/build/dist/Models/DatabaseModels/Project.js.map +1 -1
  427. package/build/dist/Models/DatabaseModels/ProxmoxResource.js +51 -1
  428. package/build/dist/Models/DatabaseModels/ProxmoxResource.js.map +1 -1
  429. package/build/dist/Models/DatabaseModels/RunnerJob.js +74 -0
  430. package/build/dist/Models/DatabaseModels/RunnerJob.js.map +1 -1
  431. package/build/dist/Models/DatabaseModels/TeamComplianceSetting.js +134 -3
  432. package/build/dist/Models/DatabaseModels/TeamComplianceSetting.js.map +1 -1
  433. package/build/dist/Models/DatabaseModels/WorkspaceProjectAuthToken.js.map +1 -1
  434. package/build/dist/Server/API/AIChatAPI.js +6 -1
  435. package/build/dist/Server/API/AIChatAPI.js.map +1 -1
  436. package/build/dist/Server/API/AIInvestigationAPI.js +17 -10
  437. package/build/dist/Server/API/AIInvestigationAPI.js.map +1 -1
  438. package/build/dist/Server/API/AutoRemediationAPI.js +144 -24
  439. package/build/dist/Server/API/AutoRemediationAPI.js.map +1 -1
  440. package/build/dist/Server/API/KubernetesClusterAiAccessAPI.js +436 -22
  441. package/build/dist/Server/API/KubernetesClusterAiAccessAPI.js.map +1 -1
  442. package/build/dist/Server/API/MicrosoftTeamsAPI.js +69 -13
  443. package/build/dist/Server/API/MicrosoftTeamsAPI.js.map +1 -1
  444. package/build/dist/Server/API/ProxmoxResourceAPI.js +187 -14
  445. package/build/dist/Server/API/ProxmoxResourceAPI.js.map +1 -1
  446. package/build/dist/Server/Infrastructure/GlobalCache.js +27 -0
  447. package/build/dist/Server/Infrastructure/GlobalCache.js.map +1 -1
  448. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1796000000000-AddTeamComplianceRuleScope.js +50 -0
  449. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1796000000000-AddTeamComplianceRuleScope.js.map +1 -0
  450. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1796100000000-AddKubernetesAiAgentAndAiDefaults.js +84 -0
  451. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1796100000000-AddKubernetesAiAgentAndAiDefaults.js.map +1 -0
  452. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1796200000000-AddProxmoxResourceNativePushColumns.js +31 -0
  453. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1796200000000-AddProxmoxResourceNativePushColumns.js.map +1 -0
  454. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js +6 -0
  455. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js.map +1 -1
  456. package/build/dist/Server/Middleware/TelemetryIngest.js +34 -19
  457. package/build/dist/Server/Middleware/TelemetryIngest.js.map +1 -1
  458. package/build/dist/Server/Services/AIService.js +59 -1
  459. package/build/dist/Server/Services/AIService.js.map +1 -1
  460. package/build/dist/Server/Services/AlertService.js +248 -13
  461. package/build/dist/Server/Services/AlertService.js.map +1 -1
  462. package/build/dist/Server/Services/AlertSeverityService.js +51 -15
  463. package/build/dist/Server/Services/AlertSeverityService.js.map +1 -1
  464. package/build/dist/Server/Services/AutoRemediationRuleService.js +1 -1
  465. package/build/dist/Server/Services/AutoRemediationRuleService.js.map +1 -1
  466. package/build/dist/Server/Services/IncidentService.js +199 -72
  467. package/build/dist/Server/Services/IncidentService.js.map +1 -1
  468. package/build/dist/Server/Services/IncidentSeverityService.js +51 -15
  469. package/build/dist/Server/Services/IncidentSeverityService.js.map +1 -1
  470. package/build/dist/Server/Services/KubernetesAiAgentJobService.js +158 -0
  471. package/build/dist/Server/Services/KubernetesAiAgentJobService.js.map +1 -0
  472. package/build/dist/Server/Services/KubernetesAiAgentService.js +1145 -0
  473. package/build/dist/Server/Services/KubernetesAiAgentService.js.map +1 -0
  474. package/build/dist/Server/Services/KubernetesClusterAiAccessService.js +842 -371
  475. package/build/dist/Server/Services/KubernetesClusterAiAccessService.js.map +1 -1
  476. package/build/dist/Server/Services/KubernetesClusterService.js +86 -17
  477. package/build/dist/Server/Services/KubernetesClusterService.js.map +1 -1
  478. package/build/dist/Server/Services/ProjectService.js +22 -0
  479. package/build/dist/Server/Services/ProjectService.js.map +1 -1
  480. package/build/dist/Server/Services/ProxmoxResourceService.js +258 -3
  481. package/build/dist/Server/Services/ProxmoxResourceService.js.map +1 -1
  482. package/build/dist/Server/Services/RunnerJobService.js +178 -92
  483. package/build/dist/Server/Services/RunnerJobService.js.map +1 -1
  484. package/build/dist/Server/Services/RunnerService.js +1 -1
  485. package/build/dist/Server/Services/RunnerService.js.map +1 -1
  486. package/build/dist/Server/Services/ScheduledMaintenanceService.js +535 -3
  487. package/build/dist/Server/Services/ScheduledMaintenanceService.js.map +1 -1
  488. package/build/dist/Server/Services/ScheduledMaintenanceStateTimelineService.js +316 -46
  489. package/build/dist/Server/Services/ScheduledMaintenanceStateTimelineService.js.map +1 -1
  490. package/build/dist/Server/Services/TeamComplianceSettingService.js +833 -7
  491. package/build/dist/Server/Services/TeamComplianceSettingService.js.map +1 -1
  492. package/build/dist/Server/Utils/AI/ClusterAccess/ClusterAccessContext.js +52 -9
  493. package/build/dist/Server/Utils/AI/ClusterAccess/ClusterAccessContext.js.map +1 -1
  494. package/build/dist/Server/Utils/AI/ClusterAccess/KubectlInvestigationToolkit.js +13 -9
  495. package/build/dist/Server/Utils/AI/ClusterAccess/KubectlInvestigationToolkit.js.map +1 -1
  496. package/build/dist/Server/Utils/AI/ClusterAccess/KubectlJobRunner.js +7 -3
  497. package/build/dist/Server/Utils/AI/ClusterAccess/KubectlJobRunner.js.map +1 -1
  498. package/build/dist/Server/Utils/AI/Remediation/RemediationCommandTools.js +29 -15
  499. package/build/dist/Server/Utils/AI/Remediation/RemediationCommandTools.js.map +1 -1
  500. package/build/dist/Server/Utils/AI/Remediation/RemediationExecutionRunner.js +56 -20
  501. package/build/dist/Server/Utils/AI/Remediation/RemediationExecutionRunner.js.map +1 -1
  502. package/build/dist/Server/Utils/AI/SRE/AIInvestigationEngine.js +39 -5
  503. package/build/dist/Server/Utils/AI/SRE/AIInvestigationEngine.js.map +1 -1
  504. package/build/dist/Server/Utils/AI/SRE/IncidentPostmortemRunner.js +30 -3
  505. package/build/dist/Server/Utils/AI/SRE/IncidentPostmortemRunner.js.map +1 -1
  506. package/build/dist/Server/Utils/AI/SRE/InvestigationEligibility.js +13 -2
  507. package/build/dist/Server/Utils/AI/SRE/InvestigationEligibility.js.map +1 -1
  508. package/build/dist/Server/Utils/AI/SRE/InvestigationGrader.js +1 -1
  509. package/build/dist/Server/Utils/AutoRemediation/CommandPlanExecutor.js +4 -2
  510. package/build/dist/Server/Utils/AutoRemediation/CommandPlanExecutor.js.map +1 -1
  511. package/build/dist/Server/Utils/Telemetry/InventoryEntityRegistry.js +111 -6
  512. package/build/dist/Server/Utils/Telemetry/InventoryEntityRegistry.js.map +1 -1
  513. package/build/dist/Server/Utils/Telemetry/ProxmoxCephSnapshotScan.js +40 -4
  514. package/build/dist/Server/Utils/Telemetry/ProxmoxCephSnapshotScan.js.map +1 -1
  515. package/build/dist/Server/Utils/Telemetry/ProxmoxNativeNodeLiveness.js +296 -0
  516. package/build/dist/Server/Utils/Telemetry/ProxmoxNativeNodeLiveness.js.map +1 -0
  517. package/build/dist/Server/Utils/Telemetry/ProxmoxNativePush.js +699 -0
  518. package/build/dist/Server/Utils/Telemetry/ProxmoxNativePush.js.map +1 -0
  519. package/build/dist/Server/Utils/Telemetry/TelemetryEntity.js +77 -19
  520. package/build/dist/Server/Utils/Telemetry/TelemetryEntity.js.map +1 -1
  521. package/build/dist/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeams.js +505 -42
  522. package/build/dist/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeams.js.map +1 -1
  523. package/build/dist/Types/AI/AIRunType.js +9 -4
  524. package/build/dist/Types/AI/AIRunType.js.map +1 -1
  525. package/build/dist/Types/AutoRemediation/AiRemediationCommandPlan.js +10 -16
  526. package/build/dist/Types/AutoRemediation/AiRemediationCommandPlan.js.map +1 -1
  527. package/build/dist/Types/AutoRemediation/AiRemediationCommandPolicyVerdict.js +28 -0
  528. package/build/dist/Types/AutoRemediation/AiRemediationCommandPolicyVerdict.js.map +1 -0
  529. package/build/dist/Types/Kubernetes/KubernetesClusterAiAccess.js +72 -16
  530. package/build/dist/Types/Kubernetes/KubernetesClusterAiAccess.js.map +1 -1
  531. package/build/dist/Types/Kubernetes/KubernetesClusterAiAccessPermissions.js +20 -16
  532. package/build/dist/Types/Kubernetes/KubernetesClusterAiAccessPermissions.js.map +1 -1
  533. package/build/dist/Types/Kubernetes/KubernetesClusterAiInsights.js +17 -0
  534. package/build/dist/Types/Kubernetes/KubernetesClusterAiInsights.js.map +1 -0
  535. package/build/dist/Types/Monitor/ProxmoxAlertTemplates.js +4 -4
  536. package/build/dist/Types/Monitor/ProxmoxAlertTemplates.js.map +1 -1
  537. package/build/dist/Types/Team/ComplianceNotificationChannel.js +23 -0
  538. package/build/dist/Types/Team/ComplianceNotificationChannel.js.map +1 -0
  539. package/build/dist/Types/Team/ComplianceRule.js +304 -0
  540. package/build/dist/Types/Team/ComplianceRule.js.map +1 -0
  541. package/build/dist/Types/Team/ComplianceRuleType.js +21 -0
  542. package/build/dist/Types/Team/ComplianceRuleType.js.map +1 -1
  543. package/build/dist/Types/Team/TeamComplianceStatus.js +2 -0
  544. package/build/dist/Types/Team/TeamComplianceStatus.js.map +1 -0
  545. package/build/dist/Types/Telemetry/TelemetryIngestSurface.js +25 -0
  546. package/build/dist/Types/Telemetry/TelemetryIngestSurface.js.map +1 -1
  547. package/build/dist/UI/Components/ActionButton/ActionButtonSchema.js +20 -1
  548. package/build/dist/UI/Components/ActionButton/ActionButtonSchema.js.map +1 -1
  549. package/build/dist/UI/Components/ActionButton/RowActions.js +113 -0
  550. package/build/dist/UI/Components/ActionButton/RowActions.js.map +1 -0
  551. package/build/dist/UI/Components/ActionButton/SplitActionButtons.js +71 -0
  552. package/build/dist/UI/Components/ActionButton/SplitActionButtons.js.map +1 -0
  553. package/build/dist/UI/Components/Charts/Area/AreaChart.js +16 -1
  554. package/build/dist/UI/Components/Charts/Area/AreaChart.js.map +1 -1
  555. package/build/dist/UI/Components/Charts/Bar/BarChart.js +16 -1
  556. package/build/dist/UI/Components/Charts/Bar/BarChart.js.map +1 -1
  557. package/build/dist/UI/Components/Charts/ChartGroup/ChartGroup.js +27 -5
  558. package/build/dist/UI/Components/Charts/ChartGroup/ChartGroup.js.map +1 -1
  559. package/build/dist/UI/Components/Charts/ChartLibrary/AreaChart/AreaChart.js +61 -194
  560. package/build/dist/UI/Components/Charts/ChartLibrary/AreaChart/AreaChart.js.map +1 -1
  561. package/build/dist/UI/Components/Charts/ChartLibrary/BarChart/BarChart.js +84 -26
  562. package/build/dist/UI/Components/Charts/ChartLibrary/BarChart/BarChart.js.map +1 -1
  563. package/build/dist/UI/Components/Charts/ChartLibrary/LineChart/LineChart.js +75 -196
  564. package/build/dist/UI/Components/Charts/ChartLibrary/LineChart/LineChart.js.map +1 -1
  565. package/build/dist/UI/Components/Charts/ChartLibrary/Utils/DoubleClick.js +133 -0
  566. package/build/dist/UI/Components/Charts/ChartLibrary/Utils/DoubleClick.js.map +1 -1
  567. package/build/dist/UI/Components/Charts/ChartLibrary/Utils/UseChartBucketsChange.js +26 -0
  568. package/build/dist/UI/Components/Charts/ChartLibrary/Utils/UseChartBucketsChange.js.map +1 -0
  569. package/build/dist/UI/Components/Charts/ChartLibrary/Utils/UseChartRangeSelection.js +371 -0
  570. package/build/dist/UI/Components/Charts/ChartLibrary/Utils/UseChartRangeSelection.js.map +1 -0
  571. package/build/dist/UI/Components/Charts/Line/LineChart.js +16 -1
  572. package/build/dist/UI/Components/Charts/Line/LineChart.js.map +1 -1
  573. package/build/dist/UI/Components/Charts/TimeRangeZoom/ResetTimeRangeZoomButton.js +35 -0
  574. package/build/dist/UI/Components/Charts/TimeRangeZoom/ResetTimeRangeZoomButton.js.map +1 -0
  575. package/build/dist/UI/Components/Charts/TimeRangeZoom/TimeRangeZoomContext.js +107 -0
  576. package/build/dist/UI/Components/Charts/TimeRangeZoom/TimeRangeZoomContext.js.map +1 -0
  577. package/build/dist/UI/Components/Charts/TimeRangeZoom/TimeRangeZoomHint.js +41 -0
  578. package/build/dist/UI/Components/Charts/TimeRangeZoom/TimeRangeZoomHint.js.map +1 -0
  579. package/build/dist/UI/Components/Charts/TimeRangeZoom/TimeRangeZoomUtil.js +117 -0
  580. package/build/dist/UI/Components/Charts/TimeRangeZoom/TimeRangeZoomUtil.js.map +1 -0
  581. package/build/dist/UI/Components/Charts/TimeRangeZoom/UseTimeRangeZoom.js +121 -0
  582. package/build/dist/UI/Components/Charts/TimeRangeZoom/UseTimeRangeZoom.js.map +1 -0
  583. package/build/dist/UI/Components/Charts/Utils/DataPoint.js +15 -7
  584. package/build/dist/UI/Components/Charts/Utils/DataPoint.js.map +1 -1
  585. package/build/dist/UI/Components/Charts/Utils/XAxis.js +70 -0
  586. package/build/dist/UI/Components/Charts/Utils/XAxis.js.map +1 -1
  587. package/build/dist/UI/Components/Charts/Utils/useHistogramRangeSelection.js +305 -38
  588. package/build/dist/UI/Components/Charts/Utils/useHistogramRangeSelection.js.map +1 -1
  589. package/build/dist/UI/Components/EntityDropdown/EntityDropdown.js +89 -14
  590. package/build/dist/UI/Components/EntityDropdown/EntityDropdown.js.map +1 -1
  591. package/build/dist/UI/Components/Forms/Fields/FormField.js +11 -2
  592. package/build/dist/UI/Components/Forms/Fields/FormField.js.map +1 -1
  593. package/build/dist/UI/Components/Input/Input.js +69 -54
  594. package/build/dist/UI/Components/Input/Input.js.map +1 -1
  595. package/build/dist/UI/Components/List/ListRow.js +4 -39
  596. package/build/dist/UI/Components/List/ListRow.js.map +1 -1
  597. package/build/dist/UI/Components/LogsViewer/LogsViewer.js +31 -8
  598. package/build/dist/UI/Components/LogsViewer/LogsViewer.js.map +1 -1
  599. package/build/dist/UI/Components/LogsViewer/components/LogTimeRangePicker.js +10 -1
  600. package/build/dist/UI/Components/LogsViewer/components/LogTimeRangePicker.js.map +1 -1
  601. package/build/dist/UI/Components/LogsViewer/components/LogsAnalyticsView.js +139 -21
  602. package/build/dist/UI/Components/LogsViewer/components/LogsAnalyticsView.js.map +1 -1
  603. package/build/dist/UI/Components/LogsViewer/components/LogsHistogram.js +30 -8
  604. package/build/dist/UI/Components/LogsViewer/components/LogsHistogram.js.map +1 -1
  605. package/build/dist/UI/Components/LogsViewer/components/LogsViewerToolbar.js +1 -1
  606. package/build/dist/UI/Components/LogsViewer/components/LogsViewerToolbar.js.map +1 -1
  607. package/build/dist/UI/Components/ModelTable/BaseModelTable.js +10 -0
  608. package/build/dist/UI/Components/ModelTable/BaseModelTable.js.map +1 -1
  609. package/build/dist/UI/Components/MoreMenu/MoreMenu.js +253 -14
  610. package/build/dist/UI/Components/MoreMenu/MoreMenu.js.map +1 -1
  611. package/build/dist/UI/Components/MoreMenu/MoreMenuItem.js +43 -13
  612. package/build/dist/UI/Components/MoreMenu/MoreMenuItem.js.map +1 -1
  613. package/build/dist/UI/Components/OrderedStatesList/Item.js +2 -38
  614. package/build/dist/UI/Components/OrderedStatesList/Item.js.map +1 -1
  615. package/build/dist/UI/Components/StatusPage/ResourceGrid.js +53 -15
  616. package/build/dist/UI/Components/StatusPage/ResourceGrid.js.map +1 -1
  617. package/build/dist/UI/Components/Table/TableBody.js +6 -2
  618. package/build/dist/UI/Components/Table/TableBody.js.map +1 -1
  619. package/build/dist/UI/Components/Table/TableRow.js +12 -75
  620. package/build/dist/UI/Components/Table/TableRow.js.map +1 -1
  621. package/build/dist/UI/Components/TelemetryViewer/TelemetryViewer.js +32 -7
  622. package/build/dist/UI/Components/TelemetryViewer/TelemetryViewer.js.map +1 -1
  623. package/build/dist/UI/Components/TelemetryViewer/components/TelemetryHistogram.js +27 -9
  624. package/build/dist/UI/Components/TelemetryViewer/components/TelemetryHistogram.js.map +1 -1
  625. package/build/dist/UI/Components/TelemetryViewer/components/TelemetryTimeRangePicker.js +11 -1
  626. package/build/dist/UI/Components/TelemetryViewer/components/TelemetryTimeRangePicker.js.map +1 -1
  627. package/build/dist/UI/Components/TelemetryViewer/useViewerTimeRangeZoom.js +272 -0
  628. package/build/dist/UI/Components/TelemetryViewer/useViewerTimeRangeZoom.js.map +1 -0
  629. package/build/dist/UI/Components/Toggle/Toggle.js +14 -1
  630. package/build/dist/UI/Components/Toggle/Toggle.js.map +1 -1
  631. package/build/dist/UI/Components/Tooltip/Tooltip.js +1 -1
  632. package/build/dist/UI/Components/Tooltip/Tooltip.js.map +1 -1
  633. package/build/dist/Utils/AiRemediation/CommandPolicy.js +1 -1
  634. package/build/dist/Utils/AiRemediation/CommandPolicy.js.map +1 -1
  635. package/build/dist/Utils/AiRemediation/KubectlPolicy.js +1 -1
  636. package/build/dist/Utils/AiRemediation/KubectlPolicy.js.map +1 -1
  637. package/build/dist/Utils/Dashboard/DashboardTimeRangeZoom.js +27 -1
  638. package/build/dist/Utils/Dashboard/DashboardTimeRangeZoom.js.map +1 -1
  639. package/package.json +1 -1
@@ -0,0 +1,928 @@
1
+ /** @timezone UTC */
2
+
3
+ import "@testing-library/jest-dom";
4
+ import {
5
+ afterEach,
6
+ beforeEach,
7
+ describe,
8
+ expect,
9
+ jest,
10
+ test,
11
+ } from "@jest/globals";
12
+ import {
13
+ act,
14
+ cleanup,
15
+ fireEvent,
16
+ render,
17
+ screen,
18
+ } from "@testing-library/react";
19
+ import React, {
20
+ FunctionComponent,
21
+ ReactElement,
22
+ useCallback,
23
+ useEffect,
24
+ useRef,
25
+ useState,
26
+ } from "react";
27
+
28
+ /*
29
+ * Issue #4116, on the explorers the customer reported it from. A drag across
30
+ * an explorer's volume histogram zooms the explorer, which at once asks for
31
+ * the zoomed window's bars - and a reader who wants the old window back
32
+ * double-clicks at about the moment those bars land. Both shells the
33
+ * explorers are built on are mounted here: TelemetryViewer (Traces,
34
+ * Exceptions, Security events) and Common's LogsViewer (Logs). Each sits
35
+ * under a host that owns its window the way the Dashboard's TracesViewer and
36
+ * DashboardLogsViewer do: it asks for a window's bars whenever its window
37
+ * changes and keeps the bars it has until the answer comes, and the test
38
+ * decides when that is. Recharts is real; only ResponsiveContainer is stood
39
+ * in for, and the pointer goes to the plot at the x recharts drew a bar's
40
+ * tick label at.
41
+ *
42
+ * When new bars come in, recharts re-keys every bar and drops a band a
43
+ * click painted over a bar the new window does not have. Chrome sends no
44
+ * click and no dblclick for a press whose node left the page before its
45
+ * release (measured in Chrome with a Playwright harness), so the events
46
+ * fired below are the ones Chrome sent:
47
+ *
48
+ * - The answer lands during the second press: no click, no dblclick. The
49
+ * explorer stayed zoomed, and that release, read against the new bars,
50
+ * was taken for a drag from an old bar to a new one, so the explorer
51
+ * jumped to a window nobody picked. The second press is now the zoom-out
52
+ * itself, and with no dblclick to do it, it happens on the next task.
53
+ * Failed before the fix.
54
+ * - The answer lands during the first press: that release, read the same
55
+ * way, zoomed in once more before the dblclick zoomed out. A press whose
56
+ * bar changed under a pointer that did not leave it now zooms nowhere.
57
+ * Failed before the fix.
58
+ * - The answer lands between the clicks, or before the double-click: the
59
+ * dblclick arrives and the explorer zooms out once, as it did before.
60
+ */
61
+
62
+ /*
63
+ * The logs viewer loads services and log attributes on mount; nothing here
64
+ * depends on what comes back.
65
+ */
66
+ jest.mock("../../../../UI/Utils/ModelAPI/ModelAPI", () => {
67
+ return {
68
+ __esModule: true,
69
+ default: {
70
+ getList: (): Promise<unknown> => {
71
+ return Promise.resolve({ data: [], count: 0, skip: 0, limit: 0 });
72
+ },
73
+ getCommonHeaders: (): Record<string, string> => {
74
+ return {};
75
+ },
76
+ },
77
+ };
78
+ });
79
+
80
+ jest.mock("../../../../UI/Utils/API/API", () => {
81
+ return {
82
+ __esModule: true,
83
+ default: {
84
+ post: (): Promise<unknown> => {
85
+ return Promise.resolve({ data: {} });
86
+ },
87
+ getFriendlyErrorMessage: (error: Error): string => {
88
+ return error.message;
89
+ },
90
+ getFriendlyMessage: (error: Error): string => {
91
+ return error.message;
92
+ },
93
+ },
94
+ };
95
+ });
96
+
97
+ /*
98
+ * The real one loads the browser's OpenTelemetry set-up, whose zone.js
99
+ * patches Promise for the whole file.
100
+ */
101
+ jest.mock("../../../../UI/Utils/Project", () => {
102
+ return {
103
+ __esModule: true,
104
+ default: {
105
+ getCurrentProjectId: (): null => {
106
+ return null;
107
+ },
108
+ },
109
+ };
110
+ });
111
+
112
+ jest.mock("recharts", () => {
113
+ const actual: Record<string, unknown> = jest.requireActual(
114
+ "recharts",
115
+ ) as Record<string, unknown>;
116
+ const react: typeof React = jest.requireActual("react") as typeof React;
117
+ return {
118
+ ...actual,
119
+ ResponsiveContainer: (props: {
120
+ children: React.ReactElement;
121
+ }): React.ReactElement => {
122
+ return react.cloneElement(props.children, { width: 600, height: 300 });
123
+ },
124
+ };
125
+ });
126
+
127
+ // Imported after the mocks so the viewers pick them up.
128
+ import TelemetryViewer from "../../../../UI/Components/TelemetryViewer/TelemetryViewer";
129
+ import {
130
+ HistogramBucket as TelemetryHistogramBucket,
131
+ HistogramSeriesOption,
132
+ } from "../../../../UI/Components/TelemetryViewer/types";
133
+ import LogsViewer, {
134
+ ComponentProps as LogsViewerProps,
135
+ } from "../../../../UI/Components/LogsViewer/LogsViewer";
136
+ import { HistogramBucket as LogsHistogramBucket } from "../../../../UI/Components/LogsViewer/types";
137
+ import useLogsHistogram, {
138
+ LogsHistogramData,
139
+ LogsHistogramState,
140
+ } from "../../../../UI/Components/LogsViewer/useLogsHistogram";
141
+ import { DOUBLE_CLICK_DISAMBIGUATION_MS } from "../../../../UI/Components/Charts/ChartLibrary/Utils/DoubleClick";
142
+ import InBetween from "../../../../Types/BaseDatabase/InBetween";
143
+ import LogSeverity from "../../../../Types/Log/LogSeverity";
144
+ import RangeStartAndEndDateTime, {
145
+ RangeStartAndEndDateTimeUtil,
146
+ } from "../../../../Types/Time/RangeStartAndEndDateTime";
147
+ import TimeRange from "../../../../Types/Time/TimeRange";
148
+
149
+ const MINUTE_MS: number = 60 * 1000;
150
+ // One animation frame, as fake timers run them.
151
+ const FRAME_MS: number = 16;
152
+ // The explorers open on the past hour: 11:00 to 12:00.
153
+ const NOW: Date = new Date("2026-09-28T12:00:00.000Z");
154
+ // Inside the plot, low enough to be over every bar.
155
+ const PLOT_Y: number = 200;
156
+
157
+ const PAST_HOUR: RangeStartAndEndDateTime = { range: TimeRange.PAST_ONE_HOUR };
158
+ const RESET_HINT: string = "Double-click to reset";
159
+ const HOST_WINDOW_TEST_ID: string = "host-window";
160
+ const TICK_LABEL_SELECTOR: string =
161
+ ".recharts-xAxis-tick-labels text.recharts-cartesian-axis-tick-value";
162
+
163
+ // The hour, in five-minute bars.
164
+ const HOUR_BARS: Array<string> = [
165
+ "11:00",
166
+ "11:05",
167
+ "11:10",
168
+ "11:15",
169
+ "11:20",
170
+ "11:25",
171
+ "11:30",
172
+ "11:35",
173
+ "11:40",
174
+ "11:45",
175
+ "11:50",
176
+ "11:55",
177
+ ];
178
+
179
+ /*
180
+ * The drag runs from the hour's 11:10 bar to its 11:15 bar, and zooms
181
+ * through the end of the last one. The double-click lands where the drag
182
+ * let go.
183
+ */
184
+ const DRAG_FROM: string = "11:10";
185
+ const DRAG_TO: string = "11:15";
186
+ const ZOOMED_WINDOW: string =
187
+ "2026-09-28T11:10:00.000Z..2026-09-28T11:20:00.000Z";
188
+
189
+ /*
190
+ * The zoomed ten minutes, in two-minute bars. The hour's 11:15 bar, where
191
+ * the double-click lands, is not one of them, so a click on it that zoomed
192
+ * in after all would show up as a zoom into 11:15..11:20.
193
+ */
194
+ const ZOOMED_BARS: Array<string> = [
195
+ "11:10",
196
+ "11:12",
197
+ "11:14",
198
+ "11:16",
199
+ "11:18",
200
+ ];
201
+
202
+ const ZOOM_CALL: string = `select ${ZOOMED_WINDOW}`;
203
+ const RESET_CALL: string = `change ${TimeRange.PAST_ONE_HOUR}`;
204
+
205
+ // "Past 1 Hour", or a custom window as start..end.
206
+ function describeRange(range: RangeStartAndEndDateTime): string {
207
+ if (range.range !== TimeRange.CUSTOM || !range.startAndEndDate) {
208
+ return range.range;
209
+ }
210
+
211
+ return `${new Date(range.startAndEndDate.startValue).toISOString()}..${new Date(
212
+ range.startAndEndDate.endValue,
213
+ ).toISOString()}`;
214
+ }
215
+
216
+ /*
217
+ * The explorers' histogram endpoint, answering only when the test says so.
218
+ * It buckets a window into about a dozen bars, never narrower than two
219
+ * minutes, and labels them the way ClickHouse does.
220
+ */
221
+ interface HistogramAnswer {
222
+ bucketStartsMs: Array<number>;
223
+ bucketIntervalMs: number;
224
+ }
225
+
226
+ interface HistogramRequest {
227
+ // The host's window when it asked.
228
+ window: string;
229
+ answer: HistogramAnswer;
230
+ resolve: (answer: HistogramAnswer) => void;
231
+ isAnswered: boolean;
232
+ }
233
+
234
+ const histogramRequests: Array<HistogramRequest> = [];
235
+
236
+ function answerFor(range: RangeStartAndEndDateTime): HistogramAnswer {
237
+ const window: InBetween<Date> =
238
+ RangeStartAndEndDateTimeUtil.getStartAndEndDate(range);
239
+ const startMs: number = new Date(window.startValue).getTime();
240
+ const endMs: number = new Date(window.endValue).getTime();
241
+ const bucketIntervalMs: number =
242
+ Math.max(2, Math.round((endMs - startMs) / MINUTE_MS / 12)) * MINUTE_MS;
243
+ const firstBucketMs: number =
244
+ Math.floor(startMs / bucketIntervalMs) * bucketIntervalMs;
245
+ const count: number = Math.round((endMs - startMs) / bucketIntervalMs);
246
+
247
+ return {
248
+ bucketStartsMs: Array.from(
249
+ { length: count },
250
+ (_: unknown, index: number): number => {
251
+ return firstBucketMs + index * bucketIntervalMs;
252
+ },
253
+ ),
254
+ bucketIntervalMs: bucketIntervalMs,
255
+ };
256
+ }
257
+
258
+ function askForHistogram(
259
+ range: RangeStartAndEndDateTime,
260
+ ): Promise<HistogramAnswer> {
261
+ return new Promise<HistogramAnswer>(
262
+ (resolve: (answer: HistogramAnswer) => void): void => {
263
+ histogramRequests.push({
264
+ window: describeRange(range),
265
+ answer: answerFor(range),
266
+ resolve: resolve,
267
+ isAnswered: false,
268
+ });
269
+ },
270
+ );
271
+ }
272
+
273
+ // The answer still held back comes in, and the host re-renders with it.
274
+ async function deliverHeldAnswer(): Promise<void> {
275
+ const held: Array<HistogramRequest> = histogramRequests.filter(
276
+ (request: HistogramRequest): boolean => {
277
+ return !request.isAnswered;
278
+ },
279
+ );
280
+ const request: HistogramRequest | undefined = held[held.length - 1];
281
+
282
+ if (!request) {
283
+ throw new Error("no histogram answer is held back");
284
+ }
285
+
286
+ await act(async () => {
287
+ request.isAnswered = true;
288
+ request.resolve(request.answer);
289
+ });
290
+ }
291
+
292
+ function requestedWindows(): Array<string> {
293
+ return histogramRequests.map((request: HistogramRequest): string => {
294
+ return request.window;
295
+ });
296
+ }
297
+
298
+ function heldWindows(): Array<string> {
299
+ return histogramRequests
300
+ .filter((request: HistogramRequest): boolean => {
301
+ return !request.isAnswered;
302
+ })
303
+ .map((request: HistogramRequest): string => {
304
+ return request.window;
305
+ });
306
+ }
307
+
308
+ function pad(value: number): string {
309
+ return String(value).padStart(2, "0");
310
+ }
311
+
312
+ // ClickHouse's DateTime rendering: "2026-09-28 11:10:00".
313
+ function bucketLabel(startMs: number): string {
314
+ const date: Date = new Date(startMs);
315
+ return `${date.getUTCFullYear()}-${pad(date.getUTCMonth() + 1)}-${pad(
316
+ date.getUTCDate(),
317
+ )} ${pad(date.getUTCHours())}:${pad(date.getUTCMinutes())}:00`;
318
+ }
319
+
320
+ function countAt(startMs: number): number {
321
+ return 20 + (Math.floor(startMs / MINUTE_MS) % 10);
322
+ }
323
+
324
+ // What each host was asked to do, in order.
325
+ const hostCalls: Array<string> = [];
326
+
327
+ // The windows the host was asked to zoom to.
328
+ function zoomCalls(): Array<string> {
329
+ return hostCalls.filter((call: string): boolean => {
330
+ return call.startsWith("select ");
331
+ });
332
+ }
333
+
334
+ interface HostWindow {
335
+ timeRange: RangeStartAndEndDateTime;
336
+ onHistogramTimeRangeSelect: (startTime: Date, endTime: Date) => void;
337
+ onTimeRangeChange: (timeRange: RangeStartAndEndDateTime) => void;
338
+ }
339
+
340
+ /*
341
+ * The window an explorer's host owns, as TracesViewer and
342
+ * DashboardLogsViewer own theirs: a drag on the histogram becomes a custom
343
+ * window, and any other range (the picker, the way back out of a zoom) is
344
+ * applied as it comes.
345
+ */
346
+ function useHostWindow(): HostWindow {
347
+ const [timeRange, setTimeRange] =
348
+ useState<RangeStartAndEndDateTime>(PAST_HOUR);
349
+
350
+ return {
351
+ timeRange: timeRange,
352
+ onHistogramTimeRangeSelect: (startTime: Date, endTime: Date): void => {
353
+ const zoomed: RangeStartAndEndDateTime = {
354
+ range: TimeRange.CUSTOM,
355
+ startAndEndDate: new InBetween<Date>(startTime, endTime),
356
+ };
357
+ hostCalls.push(`select ${describeRange(zoomed)}`);
358
+ setTimeRange(zoomed);
359
+ },
360
+ onTimeRangeChange: (next: RangeStartAndEndDateTime): void => {
361
+ hostCalls.push(`change ${describeRange(next)}`);
362
+ setTimeRange(next);
363
+ },
364
+ };
365
+ }
366
+
367
+ interface TraceRow {
368
+ id: string;
369
+ }
370
+
371
+ const TRACE_SERIES: Array<HistogramSeriesOption> = [
372
+ { key: "ok", label: "Ok", color: "#10b981" },
373
+ ];
374
+
375
+ /*
376
+ * The traces explorer's host, as TracesViewer is: it asks for the new
377
+ * window's bars on every window, keeps the bars it has while it waits, and
378
+ * drops an answer to a window it has since left.
379
+ */
380
+ const TracesExplorerHost: FunctionComponent = (): ReactElement => {
381
+ const host: HostWindow = useHostWindow();
382
+ const [buckets, setBuckets] = useState<Array<TelemetryHistogramBucket>>([]);
383
+ const [bucketIntervalMs, setBucketIntervalMs] = useState<number | undefined>(
384
+ undefined,
385
+ );
386
+ const [isLoading, setIsLoading] = useState<boolean>(false);
387
+ const latestRequest: React.MutableRefObject<number> = useRef<number>(0);
388
+
389
+ useEffect(() => {
390
+ latestRequest.current += 1;
391
+ const request: number = latestRequest.current;
392
+ setIsLoading(true);
393
+
394
+ askForHistogram(host.timeRange).then((answer: HistogramAnswer): void => {
395
+ if (request !== latestRequest.current) {
396
+ return;
397
+ }
398
+
399
+ setBuckets(
400
+ answer.bucketStartsMs.map(
401
+ (startMs: number): TelemetryHistogramBucket => {
402
+ return {
403
+ time: bucketLabel(startMs),
404
+ series: "ok",
405
+ count: countAt(startMs),
406
+ };
407
+ },
408
+ ),
409
+ );
410
+ setBucketIntervalMs(answer.bucketIntervalMs);
411
+ setIsLoading(false);
412
+ });
413
+ }, [host.timeRange]);
414
+
415
+ return (
416
+ <div>
417
+ <span data-testid={HOST_WINDOW_TEST_ID}>
418
+ {describeRange(host.timeRange)}
419
+ </span>
420
+ <TelemetryViewer<TraceRow>
421
+ items={[]}
422
+ isLoading={false}
423
+ renderRow={(row: TraceRow): ReactElement => {
424
+ return <span>{row.id}</span>;
425
+ }}
426
+ getRowKey={(row: TraceRow): string => {
427
+ return row.id;
428
+ }}
429
+ searchValue=""
430
+ onSearchChange={(): void => {}}
431
+ onSearchSubmit={(): void => {}}
432
+ timeRange={host.timeRange}
433
+ onTimeRangeChange={host.onTimeRangeChange}
434
+ page={1}
435
+ pageSize={50}
436
+ totalCount={0}
437
+ onPageChange={(): void => {}}
438
+ onPageSizeChange={(): void => {}}
439
+ showHistogram={true}
440
+ histogramBuckets={buckets}
441
+ histogramSeries={TRACE_SERIES}
442
+ histogramTitle="Traces over time"
443
+ histogramLoading={isLoading}
444
+ histogramBucketIntervalMs={bucketIntervalMs}
445
+ onHistogramTimeRangeSelect={host.onHistogramTimeRangeSelect}
446
+ />
447
+ </div>
448
+ );
449
+ };
450
+
451
+ const NO_LOG_FILTER: LogsViewerProps["filterData"] = {};
452
+
453
+ /*
454
+ * The logs explorer's host, as DashboardLogsViewer is: its bars come
455
+ * through useLogsHistogram, which keeps the bars it has while a new window
456
+ * loads and drops an answer to a window it has since left.
457
+ */
458
+ const LogsExplorerHost: FunctionComponent = (): ReactElement => {
459
+ const host: HostWindow = useHostWindow();
460
+ const timeRange: RangeStartAndEndDateTime = host.timeRange;
461
+
462
+ const fetchBuckets: () => Promise<LogsHistogramData> =
463
+ useCallback(async (): Promise<LogsHistogramData> => {
464
+ const answer: HistogramAnswer = await askForHistogram(timeRange);
465
+ return {
466
+ buckets: answer.bucketStartsMs.map(
467
+ (startMs: number): LogsHistogramBucket => {
468
+ return {
469
+ time: bucketLabel(startMs),
470
+ severity: LogSeverity.Error,
471
+ count: countAt(startMs),
472
+ };
473
+ },
474
+ ),
475
+ bucketIntervalMs: answer.bucketIntervalMs,
476
+ };
477
+ }, [timeRange]);
478
+
479
+ const histogram: LogsHistogramState = useLogsHistogram(fetchBuckets);
480
+
481
+ return (
482
+ <div>
483
+ <span data-testid={HOST_WINDOW_TEST_ID}>{describeRange(timeRange)}</span>
484
+ <LogsViewer
485
+ logs={[]}
486
+ isLoading={false}
487
+ filterData={NO_LOG_FILTER}
488
+ onFilterChanged={(): void => {}}
489
+ histogramBuckets={histogram.buckets}
490
+ histogramLoading={histogram.isLoading}
491
+ histogramBucketIntervalMs={histogram.bucketIntervalMs}
492
+ onHistogramTimeRangeSelect={host.onHistogramTimeRangeSelect}
493
+ timeRange={timeRange}
494
+ onTimeRangeChange={host.onTimeRangeChange}
495
+ />
496
+ </div>
497
+ );
498
+ };
499
+
500
+ interface ExplorerUnderTest {
501
+ name: string;
502
+ host: FunctionComponent;
503
+ }
504
+
505
+ const EXPLORERS: Array<ExplorerUnderTest> = [
506
+ {
507
+ name: "traces explorer (TelemetryViewer)",
508
+ host: TracesExplorerHost,
509
+ },
510
+ {
511
+ name: "logs explorer (LogsViewer)",
512
+ host: LogsExplorerHost,
513
+ },
514
+ ];
515
+
516
+ function hostWindow(): string {
517
+ return screen.getByTestId(HOST_WINDOW_TEST_ID).textContent || "";
518
+ }
519
+
520
+ function chartWrapper(): Element {
521
+ const wrapper: Element | null = document.querySelector(".recharts-wrapper");
522
+ if (!wrapper) {
523
+ throw new Error("the explorer drew no chart");
524
+ }
525
+ return wrapper;
526
+ }
527
+
528
+ function plotSurface(): Element {
529
+ const surface: Element | null = chartWrapper().querySelector(
530
+ "svg.recharts-surface",
531
+ );
532
+ if (!surface) {
533
+ throw new Error("the chart drew no plot surface");
534
+ }
535
+ return surface;
536
+ }
537
+
538
+ // The bars on the chart, by their tick labels: "11:15 AM" (or "11:15") -> "11:15".
539
+ function barLabels(): Array<string> {
540
+ return Array.from(chartWrapper().querySelectorAll(TICK_LABEL_SELECTOR)).map(
541
+ (element: Element): string => {
542
+ const text: string = element.textContent || "";
543
+ const time: RegExpMatchArray | null = text.match(/^\d{1,2}:\d{2}/);
544
+ return time ? time[0] : text;
545
+ },
546
+ );
547
+ }
548
+
549
+ // The pointer x that lands on a bar: the centre of its tick label.
550
+ function tickX(label: string): number {
551
+ const tick: Element | undefined = Array.from(
552
+ chartWrapper().querySelectorAll(TICK_LABEL_SELECTOR),
553
+ ).find((element: Element): boolean => {
554
+ return (element.textContent || "").startsWith(label);
555
+ });
556
+ if (!tick) {
557
+ throw new Error(`the chart drew no tick label "${label}"`);
558
+ }
559
+ return Number(tick.getAttribute("x"));
560
+ }
561
+
562
+ function drawnBand(): Element | null {
563
+ return chartWrapper().querySelector(".recharts-reference-area-rect");
564
+ }
565
+
566
+ function covers(element: Element, x: number, y: number): boolean {
567
+ const left: number = Number(element.getAttribute("x"));
568
+ const top: number = Number(element.getAttribute("y"));
569
+ const width: number = Number(element.getAttribute("width"));
570
+ const height: number = Number(element.getAttribute("height"));
571
+ return x >= left && x <= left + width && y >= top && y <= top + height;
572
+ }
573
+
574
+ /*
575
+ * The node under the pointer, as the browser hit-tests it: a band a click
576
+ * painted lies over the bars, and the bars over the plot.
577
+ */
578
+ function nodeAt(x: number): Element {
579
+ const band: Element | null = drawnBand();
580
+ if (band && covers(band, x, PLOT_Y)) {
581
+ return band;
582
+ }
583
+
584
+ const bar: Element | undefined = Array.from(
585
+ chartWrapper().querySelectorAll(".recharts-bar-rectangle path"),
586
+ ).find((element: Element): boolean => {
587
+ return covers(element, x, PLOT_Y);
588
+ });
589
+
590
+ return bar || plotSurface();
591
+ }
592
+
593
+ function commonAncestor(first: Element, second: Element): Element {
594
+ let node: Element | null = first;
595
+ while (node && !node.contains(second)) {
596
+ node = node.parentElement;
597
+ }
598
+ return node || document.body;
599
+ }
600
+
601
+ function pointerAt(
602
+ x: number,
603
+ init: Record<string, number>,
604
+ ): Record<string, number> {
605
+ return { clientX: x, clientY: PLOT_Y, ...init };
606
+ }
607
+
608
+ function nextFrame(): void {
609
+ act(() => {
610
+ jest.advanceTimersByTime(FRAME_MS);
611
+ });
612
+ }
613
+
614
+ // Lets the page's next task run: fake timers run what was set for 0 ms.
615
+ function nextTask(): void {
616
+ act(() => {
617
+ jest.advanceTimersByTime(0);
618
+ });
619
+ }
620
+
621
+ // Long past the wait a single click on a zoomed chart makes before zooming in.
622
+ function outlastDoubleClickWindow(): void {
623
+ act(() => {
624
+ jest.advanceTimersByTime(DOUBLE_CLICK_DISAMBIGUATION_MS * 2);
625
+ });
626
+ }
627
+
628
+ // The pointer comes to rest at x.
629
+ function restAt(x: number): void {
630
+ fireEvent.mouseMove(nodeAt(x), pointerAt(x, { buttons: 0 }));
631
+ nextFrame();
632
+ }
633
+
634
+ // The pointer moves to x with the button held down.
635
+ function moveHeldTo(x: number): void {
636
+ fireEvent.mouseMove(nodeAt(x), pointerAt(x, { buttons: 1 }));
637
+ }
638
+
639
+ /*
640
+ * A press at x, `detail` being the browser's click count (2 for the second
641
+ * press of a double-click). Returns the node the press landed on.
642
+ */
643
+ function pressAt(x: number, detail: number): Element {
644
+ const node: Element = nodeAt(x);
645
+ fireEvent.mouseDown(
646
+ node,
647
+ pointerAt(x, { button: 0, buttons: 1, detail: detail }),
648
+ );
649
+ return node;
650
+ }
651
+
652
+ function releaseAt(x: number, detail: number): Element {
653
+ const node: Element = nodeAt(x);
654
+ fireEvent.mouseUp(
655
+ node,
656
+ pointerAt(x, { button: 0, buttons: 0, detail: detail }),
657
+ );
658
+ return node;
659
+ }
660
+
661
+ /*
662
+ * The click the browser sends after a release, to the nearest node holding
663
+ * both the node the press landed on and the one the release did. Returns
664
+ * where it went.
665
+ */
666
+ function clickAfter(
667
+ pressed: Element,
668
+ released: Element,
669
+ x: number,
670
+ detail: number,
671
+ ): Element {
672
+ const target: Element = commonAncestor(pressed, released);
673
+ fireEvent.click(target, pointerAt(x, { button: 0, detail: detail }));
674
+ return target;
675
+ }
676
+
677
+ // A click at x that nothing disturbs.
678
+ function clickAt(x: number, detail: number): Element {
679
+ const pressed: Element = pressAt(x, detail);
680
+ return clickAfter(pressed, releaseAt(x, detail), x, detail);
681
+ }
682
+
683
+ // A second click at x that nothing disturbs, and the dblclick that follows it.
684
+ function secondClickAndDoubleClickAt(x: number): void {
685
+ const clicked: Element = clickAt(x, 2);
686
+ fireEvent.doubleClick(clicked, pointerAt(x, { button: 0, detail: 2 }));
687
+ }
688
+
689
+ // A drag from one bar to another, let go over the chart.
690
+ function dragAcross(fromX: number, toX: number): void {
691
+ restAt(fromX);
692
+ pressAt(fromX, 1);
693
+ nextFrame();
694
+ moveHeldTo(toX);
695
+ nextFrame();
696
+ releaseAt(toX, 1);
697
+ fireEvent.click(plotSurface(), pointerAt(toX, { button: 0, detail: 1 }));
698
+ }
699
+
700
+ async function openExplorer(explorer: ExplorerUnderTest): Promise<void> {
701
+ const Host: FunctionComponent = explorer.host;
702
+
703
+ await act(async () => {
704
+ render(<Host />);
705
+ });
706
+
707
+ expect(heldWindows()).toEqual([TimeRange.PAST_ONE_HOUR]);
708
+ await deliverHeldAnswer();
709
+ expect(barLabels()).toEqual(HOUR_BARS);
710
+ }
711
+
712
+ /*
713
+ * Opens the explorer on the hour and drags across two of its bars. The
714
+ * host moves to the ten minutes the drag covered and asks for their bars,
715
+ * which the test holds back: the chart keeps drawing the hour's bars, and
716
+ * offers the double-click that undoes the zoom. Returns where the drag let
717
+ * go, which is where the double-click lands.
718
+ */
719
+ async function openAndZoomByDragging(
720
+ explorer: ExplorerUnderTest,
721
+ ): Promise<number> {
722
+ await openExplorer(explorer);
723
+
724
+ const spot: number = tickX(DRAG_TO);
725
+ dragAcross(tickX(DRAG_FROM), spot);
726
+
727
+ expect(hostCalls).toEqual([ZOOM_CALL]);
728
+ expect(hostWindow()).toBe(ZOOMED_WINDOW);
729
+ expect(requestedWindows()).toEqual([TimeRange.PAST_ONE_HOUR, ZOOMED_WINDOW]);
730
+ expect(heldWindows()).toEqual([ZOOMED_WINDOW]);
731
+ expect(barLabels()).toEqual(HOUR_BARS);
732
+ expect(screen.getByText(RESET_HINT)).toBeInTheDocument();
733
+
734
+ return spot;
735
+ }
736
+
737
+ // The explorer is back on the hour, by the one way back, and asked for it again.
738
+ function expectBackOnTheHourOnce(): void {
739
+ expect(hostWindow()).toBe(TimeRange.PAST_ONE_HOUR);
740
+ expect(hostCalls).toEqual([ZOOM_CALL, RESET_CALL]);
741
+ expect(requestedWindows()).toEqual([
742
+ TimeRange.PAST_ONE_HOUR,
743
+ ZOOMED_WINDOW,
744
+ TimeRange.PAST_ONE_HOUR,
745
+ ]);
746
+ expect(screen.queryByText(RESET_HINT)).toBeNull();
747
+ }
748
+
749
+ beforeEach(() => {
750
+ /*
751
+ * Installed AT the fixed time, not moved there with setSystemTime: fake
752
+ * timers count animation frames from the moment they are installed.
753
+ */
754
+ jest.useFakeTimers({ now: NOW });
755
+ histogramRequests.splice(0, histogramRequests.length);
756
+ hostCalls.splice(0, hostCalls.length);
757
+ });
758
+
759
+ afterEach(() => {
760
+ cleanup();
761
+ jest.useRealTimers();
762
+ });
763
+
764
+ for (const explorer of EXPLORERS) {
765
+ describe(`the ${explorer.name}: a double-click right after a drag-zoom, while the zoomed bars are on their way`, () => {
766
+ test("the drag zooms the host to the bars it covered, and the chart keeps the hour's bars while that answer is held back", async () => {
767
+ await openAndZoomByDragging(explorer);
768
+
769
+ // Nothing else happens while the answer is out.
770
+ outlastDoubleClickWindow();
771
+ expect(hostCalls).toEqual([ZOOM_CALL]);
772
+ expect(barLabels()).toEqual(HOUR_BARS);
773
+
774
+ await deliverHeldAnswer();
775
+ expect(barLabels()).toEqual(ZOOMED_BARS);
776
+ expect(hostWindow()).toBe(ZOOMED_WINDOW);
777
+ expect(screen.getByText(RESET_HINT)).toBeInTheDocument();
778
+ });
779
+
780
+ // (i) Failed before the fix: a zoom to 11:15..11:18, and still zoomed.
781
+ test("the answer lands during the second press, which then gets no click and no dblclick: the next task puts the host back on the hour, and nothing zooms in", async () => {
782
+ const spot: number = await openAndZoomByDragging(explorer);
783
+
784
+ restAt(spot);
785
+ clickAt(spot, 1);
786
+
787
+ /*
788
+ * On a zoomed chart the first click paints its bar while it waits out
789
+ * the double-click window, so the second press lands on that band.
790
+ */
791
+ const band: Element | null = drawnBand();
792
+ expect(band).not.toBeNull();
793
+ const secondPress: Element = pressAt(spot, 2);
794
+ expect(secondPress).toBe(band);
795
+
796
+ await deliverHeldAnswer();
797
+
798
+ /*
799
+ * The zoomed minutes are drawn, and the band the press landed on is
800
+ * gone with the hour's bars: Chrome sends this press no click and no
801
+ * dblclick, and none is fired.
802
+ */
803
+ expect(barLabels()).toEqual(ZOOMED_BARS);
804
+ expect(secondPress.isConnected).toBe(false);
805
+ releaseAt(spot, 2);
806
+ expect(zoomCalls()).toEqual([ZOOM_CALL]);
807
+
808
+ nextTask();
809
+
810
+ expectBackOnTheHourOnce();
811
+
812
+ // Nor does the first click's zoom into 11:15 come later.
813
+ outlastDoubleClickWindow();
814
+ expect(hostCalls).toEqual([ZOOM_CALL, RESET_CALL]);
815
+ });
816
+
817
+ // (ii) Failed before the fix: a zoom to 11:15..11:18 before the zoom-out.
818
+ test("the answer lands during the first press: that release zooms nowhere, and the dblclick puts the host back on the hour once", async () => {
819
+ const spot: number = await openAndZoomByDragging(explorer);
820
+
821
+ restAt(spot);
822
+ const firstPress: Element = pressAt(spot, 1);
823
+ expect(firstPress.closest(".recharts-bar-rectangle")).not.toBeNull();
824
+
825
+ await deliverHeldAnswer();
826
+
827
+ /*
828
+ * The bar the press landed on is gone: Chrome sends this press no
829
+ * click. Its release is over a bar of the new window, at a pointer
830
+ * that never moved.
831
+ */
832
+ expect(barLabels()).toEqual(ZOOMED_BARS);
833
+ expect(firstPress.isConnected).toBe(false);
834
+ releaseAt(spot, 1);
835
+ expect(hostCalls).toEqual([ZOOM_CALL]);
836
+
837
+ secondClickAndDoubleClickAt(spot);
838
+ outlastDoubleClickWindow();
839
+
840
+ expectBackOnTheHourOnce();
841
+ });
842
+
843
+ // (ii) Failed before the fix: the drift read as a drag to 11:12..11:17.
844
+ test("the answer lands during the first press while the hand drifts a couple of pixels: still no zoom, and the dblclick puts the host back on the hour once", async () => {
845
+ const spot: number = await openAndZoomByDragging(explorer);
846
+
847
+ restAt(spot);
848
+ pressAt(spot, 1);
849
+ await deliverHeldAnswer();
850
+ expect(barLabels()).toEqual(ZOOMED_BARS);
851
+
852
+ /*
853
+ * A hand on a mouse is never quite still. The pointer drifts two
854
+ * pixels, and recharts reads a bar of the new window under it: that
855
+ * is not a drag, and paints nothing.
856
+ */
857
+ const drifted: number = spot + 2;
858
+ moveHeldTo(drifted);
859
+ expect(drawnBand()).toBeNull();
860
+ releaseAt(drifted, 1);
861
+ expect(hostCalls).toEqual([ZOOM_CALL]);
862
+
863
+ secondClickAndDoubleClickAt(drifted);
864
+ outlastDoubleClickWindow();
865
+
866
+ expectBackOnTheHourOnce();
867
+ });
868
+
869
+ // Failed before the fix: a zoom to 11:15..11:18 at the release.
870
+ test("the answer lands during the first press and no second click follows: that click zooms nowhere, and the explorer stays zoomed", async () => {
871
+ const spot: number = await openAndZoomByDragging(explorer);
872
+
873
+ restAt(spot);
874
+ const pressed: Element = pressAt(spot, 1);
875
+ await deliverHeldAnswer();
876
+ expect(pressed.isConnected).toBe(false);
877
+ releaseAt(spot, 1);
878
+
879
+ /*
880
+ * The bar it pressed is no longer on the chart, so there is nothing
881
+ * to zoom into, then or once the double-click window has passed.
882
+ */
883
+ outlastDoubleClickWindow();
884
+ expect(hostCalls).toEqual([ZOOM_CALL]);
885
+ expect(hostWindow()).toBe(ZOOMED_WINDOW);
886
+ expect(drawnBand()).toBeNull();
887
+ expect(screen.getByText(RESET_HINT)).toBeInTheDocument();
888
+ });
889
+
890
+ // (iv) As before the fix; pinned so the new zoom-out path keeps it.
891
+ test("the answer lands between the two clicks: the dblclick puts the host back on the hour once, and the first click never zooms in", async () => {
892
+ const spot: number = await openAndZoomByDragging(explorer);
893
+
894
+ restAt(spot);
895
+ clickAt(spot, 1);
896
+ expect(drawnBand()).not.toBeNull();
897
+
898
+ await deliverHeldAnswer();
899
+ expect(barLabels()).toEqual(ZOOMED_BARS);
900
+
901
+ secondClickAndDoubleClickAt(spot);
902
+ outlastDoubleClickWindow();
903
+
904
+ expectBackOnTheHourOnce();
905
+ });
906
+
907
+ // (iii) As before the fix; pinned so the new zoom-out path keeps it.
908
+ test("the answer lands before the double-click: the dblclick puts the host back on the hour once", async () => {
909
+ const spot: number = await openAndZoomByDragging(explorer);
910
+
911
+ await deliverHeldAnswer();
912
+ expect(barLabels()).toEqual(ZOOMED_BARS);
913
+
914
+ restAt(spot);
915
+ clickAt(spot, 1);
916
+ secondClickAndDoubleClickAt(spot);
917
+
918
+ /*
919
+ * The dblclick zoomed out at once. The reset the second press armed
920
+ * for the next task stood down, and neither click zooms in later.
921
+ */
922
+ expectBackOnTheHourOnce();
923
+ nextTask();
924
+ outlastDoubleClickWindow();
925
+ expect(hostCalls).toEqual([ZOOM_CALL, RESET_CALL]);
926
+ });
927
+ });
928
+ }