@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,1115 @@
1
+ import "@testing-library/jest-dom";
2
+ import {
3
+ afterEach,
4
+ beforeEach,
5
+ describe,
6
+ expect,
7
+ jest,
8
+ test,
9
+ } from "@jest/globals";
10
+ import {
11
+ act,
12
+ cleanup,
13
+ fireEvent,
14
+ render,
15
+ screen,
16
+ waitFor,
17
+ within,
18
+ } from "@testing-library/react";
19
+ import * as React from "react";
20
+ import getJestMockFunction, { MockFunction } from "../../MockType";
21
+
22
+ /*
23
+ * Issue #4105 on a network device's two time-series pages, each of which is
24
+ * one card that owns the page's range:
25
+ *
26
+ * - Metrics (DeviceHealthCharts): the interface utilization and polled
27
+ * OID panels are one MetricView. A drag on either zooms the CARD'S range,
28
+ * so its picker reads Custom (the view used to narrow only its own window
29
+ * and leave the picker on the old range), both panels re-fetch, and a
30
+ * double-click or "Reset zoom" puts the range back.
31
+ * - Traffic (FlowTopTalkers): the bandwidth chart - once a hand-drawn SVG
32
+ * with no gestures, now the shared area chart - zooms the card's range,
33
+ * and the totals and every top-N table, which come from the same fetch,
34
+ * follow it.
35
+ *
36
+ * The network is replaced (the metric fetch, the top-talkers POST); so is the
37
+ * card's range picker (a stand-in showing the range, which can pick "Past 1
38
+ * Day"). MetricView renders for real around a MetricCharts stand-in; the
39
+ * bandwidth chart is ChartZoomStandIn, which resolves its zoom exactly as
40
+ * the real area chart wrapper does.
41
+ */
42
+
43
+ const DEVICE_ID: string = "11111111-1111-4111-8111-111111111111";
44
+ const PROJECT_ID: string = "10000000-0000-4000-8000-000000000001";
45
+ const NOW: Date = new Date("2026-09-28T12:00:00.000Z");
46
+ const MINUTE: number = 60 * 1000;
47
+ const ZOOM_START: Date = new Date("2026-09-28T11:20:00.000Z");
48
+ const ZOOM_END: Date = new Date("2026-09-28T11:40:00.000Z");
49
+ const INNER_ZOOM_START: Date = new Date("2026-09-28T11:25:00.000Z");
50
+ const INNER_ZOOM_END: Date = new Date("2026-09-28T11:30:00.000Z");
51
+ const ZOOM_WINDOW: [string, string] = [
52
+ ZOOM_START.toISOString(),
53
+ ZOOM_END.toISOString(),
54
+ ];
55
+ const BANDWIDTH_CHART: string = "Bandwidth [Mbps]";
56
+
57
+ const fetchResultsMock: MockFunction = getJestMockFunction();
58
+ const apiPostMock: MockFunction = getJestMockFunction();
59
+
60
+ // The zoom the page offers, as a chart inside the panels would see it.
61
+ interface MockSeenZoom {
62
+ onTimeRangeSelect: unknown;
63
+ onTimeRangeReset: unknown;
64
+ }
65
+
66
+ interface MockMetricChartsProps {
67
+ onTimeRangeSelect?: ((startTime: Date, endTime: Date) => void) | undefined;
68
+ onTimeRangeReset?: (() => void) | undefined;
69
+ pageZoomSeen?: MockSeenZoom | null | undefined;
70
+ }
71
+
72
+ const mockMetricCharts: { current: MockMetricChartsProps | null } = {
73
+ current: null,
74
+ };
75
+
76
+ jest.mock(
77
+ "../../../../App/FeatureSet/Dashboard/src/Components/Metrics/Utils/Metrics",
78
+ () => {
79
+ return {
80
+ __esModule: true,
81
+ default: {
82
+ fetchResults: (...args: Array<unknown>): unknown => {
83
+ return fetchResultsMock(...args);
84
+ },
85
+ getMetricTypes: (): Promise<Array<unknown>> => {
86
+ return Promise.resolve([]);
87
+ },
88
+ loadAllMetricsTypes: (): Promise<Record<string, unknown>> => {
89
+ return Promise.resolve({ metricTypes: [], telemetryServices: [] });
90
+ },
91
+ clearQueryTopNOverridesForScope: (): undefined => {
92
+ return undefined;
93
+ },
94
+ },
95
+ };
96
+ },
97
+ );
98
+
99
+ /*
100
+ * The panels MetricView draws: what matters is the zoom it hands them, and
101
+ * what the page offers around them.
102
+ */
103
+ jest.mock(
104
+ "../../../../App/FeatureSet/Dashboard/src/Components/Metrics/MetricCharts",
105
+ () => {
106
+ const zoomContext: { useChartTimeRangeZoom: () => unknown } =
107
+ jest.requireActual(
108
+ "../../../UI/Components/Charts/TimeRangeZoom/TimeRangeZoomContext",
109
+ ) as { useChartTimeRangeZoom: () => unknown };
110
+ return {
111
+ __esModule: true,
112
+ default: (props: MockMetricChartsProps): React.ReactElement => {
113
+ mockMetricCharts.current = {
114
+ ...props,
115
+ pageZoomSeen:
116
+ zoomContext.useChartTimeRangeZoom() as MockSeenZoom | null,
117
+ };
118
+ return (
119
+ <div
120
+ data-testid="device-panels"
121
+ onDoubleClick={props.onTimeRangeReset}
122
+ >
123
+ <button
124
+ type="button"
125
+ onClick={() => {
126
+ const drag: { start: Date; end: Date } = (
127
+ jest.requireActual("./ChartZoomStandIn") as {
128
+ standInDrag: { start: Date; end: Date };
129
+ }
130
+ ).standInDrag;
131
+ props.onTimeRangeSelect?.(drag.start, drag.end);
132
+ }}
133
+ >
134
+ Drag across the device panels
135
+ </button>
136
+ </div>
137
+ );
138
+ },
139
+ };
140
+ },
141
+ );
142
+
143
+ jest.mock("../../../UI/Components/Charts/Area/AreaChart", () => {
144
+ return {
145
+ ...(jest.requireActual(
146
+ "../../../UI/Components/Charts/Area/AreaChart",
147
+ ) as Record<string, unknown>),
148
+ __esModule: true,
149
+ default: (jest.requireActual("./ChartZoomStandIn") as { default: unknown })
150
+ .default,
151
+ };
152
+ });
153
+
154
+ // The card's picker: shows its range, and can pick "Past 1 Day".
155
+ jest.mock("../../../UI/Components/Date/RangeStartAndEndDateView", () => {
156
+ return {
157
+ __esModule: true,
158
+ default: (props: {
159
+ dashboardStartAndEndDate: { range: string };
160
+ onChange: (value: { range: string }) => void;
161
+ }): React.ReactElement => {
162
+ return (
163
+ <button
164
+ type="button"
165
+ data-testid="card-picker"
166
+ onClick={() => {
167
+ props.onChange({ range: "Past 1 Day" });
168
+ }}
169
+ >
170
+ {props.dashboardStartAndEndDate.range}
171
+ </button>
172
+ );
173
+ },
174
+ };
175
+ });
176
+
177
+ jest.mock("../../../UI/Utils/Project", () => {
178
+ return {
179
+ __esModule: true,
180
+ default: {
181
+ getCurrentProjectId: (): unknown => {
182
+ const { default: ObjectIDType } = jest.requireActual(
183
+ "../../../Types/ObjectID",
184
+ ) as { default: new (id: string) => unknown };
185
+ return new ObjectIDType("10000000-0000-4000-8000-000000000001");
186
+ },
187
+ },
188
+ };
189
+ });
190
+
191
+ jest.mock("../../../UI/Utils/API/API", () => {
192
+ return {
193
+ __esModule: true,
194
+ default: {
195
+ post: (...args: Array<unknown>): unknown => {
196
+ return apiPostMock(...args);
197
+ },
198
+ getFriendlyMessage: (error: Error): string => {
199
+ return error.message;
200
+ },
201
+ },
202
+ };
203
+ });
204
+
205
+ import DeviceHealthCharts from "../../../../App/FeatureSet/Dashboard/src/Components/NetworkDevice/DeviceHealthCharts";
206
+ import FlowTopTalkers, {
207
+ BandwidthOverTimeChart,
208
+ getBandwidthAxisPrecision,
209
+ } from "../../../../App/FeatureSet/Dashboard/src/Components/NetworkDevice/FlowTopTalkers";
210
+ import {
211
+ StandInChartRecord,
212
+ getStandInChart,
213
+ resetStandInCharts,
214
+ standInDrag,
215
+ } from "./ChartZoomStandIn";
216
+ import { RESET_TIME_RANGE_ZOOM_BUTTON_TEST_ID } from "../../../UI/Components/Charts/TimeRangeZoom/ResetTimeRangeZoomButton";
217
+ import { TIME_RANGE_ZOOM_HINT_TEST_ID } from "../../../UI/Components/Charts/TimeRangeZoom/TimeRangeZoomHint";
218
+ import {
219
+ TimeRangeZoomProvider,
220
+ TimeRangeZoomScope,
221
+ } from "../../../UI/Components/Charts/TimeRangeZoom/TimeRangeZoomContext";
222
+ import { TimeRangeZoom } from "../../../UI/Components/Charts/TimeRangeZoom/UseTimeRangeZoom";
223
+ import XAxisPrecision from "../../../UI/Components/Charts/Types/XAxis/XAxisPrecision";
224
+ import XAxisType from "../../../UI/Components/Charts/Types/XAxis/XAxisType";
225
+ import XAxisUtil from "../../../UI/Components/Charts/Utils/XAxis";
226
+ import MetricViewData from "../../../Types/Metrics/MetricViewData";
227
+ import ObjectID from "../../../Types/ObjectID";
228
+ import TimeRange from "../../../Types/Time/TimeRange";
229
+
230
+ async function flush(): Promise<void> {
231
+ for (let i: number = 0; i < 8; i++) {
232
+ await act(async () => {
233
+ await Promise.resolve();
234
+ });
235
+ }
236
+ }
237
+
238
+ function pickerLabel(): string {
239
+ return screen.getByTestId("card-picker").textContent || "";
240
+ }
241
+
242
+ function resetButtons(): Array<HTMLElement> {
243
+ return screen.queryAllByTestId(RESET_TIME_RANGE_ZOOM_BUTTON_TEST_ID);
244
+ }
245
+
246
+ beforeEach(() => {
247
+ jest.useFakeTimers({ doNotFake: ["nextTick", "setImmediate"] });
248
+ jest.setSystemTime(NOW);
249
+ fetchResultsMock.mockReset();
250
+ apiPostMock.mockReset();
251
+ mockMetricCharts.current = null;
252
+ resetStandInCharts();
253
+ });
254
+
255
+ afterEach(() => {
256
+ cleanup();
257
+ jest.useRealTimers();
258
+ });
259
+
260
+ // ------------------------------------------------ Metrics: DeviceHealthCharts
261
+
262
+ function fetchedViews(): Array<MetricViewData> {
263
+ return fetchResultsMock.mock.calls.map(
264
+ (call: Array<unknown>): MetricViewData => {
265
+ return (call[0] as { metricViewData: MetricViewData }).metricViewData;
266
+ },
267
+ );
268
+ }
269
+
270
+ function lastFetchedView(): MetricViewData {
271
+ const views: Array<MetricViewData> = fetchedViews();
272
+ const last: MetricViewData | undefined = views[views.length - 1];
273
+ if (!last) {
274
+ throw new Error("The device panels were never fetched");
275
+ }
276
+ return last;
277
+ }
278
+
279
+ function lastFetchedWindow(): [string, string] {
280
+ const view: MetricViewData = lastFetchedView();
281
+ return [
282
+ view.startAndEndDate!.startValue.toISOString(),
283
+ view.startAndEndDate!.endValue.toISOString(),
284
+ ];
285
+ }
286
+
287
+ function lastFetchedMinutes(): number {
288
+ const view: MetricViewData = lastFetchedView();
289
+ return (
290
+ (view.startAndEndDate!.endValue.getTime() -
291
+ view.startAndEndDate!.startValue.getTime()) /
292
+ MINUTE
293
+ );
294
+ }
295
+
296
+ function panels(): MockMetricChartsProps {
297
+ if (!mockMetricCharts.current) {
298
+ throw new Error("The device panels have not rendered");
299
+ }
300
+ return mockMetricCharts.current;
301
+ }
302
+
303
+ async function renderHealth(): Promise<void> {
304
+ fetchResultsMock.mockResolvedValue([
305
+ { data: [], truncated: false },
306
+ { data: [], truncated: false },
307
+ ]);
308
+ render(<DeviceHealthCharts networkDeviceId={new ObjectID(DEVICE_ID)} />);
309
+ await flush();
310
+ await waitFor(() => {
311
+ expect(screen.getByTestId("device-panels")).toBeInTheDocument();
312
+ });
313
+ await waitFor(() => {
314
+ expect(fetchResultsMock).toHaveBeenCalled();
315
+ });
316
+ }
317
+
318
+ async function dragAcrossPanels(
319
+ start: Date = ZOOM_START,
320
+ end: Date = ZOOM_END,
321
+ ): Promise<void> {
322
+ standInDrag.start = start;
323
+ standInDrag.end = end;
324
+ fireEvent.click(
325
+ screen.getByRole("button", { name: "Drag across the device panels" }),
326
+ );
327
+ await flush();
328
+ }
329
+
330
+ async function doubleClickPanels(): Promise<void> {
331
+ fireEvent.doubleClick(screen.getByTestId("device-panels"));
332
+ await flush();
333
+ }
334
+
335
+ describe("Network device Metrics: the health panels zoom the card's range", () => {
336
+ test("both panels take the card's zoom, the one the page offers", async () => {
337
+ await renderHealth();
338
+
339
+ expect(panels().pageZoomSeen).not.toBeNull();
340
+ expect(panels().onTimeRangeSelect).toBe(
341
+ panels().pageZoomSeen!.onTimeRangeSelect,
342
+ );
343
+ expect(panels().onTimeRangeReset).toBeUndefined();
344
+ // One MetricView, both panels: the interface and OID queries together.
345
+ expect(lastFetchedView().queryConfigs).toHaveLength(2);
346
+ expect(lastFetchedMinutes()).toBe(60);
347
+ expect(pickerLabel()).toBe(TimeRange.PAST_ONE_HOUR);
348
+ expect(resetButtons()).toHaveLength(0);
349
+ });
350
+
351
+ test("a drag zooms the card: both panels re-fetch the dragged window and the picker reads Custom", async () => {
352
+ await renderHealth();
353
+
354
+ await dragAcrossPanels();
355
+
356
+ expect(lastFetchedWindow()).toEqual(ZOOM_WINDOW);
357
+ expect(lastFetchedView().queryConfigs).toHaveLength(2);
358
+ // It used to keep showing "Past 1 Hour" over a zoomed chart.
359
+ expect(pickerLabel()).toBe(TimeRange.CUSTOM);
360
+ });
361
+
362
+ test("while zoomed there is exactly one Reset zoom, beside the picker", async () => {
363
+ await renderHealth();
364
+
365
+ await dragAcrossPanels();
366
+
367
+ expect(resetButtons()).toHaveLength(1);
368
+ const picker: HTMLElement = screen.getByTestId("card-picker");
369
+ expect(picker.parentElement).toContainElement(resetButtons()[0]!);
370
+ expect(panels().onTimeRangeReset).toBe(
371
+ panels().pageZoomSeen!.onTimeRangeReset,
372
+ );
373
+ });
374
+
375
+ test("a double-click puts the card's range back", async () => {
376
+ await renderHealth();
377
+
378
+ await dragAcrossPanels();
379
+ await doubleClickPanels();
380
+
381
+ expect(lastFetchedMinutes()).toBe(60);
382
+ expect(pickerLabel()).toBe(TimeRange.PAST_ONE_HOUR);
383
+ expect(resetButtons()).toHaveLength(0);
384
+ expect(panels().onTimeRangeReset).toBeUndefined();
385
+ });
386
+
387
+ test("Reset zoom does the same", async () => {
388
+ await renderHealth();
389
+
390
+ await dragAcrossPanels();
391
+ fireEvent.click(resetButtons()[0]!);
392
+ await flush();
393
+
394
+ expect(lastFetchedMinutes()).toBe(60);
395
+ expect(pickerLabel()).toBe(TimeRange.PAST_ONE_HOUR);
396
+ });
397
+
398
+ test("a zoom within a zoom: one double-click goes back to the past hour", async () => {
399
+ await renderHealth();
400
+
401
+ await dragAcrossPanels();
402
+ await dragAcrossPanels(INNER_ZOOM_START, INNER_ZOOM_END);
403
+ expect(lastFetchedWindow()).toEqual([
404
+ INNER_ZOOM_START.toISOString(),
405
+ INNER_ZOOM_END.toISOString(),
406
+ ]);
407
+
408
+ await doubleClickPanels();
409
+
410
+ expect(lastFetchedMinutes()).toBe(60);
411
+ expect(pickerLabel()).toBe(TimeRange.PAST_ONE_HOUR);
412
+ });
413
+
414
+ test("a double-click with nothing to undo does not re-fetch", async () => {
415
+ await renderHealth();
416
+ const fetches: number = fetchResultsMock.mock.calls.length;
417
+
418
+ await doubleClickPanels();
419
+
420
+ expect(fetchResultsMock.mock.calls.length).toBe(fetches);
421
+ });
422
+
423
+ test("picking a range ends the zoom", async () => {
424
+ await renderHealth();
425
+
426
+ await dragAcrossPanels();
427
+ fireEvent.click(screen.getByTestId("card-picker"));
428
+ await flush();
429
+
430
+ expect(pickerLabel()).toBe(TimeRange.PAST_ONE_DAY);
431
+ expect(lastFetchedMinutes()).toBe(24 * 60);
432
+ expect(resetButtons()).toHaveLength(0);
433
+ });
434
+
435
+ test("the card keeps its own zoom inside a page that zooms", async () => {
436
+ const pageZoomToTimeRange: MockFunction = getJestMockFunction();
437
+ const pageZoom: TimeRangeZoom = {
438
+ isZoomed: false,
439
+ rangeBeforeZoom: null,
440
+ zoomToTimeRange: pageZoomToTimeRange as unknown as (
441
+ startTime: Date,
442
+ endTime: Date,
443
+ ) => void,
444
+ resetZoom: () => {},
445
+ };
446
+ fetchResultsMock.mockResolvedValue([{ data: [], truncated: false }]);
447
+ render(
448
+ <TimeRangeZoomProvider zoom={pageZoom}>
449
+ <DeviceHealthCharts networkDeviceId={new ObjectID(DEVICE_ID)} />
450
+ </TimeRangeZoomProvider>,
451
+ );
452
+ await flush();
453
+ await waitFor(() => {
454
+ expect(screen.getByTestId("device-panels")).toBeInTheDocument();
455
+ });
456
+
457
+ await dragAcrossPanels();
458
+
459
+ expect(pageZoomToTimeRange).not.toHaveBeenCalled();
460
+ expect(pickerLabel()).toBe(TimeRange.CUSTOM);
461
+ });
462
+ });
463
+
464
+ // -------------------------------------------------- Traffic: FlowTopTalkers
465
+
466
+ interface FlowRequest {
467
+ startTime: string;
468
+ endTime: string;
469
+ networkDeviceId: string;
470
+ projectId: string;
471
+ }
472
+
473
+ function flowRequests(): Array<FlowRequest> {
474
+ return apiPostMock.mock.calls.map((call: Array<unknown>): FlowRequest => {
475
+ return (call[0] as { data: FlowRequest }).data;
476
+ });
477
+ }
478
+
479
+ function lastFlowRequest(): FlowRequest {
480
+ const requests: Array<FlowRequest> = flowRequests();
481
+ const last: FlowRequest | undefined = requests[requests.length - 1];
482
+ if (!last) {
483
+ throw new Error("The top talkers were never fetched");
484
+ }
485
+ return last;
486
+ }
487
+
488
+ function requestMinutes(request: FlowRequest): number {
489
+ return (Date.parse(request.endTime) - Date.parse(request.startTime)) / MINUTE;
490
+ }
491
+
492
+ /*
493
+ * What the top-talkers endpoint answers for a window: figures that depend
494
+ * on its length (so a card that did not follow the zoom shows the wrong
495
+ * number), one-minute buckets, and the window echoed back.
496
+ */
497
+ function flowResponse(request: FlowRequest): Record<string, unknown> {
498
+ const minutes: number = requestMinutes(request);
499
+ const start: number = Date.parse(request.startTime);
500
+ return {
501
+ data: {
502
+ totalOctets: minutes * 1000,
503
+ totalPackets: minutes * 10,
504
+ totalFlows: minutes,
505
+ topSources: [{ key: `10.0.0.${minutes}`, octets: 3000, packets: 20 }],
506
+ topDestinations: [{ key: "10.0.0.9", octets: 2000, packets: 10 }],
507
+ topProtocolPorts: [
508
+ { protocolNumber: 6, destinationPort: 443, octets: 5000, packets: 30 },
509
+ ],
510
+ topConversations: [],
511
+ series: [
512
+ // 7.5 MB in the first minute: 1 Mbps.
513
+ {
514
+ time: new Date(start).toISOString(),
515
+ octets: 7_500_000,
516
+ packets: 50,
517
+ },
518
+ // A naive ClickHouse string, UTC: 0.5 Mbps in the third minute.
519
+ {
520
+ time: new Date(start + 2 * MINUTE)
521
+ .toISOString()
522
+ .replace("T", " ")
523
+ .replace(/\.\d{3}Z$/, ""),
524
+ octets: 3_750_000,
525
+ packets: 25,
526
+ },
527
+ ],
528
+ seriesBucketSeconds: 60,
529
+ windowStartAt: request.startTime,
530
+ windowEndAt: request.endTime,
531
+ },
532
+ };
533
+ }
534
+
535
+ async function renderTraffic(): Promise<void> {
536
+ apiPostMock.mockImplementation(async (args: unknown) => {
537
+ return flowResponse((args as { data: FlowRequest }).data);
538
+ });
539
+ render(<FlowTopTalkers networkDeviceId={new ObjectID(DEVICE_ID)} />);
540
+ await flush();
541
+ await waitFor(() => {
542
+ expect(screen.getByTestId(`chart ${BANDWIDTH_CHART}`)).toBeInTheDocument();
543
+ });
544
+ }
545
+
546
+ async function dragAcrossBandwidth(
547
+ start: Date = ZOOM_START,
548
+ end: Date = ZOOM_END,
549
+ ): Promise<void> {
550
+ standInDrag.start = start;
551
+ standInDrag.end = end;
552
+ fireEvent.click(
553
+ screen.getByRole("button", { name: `Drag across ${BANDWIDTH_CHART}` }),
554
+ );
555
+ await flush();
556
+ }
557
+
558
+ async function doubleClickBandwidth(): Promise<void> {
559
+ fireEvent.doubleClick(screen.getByTestId(`chart ${BANDWIDTH_CHART}`));
560
+ await flush();
561
+ }
562
+
563
+ function tileValue(title: string): string {
564
+ const tile: HTMLElement | null = screen
565
+ .getByRole("button", { name: `About ${title}` })
566
+ .closest("div.rounded-md");
567
+ return (
568
+ (tile?.querySelector("div.text-2xl") as HTMLElement | null)?.textContent ||
569
+ ""
570
+ );
571
+ }
572
+
573
+ describe("Network device Traffic: the bandwidth chart zooms the card's range", () => {
574
+ test("the chart is the shared area chart over the fetched window, in Mbps", async () => {
575
+ await renderTraffic();
576
+
577
+ const record: StandInChartRecord = getStandInChart(BANDWIDTH_CHART);
578
+ const request: FlowRequest = lastFlowRequest();
579
+ expect(record.props.xAxis.options.type).toBe(XAxisType.Time);
580
+ expect((record.props.xAxis.options.min as Date).toISOString()).toBe(
581
+ request.startTime,
582
+ );
583
+ expect((record.props.xAxis.options.max as Date).toISOString()).toBe(
584
+ request.endTime,
585
+ );
586
+ // One-minute buckets: the axis is pinned to them.
587
+ expect(record.props.xAxis.options.precision).toBe(
588
+ XAxisPrecision.EVERY_MINUTE,
589
+ );
590
+ // Every bucket of the window, the silent ones as zero, as Mbps.
591
+ const points: Array<{ x: Date; y: number }> = record.props.data[0]!.data;
592
+ expect(points).toHaveLength(60);
593
+ expect(points[0]).toEqual({ x: new Date(request.startTime), y: 1 });
594
+ expect(points[1]!.y).toBe(0);
595
+ // The naive ClickHouse bucket is read as UTC, not local time.
596
+ expect(points[2]).toEqual({
597
+ x: new Date(Date.parse(request.startTime) + 2 * MINUTE),
598
+ y: 0.5,
599
+ });
600
+ });
601
+
602
+ test("it passes no handlers of its own: it takes the card's zoom", async () => {
603
+ await renderTraffic();
604
+
605
+ const record: StandInChartRecord = getStandInChart(BANDWIDTH_CHART);
606
+ expect(record.props.onTimeRangeSelect).toBeUndefined();
607
+ expect(record.props.onTimeRangeReset).toBeUndefined();
608
+ expect(record.zoom.onTimeRangeSelect).toBeInstanceOf(Function);
609
+ expect(record.zoom.onTimeRangeReset).toBeUndefined();
610
+ });
611
+
612
+ test("the section names the gesture, revealed on hover", async () => {
613
+ await renderTraffic();
614
+
615
+ const hint: HTMLElement = screen.getByTestId(TIME_RANGE_ZOOM_HINT_TEST_ID);
616
+ expect(hint).toHaveTextContent(/^Drag to zoom$/);
617
+ expect(hint).toHaveClass("group-hover/zoomhint:opacity-100");
618
+ expect(hint.closest('[class~="group/zoomhint"]')).toContainElement(
619
+ screen.getByText("Bandwidth Over Time"),
620
+ );
621
+ });
622
+
623
+ test("Min / Avg / Max still read the gap-filled buckets", async () => {
624
+ await renderTraffic();
625
+
626
+ const stats: HTMLElement = screen.getByText("Min").parentElement!;
627
+ // Silent minutes count as zero: the minimum is 0.
628
+ expect(stats).toHaveTextContent("Min 0.00 Mbps");
629
+ expect(stats).toHaveTextContent("Max 1.00 Mbps");
630
+ // (1 + 0.5) Mbps over 60 minutes.
631
+ expect(stats).toHaveTextContent("Avg 0.03 Mbps");
632
+ });
633
+
634
+ test("a drag re-fetches the card over the dragged window: chart, totals and tables follow", async () => {
635
+ await renderTraffic();
636
+ expect(tileValue("Flows")).toBe("60");
637
+ expect(screen.getByText("10.0.0.60")).toBeInTheDocument();
638
+
639
+ await dragAcrossBandwidth();
640
+ await waitFor(() => {
641
+ expect(
642
+ screen.getByTestId(`chart ${BANDWIDTH_CHART}`),
643
+ ).toBeInTheDocument();
644
+ });
645
+
646
+ expect([lastFlowRequest().startTime, lastFlowRequest().endTime]).toEqual(
647
+ ZOOM_WINDOW,
648
+ );
649
+ expect(lastFlowRequest().networkDeviceId).toBe(DEVICE_ID);
650
+ expect(lastFlowRequest().projectId).toBe(PROJECT_ID);
651
+ expect(tileValue("Flows")).toBe("20");
652
+ expect(screen.getByText("10.0.0.20")).toBeInTheDocument();
653
+ expect(screen.queryByText("10.0.0.60")).not.toBeInTheDocument();
654
+ const record: StandInChartRecord = getStandInChart(BANDWIDTH_CHART);
655
+ expect((record.props.xAxis.options.min as Date).toISOString()).toBe(
656
+ ZOOM_WINDOW[0],
657
+ );
658
+ expect(record.props.data[0]!.data).toHaveLength(20);
659
+ });
660
+
661
+ test("after a drag the picker reads Custom, Reset zoom appears, and the hint names the reset", async () => {
662
+ await renderTraffic();
663
+
664
+ await dragAcrossBandwidth();
665
+ await waitFor(() => {
666
+ expect(
667
+ screen.getByTestId(`chart ${BANDWIDTH_CHART}`),
668
+ ).toBeInTheDocument();
669
+ });
670
+
671
+ expect(pickerLabel()).toBe(TimeRange.CUSTOM);
672
+ expect(resetButtons()).toHaveLength(1);
673
+ expect(screen.getByTestId(TIME_RANGE_ZOOM_HINT_TEST_ID)).toHaveTextContent(
674
+ "Double-click to reset",
675
+ );
676
+ // The card says which window it describes.
677
+ expect(
678
+ screen.getByText(
679
+ /Who this device saw talking over the selected time range/,
680
+ ),
681
+ ).toBeInTheDocument();
682
+ });
683
+
684
+ test("a double-click on the chart puts the past hour back", async () => {
685
+ await renderTraffic();
686
+
687
+ await dragAcrossBandwidth();
688
+ await waitFor(() => {
689
+ expect(
690
+ getStandInChart(BANDWIDTH_CHART).zoom.onTimeRangeReset,
691
+ ).toBeInstanceOf(Function);
692
+ });
693
+ await doubleClickBandwidth();
694
+ await waitFor(() => {
695
+ expect(
696
+ screen.getByTestId(`chart ${BANDWIDTH_CHART}`),
697
+ ).toBeInTheDocument();
698
+ });
699
+
700
+ expect(requestMinutes(lastFlowRequest())).toBe(60);
701
+ expect(Date.parse(lastFlowRequest().endTime)).toBeGreaterThanOrEqual(
702
+ NOW.getTime(),
703
+ );
704
+ expect(pickerLabel()).toBe(TimeRange.PAST_ONE_HOUR);
705
+ expect(resetButtons()).toHaveLength(0);
706
+ expect(tileValue("Flows")).toBe("60");
707
+ });
708
+
709
+ test("Reset zoom beside the picker does the same", async () => {
710
+ await renderTraffic();
711
+
712
+ await dragAcrossBandwidth();
713
+ fireEvent.click(resetButtons()[0]!);
714
+ await flush();
715
+
716
+ expect(requestMinutes(lastFlowRequest())).toBe(60);
717
+ expect(pickerLabel()).toBe(TimeRange.PAST_ONE_HOUR);
718
+ });
719
+
720
+ test("a zoom within a zoom: one double-click goes back to the past hour", async () => {
721
+ await renderTraffic();
722
+
723
+ await dragAcrossBandwidth();
724
+ await waitFor(() => {
725
+ expect(
726
+ screen.getByTestId(`chart ${BANDWIDTH_CHART}`),
727
+ ).toBeInTheDocument();
728
+ });
729
+ await dragAcrossBandwidth(INNER_ZOOM_START, INNER_ZOOM_END);
730
+ await waitFor(() => {
731
+ expect(
732
+ screen.getByTestId(`chart ${BANDWIDTH_CHART}`),
733
+ ).toBeInTheDocument();
734
+ });
735
+ expect([lastFlowRequest().startTime, lastFlowRequest().endTime]).toEqual([
736
+ INNER_ZOOM_START.toISOString(),
737
+ INNER_ZOOM_END.toISOString(),
738
+ ]);
739
+
740
+ await doubleClickBandwidth();
741
+
742
+ expect(requestMinutes(lastFlowRequest())).toBe(60);
743
+ expect(pickerLabel()).toBe(TimeRange.PAST_ONE_HOUR);
744
+ });
745
+
746
+ test("picking a range ends the zoom", async () => {
747
+ await renderTraffic();
748
+
749
+ await dragAcrossBandwidth();
750
+ fireEvent.click(screen.getByTestId("card-picker"));
751
+ await flush();
752
+
753
+ expect(pickerLabel()).toBe(TimeRange.PAST_ONE_DAY);
754
+ expect(requestMinutes(lastFlowRequest())).toBe(24 * 60);
755
+ expect(resetButtons()).toHaveLength(0);
756
+ });
757
+
758
+ test("a slower, older fetch never overwrites the window the picker shows", async () => {
759
+ await renderTraffic();
760
+
761
+ /*
762
+ * The zoom's fetch is still in flight when the reset's fetch lands:
763
+ * the card must end up on the past hour's figures, not the zoom's.
764
+ */
765
+ let resolveZoomFetch: (value: unknown) => void = (): void => {};
766
+ apiPostMock.mockImplementationOnce(() => {
767
+ return new Promise((resolve: (value: unknown) => void) => {
768
+ resolveZoomFetch = resolve;
769
+ });
770
+ });
771
+
772
+ await dragAcrossBandwidth();
773
+ fireEvent.click(resetButtons()[0]!);
774
+ await flush();
775
+ await waitFor(() => {
776
+ expect(tileValue("Flows")).toBe("60");
777
+ });
778
+
779
+ await act(async () => {
780
+ resolveZoomFetch(
781
+ flowResponse({
782
+ startTime: ZOOM_WINDOW[0],
783
+ endTime: ZOOM_WINDOW[1],
784
+ networkDeviceId: DEVICE_ID,
785
+ projectId: PROJECT_ID,
786
+ }),
787
+ );
788
+ });
789
+ await flush();
790
+
791
+ expect(tileValue("Flows")).toBe("60");
792
+ expect(pickerLabel()).toBe(TimeRange.PAST_ONE_HOUR);
793
+ });
794
+
795
+ test("a zoom into a quiet stretch: the empty state takes the double-click", async () => {
796
+ await renderTraffic();
797
+ apiPostMock.mockImplementation(async (args: unknown) => {
798
+ const request: FlowRequest = (args as { data: FlowRequest }).data;
799
+ return requestMinutes(request) < 60
800
+ ? { data: { totalOctets: 0, totalPackets: 0, totalFlows: 0 } }
801
+ : flowResponse(request);
802
+ });
803
+
804
+ await dragAcrossBandwidth();
805
+ const empty: HTMLElement = await screen.findByTestId("flow-no-data");
806
+ // Not the NetFlow setup steps: the device was just seen exporting.
807
+ expect(empty).toHaveTextContent("No flows in the selected time range.");
808
+
809
+ fireEvent.doubleClick(empty);
810
+ await flush();
811
+
812
+ expect(requestMinutes(lastFlowRequest())).toBe(60);
813
+ expect(pickerLabel()).toBe(TimeRange.PAST_ONE_HOUR);
814
+ await waitFor(() => {
815
+ expect(
816
+ screen.getByTestId(`chart ${BANDWIDTH_CHART}`),
817
+ ).toBeInTheDocument();
818
+ });
819
+ });
820
+
821
+ test("the empty state ignores a double-click when there is nothing to undo", async () => {
822
+ apiPostMock.mockResolvedValue({
823
+ data: { totalOctets: 0, totalPackets: 0, totalFlows: 0 },
824
+ });
825
+ render(<FlowTopTalkers networkDeviceId={new ObjectID(DEVICE_ID)} />);
826
+ await flush();
827
+ const requests: number = apiPostMock.mock.calls.length;
828
+
829
+ fireEvent.doubleClick(await screen.findByTestId("flow-no-data"));
830
+ await flush();
831
+
832
+ expect(apiPostMock.mock.calls.length).toBe(requests);
833
+ expect(pickerLabel()).toBe(TimeRange.PAST_ONE_HOUR);
834
+ });
835
+
836
+ test("the card keeps its own zoom inside a page that zooms", async () => {
837
+ const pageZoomToTimeRange: MockFunction = getJestMockFunction();
838
+ apiPostMock.mockImplementation(async (args: unknown) => {
839
+ return flowResponse((args as { data: FlowRequest }).data);
840
+ });
841
+ render(
842
+ <TimeRangeZoomScope
843
+ timeRange={{ range: TimeRange.PAST_ONE_WEEK }}
844
+ onTimeRangeChange={
845
+ pageZoomToTimeRange as unknown as (value: unknown) => void
846
+ }
847
+ >
848
+ <FlowTopTalkers networkDeviceId={new ObjectID(DEVICE_ID)} />
849
+ </TimeRangeZoomScope>,
850
+ );
851
+ await flush();
852
+ await waitFor(() => {
853
+ expect(
854
+ screen.getByTestId(`chart ${BANDWIDTH_CHART}`),
855
+ ).toBeInTheDocument();
856
+ });
857
+
858
+ await dragAcrossBandwidth();
859
+
860
+ expect(pageZoomToTimeRange).not.toHaveBeenCalled();
861
+ expect([lastFlowRequest().startTime, lastFlowRequest().endTime]).toEqual(
862
+ ZOOM_WINDOW,
863
+ );
864
+ });
865
+ });
866
+
867
+ describe("BandwidthOverTimeChart", () => {
868
+ const WINDOW_START: string = "2026-09-28T10:00:00.000Z";
869
+ const WINDOW_END: string = "2026-09-28T11:00:00.000Z";
870
+
871
+ test("an axis that spans the whole window, even when traffic is only at its start", () => {
872
+ render(
873
+ <BandwidthOverTimeChart
874
+ series={[{ time: WINDOW_START, octets: 7_500_000, packets: 1 }]}
875
+ bucketSeconds={60}
876
+ windowStartAt={WINDOW_START}
877
+ windowEndAt={WINDOW_END}
878
+ />,
879
+ );
880
+
881
+ const record: StandInChartRecord = getStandInChart(BANDWIDTH_CHART);
882
+ expect((record.props.xAxis.options.min as Date).toISOString()).toBe(
883
+ WINDOW_START,
884
+ );
885
+ expect((record.props.xAxis.options.max as Date).toISOString()).toBe(
886
+ WINDOW_END,
887
+ );
888
+ });
889
+
890
+ test("without a window from the API, the first and last buckets bound the axis", () => {
891
+ render(
892
+ <BandwidthOverTimeChart
893
+ series={[
894
+ { time: "2026-09-28T10:00:00.000Z", octets: 1, packets: 1 },
895
+ { time: "2026-09-28T10:05:00.000Z", octets: 1, packets: 1 },
896
+ ]}
897
+ bucketSeconds={300}
898
+ windowStartAt=""
899
+ windowEndAt=""
900
+ />,
901
+ );
902
+
903
+ const record: StandInChartRecord = getStandInChart(BANDWIDTH_CHART);
904
+ expect((record.props.xAxis.options.min as Date).toISOString()).toBe(
905
+ "2026-09-28T10:00:00.000Z",
906
+ );
907
+ // The last bucket runs to its end.
908
+ expect((record.props.xAxis.options.max as Date).toISOString()).toBe(
909
+ "2026-09-28T10:10:00.000Z",
910
+ );
911
+ });
912
+
913
+ test("keeps its indigo look and draws no legend (Min / Avg / Max is the legend)", () => {
914
+ render(
915
+ <BandwidthOverTimeChart
916
+ series={[{ time: WINDOW_START, octets: 7_500_000, packets: 1 }]}
917
+ bucketSeconds={60}
918
+ windowStartAt={WINDOW_START}
919
+ windowEndAt={WINDOW_END}
920
+ />,
921
+ );
922
+
923
+ const props: Record<string, unknown> = getStandInChart(BANDWIDTH_CHART)
924
+ .props as unknown as Record<string, unknown>;
925
+ expect(props["colors"]).toEqual(["indigo"]);
926
+ expect(props["showLegend"]).toBe(false);
927
+ expect(
928
+ screen.getByRole("figure", {
929
+ name: "Bandwidth over time in megabits per second",
930
+ }),
931
+ ).toBeInTheDocument();
932
+ });
933
+
934
+ test("the tooltip and axis read Mbps", () => {
935
+ render(
936
+ <BandwidthOverTimeChart
937
+ series={[{ time: WINDOW_START, octets: 7_500_000, packets: 1 }]}
938
+ bucketSeconds={60}
939
+ windowStartAt={WINDOW_START}
940
+ windowEndAt={WINDOW_END}
941
+ />,
942
+ );
943
+
944
+ const yAxis: { options: { formatter: (value: number) => string } } =
945
+ getStandInChart(BANDWIDTH_CHART).props.yAxis as unknown as {
946
+ options: { formatter: (value: number) => string };
947
+ };
948
+ expect(yAxis.options.formatter(12.345)).toBe("12.3 Mbps");
949
+ expect(yAxis.options.formatter(0.5)).toBe("0.50 Mbps");
950
+ });
951
+
952
+ test("a series with no parseable bucket keeps its figures but draws no chart", () => {
953
+ render(
954
+ <BandwidthOverTimeChart
955
+ series={[{ time: "not a time", octets: 7_500_000, packets: 1 }]}
956
+ bucketSeconds={60}
957
+ windowStartAt=""
958
+ windowEndAt=""
959
+ />,
960
+ );
961
+
962
+ expect(screen.getByText("Max")).toBeInTheDocument();
963
+ expect(
964
+ screen.queryByTestId(`chart ${BANDWIDTH_CHART}`),
965
+ ).not.toBeInTheDocument();
966
+ });
967
+
968
+ test("inside a zoomable card the chart offers the card's drag", () => {
969
+ const zoomToTimeRange: MockFunction = getJestMockFunction();
970
+ render(
971
+ <TimeRangeZoomProvider
972
+ zoom={{
973
+ isZoomed: false,
974
+ rangeBeforeZoom: null,
975
+ zoomToTimeRange: zoomToTimeRange as unknown as (
976
+ startTime: Date,
977
+ endTime: Date,
978
+ ) => void,
979
+ resetZoom: () => {},
980
+ }}
981
+ >
982
+ <BandwidthOverTimeChart
983
+ series={[{ time: WINDOW_START, octets: 7_500_000, packets: 1 }]}
984
+ bucketSeconds={60}
985
+ windowStartAt={WINDOW_START}
986
+ windowEndAt={WINDOW_END}
987
+ />
988
+ </TimeRangeZoomProvider>,
989
+ );
990
+
991
+ standInDrag.start = new Date("2026-09-28T10:10:00.000Z");
992
+ standInDrag.end = new Date("2026-09-28T10:20:00.000Z");
993
+ fireEvent.click(
994
+ within(screen.getByTestId(`chart ${BANDWIDTH_CHART}`)).getByRole(
995
+ "button",
996
+ ),
997
+ );
998
+
999
+ expect(zoomToTimeRange).toHaveBeenCalledWith(
1000
+ standInDrag.start,
1001
+ standInDrag.end,
1002
+ );
1003
+ });
1004
+ });
1005
+
1006
+ /*
1007
+ * How far apart the chart library's walker puts two slots of a precision,
1008
+ * measured on the walker itself (a UTC Sunday, far from any clock change).
1009
+ */
1010
+ function walkedStepSeconds(precision: XAxisPrecision): number {
1011
+ const from: Date = new Date("2026-09-27T00:00:00.000Z");
1012
+ const intervals: Array<Date> = XAxisUtil.getPrecisionIntervals({
1013
+ xAxisMin: from,
1014
+ xAxisMax: new Date(from.getTime() + 2 * 24 * 60 * MINUTE),
1015
+ precision: precision,
1016
+ });
1017
+ return (intervals[1]!.getTime() - intervals[0]!.getTime()) / 1000;
1018
+ }
1019
+
1020
+ // The API's bucket width for a window, as pickBucketSeconds sizes it.
1021
+ function apiBucketSeconds(windowSeconds: number): number {
1022
+ return Math.max(60, Math.ceil(Math.ceil(windowSeconds / 120) / 60) * 60);
1023
+ }
1024
+
1025
+ describe("getBandwidthAxisPrecision", () => {
1026
+ test("pins one-minute buckets to a one-minute grid", () => {
1027
+ expect(getBandwidthAxisPrecision(60)).toBe(XAxisPrecision.EVERY_MINUTE);
1028
+ });
1029
+
1030
+ /*
1031
+ * sdn-1: every other width used to be left to the axis, which picks a
1032
+ * step from the window's length - daily over two weeks - and averaged
1033
+ * several buckets into each slot.
1034
+ */
1035
+ test.each([
1036
+ // The presets: 3 hours, a day, 2 days, a week, 2 weeks, a 31-day month.
1037
+ [120, XAxisPrecision.EVERY_MINUTE],
1038
+ [720, XAxisPrecision.EVERY_TEN_MINUTES],
1039
+ [1440, XAxisPrecision.EVERY_FIFTEEN_MINUTES],
1040
+ [5040, XAxisPrecision.EVERY_HOUR],
1041
+ [10080, XAxisPrecision.EVERY_TWO_HOURS],
1042
+ [22320, XAxisPrecision.EVERY_SIX_HOURS],
1043
+ // Zooms and custom ranges: any whole number of minutes.
1044
+ [180, XAxisPrecision.EVERY_MINUTE],
1045
+ [240, XAxisPrecision.EVERY_MINUTE],
1046
+ [300, XAxisPrecision.EVERY_FIVE_MINUTES],
1047
+ [540, XAxisPrecision.EVERY_FIVE_MINUTES],
1048
+ [600, XAxisPrecision.EVERY_TEN_MINUTES],
1049
+ [840, XAxisPrecision.EVERY_TEN_MINUTES],
1050
+ [900, XAxisPrecision.EVERY_FIFTEEN_MINUTES],
1051
+ [1740, XAxisPrecision.EVERY_FIFTEEN_MINUTES],
1052
+ [1800, XAxisPrecision.EVERY_THIRTY_MINUTES],
1053
+ [3540, XAxisPrecision.EVERY_THIRTY_MINUTES],
1054
+ [3600, XAxisPrecision.EVERY_HOUR],
1055
+ [7140, XAxisPrecision.EVERY_HOUR],
1056
+ [7200, XAxisPrecision.EVERY_TWO_HOURS],
1057
+ [10800, XAxisPrecision.EVERY_THREE_HOURS],
1058
+ [21540, XAxisPrecision.EVERY_THREE_HOURS],
1059
+ [21600, XAxisPrecision.EVERY_SIX_HOURS],
1060
+ [43200, XAxisPrecision.EVERY_TWELVE_HOURS],
1061
+ // Wider than any fixed step: the widest one.
1062
+ [86400, XAxisPrecision.EVERY_TWELVE_HOURS],
1063
+ ])(
1064
+ "pins %i-second buckets to the coarsest step no wider than one: %s",
1065
+ (bucketSeconds: number, precision: XAxisPrecision) => {
1066
+ expect(getBandwidthAxisPrecision(bucketSeconds)).toBe(precision);
1067
+ },
1068
+ );
1069
+
1070
+ test("for every bucket the API can send (one minute up to the 31-day window's), the step fits in a bucket and the next one up would not", () => {
1071
+ const steps: Array<number> = [
1072
+ 60, 300, 600, 900, 1800, 3600, 7200, 10800, 21600, 43200,
1073
+ ];
1074
+ const widest: number = apiBucketSeconds(31 * 24 * 60 * 60);
1075
+ expect(widest).toBe(22320);
1076
+ for (
1077
+ let bucketSeconds: number = 60;
1078
+ bucketSeconds <= widest;
1079
+ bucketSeconds += 60
1080
+ ) {
1081
+ const precision: XAxisPrecision | undefined =
1082
+ getBandwidthAxisPrecision(bucketSeconds);
1083
+ expect([bucketSeconds, precision]).not.toEqual([
1084
+ bucketSeconds,
1085
+ undefined,
1086
+ ]);
1087
+ const stepSeconds: number = walkedStepSeconds(precision!);
1088
+ const nextStep: number | undefined = steps.find((step: number) => {
1089
+ return step > stepSeconds;
1090
+ });
1091
+ expect([bucketSeconds, stepSeconds <= bucketSeconds]).toEqual([
1092
+ bucketSeconds,
1093
+ true,
1094
+ ]);
1095
+ expect([bucketSeconds, nextStep! > bucketSeconds]).toEqual([
1096
+ bucketSeconds,
1097
+ true,
1098
+ ]);
1099
+ }
1100
+ });
1101
+
1102
+ test("the pinned steps are exactly as wide as the chart walks them", () => {
1103
+ expect(
1104
+ [60, 300, 600, 900, 1800, 3600, 7200, 10800, 21600, 43200].map(
1105
+ (seconds: number): number => {
1106
+ return walkedStepSeconds(getBandwidthAxisPrecision(seconds)!);
1107
+ },
1108
+ ),
1109
+ ).toEqual([60, 300, 600, 900, 1800, 3600, 7200, 10800, 21600, 43200]);
1110
+ });
1111
+
1112
+ test("a bucket narrower than a second fits no step: the axis picks its own", () => {
1113
+ expect(getBandwidthAxisPrecision(0.5)).toBeUndefined();
1114
+ });
1115
+ });