@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,1214 @@
1
+ import "@testing-library/jest-dom";
2
+ import {
3
+ cleanup,
4
+ fireEvent,
5
+ render,
6
+ screen,
7
+ within,
8
+ } from "@testing-library/react";
9
+ import * as React from "react";
10
+ import {
11
+ afterEach,
12
+ beforeEach,
13
+ describe,
14
+ expect,
15
+ jest,
16
+ test,
17
+ } from "@jest/globals";
18
+ import getJestMockFunction, { MockFunction } from "../../MockType";
19
+ import {
20
+ chartZoomStandIns,
21
+ doubleClickOn,
22
+ dragAcross,
23
+ resetChartZoomStandIns,
24
+ settle,
25
+ windowKey,
26
+ windowOf,
27
+ } from "./ChartZoomHarness";
28
+
29
+ /*
30
+ * Issue #4105 on the Kubernetes Costs pages - the cluster's and the
31
+ * project-wide one. Their spend-over-time chart zooms the PAGE: the spend
32
+ * tiles, both breakdown tables and the right-sizing card are all read for
33
+ * the page's window, so a drag across the chart must re-read every one of
34
+ * them for the window dragged out, and a double-click (or Reset zoom beside
35
+ * the card's picker) must put them all back on the week they started on.
36
+ *
37
+ * Cost rows are hourly, so a selection under an hour is widened to an hour
38
+ * around it before the page zooms - otherwise the window would usually hold
39
+ * no cost row at all and every figure on the page would read $0. And the
40
+ * zoom ends a millisecond before the selection does: the cost queries count
41
+ * a row whose hour starts exactly at a window's end, which is where the
42
+ * next, unselected hour starts (KubernetesCostsZoomExactHours drags real
43
+ * bars on a clock off the hour).
44
+ *
45
+ * The pages, their EmbeddedMetricCard and the cost chart are real; the cost
46
+ * fetches are stubbed and answer with one $1 row per hour of exactly the
47
+ * window asked for, so the Total Spend tile reads back the window's length.
48
+ * The line chart is stood in for (see ChartZoomHarness).
49
+ */
50
+
51
+ type CostFetch = { start: Date; end: Date };
52
+
53
+ const mockFetchCostTrend: MockFunction = getJestMockFunction();
54
+ const mockFetchNamespaceBreakdown: MockFunction = getJestMockFunction();
55
+ const mockFetchWorkloadBreakdown: MockFunction = getJestMockFunction();
56
+ const mockFetchClusterBreakdown: MockFunction = getJestMockFunction();
57
+ const mockFetchRightSizing: MockFunction = getJestMockFunction();
58
+ const mockGetClusterList: MockFunction = getJestMockFunction();
59
+
60
+ // While true, no window has any cost rows.
61
+ let mockCostIsQuiet: boolean = false;
62
+
63
+ jest.mock("../../../UI/Utils/Navigation", () => {
64
+ return {
65
+ __esModule: true,
66
+ default: {
67
+ getLastParamAsObjectID: () => {
68
+ const { default: ObjectIDType } = jest.requireActual(
69
+ "../../../Types/ObjectID",
70
+ ) as { default: new (id: string) => unknown };
71
+ return new ObjectIDType("0193c0de-8888-4aaa-8bbb-000000000008");
72
+ },
73
+ navigate: () => {
74
+ return undefined;
75
+ },
76
+ },
77
+ };
78
+ });
79
+
80
+ jest.mock("../../../UI/Utils/Project", () => {
81
+ return {
82
+ __esModule: true,
83
+ default: {
84
+ getCurrentProjectId: () => {
85
+ const { default: ObjectIDType } = jest.requireActual(
86
+ "../../../Types/ObjectID",
87
+ ) as { default: new (id: string) => unknown };
88
+ return new ObjectIDType("10000000-0000-4000-8000-000000000001");
89
+ },
90
+ },
91
+ };
92
+ });
93
+
94
+ jest.mock("../../../UI/Utils/ModelAPI/ModelAPI", () => {
95
+ return {
96
+ __esModule: true,
97
+ default: {
98
+ getList: (...args: Array<unknown>) => {
99
+ return mockGetClusterList(...args);
100
+ },
101
+ },
102
+ };
103
+ });
104
+
105
+ jest.mock("../../../UI/Utils/API/API", () => {
106
+ return {
107
+ __esModule: true,
108
+ default: {
109
+ getFriendlyMessage: () => {
110
+ return "Could not load";
111
+ },
112
+ },
113
+ };
114
+ });
115
+
116
+ jest.mock(
117
+ "../../../../App/FeatureSet/Dashboard/src/Pages/Kubernetes/Utils/KubernetesCostUtils",
118
+ () => {
119
+ return {
120
+ __esModule: true,
121
+ ...(jest.requireActual(
122
+ "../../../../App/FeatureSet/Dashboard/src/Pages/Kubernetes/Utils/KubernetesCostUtils",
123
+ ) as Record<string, unknown>),
124
+ fetchCostTrend: (...args: Array<unknown>) => {
125
+ return mockFetchCostTrend(...args);
126
+ },
127
+ fetchNamespaceBreakdown: (...args: Array<unknown>) => {
128
+ return mockFetchNamespaceBreakdown(...args);
129
+ },
130
+ fetchWorkloadBreakdown: (...args: Array<unknown>) => {
131
+ return mockFetchWorkloadBreakdown(...args);
132
+ },
133
+ fetchClusterBreakdown: (...args: Array<unknown>) => {
134
+ return mockFetchClusterBreakdown(...args);
135
+ },
136
+ };
137
+ },
138
+ );
139
+
140
+ jest.mock(
141
+ "../../../../App/FeatureSet/Dashboard/src/Pages/Kubernetes/Utils/KubernetesRightSizingUtils",
142
+ () => {
143
+ return {
144
+ __esModule: true,
145
+ fetchRightSizingRecommendations: (...args: Array<unknown>) => {
146
+ return mockFetchRightSizing(...args);
147
+ },
148
+ };
149
+ },
150
+ );
151
+
152
+ jest.mock("../../../UI/Components/Charts/Line/LineChart", () => {
153
+ return {
154
+ __esModule: true,
155
+ default: (
156
+ jest.requireActual(
157
+ "./ChartZoomHarness",
158
+ ) as typeof import("./ChartZoomHarness")
159
+ ).StandInLineChart,
160
+ };
161
+ });
162
+
163
+ jest.mock(
164
+ "../../../../App/FeatureSet/Dashboard/src/Components/Metrics/Utils/UseEventTimeReferenceLines",
165
+ () => {
166
+ return {
167
+ __esModule: true,
168
+ default: () => {
169
+ return { lines: [], markerCount: 0 };
170
+ },
171
+ };
172
+ },
173
+ );
174
+
175
+ // The card's own picker: shows its range, and can pick "Past 1 Day".
176
+ jest.mock("../../../UI/Components/Date/RangeStartAndEndDateView", () => {
177
+ return {
178
+ __esModule: true,
179
+ default: (props: {
180
+ dashboardStartAndEndDate: { range: string };
181
+ onChange: (value: { range: string }) => void;
182
+ }) => {
183
+ return (
184
+ <button
185
+ type="button"
186
+ data-testid="card-picker"
187
+ onClick={() => {
188
+ props.onChange({ range: "Past 1 Day" });
189
+ }}
190
+ >
191
+ {props.dashboardStartAndEndDate.range}
192
+ </button>
193
+ );
194
+ },
195
+ };
196
+ });
197
+
198
+ import KubernetesClusterCosts from "../../../../App/FeatureSet/Dashboard/src/Pages/Kubernetes/View/Costs";
199
+ import KubernetesCosts from "../../../../App/FeatureSet/Dashboard/src/Pages/Kubernetes/Costs";
200
+ import KubernetesCostTrendChart from "../../../../App/FeatureSet/Dashboard/src/Pages/Kubernetes/View/KubernetesCostTrendChart";
201
+ import {
202
+ CostZoomWindow,
203
+ MIN_COST_ZOOM_SPAN_IN_MS,
204
+ getCostZoomWindow,
205
+ widenCostZoomWindow,
206
+ } from "../../../../App/FeatureSet/Dashboard/src/Pages/Kubernetes/Utils/KubernetesCostUtils";
207
+ import PageComponentProps from "../../../../App/FeatureSet/Dashboard/src/Pages/PageComponentProps";
208
+ import { TimeRangeZoomProvider } from "../../../UI/Components/Charts/TimeRangeZoom/TimeRangeZoomContext";
209
+ import { TimeRangeZoom } from "../../../UI/Components/Charts/TimeRangeZoom/UseTimeRangeZoom";
210
+ import { RESET_TIME_RANGE_ZOOM_BUTTON_TEST_ID } from "../../../UI/Components/Charts/TimeRangeZoom/ResetTimeRangeZoomButton";
211
+ import { TIME_RANGE_ZOOM_HINT_TEST_ID } from "../../../UI/Components/Charts/TimeRangeZoom/TimeRangeZoomHint";
212
+ import InBetween from "../../../Types/BaseDatabase/InBetween";
213
+ import TimeRange from "../../../Types/Time/TimeRange";
214
+
215
+ const PAGE_PROPS: PageComponentProps = {} as PageComponentProps;
216
+
217
+ const HOUR_MS: number = 60 * 60 * 1000;
218
+ const DAY_MS: number = 24 * HOUR_MS;
219
+
220
+ /*
221
+ * Half past, so the week holds exactly 168 hourly rows (the query's bounds
222
+ * are inclusive: a window ending on the hour would also take the next
223
+ * hour's row).
224
+ */
225
+ const NOW: Date = new Date("2026-09-28T12:30:00.000Z");
226
+ const ONE_WEEK_AGO: Date = new Date(NOW.getTime() - 7 * DAY_MS);
227
+
228
+ /*
229
+ * Two whole days inside the week: the chart hands the page [00:00 on the
230
+ * 24th, 00:00 on the 26th). The page reads them to a millisecond before
231
+ * the 26th, so 48 hourly rows start in the window - "$48.00" spent - and
232
+ * not the 49th, the first hour of the 26th, which starts exactly where the
233
+ * selection ends.
234
+ */
235
+ const DRAG_START: Date = new Date("2026-09-24T00:00:00.000Z");
236
+ const DRAG_END: Date = new Date("2026-09-26T00:00:00.000Z");
237
+ // Six hours inside those two days.
238
+ const NESTED_DRAG_START: Date = new Date("2026-09-25T06:00:00.000Z");
239
+ const NESTED_DRAG_END: Date = new Date("2026-09-25T12:00:00.000Z");
240
+
241
+ // A millisecond before `end`: where a zoom to [start, end) ends.
242
+ function justBefore(end: Date): Date {
243
+ return new Date(end.getTime() - 1);
244
+ }
245
+
246
+ const WEEK_WINDOW: string = windowKey(ONE_WEEK_AGO, NOW);
247
+ const DRAG_WINDOW: string = windowKey(DRAG_START, justBefore(DRAG_END));
248
+ const NESTED_DRAG_WINDOW: string = windowKey(
249
+ NESTED_DRAG_START,
250
+ justBefore(NESTED_DRAG_END),
251
+ );
252
+ // A sub-hour drag at 10:10-10:40 is widened to the hour around it.
253
+ const WIDENED_HOUR_WINDOW: string =
254
+ "2026-09-25T09:55:00.000Z/2026-09-25T10:54:59.999Z";
255
+
256
+ const NO_COST_DATA: string =
257
+ "No cost data reported for the selected time range.";
258
+
259
+ // The hours a window's cost rows start at: [start, end], as the query reads.
260
+ function hourStartsWithin(start: Date, end: Date): Array<number> {
261
+ const hours: Array<number> = [];
262
+ const first: number = Math.ceil(start.getTime() / HOUR_MS) * HOUR_MS;
263
+
264
+ for (let time: number = first; time <= end.getTime(); time += HOUR_MS) {
265
+ hours.push(time);
266
+ }
267
+
268
+ return hours;
269
+ }
270
+
271
+ function paramsOf(call: Array<unknown>): { startDate: Date; endDate: Date } {
272
+ return call[0] as { startDate: Date; endDate: Date };
273
+ }
274
+
275
+ function fetchesOf(mock: MockFunction): Array<CostFetch> {
276
+ return mock.mock.calls.map((call: Array<unknown>): CostFetch => {
277
+ return { start: paramsOf(call).startDate, end: paramsOf(call).endDate };
278
+ });
279
+ }
280
+
281
+ function latestWindowOf(mock: MockFunction): string {
282
+ const fetches: Array<CostFetch> = fetchesOf(mock);
283
+ const latest: CostFetch | undefined = fetches[fetches.length - 1];
284
+
285
+ if (!latest) {
286
+ throw new Error("Never fetched");
287
+ }
288
+
289
+ return windowKey(latest.start, latest.end);
290
+ }
291
+
292
+ function spendChart(): HTMLElement {
293
+ const chart: HTMLElement | null = screen
294
+ .queryByTestId("line-chart")
295
+ ?.closest('[class~="group/zoomhint"]') as HTMLElement | null;
296
+
297
+ if (!chart) {
298
+ throw new Error("The spend chart is not drawn");
299
+ }
300
+
301
+ return chart;
302
+ }
303
+
304
+ function totalSpendTile(): string {
305
+ const title: HTMLElement = screen.getByText("Total Spend");
306
+ const tile: HTMLElement | null = title.closest(".rounded-xl");
307
+
308
+ return tile?.textContent || "";
309
+ }
310
+
311
+ function picker(): string {
312
+ return screen.getByTestId("card-picker").textContent || "";
313
+ }
314
+
315
+ function resetZoomButton(): HTMLElement | null {
316
+ return screen.queryByTestId(RESET_TIME_RANGE_ZOOM_BUTTON_TEST_ID);
317
+ }
318
+
319
+ beforeEach(() => {
320
+ jest.useFakeTimers();
321
+ jest.setSystemTime(NOW);
322
+ resetChartZoomStandIns();
323
+ mockCostIsQuiet = false;
324
+
325
+ const hourlyRows: (params: unknown) => Array<number> = (
326
+ params: unknown,
327
+ ): Array<number> => {
328
+ if (mockCostIsQuiet) {
329
+ return [];
330
+ }
331
+ const window: { startDate: Date; endDate: Date } = params as {
332
+ startDate: Date;
333
+ endDate: Date;
334
+ };
335
+ return hourStartsWithin(window.startDate, window.endDate);
336
+ };
337
+
338
+ mockFetchCostTrend.mockReset();
339
+ mockFetchCostTrend.mockImplementation((params: unknown) => {
340
+ return Promise.resolve(
341
+ hourlyRows(params).map((time: number): { x: Date; y: number } => {
342
+ return { x: new Date(time), y: 1 };
343
+ }),
344
+ );
345
+ });
346
+
347
+ mockFetchNamespaceBreakdown.mockReset();
348
+ mockFetchNamespaceBreakdown.mockImplementation((params: unknown) => {
349
+ const hours: number = hourlyRows(params).length;
350
+ return Promise.resolve(
351
+ hours === 0
352
+ ? []
353
+ : [
354
+ {
355
+ namespace: "shop",
356
+ cpuCost: hours / 2,
357
+ ramCost: hours / 2,
358
+ pvCost: 0,
359
+ otherCost: 0,
360
+ totalCost: hours,
361
+ efficiency: 0.5,
362
+ },
363
+ ],
364
+ );
365
+ });
366
+
367
+ mockFetchWorkloadBreakdown.mockReset();
368
+ mockFetchWorkloadBreakdown.mockImplementation(() => {
369
+ return Promise.resolve([]);
370
+ });
371
+
372
+ mockFetchClusterBreakdown.mockReset();
373
+ mockFetchClusterBreakdown.mockImplementation((params: unknown) => {
374
+ const hours: number = hourlyRows(params).length;
375
+ return Promise.resolve(
376
+ hours === 0
377
+ ? []
378
+ : [
379
+ {
380
+ clusterName: "production-us-east-1",
381
+ totalCost: hours,
382
+ workloadCost: hours,
383
+ idleCost: 0,
384
+ efficiency: 0.5,
385
+ },
386
+ ],
387
+ );
388
+ });
389
+
390
+ mockFetchRightSizing.mockReset();
391
+ mockFetchRightSizing.mockImplementation(() => {
392
+ return Promise.resolve({
393
+ recommendations: [],
394
+ summary: {
395
+ totalMonthlySavings: 0,
396
+ totalMonthlyIncrease: 0,
397
+ overprovisionedCount: 0,
398
+ underprovisionedCount: 0,
399
+ noRequestSetCount: 0,
400
+ analyzedCount: 0,
401
+ missingMemoryPeakCount: 0,
402
+ },
403
+ observedHours: 168,
404
+ });
405
+ });
406
+
407
+ mockGetClusterList.mockReset();
408
+ mockGetClusterList.mockImplementation(() => {
409
+ return Promise.resolve({ data: [], count: 0 });
410
+ });
411
+ });
412
+
413
+ afterEach(() => {
414
+ cleanup();
415
+ jest.useRealTimers();
416
+ });
417
+
418
+ describe("widenCostZoomWindow", () => {
419
+ const WINDOW_START: Date = ONE_WEEK_AGO;
420
+ const WINDOW_END: Date = NOW;
421
+
422
+ function widen(start: string, end: string): string {
423
+ const widened: CostZoomWindow = widenCostZoomWindow({
424
+ startTime: new Date(start),
425
+ endTime: new Date(end),
426
+ windowStart: WINDOW_START,
427
+ windowEnd: WINDOW_END,
428
+ });
429
+ return windowKey(widened.startTime, widened.endTime);
430
+ }
431
+
432
+ test("keeps a selection of an hour or more as it is", () => {
433
+ expect(widen("2026-09-25T10:00:00.000Z", "2026-09-25T11:00:00.000Z")).toBe(
434
+ "2026-09-25T10:00:00.000Z/2026-09-25T11:00:00.000Z",
435
+ );
436
+ expect(widen("2026-09-24T00:00:00.000Z", "2026-09-26T00:00:00.000Z")).toBe(
437
+ windowKey(DRAG_START, DRAG_END),
438
+ );
439
+ });
440
+
441
+ test("puts a right-to-left selection the right way round", () => {
442
+ expect(widen("2026-09-26T00:00:00.000Z", "2026-09-24T00:00:00.000Z")).toBe(
443
+ windowKey(DRAG_START, DRAG_END),
444
+ );
445
+ });
446
+
447
+ test("widens a selection under an hour to an hour centred on it", () => {
448
+ expect(widen("2026-09-25T10:10:00.000Z", "2026-09-25T10:40:00.000Z")).toBe(
449
+ "2026-09-25T09:55:00.000Z/2026-09-25T10:55:00.000Z",
450
+ );
451
+ expect(MIN_COST_ZOOM_SPAN_IN_MS).toBe(HOUR_MS);
452
+ });
453
+
454
+ test("an hour always holds the start of an hour - the row the page can show", () => {
455
+ const widened: CostZoomWindow = widenCostZoomWindow({
456
+ startTime: new Date("2026-09-25T10:07:00.000Z"),
457
+ endTime: new Date("2026-09-25T10:37:00.000Z"),
458
+ windowStart: WINDOW_START,
459
+ windowEnd: WINDOW_END,
460
+ });
461
+
462
+ expect(hourStartsWithin(widened.startTime, widened.endTime)).toHaveLength(
463
+ 1,
464
+ );
465
+ });
466
+
467
+ test("never reaches past the end of the window on screen (so never past now)", () => {
468
+ const tenMinutesAgo: string = new Date(
469
+ NOW.getTime() - 10 * 60 * 1000,
470
+ ).toISOString();
471
+
472
+ expect(widen(tenMinutesAgo, NOW.toISOString())).toBe(
473
+ windowKey(new Date(NOW.getTime() - HOUR_MS), NOW),
474
+ );
475
+ });
476
+
477
+ test("never reaches before the start of the window on screen", () => {
478
+ const start: string = ONE_WEEK_AGO.toISOString();
479
+ const tenMinutesIn: string = new Date(
480
+ ONE_WEEK_AGO.getTime() + 10 * 60 * 1000,
481
+ ).toISOString();
482
+
483
+ expect(widen(start, tenMinutesIn)).toBe(
484
+ windowKey(ONE_WEEK_AGO, new Date(ONE_WEEK_AGO.getTime() + HOUR_MS)),
485
+ );
486
+ });
487
+
488
+ test("on a window under an hour, a drag keeps the whole window: there is nothing finer to see", () => {
489
+ const thirtyMinutesAgo: Date = new Date(NOW.getTime() - 30 * 60 * 1000);
490
+ const widened: CostZoomWindow = widenCostZoomWindow({
491
+ startTime: new Date(NOW.getTime() - 20 * 60 * 1000),
492
+ endTime: new Date(NOW.getTime() - 15 * 60 * 1000),
493
+ windowStart: thirtyMinutesAgo,
494
+ windowEnd: NOW,
495
+ });
496
+
497
+ expect(windowKey(widened.startTime, widened.endTime)).toBe(
498
+ windowKey(thirtyMinutesAgo, NOW),
499
+ );
500
+ });
501
+
502
+ test("an empty or backwards window leaves the selection alone", () => {
503
+ const widened: CostZoomWindow = widenCostZoomWindow({
504
+ startTime: new Date("2026-09-28T11:40:00.000Z"),
505
+ endTime: new Date("2026-09-28T11:45:00.000Z"),
506
+ windowStart: NOW,
507
+ windowEnd: NOW,
508
+ });
509
+
510
+ expect(windowKey(widened.startTime, widened.endTime)).toBe(
511
+ "2026-09-28T11:40:00.000Z/2026-09-28T11:45:00.000Z",
512
+ );
513
+ });
514
+ });
515
+
516
+ describe("getCostZoomWindow: the window a drag zooms the page to", () => {
517
+ const WINDOW_START: Date = ONE_WEEK_AGO;
518
+ const WINDOW_END: Date = NOW;
519
+
520
+ function zoomTo(
521
+ start: string,
522
+ end: string,
523
+ window?: { start: Date; end: Date } | undefined,
524
+ ): CostZoomWindow {
525
+ return getCostZoomWindow({
526
+ startTime: new Date(start),
527
+ endTime: new Date(end),
528
+ windowStart: window ? window.start : WINDOW_START,
529
+ windowEnd: window ? window.end : WINDOW_END,
530
+ });
531
+ }
532
+
533
+ function keyOf(zoomWindow: CostZoomWindow): string {
534
+ return windowKey(zoomWindow.startTime, zoomWindow.endTime);
535
+ }
536
+
537
+ function hoursOf(zoomWindow: CostZoomWindow): Array<string> {
538
+ return hourStartsWithin(zoomWindow.startTime, zoomWindow.endTime).map(
539
+ (time: number): string => {
540
+ return new Date(time).toISOString();
541
+ },
542
+ );
543
+ }
544
+
545
+ test("ends a millisecond before the selection does, so the hour starting at its end is not read", () => {
546
+ const zoomed: CostZoomWindow = zoomTo(
547
+ "2026-09-25T10:00:00.000Z",
548
+ "2026-09-25T12:00:00.000Z",
549
+ );
550
+
551
+ expect(keyOf(zoomed)).toBe(
552
+ "2026-09-25T10:00:00.000Z/2026-09-25T11:59:59.999Z",
553
+ );
554
+ expect(hoursOf(zoomed)).toEqual([
555
+ "2026-09-25T10:00:00.000Z",
556
+ "2026-09-25T11:00:00.000Z",
557
+ ]);
558
+ // What the selection itself reads: the noon hour nobody dragged across.
559
+ expect(
560
+ hourStartsWithin(
561
+ new Date("2026-09-25T10:00:00.000Z"),
562
+ new Date("2026-09-25T12:00:00.000Z"),
563
+ ),
564
+ ).toHaveLength(3);
565
+ });
566
+
567
+ test("a right-to-left selection is the same window", () => {
568
+ expect(
569
+ keyOf(zoomTo("2026-09-25T12:00:00.000Z", "2026-09-25T10:00:00.000Z")),
570
+ ).toBe("2026-09-25T10:00:00.000Z/2026-09-25T11:59:59.999Z");
571
+ });
572
+
573
+ test("any run of whole hours reads exactly those hours", () => {
574
+ for (const [first, count] of [
575
+ ["2026-09-21T13:00:00.000Z", 1],
576
+ ["2026-09-24T00:00:00.000Z", 2],
577
+ ["2026-09-25T07:00:00.000Z", 5],
578
+ ["2026-09-26T23:00:00.000Z", 24],
579
+ ["2026-09-22T00:00:00.000Z", 72],
580
+ ] as Array<[string, number]>) {
581
+ const start: Date = new Date(first);
582
+ const end: Date = new Date(start.getTime() + count * HOUR_MS);
583
+ const expected: Array<string> = [];
584
+
585
+ for (let hour: number = 0; hour < count; hour++) {
586
+ expected.push(new Date(start.getTime() + hour * HOUR_MS).toISOString());
587
+ }
588
+
589
+ expect({
590
+ selection: windowKey(start, end),
591
+ hours: hoursOf(zoomTo(start.toISOString(), end.toISOString())),
592
+ }).toEqual({ selection: windowKey(start, end), hours: expected });
593
+ }
594
+ });
595
+
596
+ test("a selection of exactly one hour keeps that one hour: the millisecond comes off after the widening", () => {
597
+ const zoomed: CostZoomWindow = zoomTo(
598
+ "2026-09-25T10:00:00.000Z",
599
+ "2026-09-25T11:00:00.000Z",
600
+ );
601
+
602
+ expect(keyOf(zoomed)).toBe(
603
+ "2026-09-25T10:00:00.000Z/2026-09-25T10:59:59.999Z",
604
+ );
605
+ expect(hoursOf(zoomed)).toEqual(["2026-09-25T10:00:00.000Z"]);
606
+
607
+ // Taken off first, the hour would be widened back out over two rows.
608
+ const shortenedThenWidened: CostZoomWindow = widenCostZoomWindow({
609
+ startTime: new Date("2026-09-25T10:00:00.000Z"),
610
+ endTime: new Date("2026-09-25T10:59:59.999Z"),
611
+ windowStart: WINDOW_START,
612
+ windowEnd: WINDOW_END,
613
+ });
614
+ expect(
615
+ hourStartsWithin(
616
+ shortenedThenWidened.startTime,
617
+ shortenedThenWidened.endTime,
618
+ ),
619
+ ).toHaveLength(2);
620
+ });
621
+
622
+ test("a sub-hour selection is widened to the hour around it, and reads the one hour in it", () => {
623
+ const zoomed: CostZoomWindow = zoomTo(
624
+ "2026-09-25T10:15:00.000Z",
625
+ "2026-09-25T10:45:00.000Z",
626
+ );
627
+
628
+ expect(keyOf(zoomed)).toBe(
629
+ "2026-09-25T10:00:00.000Z/2026-09-25T10:59:59.999Z",
630
+ );
631
+ expect(hoursOf(zoomed)).toEqual(["2026-09-25T10:00:00.000Z"]);
632
+ });
633
+
634
+ test("a sub-hour selection over the start of an hour reads that hour", () => {
635
+ for (const [start, end] of [
636
+ ["2026-09-25T09:45:00.000Z", "2026-09-25T10:15:00.000Z"],
637
+ ["2026-09-25T09:59:00.000Z", "2026-09-25T10:01:00.000Z"],
638
+ ["2026-09-25T10:00:00.000Z", "2026-09-25T10:30:00.000Z"],
639
+ ["2026-09-25T09:30:00.000Z", "2026-09-25T10:15:00.000Z"],
640
+ ] as Array<[string, string]>) {
641
+ expect({
642
+ selection: `${start}/${end}`,
643
+ hours: hoursOf(zoomTo(start, end)),
644
+ }).toEqual({
645
+ selection: `${start}/${end}`,
646
+ hours: ["2026-09-25T10:00:00.000Z"],
647
+ });
648
+ }
649
+ });
650
+
651
+ test("never reaches past the end of the window on screen, and stops a millisecond short of it", () => {
652
+ const tenMinutesAgo: string = new Date(
653
+ NOW.getTime() - 10 * 60 * 1000,
654
+ ).toISOString();
655
+
656
+ expect(keyOf(zoomTo(tenMinutesAgo, NOW.toISOString()))).toBe(
657
+ windowKey(new Date(NOW.getTime() - HOUR_MS), justBefore(NOW)),
658
+ );
659
+ });
660
+
661
+ test("never reaches before the start of the window on screen", () => {
662
+ const tenMinutesIn: string = new Date(
663
+ ONE_WEEK_AGO.getTime() + 10 * 60 * 1000,
664
+ ).toISOString();
665
+
666
+ expect(keyOf(zoomTo(ONE_WEEK_AGO.toISOString(), tenMinutesIn))).toBe(
667
+ windowKey(
668
+ ONE_WEEK_AGO,
669
+ justBefore(new Date(ONE_WEEK_AGO.getTime() + HOUR_MS)),
670
+ ),
671
+ );
672
+ });
673
+
674
+ test("on a window under an hour, a drag keeps the whole window, less the millisecond", () => {
675
+ const thirtyMinutesAgo: Date = new Date(NOW.getTime() - 30 * 60 * 1000);
676
+
677
+ expect(
678
+ keyOf(
679
+ zoomTo(
680
+ new Date(NOW.getTime() - 20 * 60 * 1000).toISOString(),
681
+ new Date(NOW.getTime() - 15 * 60 * 1000).toISOString(),
682
+ { start: thirtyMinutesAgo, end: NOW },
683
+ ),
684
+ ),
685
+ ).toBe(windowKey(thirtyMinutesAgo, justBefore(NOW)));
686
+ });
687
+
688
+ test("a window a millisecond wide has nothing to take off, and is left as it is", () => {
689
+ const zoomed: CostZoomWindow = zoomTo(
690
+ "2026-09-28T11:40:00.000Z",
691
+ "2026-09-28T11:40:00.001Z",
692
+ { start: NOW, end: NOW },
693
+ );
694
+
695
+ expect(keyOf(zoomed)).toBe(
696
+ "2026-09-28T11:40:00.000Z/2026-09-28T11:40:00.001Z",
697
+ );
698
+ });
699
+ });
700
+
701
+ describe("KubernetesCostTrendChart", () => {
702
+ const TREND: Array<{ x: Date; y: number }> = [
703
+ { x: new Date("2026-09-25T10:00:00.000Z"), y: 1 },
704
+ { x: new Date("2026-09-25T11:00:00.000Z"), y: 1 },
705
+ ];
706
+ const WINDOW: InBetween<Date> = new InBetween<Date>(ONE_WEEK_AGO, NOW);
707
+
708
+ function zoom(
709
+ isZoomed: boolean,
710
+ zoomToTimeRange: (startTime: Date, endTime: Date) => void,
711
+ resetZoom: () => void,
712
+ ): TimeRangeZoom {
713
+ return {
714
+ isZoomed: isZoomed,
715
+ rangeBeforeZoom: isZoomed ? { range: TimeRange.PAST_ONE_WEEK } : null,
716
+ zoomToTimeRange: zoomToTimeRange,
717
+ resetZoom: resetZoom,
718
+ };
719
+ }
720
+
721
+ test("outside a page that zooms it draws the chart with no zoom and no hint", () => {
722
+ render(
723
+ <KubernetesCostTrendChart
724
+ trend={TREND}
725
+ isLoading={false}
726
+ startAndEndDate={WINDOW}
727
+ syncid="costs"
728
+ />,
729
+ );
730
+
731
+ const drawn: { select: unknown; reset: unknown } | undefined =
732
+ chartZoomStandIns.lineCharts[chartZoomStandIns.lineCharts.length - 1];
733
+
734
+ expect(drawn?.select).toBeUndefined();
735
+ expect(drawn?.reset).toBeUndefined();
736
+ expect(screen.queryByTestId(TIME_RANGE_ZOOM_HINT_TEST_ID)).toBeNull();
737
+ });
738
+
739
+ test("inside one, a sub-hour drag reaches the page widened to an hour", () => {
740
+ const zoomTo: MockFunction = getJestMockFunction();
741
+ render(
742
+ <TimeRangeZoomProvider
743
+ zoom={zoom(
744
+ false,
745
+ zoomTo as unknown as (startTime: Date, endTime: Date) => void,
746
+ () => {},
747
+ )}
748
+ >
749
+ <KubernetesCostTrendChart
750
+ trend={TREND}
751
+ isLoading={false}
752
+ startAndEndDate={WINDOW}
753
+ syncid="costs"
754
+ />
755
+ </TimeRangeZoomProvider>,
756
+ );
757
+
758
+ chartZoomStandIns.dragWindow = {
759
+ start: new Date("2026-09-25T10:10:00.000Z"),
760
+ end: new Date("2026-09-25T10:40:00.000Z"),
761
+ };
762
+ fireEvent.click(screen.getByTestId("chart-drag"));
763
+
764
+ expect(zoomTo).toHaveBeenCalledTimes(1);
765
+ expect(
766
+ windowKey(
767
+ zoomTo.mock.calls[0]![0] as Date,
768
+ zoomTo.mock.calls[0]![1] as Date,
769
+ ),
770
+ ).toBe(WIDENED_HOUR_WINDOW);
771
+ });
772
+
773
+ test("offers the page's reset only while the page is zoomed", () => {
774
+ const resetZoom: MockFunction = getJestMockFunction();
775
+ const { rerender } = render(
776
+ <TimeRangeZoomProvider
777
+ zoom={zoom(false, () => {}, resetZoom as unknown as () => void)}
778
+ >
779
+ <KubernetesCostTrendChart
780
+ trend={TREND}
781
+ isLoading={false}
782
+ startAndEndDate={WINDOW}
783
+ syncid="costs"
784
+ />
785
+ </TimeRangeZoomProvider>,
786
+ );
787
+
788
+ expect(
789
+ chartZoomStandIns.lineCharts[chartZoomStandIns.lineCharts.length - 1]
790
+ ?.reset,
791
+ ).toBeUndefined();
792
+
793
+ rerender(
794
+ <TimeRangeZoomProvider
795
+ zoom={zoom(true, () => {}, resetZoom as unknown as () => void)}
796
+ >
797
+ <KubernetesCostTrendChart
798
+ trend={TREND}
799
+ isLoading={false}
800
+ startAndEndDate={WINDOW}
801
+ syncid="costs"
802
+ />
803
+ </TimeRangeZoomProvider>,
804
+ );
805
+
806
+ fireEvent.doubleClick(screen.getByTestId("chart-plot"));
807
+
808
+ expect(resetZoom).toHaveBeenCalledTimes(1);
809
+ });
810
+
811
+ test("names the drag above the chart, revealed on hover, and keeps the row while loading", () => {
812
+ const { rerender } = render(
813
+ <TimeRangeZoomProvider
814
+ zoom={zoom(
815
+ false,
816
+ () => {},
817
+ () => {},
818
+ )}
819
+ >
820
+ <KubernetesCostTrendChart
821
+ trend={TREND}
822
+ isLoading={true}
823
+ startAndEndDate={WINDOW}
824
+ syncid="costs"
825
+ />
826
+ </TimeRangeZoomProvider>,
827
+ );
828
+
829
+ expect(screen.queryByTestId("line-chart")).toBeNull();
830
+ expect(screen.getByTestId(TIME_RANGE_ZOOM_HINT_TEST_ID)).toHaveTextContent(
831
+ "Drag to zoom",
832
+ );
833
+
834
+ rerender(
835
+ <TimeRangeZoomProvider
836
+ zoom={zoom(
837
+ false,
838
+ () => {},
839
+ () => {},
840
+ )}
841
+ >
842
+ <KubernetesCostTrendChart
843
+ trend={TREND}
844
+ isLoading={false}
845
+ startAndEndDate={WINDOW}
846
+ syncid="costs"
847
+ />
848
+ </TimeRangeZoomProvider>,
849
+ );
850
+
851
+ const hint: HTMLElement = screen.getByTestId(TIME_RANGE_ZOOM_HINT_TEST_ID);
852
+
853
+ expect(hint).toHaveClass("group-hover/zoomhint:opacity-100");
854
+ expect(hint.closest('[class~="group/zoomhint"]')).toBe(
855
+ screen.getByTestId("line-chart").closest('[class~="group/zoomhint"]'),
856
+ );
857
+ });
858
+
859
+ test("the no-cost message resets a zoom on a double-click, and only while zoomed", () => {
860
+ const resetZoom: MockFunction = getJestMockFunction();
861
+ const { rerender } = render(
862
+ <TimeRangeZoomProvider
863
+ zoom={zoom(false, () => {}, resetZoom as unknown as () => void)}
864
+ >
865
+ <KubernetesCostTrendChart
866
+ trend={[]}
867
+ isLoading={false}
868
+ startAndEndDate={WINDOW}
869
+ syncid="costs"
870
+ />
871
+ </TimeRangeZoomProvider>,
872
+ );
873
+
874
+ fireEvent.doubleClick(screen.getByText(NO_COST_DATA));
875
+ expect(resetZoom).not.toHaveBeenCalled();
876
+
877
+ rerender(
878
+ <TimeRangeZoomProvider
879
+ zoom={zoom(true, () => {}, resetZoom as unknown as () => void)}
880
+ >
881
+ <KubernetesCostTrendChart
882
+ trend={[]}
883
+ isLoading={false}
884
+ startAndEndDate={WINDOW}
885
+ syncid="costs"
886
+ />
887
+ </TimeRangeZoomProvider>,
888
+ );
889
+
890
+ fireEvent.doubleClick(screen.getByText(NO_COST_DATA));
891
+ expect(resetZoom).toHaveBeenCalledTimes(1);
892
+ });
893
+
894
+ test("the no-cost message is not selectable, so the double-click that resets does not select a word of it", () => {
895
+ render(
896
+ <TimeRangeZoomProvider
897
+ zoom={zoom(
898
+ true,
899
+ () => {},
900
+ () => {},
901
+ )}
902
+ >
903
+ <KubernetesCostTrendChart
904
+ trend={[]}
905
+ isLoading={false}
906
+ startAndEndDate={WINDOW}
907
+ syncid="costs"
908
+ />
909
+ </TimeRangeZoomProvider>,
910
+ );
911
+
912
+ expect(screen.getByText(NO_COST_DATA).closest(".select-none")).not.toBe(
913
+ null,
914
+ );
915
+ });
916
+
917
+ test("a failed load is shown where the chart was, with a retry, and the hint row stays", () => {
918
+ const onRetry: MockFunction = getJestMockFunction();
919
+ render(
920
+ <TimeRangeZoomProvider
921
+ zoom={zoom(
922
+ false,
923
+ () => {},
924
+ () => {},
925
+ )}
926
+ >
927
+ <KubernetesCostTrendChart
928
+ trend={TREND}
929
+ isLoading={false}
930
+ error="Could not load"
931
+ onRetry={onRetry as unknown as () => void}
932
+ startAndEndDate={WINDOW}
933
+ syncid="costs"
934
+ />
935
+ </TimeRangeZoomProvider>,
936
+ );
937
+
938
+ // The error, not the previous window's trend.
939
+ expect(screen.queryByTestId("line-chart")).toBeNull();
940
+ expect(screen.getByText("Could not load")).toBeInTheDocument();
941
+ expect(screen.getByTestId(TIME_RANGE_ZOOM_HINT_TEST_ID)).toBeTruthy();
942
+
943
+ fireEvent.click(screen.getByTestId("refresh-button"));
944
+ expect(onRetry).toHaveBeenCalledTimes(1);
945
+ });
946
+
947
+ test("the failed-load message resets a zoom on a double-click, only while zoomed, and is not selectable", () => {
948
+ const resetZoom: MockFunction = getJestMockFunction();
949
+ const chart: (isZoomed: boolean) => React.ReactElement = (
950
+ isZoomed: boolean,
951
+ ): React.ReactElement => {
952
+ return (
953
+ <TimeRangeZoomProvider
954
+ zoom={zoom(isZoomed, () => {}, resetZoom as unknown as () => void)}
955
+ >
956
+ <KubernetesCostTrendChart
957
+ trend={[]}
958
+ isLoading={false}
959
+ error="Could not load"
960
+ startAndEndDate={WINDOW}
961
+ syncid="costs"
962
+ />
963
+ </TimeRangeZoomProvider>
964
+ );
965
+ };
966
+ const { rerender } = render(chart(false));
967
+
968
+ fireEvent.doubleClick(screen.getByText("Could not load"));
969
+ expect(resetZoom).not.toHaveBeenCalled();
970
+
971
+ rerender(chart(true));
972
+
973
+ const message: HTMLElement = screen.getByText("Could not load");
974
+ expect(message.closest(".select-none")).not.toBe(null);
975
+ fireEvent.doubleClick(message);
976
+ expect(resetZoom).toHaveBeenCalledTimes(1);
977
+ });
978
+
979
+ test("while the page loads, the skeleton is the chart's height, so the tables below do not jump", () => {
980
+ const { rerender } = render(
981
+ <KubernetesCostTrendChart
982
+ trend={TREND}
983
+ isLoading={true}
984
+ startAndEndDate={WINDOW}
985
+ syncid="costs"
986
+ />,
987
+ );
988
+
989
+ expect(screen.getByTestId("chart-loading-skeleton")).toHaveStyle({
990
+ height: "300px",
991
+ });
992
+
993
+ rerender(
994
+ <KubernetesCostTrendChart
995
+ trend={TREND}
996
+ isLoading={false}
997
+ startAndEndDate={WINDOW}
998
+ syncid="costs"
999
+ />,
1000
+ );
1001
+
1002
+ expect(screen.queryByTestId("chart-loading-skeleton")).toBeNull();
1003
+ expect(
1004
+ chartZoomStandIns.lineCharts[chartZoomStandIns.lineCharts.length - 1],
1005
+ ).toBeTruthy();
1006
+ });
1007
+ });
1008
+
1009
+ describe("the cluster Costs page", () => {
1010
+ async function renderClusterCosts(): Promise<void> {
1011
+ render(<KubernetesClusterCosts {...PAGE_PROPS} />);
1012
+ await settle();
1013
+ expect(screen.getByTestId("line-chart")).toBeInTheDocument();
1014
+ }
1015
+
1016
+ function expectEveryFetchFor(window: string): void {
1017
+ expect(latestWindowOf(mockFetchCostTrend)).toBe(window);
1018
+ expect(latestWindowOf(mockFetchNamespaceBreakdown)).toBe(window);
1019
+ expect(latestWindowOf(mockFetchWorkloadBreakdown)).toBe(window);
1020
+ expect(latestWindowOf(mockFetchRightSizing)).toBe(window);
1021
+ }
1022
+
1023
+ test("opens on the past week: the chart, the tiles, both tables and right-sizing all read it", async () => {
1024
+ await renderClusterCosts();
1025
+
1026
+ expect(windowOf(spendChart())).toBe(WEEK_WINDOW);
1027
+ expectEveryFetchFor(WEEK_WINDOW);
1028
+ expect(picker()).toBe(TimeRange.PAST_ONE_WEEK);
1029
+ expect(totalSpendTile()).toContain("$168.00");
1030
+ expect(resetZoomButton()).toBeNull();
1031
+ });
1032
+
1033
+ test("a drag across the spend chart re-reads the whole page for the dragged window", async () => {
1034
+ await renderClusterCosts();
1035
+
1036
+ await dragAcross(spendChart(), DRAG_START, DRAG_END);
1037
+
1038
+ expect(windowOf(spendChart())).toBe(DRAG_WINDOW);
1039
+ expectEveryFetchFor(DRAG_WINDOW);
1040
+ expect(picker()).toBe(TimeRange.CUSTOM);
1041
+ expect(totalSpendTile()).toContain("$48.00");
1042
+ expect(resetZoomButton()).toBeVisible();
1043
+ });
1044
+
1045
+ test("a double-click on the chart puts the whole page back on the past week", async () => {
1046
+ await renderClusterCosts();
1047
+
1048
+ await dragAcross(spendChart(), DRAG_START, DRAG_END);
1049
+ await doubleClickOn(spendChart());
1050
+
1051
+ expect(windowOf(spendChart())).toBe(WEEK_WINDOW);
1052
+ expectEveryFetchFor(WEEK_WINDOW);
1053
+ expect(picker()).toBe(TimeRange.PAST_ONE_WEEK);
1054
+ expect(totalSpendTile()).toContain("$168.00");
1055
+ expect(resetZoomButton()).toBeNull();
1056
+ });
1057
+
1058
+ test("Reset zoom beside the card's picker does the same", async () => {
1059
+ await renderClusterCosts();
1060
+
1061
+ await dragAcross(spendChart(), DRAG_START, DRAG_END);
1062
+ fireEvent.click(resetZoomButton()!);
1063
+ await settle();
1064
+
1065
+ expect(windowOf(spendChart())).toBe(WEEK_WINDOW);
1066
+ expectEveryFetchFor(WEEK_WINDOW);
1067
+ });
1068
+
1069
+ test("zooming twice and resetting once returns to the past week", async () => {
1070
+ await renderClusterCosts();
1071
+
1072
+ await dragAcross(spendChart(), DRAG_START, DRAG_END);
1073
+ await dragAcross(spendChart(), NESTED_DRAG_START, NESTED_DRAG_END);
1074
+
1075
+ expect(windowOf(spendChart())).toBe(NESTED_DRAG_WINDOW);
1076
+ expectEveryFetchFor(NESTED_DRAG_WINDOW);
1077
+
1078
+ await doubleClickOn(spendChart());
1079
+
1080
+ expect(windowOf(spendChart())).toBe(WEEK_WINDOW);
1081
+ expectEveryFetchFor(WEEK_WINDOW);
1082
+ });
1083
+
1084
+ test("a sub-hour drag zooms to the hour around it, so the page still has a cost row to show", async () => {
1085
+ await renderClusterCosts();
1086
+
1087
+ await dragAcross(
1088
+ spendChart(),
1089
+ new Date("2026-09-25T10:10:00.000Z"),
1090
+ new Date("2026-09-25T10:40:00.000Z"),
1091
+ );
1092
+
1093
+ expect(windowOf(spendChart())).toBe(WIDENED_HOUR_WINDOW);
1094
+ expectEveryFetchFor(WIDENED_HOUR_WINDOW);
1095
+ expect(totalSpendTile()).toContain("$1.00");
1096
+ });
1097
+
1098
+ test("a drag up to now zooms to the last hour, never past now", async () => {
1099
+ await renderClusterCosts();
1100
+
1101
+ await dragAcross(
1102
+ spendChart(),
1103
+ new Date(NOW.getTime() - 10 * 60 * 1000),
1104
+ NOW,
1105
+ );
1106
+
1107
+ expectEveryFetchFor(
1108
+ windowKey(new Date(NOW.getTime() - HOUR_MS), justBefore(NOW)),
1109
+ );
1110
+ });
1111
+
1112
+ test("a zoom into a stretch with no cost rows shows the message, and a double-click on it goes back", async () => {
1113
+ await renderClusterCosts();
1114
+
1115
+ mockCostIsQuiet = true;
1116
+ await dragAcross(spendChart(), DRAG_START, DRAG_END);
1117
+
1118
+ expect(screen.queryByTestId("line-chart")).toBeNull();
1119
+ expect(resetZoomButton()).toBeVisible();
1120
+
1121
+ mockCostIsQuiet = false;
1122
+ fireEvent.doubleClick(screen.getAllByText(NO_COST_DATA)[0]!);
1123
+ await settle();
1124
+
1125
+ expect(windowOf(spendChart())).toBe(WEEK_WINDOW);
1126
+ expectEveryFetchFor(WEEK_WINDOW);
1127
+ expect(resetZoomButton()).toBeNull();
1128
+ });
1129
+
1130
+ test("picking a range in the card's picker ends the zoom", async () => {
1131
+ await renderClusterCosts();
1132
+
1133
+ await dragAcross(spendChart(), DRAG_START, DRAG_END);
1134
+ expect(resetZoomButton()).toBeVisible();
1135
+
1136
+ fireEvent.click(screen.getByTestId("card-picker"));
1137
+ await settle();
1138
+
1139
+ expect(picker()).toBe(TimeRange.PAST_ONE_DAY);
1140
+ expect(resetZoomButton()).toBeNull();
1141
+ expect(windowOf(spendChart())).toBe(
1142
+ windowKey(new Date(NOW.getTime() - DAY_MS), NOW),
1143
+ );
1144
+ expect(
1145
+ chartZoomStandIns.lineCharts[chartZoomStandIns.lineCharts.length - 1]
1146
+ ?.reset,
1147
+ ).toBeUndefined();
1148
+ });
1149
+
1150
+ test("the spend chart takes the page's zoom, widened, and names the drag", async () => {
1151
+ await renderClusterCosts();
1152
+
1153
+ const drawn: { hostSelect: unknown; select: unknown } | undefined =
1154
+ chartZoomStandIns.lineCharts[chartZoomStandIns.lineCharts.length - 1];
1155
+
1156
+ // Its own handler: the page's zoom behind the one-hour widening.
1157
+ expect(drawn?.hostSelect).toBeInstanceOf(Function);
1158
+ expect(drawn?.select).toBe(drawn?.hostSelect);
1159
+ expect(
1160
+ within(spendChart()).getByTestId(TIME_RANGE_ZOOM_HINT_TEST_ID),
1161
+ ).toHaveTextContent("Drag to zoom");
1162
+ });
1163
+ });
1164
+
1165
+ describe("the project Costs page", () => {
1166
+ async function renderProjectCosts(): Promise<void> {
1167
+ render(<KubernetesCosts {...PAGE_PROPS} />);
1168
+ await settle();
1169
+ expect(screen.getByTestId("line-chart")).toBeInTheDocument();
1170
+ }
1171
+
1172
+ test("a drag across the spend chart re-reads the tiles and the cluster table for the window", async () => {
1173
+ await renderProjectCosts();
1174
+
1175
+ expect(latestWindowOf(mockFetchClusterBreakdown)).toBe(WEEK_WINDOW);
1176
+ const clusterListsBefore: number = mockGetClusterList.mock.calls.length;
1177
+
1178
+ await dragAcross(spendChart(), DRAG_START, DRAG_END);
1179
+
1180
+ expect(windowOf(spendChart())).toBe(DRAG_WINDOW);
1181
+ expect(latestWindowOf(mockFetchCostTrend)).toBe(DRAG_WINDOW);
1182
+ expect(latestWindowOf(mockFetchClusterBreakdown)).toBe(DRAG_WINDOW);
1183
+ expect(mockGetClusterList.mock.calls.length).toBe(clusterListsBefore + 1);
1184
+ expect(totalSpendTile()).toContain("$48.00");
1185
+ expect(picker()).toBe(TimeRange.CUSTOM);
1186
+ expect(resetZoomButton()).toBeVisible();
1187
+ });
1188
+
1189
+ test("a double-click on the chart puts the page back on the past week", async () => {
1190
+ await renderProjectCosts();
1191
+
1192
+ await dragAcross(spendChart(), DRAG_START, DRAG_END);
1193
+ await doubleClickOn(spendChart());
1194
+
1195
+ expect(windowOf(spendChart())).toBe(WEEK_WINDOW);
1196
+ expect(latestWindowOf(mockFetchCostTrend)).toBe(WEEK_WINDOW);
1197
+ expect(latestWindowOf(mockFetchClusterBreakdown)).toBe(WEEK_WINDOW);
1198
+ expect(totalSpendTile()).toContain("$168.00");
1199
+ expect(picker()).toBe(TimeRange.PAST_ONE_WEEK);
1200
+ expect(resetZoomButton()).toBeNull();
1201
+ });
1202
+
1203
+ test("a sub-hour drag is widened to an hour here too", async () => {
1204
+ await renderProjectCosts();
1205
+
1206
+ await dragAcross(
1207
+ spendChart(),
1208
+ new Date("2026-09-25T10:10:00.000Z"),
1209
+ new Date("2026-09-25T10:40:00.000Z"),
1210
+ );
1211
+
1212
+ expect(latestWindowOf(mockFetchClusterBreakdown)).toBe(WIDENED_HOUR_WINDOW);
1213
+ });
1214
+ });