@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,1229 @@
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 the Ceph cluster pages. Every time-series chart on them
24
+ * zooms the range it is drawn over, and a double-click on any of them (or
25
+ * "Reset zoom" in the card header) puts the range back:
26
+ *
27
+ * - Overview: the Golden Signals card's range is the PAGE's (it feeds the
28
+ * card's metric charts and its two rate charts, and the auto-refresh
29
+ * slides it), so every chart in the card zooms the page;
30
+ * - Insights: four cards share the page's one range, so they share one
31
+ * zoom - a drag in one card retimes all four, a double-click in any card
32
+ * resets them all;
33
+ * - Pool and OSD detail, Metrics tab: the tab's card owns its range, and
34
+ * its metric charts and the pool's rate charts share the card's zoom.
35
+ *
36
+ * The network is replaced (model and analytics APIs), and so are the
37
+ * MetricView inside each card (its own zoom handling has its own suite: the
38
+ * stand-in records the handlers the card hands it and the window it is
39
+ * asked for) and the card's range picker (a stand-in that shows the range
40
+ * and can pick "Past 1 Day"). EmbeddedMetricCard, CephRateChart, the pages
41
+ * and ResourceMetricsTab render for real; the rate charts' line chart is
42
+ * ChartZoomStandIn, which resolves its zoom as the real wrapper does.
43
+ */
44
+
45
+ const CLUSTER_ID: string = "0193c0de-6666-4aaa-8bbb-000000000006";
46
+ const NOW: Date = new Date("2026-09-28T12:00:00.000Z");
47
+ const MINUTE: number = 60 * 1000;
48
+ const GIB: number = 1024 * 1024 * 1024;
49
+ const ZOOM_START: Date = new Date("2026-09-28T11:20:00.000Z");
50
+ const ZOOM_END: Date = new Date("2026-09-28T11:40:00.000Z");
51
+ const INNER_ZOOM_START: Date = new Date("2026-09-28T11:25:00.000Z");
52
+ const INNER_ZOOM_END: Date = new Date("2026-09-28T11:30:00.000Z");
53
+
54
+ const IOPS_CHART: string = "Read + Write [ops/s]";
55
+ const THROUGHPUT_CHART: string = "Read + Write [By/s]";
56
+
57
+ const modelGetItemMock: MockFunction = getJestMockFunction();
58
+ const modelGetListMock: MockFunction = getJestMockFunction();
59
+ const analyticsGetListMock: MockFunction = getJestMockFunction();
60
+ const analyticsAggregateMock: MockFunction = getJestMockFunction();
61
+
62
+ let mockLastParam: string = "1";
63
+
64
+ interface MockMetricViewProps {
65
+ data: {
66
+ startAndEndDate: { startValue: Date; endValue: Date };
67
+ queryConfigs: Array<{ metricAliasData?: { metricVariable?: string } }>;
68
+ };
69
+ onTimeRangeSelect?: ((startTime: Date, endTime: Date) => void) | undefined;
70
+ onTimeRangeReset?: (() => void) | undefined;
71
+ }
72
+
73
+ // The latest props of each MetricView, by its query variables.
74
+ const mockMetricViews: Map<string, MockMetricViewProps> = new Map();
75
+
76
+ jest.mock(
77
+ "../../../../App/FeatureSet/Dashboard/src/Components/Metrics/MetricView",
78
+ () => {
79
+ return {
80
+ __esModule: true,
81
+ default: (props: MockMetricViewProps): React.ReactElement => {
82
+ const name: string = props.data.queryConfigs
83
+ .map((query: { metricAliasData?: { metricVariable?: string } }) => {
84
+ return query.metricAliasData?.metricVariable || "";
85
+ })
86
+ .join("+");
87
+ mockMetricViews.set(name, props);
88
+ return (
89
+ <div
90
+ data-testid={`metric-view ${name}`}
91
+ onDoubleClick={props.onTimeRangeReset}
92
+ >
93
+ <button
94
+ type="button"
95
+ onClick={() => {
96
+ const drag: { start: Date; end: Date } = (
97
+ jest.requireActual("./ChartZoomStandIn") as {
98
+ standInDrag: { start: Date; end: Date };
99
+ }
100
+ ).standInDrag;
101
+ props.onTimeRangeSelect?.(drag.start, drag.end);
102
+ }}
103
+ >
104
+ {`Drag across metrics ${name}`}
105
+ </button>
106
+ </div>
107
+ );
108
+ },
109
+ };
110
+ },
111
+ );
112
+
113
+ jest.mock(
114
+ "../../../../App/FeatureSet/Dashboard/src/Components/Metrics/Utils/UseEventTimeReferenceLines",
115
+ () => {
116
+ return {
117
+ __esModule: true,
118
+ default: () => {
119
+ return { lines: [], markerCount: 0 };
120
+ },
121
+ };
122
+ },
123
+ );
124
+
125
+ // Each card's picker: shows its range, and can pick "Past 1 Day".
126
+ jest.mock("../../../UI/Components/Date/RangeStartAndEndDateView", () => {
127
+ return {
128
+ __esModule: true,
129
+ default: (props: {
130
+ dashboardStartAndEndDate: { range: string };
131
+ onChange: (value: { range: string }) => void;
132
+ }): React.ReactElement => {
133
+ return (
134
+ <button
135
+ type="button"
136
+ data-testid="card-picker"
137
+ onClick={() => {
138
+ props.onChange({ range: "Past 1 Day" });
139
+ }}
140
+ >
141
+ {props.dashboardStartAndEndDate.range}
142
+ </button>
143
+ );
144
+ },
145
+ };
146
+ });
147
+
148
+ jest.mock("../../../UI/Components/Charts/Line/LineChart", () => {
149
+ return {
150
+ ...(jest.requireActual(
151
+ "../../../UI/Components/Charts/Line/LineChart",
152
+ ) as Record<string, unknown>),
153
+ __esModule: true,
154
+ default: (jest.requireActual("./ChartZoomStandIn") as { default: unknown })
155
+ .default,
156
+ };
157
+ });
158
+
159
+ jest.mock("react-i18next", () => {
160
+ return {
161
+ useTranslation: () => {
162
+ return {
163
+ t: (key: string, opts?: { defaultValue?: string }): string => {
164
+ return opts?.defaultValue ?? key;
165
+ },
166
+ };
167
+ },
168
+ };
169
+ });
170
+
171
+ jest.mock("../../../UI/Utils/ModelAPI/ModelAPI", () => {
172
+ return {
173
+ __esModule: true,
174
+ default: {
175
+ getItem: (...args: Array<unknown>): unknown => {
176
+ return modelGetItemMock(...args);
177
+ },
178
+ getList: (...args: Array<unknown>): unknown => {
179
+ return modelGetListMock(...args);
180
+ },
181
+ count: (): Promise<number> => {
182
+ return Promise.resolve(1);
183
+ },
184
+ getCommonHeaders: (): Record<string, string> => {
185
+ return {};
186
+ },
187
+ },
188
+ };
189
+ });
190
+
191
+ jest.mock("../../../UI/Utils/AnalyticsModelAPI/AnalyticsModelAPI", () => {
192
+ return {
193
+ __esModule: true,
194
+ default: {
195
+ getList: (...args: Array<unknown>): unknown => {
196
+ return analyticsGetListMock(...args);
197
+ },
198
+ aggregate: (...args: Array<unknown>): unknown => {
199
+ return analyticsAggregateMock(...args);
200
+ },
201
+ },
202
+ };
203
+ });
204
+
205
+ jest.mock("../../../UI/Utils/Navigation", () => {
206
+ return {
207
+ __esModule: true,
208
+ default: {
209
+ getLastParamAsObjectID: (): unknown => {
210
+ const { default: ObjectIDType } = jest.requireActual(
211
+ "../../../Types/ObjectID",
212
+ ) as { default: new (id: string) => unknown };
213
+ return new ObjectIDType("0193c0de-6666-4aaa-8bbb-000000000006");
214
+ },
215
+ getLastParamAsString: (): string => {
216
+ return mockLastParam;
217
+ },
218
+ navigate: (): void => {},
219
+ isOnThisPage: (): boolean => {
220
+ return false;
221
+ },
222
+ },
223
+ };
224
+ });
225
+
226
+ jest.mock("../../../UI/Utils/Project", () => {
227
+ return {
228
+ __esModule: true,
229
+ default: {
230
+ getCurrentProjectId: (): unknown => {
231
+ const { default: ObjectIDType } = jest.requireActual(
232
+ "../../../Types/ObjectID",
233
+ ) as { default: new (id: string) => unknown };
234
+ return new ObjectIDType("10000000-0000-4000-8000-000000000001");
235
+ },
236
+ },
237
+ };
238
+ });
239
+
240
+ jest.mock("../../../UI/Components/ModelDetail/CardModelDetail", () => {
241
+ return {
242
+ __esModule: true,
243
+ default: (): React.ReactElement => {
244
+ return <div data-testid="card-model-detail" />;
245
+ },
246
+ };
247
+ });
248
+
249
+ /*
250
+ * The hero's refresh control: "Refresh now" is what an auto-refresh tick
251
+ * calls too (fetchAll(false)).
252
+ */
253
+ jest.mock(
254
+ "../../../../App/FeatureSet/Dashboard/src/Components/TelemetryResource/AutoRefreshControl",
255
+ () => {
256
+ return {
257
+ __esModule: true,
258
+ default: (props: { onManualRefresh: () => void }): React.ReactElement => {
259
+ return (
260
+ <button type="button" onClick={props.onManualRefresh}>
261
+ Refresh now
262
+ </button>
263
+ );
264
+ },
265
+ };
266
+ },
267
+ );
268
+
269
+ jest.mock(
270
+ "../../../../App/FeatureSet/Dashboard/src/Components/TelemetryResource/useAutoRefresh",
271
+ () => {
272
+ return {
273
+ __esModule: true,
274
+ default: () => {
275
+ return {
276
+ autoRefreshInterval: "off",
277
+ setAutoRefreshInterval: () => {},
278
+ };
279
+ },
280
+ };
281
+ },
282
+ );
283
+
284
+ import CephClusterOverview from "../../../../App/FeatureSet/Dashboard/src/Pages/Ceph/View/Index";
285
+ import CephClusterInsights from "../../../../App/FeatureSet/Dashboard/src/Pages/Ceph/View/Insights";
286
+ import CephClusterPoolDetail from "../../../../App/FeatureSet/Dashboard/src/Pages/Ceph/View/PoolDetail";
287
+ import CephClusterOsdDetail from "../../../../App/FeatureSet/Dashboard/src/Pages/Ceph/View/OsdDetail";
288
+ import CephRateChart from "../../../../App/FeatureSet/Dashboard/src/Components/Ceph/CephRateChart";
289
+ import PageComponentProps from "../../../../App/FeatureSet/Dashboard/src/Pages/PageComponentProps";
290
+ import {
291
+ StandInChartRecord,
292
+ getStandInChart,
293
+ resetStandInCharts,
294
+ standInCharts,
295
+ standInDrag,
296
+ } from "./ChartZoomStandIn";
297
+ import { TimeRangeZoomProvider } from "../../../UI/Components/Charts/TimeRangeZoom/TimeRangeZoomContext";
298
+ import { TimeRangeZoom } from "../../../UI/Components/Charts/TimeRangeZoom/UseTimeRangeZoom";
299
+ import { RESET_TIME_RANGE_ZOOM_BUTTON_TEST_ID } from "../../../UI/Components/Charts/TimeRangeZoom/ResetTimeRangeZoomButton";
300
+ import TimeRange from "../../../Types/Time/TimeRange";
301
+
302
+ const PAGE_PROPS: PageComponentProps = {} as PageComponentProps;
303
+
304
+ // ---------------------------------------------------------------- fixtures
305
+
306
+ function minutesAgo(minutes: number): Date {
307
+ return new Date(NOW.getTime() - minutes * MINUTE);
308
+ }
309
+
310
+ const CLUSTER: Record<string, unknown> = {
311
+ _id: CLUSTER_ID,
312
+ name: "ceph-prod",
313
+ fsid: "3b0c1f7e-aaaa-bbbb-cccc-000000000001",
314
+ otelCollectorStatus: "connected",
315
+ lastSeenAt: minutesAgo(1),
316
+ cephVersion: "18.2.4",
317
+ healthStatus: 0,
318
+ monCount: 3,
319
+ osdCount: 2,
320
+ osdUpCount: 2,
321
+ osdInCount: 2,
322
+ poolCount: 1,
323
+ capacityUsedPercent: 12.34,
324
+ };
325
+
326
+ type Row = Record<string, unknown>;
327
+
328
+ const ROWS_BY_KIND: Record<string, Array<Row>> = {
329
+ Osd: [
330
+ {
331
+ kind: "Osd",
332
+ externalId: "osd.1",
333
+ hostname: "node-b",
334
+ deviceClass: "hdd",
335
+ isUp: true,
336
+ isIn: true,
337
+ statBytes: 2000 * GIB,
338
+ statBytesUsed: 500 * GIB,
339
+ pgCount: 101,
340
+ applyLatencyMs: 12,
341
+ commitLatencyMs: 13,
342
+ metricsUpdatedAt: minutesAgo(1),
343
+ lastSeenAt: minutesAgo(1),
344
+ createdAt: minutesAgo(60 * 24 * 3),
345
+ },
346
+ ],
347
+ Pool: [
348
+ {
349
+ kind: "Pool",
350
+ externalId: "1",
351
+ name: "rbd",
352
+ storedBytes: 300 * GIB,
353
+ maxAvailBytes: 700 * GIB,
354
+ objects: 76800,
355
+ metricsUpdatedAt: minutesAgo(1),
356
+ lastSeenAt: minutesAgo(1),
357
+ createdAt: minutesAgo(60 * 24 * 3),
358
+ },
359
+ ],
360
+ Mon: [],
361
+ Mgr: [],
362
+ Mds: [],
363
+ Rgw: [],
364
+ };
365
+
366
+ const RATE_METRICS: Array<string> = [
367
+ "ceph_pool_rd",
368
+ "ceph_pool_wr",
369
+ "ceph_pool_rd_bytes",
370
+ "ceph_pool_wr_bytes",
371
+ ];
372
+
373
+ interface CapturedAggregate {
374
+ name: string;
375
+ start: Date;
376
+ end: Date;
377
+ attributes: Record<string, unknown>;
378
+ }
379
+
380
+ function aggregateCall(args: unknown): CapturedAggregate {
381
+ const aggregateBy: {
382
+ query: { name: string; attributes?: Record<string, unknown> };
383
+ startTimestamp: Date;
384
+ endTimestamp: Date;
385
+ } = (args as { aggregateBy: never }).aggregateBy;
386
+ return {
387
+ name: aggregateBy.query.name,
388
+ start: aggregateBy.startTimestamp,
389
+ end: aggregateBy.endTimestamp,
390
+ attributes: aggregateBy.query.attributes || {},
391
+ };
392
+ }
393
+
394
+ // A cumulative counter climbing through the window it is asked for.
395
+ function counterRows(start: Date, end: Date, perSecond: number): Array<Row> {
396
+ const step: number = (end.getTime() - start.getTime()) / 4;
397
+ return [1, 2, 3].map((i: number) => {
398
+ return {
399
+ timestamp: new Date(start.getTime() + i * step),
400
+ value: 1000 + perSecond * ((i * step) / 1000),
401
+ attributes: { pool_id: "1" },
402
+ };
403
+ });
404
+ }
405
+
406
+ // Rate metrics answer this for a window of the given length (null: data).
407
+ let mockQuietWindowMinutes: number | null = null;
408
+
409
+ function arrange(): void {
410
+ modelGetItemMock.mockImplementation(async () => {
411
+ return CLUSTER;
412
+ });
413
+
414
+ modelGetListMock.mockImplementation(async (args: unknown) => {
415
+ const query: Record<string, unknown> = (
416
+ args as { query: Record<string, unknown> }
417
+ ).query;
418
+ const rows: Array<Row> = ROWS_BY_KIND[String(query["kind"])] || [];
419
+ const filtered: Array<Row> = query["externalId"]
420
+ ? rows.filter((row: Row) => {
421
+ return row["externalId"] === query["externalId"];
422
+ })
423
+ : rows;
424
+ return { data: filtered, count: filtered.length, skip: 0, limit: 100 };
425
+ });
426
+
427
+ analyticsGetListMock.mockImplementation(async () => {
428
+ return { data: [], count: 0 };
429
+ });
430
+
431
+ analyticsAggregateMock.mockImplementation(async (args: unknown) => {
432
+ const call: CapturedAggregate = aggregateCall(args);
433
+ if (RATE_METRICS.includes(call.name)) {
434
+ const minutes: number =
435
+ (call.end.getTime() - call.start.getTime()) / MINUTE;
436
+ if (
437
+ mockQuietWindowMinutes !== null &&
438
+ minutes === mockQuietWindowMinutes
439
+ ) {
440
+ return { data: [] };
441
+ }
442
+ return { data: counterRows(call.start, call.end, 10) };
443
+ }
444
+ if (call.name === "ceph_cluster_total_used_bytes") {
445
+ return {
446
+ data: [
447
+ { timestamp: minutesAgo(120), value: 100 * GIB },
448
+ { timestamp: minutesAgo(0), value: 300 * GIB },
449
+ ],
450
+ };
451
+ }
452
+ return { data: [] };
453
+ });
454
+ }
455
+
456
+ // ----------------------------------------------------------------- helpers
457
+
458
+ async function flush(): Promise<void> {
459
+ for (let i: number = 0; i < 8; i++) {
460
+ await act(async () => {
461
+ await Promise.resolve();
462
+ });
463
+ }
464
+ }
465
+
466
+ function metricView(name: string): MockMetricViewProps {
467
+ const props: MockMetricViewProps | undefined = mockMetricViews.get(name);
468
+ if (!props) {
469
+ throw new Error(
470
+ `MetricView "${name}" has not rendered. Rendered: ${Array.from(
471
+ mockMetricViews.keys(),
472
+ ).join(", ")}`,
473
+ );
474
+ }
475
+ return props;
476
+ }
477
+
478
+ function metricViewWindow(name: string): [string, string] {
479
+ const window: { startValue: Date; endValue: Date } =
480
+ metricView(name).data.startAndEndDate;
481
+ return [window.startValue.toISOString(), window.endValue.toISOString()];
482
+ }
483
+
484
+ function metricViewMinutes(name: string): number {
485
+ const window: { startValue: Date; endValue: Date } =
486
+ metricView(name).data.startAndEndDate;
487
+ return (window.endValue.getTime() - window.startValue.getTime()) / MINUTE;
488
+ }
489
+
490
+ // The latest window each rate metric was fetched over.
491
+ function rateWindow(metricName: string): [string, string] {
492
+ const calls: Array<CapturedAggregate> = analyticsAggregateMock.mock.calls
493
+ .map((call: Array<unknown>) => {
494
+ return aggregateCall(call[0]);
495
+ })
496
+ .filter((call: CapturedAggregate) => {
497
+ return call.name === metricName;
498
+ });
499
+ const last: CapturedAggregate | undefined = calls[calls.length - 1];
500
+ if (!last) {
501
+ throw new Error(`${metricName} was never fetched`);
502
+ }
503
+ return [last.start.toISOString(), last.end.toISOString()];
504
+ }
505
+
506
+ function rateCalls(metricName: string): Array<CapturedAggregate> {
507
+ return analyticsAggregateMock.mock.calls
508
+ .map((call: Array<unknown>) => {
509
+ return aggregateCall(call[0]);
510
+ })
511
+ .filter((call: CapturedAggregate) => {
512
+ return call.name === metricName;
513
+ });
514
+ }
515
+
516
+ function chartWindow(name: string): [string, string] {
517
+ const record: StandInChartRecord = getStandInChart(name);
518
+ return [
519
+ (record.props.xAxis.options.min as Date).toISOString(),
520
+ (record.props.xAxis.options.max as Date).toISOString(),
521
+ ];
522
+ }
523
+
524
+ const ZOOM_WINDOW: [string, string] = [
525
+ ZOOM_START.toISOString(),
526
+ ZOOM_END.toISOString(),
527
+ ];
528
+
529
+ async function waitForCharts(names: Array<string>): Promise<void> {
530
+ await waitFor(() => {
531
+ for (const name of names) {
532
+ expect(screen.getByTestId(`chart ${name}`)).toBeInTheDocument();
533
+ }
534
+ });
535
+ }
536
+
537
+ async function dragAcrossChart(
538
+ name: string,
539
+ start: Date = ZOOM_START,
540
+ end: Date = ZOOM_END,
541
+ ): Promise<void> {
542
+ standInDrag.start = start;
543
+ standInDrag.end = end;
544
+ fireEvent.click(screen.getByRole("button", { name: `Drag across ${name}` }));
545
+ await flush();
546
+ }
547
+
548
+ async function dragAcrossMetrics(
549
+ name: string,
550
+ start: Date = ZOOM_START,
551
+ end: Date = ZOOM_END,
552
+ ): Promise<void> {
553
+ standInDrag.start = start;
554
+ standInDrag.end = end;
555
+ fireEvent.click(
556
+ screen.getByRole("button", { name: `Drag across metrics ${name}` }),
557
+ );
558
+ await flush();
559
+ }
560
+
561
+ async function doubleClickTestId(testId: string): Promise<void> {
562
+ fireEvent.doubleClick(screen.getByTestId(testId));
563
+ await flush();
564
+ }
565
+
566
+ function pickers(): Array<string> {
567
+ return screen
568
+ .getAllByTestId("card-picker")
569
+ .map((picker: HTMLElement): string => {
570
+ return picker.textContent || "";
571
+ });
572
+ }
573
+
574
+ function resetButtons(): Array<HTMLElement> {
575
+ return screen.queryAllByTestId(RESET_TIME_RANGE_ZOOM_BUTTON_TEST_ID);
576
+ }
577
+
578
+ beforeEach(() => {
579
+ jest.useFakeTimers({ doNotFake: ["nextTick", "setImmediate"] });
580
+ jest.setSystemTime(NOW);
581
+ mockLastParam = "1";
582
+ mockQuietWindowMinutes = null;
583
+ mockMetricViews.clear();
584
+ resetStandInCharts();
585
+ for (const mock of [
586
+ modelGetItemMock,
587
+ modelGetListMock,
588
+ analyticsGetListMock,
589
+ analyticsAggregateMock,
590
+ ]) {
591
+ mock.mockReset();
592
+ }
593
+ arrange();
594
+ });
595
+
596
+ afterEach(() => {
597
+ cleanup();
598
+ jest.useRealTimers();
599
+ });
600
+
601
+ // --------------------------------------------------------------- Overview
602
+
603
+ const GOLDEN_METRICS: string =
604
+ "cluster_used_bytes+osd_apply_latency+osd_commit_latency";
605
+
606
+ async function renderOverview(): Promise<void> {
607
+ render(<CephClusterOverview {...PAGE_PROPS} />);
608
+ await flush();
609
+ await waitFor(() => {
610
+ expect(
611
+ screen.getByTestId(`metric-view ${GOLDEN_METRICS}`),
612
+ ).toBeInTheDocument();
613
+ });
614
+ await waitForCharts([IOPS_CHART, THROUGHPUT_CHART]);
615
+ }
616
+
617
+ describe("Ceph cluster Overview: the Golden Signals card zooms the page's range", () => {
618
+ test("the card's metric charts and both rate charts share the page's zoom", async () => {
619
+ await renderOverview();
620
+
621
+ const select: unknown = metricView(GOLDEN_METRICS).onTimeRangeSelect;
622
+ expect(select).toBeInstanceOf(Function);
623
+ expect(getStandInChart(IOPS_CHART).zoom.onTimeRangeSelect).toBe(select);
624
+ expect(getStandInChart(THROUGHPUT_CHART).zoom.onTimeRangeSelect).toBe(
625
+ select,
626
+ );
627
+ // The rate charts pass no handlers of their own: they take the page's.
628
+ expect(getStandInChart(IOPS_CHART).props.onTimeRangeSelect).toBeUndefined();
629
+ // Nothing to reset yet.
630
+ expect(metricView(GOLDEN_METRICS).onTimeRangeReset).toBeUndefined();
631
+ expect(getStandInChart(IOPS_CHART).zoom.onTimeRangeReset).toBeUndefined();
632
+ expect(resetButtons()).toHaveLength(0);
633
+ });
634
+
635
+ test("a drag on a rate chart re-fetches the metric charts and both rate charts over the dragged window", async () => {
636
+ await renderOverview();
637
+ expect(metricViewMinutes(GOLDEN_METRICS)).toBe(60);
638
+
639
+ await dragAcrossChart(IOPS_CHART);
640
+ await waitForCharts([IOPS_CHART, THROUGHPUT_CHART]);
641
+
642
+ expect(metricViewWindow(GOLDEN_METRICS)).toEqual(ZOOM_WINDOW);
643
+ for (const metricName of RATE_METRICS) {
644
+ expect([metricName, ...rateWindow(metricName)]).toEqual([
645
+ metricName,
646
+ ...ZOOM_WINDOW,
647
+ ]);
648
+ }
649
+ expect(chartWindow(IOPS_CHART)).toEqual(ZOOM_WINDOW);
650
+ expect(chartWindow(THROUGHPUT_CHART)).toEqual(ZOOM_WINDOW);
651
+ });
652
+
653
+ test("after a drag the card's picker reads Custom and its header offers Reset zoom", async () => {
654
+ await renderOverview();
655
+
656
+ await dragAcrossMetrics(GOLDEN_METRICS);
657
+
658
+ expect(pickers()).toEqual([TimeRange.CUSTOM]);
659
+ expect(resetButtons()).toHaveLength(1);
660
+ expect(metricView(GOLDEN_METRICS).onTimeRangeReset).toBeInstanceOf(
661
+ Function,
662
+ );
663
+ await waitForCharts([IOPS_CHART]);
664
+ expect(getStandInChart(IOPS_CHART).zoom.onTimeRangeReset).toBe(
665
+ metricView(GOLDEN_METRICS).onTimeRangeReset,
666
+ );
667
+ });
668
+
669
+ test("a double-click on the metric chart undoes a zoom made on a rate chart", async () => {
670
+ await renderOverview();
671
+
672
+ await dragAcrossChart(THROUGHPUT_CHART);
673
+ await doubleClickTestId(`metric-view ${GOLDEN_METRICS}`);
674
+ await waitForCharts([IOPS_CHART, THROUGHPUT_CHART]);
675
+
676
+ expect(pickers()).toEqual([TimeRange.PAST_ONE_HOUR]);
677
+ expect(metricViewMinutes(GOLDEN_METRICS)).toBe(60);
678
+ expect(resetButtons()).toHaveLength(0);
679
+ const [start, end]: [string, string] = rateWindow("ceph_pool_rd");
680
+ expect((Date.parse(end) - Date.parse(start)) / MINUTE).toBe(60);
681
+ });
682
+
683
+ test("a double-click on a rate chart undoes a zoom made on the metric chart", async () => {
684
+ await renderOverview();
685
+
686
+ await dragAcrossMetrics(GOLDEN_METRICS);
687
+ await waitForCharts([IOPS_CHART]);
688
+ await doubleClickTestId(`chart ${IOPS_CHART}`);
689
+
690
+ expect(pickers()).toEqual([TimeRange.PAST_ONE_HOUR]);
691
+ expect(metricViewMinutes(GOLDEN_METRICS)).toBe(60);
692
+ });
693
+
694
+ test("Reset zoom in the card header puts the range back", async () => {
695
+ await renderOverview();
696
+
697
+ await dragAcrossChart(IOPS_CHART);
698
+ fireEvent.click(screen.getByTestId(RESET_TIME_RANGE_ZOOM_BUTTON_TEST_ID));
699
+ await flush();
700
+
701
+ expect(pickers()).toEqual([TimeRange.PAST_ONE_HOUR]);
702
+ expect(metricViewMinutes(GOLDEN_METRICS)).toBe(60);
703
+ expect(resetButtons()).toHaveLength(0);
704
+ });
705
+
706
+ test("a zoom within a zoom: one double-click returns to the past hour", async () => {
707
+ await renderOverview();
708
+
709
+ await dragAcrossChart(IOPS_CHART);
710
+ await waitForCharts([THROUGHPUT_CHART]);
711
+ await dragAcrossChart(THROUGHPUT_CHART, INNER_ZOOM_START, INNER_ZOOM_END);
712
+ expect(metricViewWindow(GOLDEN_METRICS)).toEqual([
713
+ INNER_ZOOM_START.toISOString(),
714
+ INNER_ZOOM_END.toISOString(),
715
+ ]);
716
+
717
+ await doubleClickTestId(`metric-view ${GOLDEN_METRICS}`);
718
+
719
+ expect(pickers()).toEqual([TimeRange.PAST_ONE_HOUR]);
720
+ expect(metricViewMinutes(GOLDEN_METRICS)).toBe(60);
721
+ });
722
+
723
+ test("an auto-refresh leaves a zoom pinned; after a reset it slides the past hour again", async () => {
724
+ await renderOverview();
725
+
726
+ await dragAcrossChart(IOPS_CHART);
727
+ await waitForCharts([IOPS_CHART]);
728
+
729
+ // A minute later, the auto-refresh ticks.
730
+ jest.setSystemTime(new Date(NOW.getTime() + MINUTE));
731
+ fireEvent.click(screen.getByRole("button", { name: "Refresh now" }));
732
+ await flush();
733
+
734
+ expect(metricViewWindow(GOLDEN_METRICS)).toEqual(ZOOM_WINDOW);
735
+ expect(resetButtons()).toHaveLength(1);
736
+
737
+ fireEvent.click(screen.getByTestId(RESET_TIME_RANGE_ZOOM_BUTTON_TEST_ID));
738
+ await flush();
739
+ const before: string = metricViewWindow(GOLDEN_METRICS)[1];
740
+
741
+ jest.setSystemTime(new Date(NOW.getTime() + 2 * MINUTE));
742
+ fireEvent.click(screen.getByRole("button", { name: "Refresh now" }));
743
+ await flush();
744
+
745
+ // The preset slides forward with the clock again.
746
+ expect(Date.parse(metricViewWindow(GOLDEN_METRICS)[1])).toBeGreaterThan(
747
+ Date.parse(before),
748
+ );
749
+ expect(metricViewMinutes(GOLDEN_METRICS)).toBe(60);
750
+ });
751
+
752
+ test("the card's own Refresh keeps the zoom", async () => {
753
+ await renderOverview();
754
+
755
+ await dragAcrossChart(IOPS_CHART);
756
+ fireEvent.click(screen.getByRole("button", { name: "Refresh" }));
757
+ await flush();
758
+
759
+ expect(pickers()).toEqual([TimeRange.CUSTOM]);
760
+ expect(metricViewWindow(GOLDEN_METRICS)).toEqual(ZOOM_WINDOW);
761
+ expect(resetButtons()).toHaveLength(1);
762
+ });
763
+
764
+ test("the Capacity tile's 24-hour projection does not follow the zoom", async () => {
765
+ await renderOverview();
766
+ const capacityCalls: number = rateCalls(
767
+ "ceph_cluster_total_used_bytes",
768
+ ).length;
769
+
770
+ await dragAcrossChart(IOPS_CHART);
771
+
772
+ expect(rateCalls("ceph_cluster_total_used_bytes")).toHaveLength(
773
+ capacityCalls,
774
+ );
775
+ });
776
+
777
+ test("a zoom into a quiet stretch: the empty rate chart takes the double-click", async () => {
778
+ await renderOverview();
779
+ mockQuietWindowMinutes = 20;
780
+
781
+ await dragAcrossChart(IOPS_CHART);
782
+
783
+ const empty: HTMLElement = await screen.findByText(
784
+ "No client I/O reported in the selected time range.",
785
+ );
786
+ fireEvent.doubleClick(empty);
787
+ await flush();
788
+
789
+ expect(pickers()).toEqual([TimeRange.PAST_ONE_HOUR]);
790
+ expect(metricViewMinutes(GOLDEN_METRICS)).toBe(60);
791
+ });
792
+
793
+ test("picking a range in the card's picker ends the zoom", async () => {
794
+ await renderOverview();
795
+
796
+ await dragAcrossChart(IOPS_CHART);
797
+ fireEvent.click(screen.getByTestId("card-picker"));
798
+ await flush();
799
+
800
+ expect(pickers()).toEqual([TimeRange.PAST_ONE_DAY]);
801
+ expect(metricViewMinutes(GOLDEN_METRICS)).toBe(24 * 60);
802
+ expect(resetButtons()).toHaveLength(0);
803
+ });
804
+ });
805
+
806
+ // --------------------------------------------------------------- Insights
807
+
808
+ const CAPACITY_METRICS: string = "cluster_used_bytes+pool_stored";
809
+ const LATENCY_METRICS: string = "osd_apply_latency+osd_commit_latency";
810
+ const DATA_HEALTH_METRICS: string =
811
+ "objects_degraded+objects_misplaced+pg_degraded+pg_undersized";
812
+ const INSIGHTS_METRIC_VIEWS: Array<string> = [
813
+ CAPACITY_METRICS,
814
+ LATENCY_METRICS,
815
+ DATA_HEALTH_METRICS,
816
+ ];
817
+
818
+ async function renderInsights(): Promise<void> {
819
+ render(<CephClusterInsights {...PAGE_PROPS} />);
820
+ await flush();
821
+ await waitFor(() => {
822
+ for (const name of INSIGHTS_METRIC_VIEWS) {
823
+ expect(screen.getByTestId(`metric-view ${name}`)).toBeInTheDocument();
824
+ }
825
+ });
826
+ await waitForCharts([IOPS_CHART, THROUGHPUT_CHART]);
827
+ }
828
+
829
+ function expectEveryInsightsChartOn(window: [string, string]): void {
830
+ for (const name of INSIGHTS_METRIC_VIEWS) {
831
+ expect([name, ...metricViewWindow(name)]).toEqual([name, ...window]);
832
+ }
833
+ for (const metricName of RATE_METRICS) {
834
+ expect([metricName, ...rateWindow(metricName)]).toEqual([
835
+ metricName,
836
+ ...window,
837
+ ]);
838
+ }
839
+ }
840
+
841
+ function expectEveryInsightsChartOnThePastHour(): void {
842
+ for (const name of INSIGHTS_METRIC_VIEWS) {
843
+ expect([name, metricViewMinutes(name)]).toEqual([name, 60]);
844
+ }
845
+ expect(pickers()).toEqual([
846
+ TimeRange.PAST_ONE_HOUR,
847
+ TimeRange.PAST_ONE_HOUR,
848
+ TimeRange.PAST_ONE_HOUR,
849
+ TimeRange.PAST_ONE_HOUR,
850
+ ]);
851
+ expect(resetButtons()).toHaveLength(0);
852
+ }
853
+
854
+ describe("Ceph Insights: four cards, one range, one zoom", () => {
855
+ test("every chart in every card takes the same page zoom", async () => {
856
+ await renderInsights();
857
+
858
+ const select: unknown = metricView(CAPACITY_METRICS).onTimeRangeSelect;
859
+ expect(select).toBeInstanceOf(Function);
860
+ for (const name of INSIGHTS_METRIC_VIEWS) {
861
+ expect([name, metricView(name).onTimeRangeSelect]).toEqual([
862
+ name,
863
+ select,
864
+ ]);
865
+ }
866
+ expect(getStandInChart(IOPS_CHART).zoom.onTimeRangeSelect).toBe(select);
867
+ expect(getStandInChart(THROUGHPUT_CHART).zoom.onTimeRangeSelect).toBe(
868
+ select,
869
+ );
870
+ });
871
+
872
+ test("a drag in the Capacity card retimes all four cards", async () => {
873
+ await renderInsights();
874
+
875
+ await dragAcrossMetrics(CAPACITY_METRICS);
876
+ await waitForCharts([IOPS_CHART, THROUGHPUT_CHART]);
877
+
878
+ expectEveryInsightsChartOn(ZOOM_WINDOW);
879
+ expect(pickers()).toEqual([
880
+ TimeRange.CUSTOM,
881
+ TimeRange.CUSTOM,
882
+ TimeRange.CUSTOM,
883
+ TimeRange.CUSTOM,
884
+ ]);
885
+ // Every card's header offers the way back.
886
+ expect(resetButtons()).toHaveLength(4);
887
+ });
888
+
889
+ test("a drag on a rate chart in the Client I/O card retimes the other three", async () => {
890
+ await renderInsights();
891
+
892
+ await dragAcrossChart(THROUGHPUT_CHART);
893
+ await waitForCharts([IOPS_CHART, THROUGHPUT_CHART]);
894
+
895
+ expectEveryInsightsChartOn(ZOOM_WINDOW);
896
+ });
897
+
898
+ test("a double-click in ANOTHER card undoes the zoom in every card", async () => {
899
+ await renderInsights();
900
+
901
+ await dragAcrossMetrics(CAPACITY_METRICS);
902
+ await doubleClickTestId(`metric-view ${DATA_HEALTH_METRICS}`);
903
+ await waitForCharts([IOPS_CHART, THROUGHPUT_CHART]);
904
+
905
+ expectEveryInsightsChartOnThePastHour();
906
+ });
907
+
908
+ test("a double-click on a rate chart undoes a zoom made in the Latency card", async () => {
909
+ await renderInsights();
910
+
911
+ await dragAcrossMetrics(LATENCY_METRICS);
912
+ await waitForCharts([IOPS_CHART]);
913
+ await doubleClickTestId(`chart ${IOPS_CHART}`);
914
+
915
+ expectEveryInsightsChartOnThePastHour();
916
+ });
917
+
918
+ test("Reset zoom in any card's header resets them all", async () => {
919
+ await renderInsights();
920
+
921
+ await dragAcrossMetrics(LATENCY_METRICS);
922
+ // The Data Health card's button (the last card).
923
+ fireEvent.click(resetButtons()[3]!);
924
+ await flush();
925
+
926
+ expectEveryInsightsChartOnThePastHour();
927
+ });
928
+
929
+ test("a zoom within a zoom across cards: one reset goes back to the past hour", async () => {
930
+ await renderInsights();
931
+
932
+ await dragAcrossMetrics(CAPACITY_METRICS);
933
+ await dragAcrossMetrics(
934
+ DATA_HEALTH_METRICS,
935
+ INNER_ZOOM_START,
936
+ INNER_ZOOM_END,
937
+ );
938
+ await waitForCharts([IOPS_CHART, THROUGHPUT_CHART]);
939
+ expectEveryInsightsChartOn([
940
+ INNER_ZOOM_START.toISOString(),
941
+ INNER_ZOOM_END.toISOString(),
942
+ ]);
943
+
944
+ await doubleClickTestId(`metric-view ${LATENCY_METRICS}`);
945
+
946
+ expectEveryInsightsChartOnThePastHour();
947
+ });
948
+
949
+ test("a card's Refresh keeps the zoom for every card", async () => {
950
+ await renderInsights();
951
+
952
+ await dragAcrossMetrics(CAPACITY_METRICS);
953
+ fireEvent.click(screen.getAllByRole("button", { name: "Refresh" })[2]!);
954
+ await flush();
955
+
956
+ for (const name of INSIGHTS_METRIC_VIEWS) {
957
+ expect([name, ...metricViewWindow(name)]).toEqual([name, ...ZOOM_WINDOW]);
958
+ }
959
+ expect(resetButtons()).toHaveLength(4);
960
+ });
961
+
962
+ test("picking a range in one card ends the zoom in all of them", async () => {
963
+ await renderInsights();
964
+
965
+ await dragAcrossMetrics(CAPACITY_METRICS);
966
+ fireEvent.click(screen.getAllByTestId("card-picker")[1]!);
967
+ await flush();
968
+
969
+ expect(pickers()).toEqual([
970
+ TimeRange.PAST_ONE_DAY,
971
+ TimeRange.PAST_ONE_DAY,
972
+ TimeRange.PAST_ONE_DAY,
973
+ TimeRange.PAST_ONE_DAY,
974
+ ]);
975
+ expect(resetButtons()).toHaveLength(0);
976
+ for (const name of INSIGHTS_METRIC_VIEWS) {
977
+ expect([name, metricViewMinutes(name)]).toEqual([name, 24 * 60]);
978
+ }
979
+ });
980
+ });
981
+
982
+ // ------------------------------------------------ Pool and OSD detail tabs
983
+
984
+ const POOL_METRICS: string = "pool_stored+pool_max_avail+pool_objects";
985
+ const OSD_METRICS: string =
986
+ "osd_apply_latency+osd_commit_latency+osd_stat_bytes_used+osd_numpg";
987
+
988
+ async function openMetricsTab(): Promise<void> {
989
+ fireEvent.click(await screen.findByTestId("tab-Metrics"));
990
+ await flush();
991
+ }
992
+
993
+ describe("Ceph pool detail, Metrics tab: the card's zoom covers the pool's rate charts", () => {
994
+ async function renderPool(): Promise<void> {
995
+ mockLastParam = "1";
996
+ render(<CephClusterPoolDetail {...PAGE_PROPS} />);
997
+ await flush();
998
+ await openMetricsTab();
999
+ await waitFor(() => {
1000
+ expect(
1001
+ screen.getByTestId(`metric-view ${POOL_METRICS}`),
1002
+ ).toBeInTheDocument();
1003
+ });
1004
+ await waitForCharts([IOPS_CHART, THROUGHPUT_CHART]);
1005
+ }
1006
+
1007
+ test("the metric charts and the pool's rate charts share the card's zoom", async () => {
1008
+ await renderPool();
1009
+
1010
+ const select: unknown = metricView(POOL_METRICS).onTimeRangeSelect;
1011
+ expect(select).toBeInstanceOf(Function);
1012
+ expect(getStandInChart(IOPS_CHART).zoom.onTimeRangeSelect).toBe(select);
1013
+ expect(getStandInChart(THROUGHPUT_CHART).zoom.onTimeRangeSelect).toBe(
1014
+ select,
1015
+ );
1016
+ });
1017
+
1018
+ test("a drag on a pool rate chart narrows the metric charts and both rate charts", async () => {
1019
+ await renderPool();
1020
+
1021
+ await dragAcrossChart(THROUGHPUT_CHART);
1022
+ await waitForCharts([IOPS_CHART, THROUGHPUT_CHART]);
1023
+
1024
+ expect(metricViewWindow(POOL_METRICS)).toEqual(ZOOM_WINDOW);
1025
+ for (const metricName of RATE_METRICS) {
1026
+ const calls: Array<CapturedAggregate> = rateCalls(metricName);
1027
+ const last: CapturedAggregate = calls[calls.length - 1]!;
1028
+ expect([
1029
+ metricName,
1030
+ last.start.toISOString(),
1031
+ last.end.toISOString(),
1032
+ ]).toEqual([metricName, ...ZOOM_WINDOW]);
1033
+ // Still this pool's counters only.
1034
+ expect(last.attributes["pool_id"]).toBe("1");
1035
+ }
1036
+ expect(pickers()).toEqual([TimeRange.CUSTOM]);
1037
+ expect(resetButtons()).toHaveLength(1);
1038
+ });
1039
+
1040
+ test("a double-click on the metric chart puts the tab's range back", async () => {
1041
+ await renderPool();
1042
+
1043
+ await dragAcrossChart(IOPS_CHART);
1044
+ await doubleClickTestId(`metric-view ${POOL_METRICS}`);
1045
+ await waitForCharts([IOPS_CHART, THROUGHPUT_CHART]);
1046
+
1047
+ expect(pickers()).toEqual([TimeRange.PAST_ONE_HOUR]);
1048
+ expect(metricViewMinutes(POOL_METRICS)).toBe(60);
1049
+ expect(resetButtons()).toHaveLength(0);
1050
+ });
1051
+
1052
+ test("Reset zoom beside the tab's picker puts the range back", async () => {
1053
+ await renderPool();
1054
+
1055
+ await dragAcrossMetrics(POOL_METRICS);
1056
+ fireEvent.click(screen.getByTestId(RESET_TIME_RANGE_ZOOM_BUTTON_TEST_ID));
1057
+ await flush();
1058
+
1059
+ expect(pickers()).toEqual([TimeRange.PAST_ONE_HOUR]);
1060
+ expect(metricViewMinutes(POOL_METRICS)).toBe(60);
1061
+ });
1062
+ });
1063
+
1064
+ describe("Ceph OSD detail, Metrics tab", () => {
1065
+ async function renderOsd(): Promise<void> {
1066
+ mockLastParam = "osd.1";
1067
+ render(<CephClusterOsdDetail {...PAGE_PROPS} />);
1068
+ await flush();
1069
+ await openMetricsTab();
1070
+ await waitFor(() => {
1071
+ expect(
1072
+ screen.getByTestId(`metric-view ${OSD_METRICS}`),
1073
+ ).toBeInTheDocument();
1074
+ });
1075
+ }
1076
+
1077
+ test("a drag narrows the tab's charts, and a double-click puts the range back", async () => {
1078
+ await renderOsd();
1079
+ expect(metricView(OSD_METRICS).onTimeRangeReset).toBeUndefined();
1080
+
1081
+ await dragAcrossMetrics(OSD_METRICS);
1082
+
1083
+ expect(metricViewWindow(OSD_METRICS)).toEqual(ZOOM_WINDOW);
1084
+ expect(pickers()).toEqual([TimeRange.CUSTOM]);
1085
+ expect(resetButtons()).toHaveLength(1);
1086
+ expect(metricView(OSD_METRICS).onTimeRangeReset).toBeInstanceOf(Function);
1087
+
1088
+ await doubleClickTestId(`metric-view ${OSD_METRICS}`);
1089
+
1090
+ expect(metricViewMinutes(OSD_METRICS)).toBe(60);
1091
+ expect(pickers()).toEqual([TimeRange.PAST_ONE_HOUR]);
1092
+ expect(resetButtons()).toHaveLength(0);
1093
+ });
1094
+
1095
+ test("Reset zoom beside the tab's picker does the same", async () => {
1096
+ await renderOsd();
1097
+
1098
+ await dragAcrossMetrics(OSD_METRICS);
1099
+ fireEvent.click(screen.getByTestId(RESET_TIME_RANGE_ZOOM_BUTTON_TEST_ID));
1100
+ await flush();
1101
+
1102
+ expect(metricViewMinutes(OSD_METRICS)).toBe(60);
1103
+ expect(pickers()).toEqual([TimeRange.PAST_ONE_HOUR]);
1104
+ });
1105
+ });
1106
+
1107
+ // ------------------------------------------------------- CephRateChart
1108
+
1109
+ describe("CephRateChart on its own", () => {
1110
+ const WINDOW_START: Date = new Date("2026-09-28T11:00:00.000Z");
1111
+
1112
+ function fakeZoom(
1113
+ isZoomed: boolean,
1114
+ resetZoom: MockFunction,
1115
+ zoomToTimeRange: MockFunction,
1116
+ ): TimeRangeZoom {
1117
+ return {
1118
+ isZoomed: isZoomed,
1119
+ rangeBeforeZoom: isZoomed ? { range: TimeRange.PAST_ONE_HOUR } : null,
1120
+ zoomToTimeRange: zoomToTimeRange as unknown as (
1121
+ startTime: Date,
1122
+ endTime: Date,
1123
+ ) => void,
1124
+ resetZoom: resetZoom as unknown as () => void,
1125
+ };
1126
+ }
1127
+
1128
+ function rateChart(): React.ReactElement {
1129
+ return (
1130
+ <CephRateChart
1131
+ clusterName="ceph-prod"
1132
+ series={[
1133
+ { metricName: "ceph_pool_rd", label: "Read" },
1134
+ { metricName: "ceph_pool_wr", label: "Write" },
1135
+ ]}
1136
+ seriesKeyAttributes={["pool_id"]}
1137
+ startDate={WINDOW_START}
1138
+ endDate={NOW}
1139
+ emptyMessage="Nothing in this window."
1140
+ />
1141
+ );
1142
+ }
1143
+
1144
+ test("hands its line chart no handlers of its own, so the chart takes the zoom around it", async () => {
1145
+ const zoomToTimeRange: MockFunction = getJestMockFunction();
1146
+ render(
1147
+ <TimeRangeZoomProvider
1148
+ zoom={fakeZoom(false, getJestMockFunction(), zoomToTimeRange)}
1149
+ >
1150
+ {rateChart()}
1151
+ </TimeRangeZoomProvider>,
1152
+ );
1153
+ await flush();
1154
+ await waitForCharts([IOPS_CHART]);
1155
+
1156
+ const record: StandInChartRecord = getStandInChart(IOPS_CHART);
1157
+ expect(record.props.onTimeRangeSelect).toBeUndefined();
1158
+ expect(record.props.onTimeRangeReset).toBeUndefined();
1159
+ expect(record.props.disableTimeRangeZoom).toBeUndefined();
1160
+
1161
+ standInDrag.start = ZOOM_START;
1162
+ standInDrag.end = ZOOM_END;
1163
+ fireEvent.click(
1164
+ screen.getByRole("button", { name: `Drag across ${IOPS_CHART}` }),
1165
+ );
1166
+ expect(zoomToTimeRange).toHaveBeenCalledWith(ZOOM_START, ZOOM_END);
1167
+ });
1168
+
1169
+ test("its window comes from its props, and it is fetched over exactly that window", async () => {
1170
+ render(rateChart());
1171
+ await flush();
1172
+ await waitForCharts([IOPS_CHART]);
1173
+
1174
+ expect(chartWindow(IOPS_CHART)).toEqual([
1175
+ WINDOW_START.toISOString(),
1176
+ NOW.toISOString(),
1177
+ ]);
1178
+ expect(rateWindow("ceph_pool_rd")).toEqual([
1179
+ WINDOW_START.toISOString(),
1180
+ NOW.toISOString(),
1181
+ ]);
1182
+ });
1183
+
1184
+ test("its empty state resets a zoom that found no data on double-click", async () => {
1185
+ analyticsAggregateMock.mockResolvedValue({ data: [] });
1186
+ const resetZoom: MockFunction = getJestMockFunction();
1187
+ render(
1188
+ <TimeRangeZoomProvider
1189
+ zoom={fakeZoom(true, resetZoom, getJestMockFunction())}
1190
+ >
1191
+ {rateChart()}
1192
+ </TimeRangeZoomProvider>,
1193
+ );
1194
+ await flush();
1195
+
1196
+ fireEvent.doubleClick(await screen.findByText("Nothing in this window."));
1197
+
1198
+ expect(resetZoom).toHaveBeenCalledTimes(1);
1199
+ });
1200
+
1201
+ test("its empty state ignores a double-click when there is no zoom to undo", async () => {
1202
+ analyticsAggregateMock.mockResolvedValue({ data: [] });
1203
+ const resetZoom: MockFunction = getJestMockFunction();
1204
+ render(
1205
+ <TimeRangeZoomProvider
1206
+ zoom={fakeZoom(false, resetZoom, getJestMockFunction())}
1207
+ >
1208
+ {rateChart()}
1209
+ </TimeRangeZoomProvider>,
1210
+ );
1211
+ await flush();
1212
+
1213
+ fireEvent.doubleClick(await screen.findByText("Nothing in this window."));
1214
+
1215
+ expect(resetZoom).not.toHaveBeenCalled();
1216
+ });
1217
+
1218
+ test("outside any zoom it draws with no drag at all", async () => {
1219
+ render(rateChart());
1220
+ await flush();
1221
+ await waitForCharts([IOPS_CHART]);
1222
+
1223
+ expect(getStandInChart(IOPS_CHART).zoom.onTimeRangeSelect).toBeUndefined();
1224
+ expect(
1225
+ within(screen.getByTestId(`chart ${IOPS_CHART}`)).getByRole("button"),
1226
+ ).toBeInTheDocument();
1227
+ expect(standInCharts.size).toBe(1);
1228
+ });
1229
+ });