@skyhook-io/radar-app 1.16.3 → 1.17.1

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 (321) hide show
  1. package/README.md +1 -1
  2. package/package.json +3 -3
  3. package/src/App.tsx +138 -93
  4. package/src/RadarApp.tsx +27 -17
  5. package/src/api/actions.errorCode.test.ts +40 -0
  6. package/src/api/actions.test.ts +48 -0
  7. package/src/api/actions.ts +79 -0
  8. package/src/api/apiResources.navigation.test.tsx +232 -0
  9. package/src/api/apiResources.ts +46 -3
  10. package/src/api/client.goneResource.test.ts +49 -0
  11. package/src/api/client.helmSource.test.ts +39 -0
  12. package/src/api/client.namespaceSwitch.hook.test.tsx +54 -0
  13. package/src/api/client.namespaceSwitch.test.ts +107 -0
  14. package/src/api/client.ts +381 -47
  15. package/src/api/cnpg-ha.live.test.ts +140 -0
  16. package/src/api/cnpg-ha.test.ts +78 -0
  17. package/src/api/cnpg-ha.ts +19 -0
  18. package/src/api/cnpg-history.ts +123 -0
  19. package/src/api/cnpg-inspect.ts +102 -0
  20. package/src/api/cnpg-protection.ts +112 -0
  21. package/src/api/cnpg-recovery.test.tsx +60 -0
  22. package/src/api/cnpg-recovery.ts +262 -0
  23. package/src/api/cnpg-sessions.ts +175 -0
  24. package/src/api/cnpg-storage.ts +138 -0
  25. package/src/api/cnpg.legacy-reads.test.ts +43 -0
  26. package/src/api/cnpg.pooler-runtime.test.ts +15 -0
  27. package/src/api/cnpg.ts +428 -10
  28. package/src/api/diagnose.test.ts +40 -0
  29. package/src/api/diagnose.ts +12 -1
  30. package/src/api/radarFeatures.render.test.tsx +65 -3
  31. package/src/api/radarFeatures.ts +6 -0
  32. package/src/api/reviewedActionMutation.test.tsx +164 -0
  33. package/src/api/reviewedActionMutation.ts +66 -0
  34. package/src/api/timelineSource.test.ts +9 -0
  35. package/src/api/timelineSource.ts +8 -0
  36. package/src/api/traffic.sources.test.ts +55 -0
  37. package/src/api/traffic.ts +173 -14
  38. package/src/components/applications/ApplicationsView.tsx +3 -0
  39. package/src/components/audit/AuditView.tsx +9 -0
  40. package/src/components/capacity/CapacityActivity.tsx +3 -31
  41. package/src/components/capacity/CapacityDemand.tsx +4 -36
  42. package/src/components/capacity/CapacityOverview.tsx +217 -56
  43. package/src/components/capacity/CapacityPoolDetail.tsx +24 -62
  44. package/src/components/capacity/CapacityView.test.tsx +354 -1
  45. package/src/components/capacity/ClusterSchedulingCard.tsx +2 -10
  46. package/src/components/capacity/certaintyGlyph.test.tsx +2 -9
  47. package/src/components/capacity/routes.ts +42 -0
  48. package/src/components/capacity/shared.tsx +23 -150
  49. package/src/components/capacity/workspaceRoute.ts +18 -0
  50. package/src/components/cnpg/CNPGActionHost.compatibility.test.tsx +30 -0
  51. package/src/components/cnpg/CNPGArchivingRepair.tsx +191 -0
  52. package/src/components/cnpg/CNPGBlockingSessions.test.tsx +50 -0
  53. package/src/components/cnpg/CNPGBlockingSessions.tsx +333 -0
  54. package/src/components/cnpg/CNPGClusterActivity.test.tsx +11 -0
  55. package/src/components/cnpg/CNPGClusterActivity.tsx +18 -9
  56. package/src/components/cnpg/CNPGClusterLogs.compatibility.test.tsx +130 -0
  57. package/src/components/cnpg/CNPGClusterLogs.empty.test.tsx +29 -0
  58. package/src/components/cnpg/CNPGClusterLogs.test.ts +9 -0
  59. package/src/components/cnpg/CNPGClusterLogs.tsx +81 -24
  60. package/src/components/cnpg/CNPGClusterTabs.test.tsx +152 -0
  61. package/src/components/cnpg/CNPGClusterTabs.tsx +187 -0
  62. package/src/components/cnpg/CNPGClustersKindView.tsx +46 -0
  63. package/src/components/cnpg/CNPGConfiguration.test.tsx +223 -0
  64. package/src/components/cnpg/CNPGConfiguration.tsx +190 -0
  65. package/src/components/cnpg/CNPGCreateClusterDialog.test.tsx +113 -0
  66. package/src/components/cnpg/CNPGCreateClusterDialog.tsx +74 -0
  67. package/src/components/cnpg/CNPGDeclarations.test.ts +107 -0
  68. package/src/components/cnpg/CNPGDeclarations.tsx +236 -78
  69. package/src/components/cnpg/CNPGDetailPage.test.tsx +62 -0
  70. package/src/components/cnpg/CNPGDetailPage.tsx +159 -98
  71. package/src/components/cnpg/CNPGDrawerTrail.tsx +2 -2
  72. package/src/components/cnpg/CNPGInvestigationAction.test.tsx +30 -0
  73. package/src/components/cnpg/CNPGOperator.test.tsx +34 -0
  74. package/src/components/cnpg/CNPGOperator.tsx +78 -30
  75. package/src/components/cnpg/CNPGOperatorBanner.tsx +39 -0
  76. package/src/components/cnpg/CNPGOperatorConditions.test.tsx +45 -0
  77. package/src/components/cnpg/CNPGOperatorConditions.tsx +16 -0
  78. package/src/components/cnpg/CNPGOperatorConfig.test.tsx +27 -0
  79. package/src/components/cnpg/CNPGOperatorDiagnosis.tsx +247 -0
  80. package/src/components/cnpg/CNPGOverview.test.tsx +171 -0
  81. package/src/components/cnpg/CNPGOverview.tsx +238 -63
  82. package/src/components/cnpg/CNPGParametersInEffect.test.tsx +51 -0
  83. package/src/components/cnpg/CNPGParametersInEffect.tsx +120 -0
  84. package/src/components/cnpg/CNPGPerformance.prerequisite.test.tsx +48 -0
  85. package/src/components/cnpg/CNPGPerformance.test.tsx +73 -0
  86. package/src/components/cnpg/CNPGPerformance.tsx +477 -0
  87. package/src/components/cnpg/CNPGPooling.test.tsx +89 -0
  88. package/src/components/cnpg/CNPGPooling.tsx +97 -40
  89. package/src/components/cnpg/CNPGProtection.test.tsx +145 -0
  90. package/src/components/cnpg/CNPGProtection.tsx +230 -124
  91. package/src/components/cnpg/CNPGReplicationTab.test.tsx +87 -0
  92. package/src/components/cnpg/CNPGReplicationTab.tsx +212 -0
  93. package/src/components/cnpg/CNPGReplicationView.tsx +264 -0
  94. package/src/components/cnpg/CNPGStorage.test.tsx +140 -0
  95. package/src/components/cnpg/CNPGStorage.tsx +619 -0
  96. package/src/components/cnpg/CNPGSummaryHost.overview.test.tsx +44 -0
  97. package/src/components/cnpg/CNPGSummaryHost.test.tsx +23 -0
  98. package/src/components/cnpg/CNPGSummaryHost.tsx +228 -54
  99. package/src/components/cnpg/CNPGTrends.denied.test.tsx +33 -0
  100. package/src/components/cnpg/CNPGTrends.missing.test.tsx +69 -0
  101. package/src/components/cnpg/CNPGTrends.test.ts +38 -0
  102. package/src/components/cnpg/CNPGTrends.tsx +702 -0
  103. package/src/components/cnpg/CNPGView.tsx +34 -21
  104. package/src/components/cnpg/CNPGWorkspace.compatibility.test.tsx +90 -0
  105. package/src/components/cnpg/actions/CNPGClusterActions.dialog.test.tsx +79 -0
  106. package/src/components/cnpg/actions/CNPGClusterActions.test.tsx +142 -0
  107. package/src/components/cnpg/actions/CNPGClusterActions.tsx +853 -0
  108. package/src/components/cnpg/actions/CNPGConnectButton.read.test.tsx +40 -0
  109. package/src/components/cnpg/actions/CNPGConnectButton.test.ts +25 -0
  110. package/src/components/cnpg/actions/CNPGConnectButton.tsx +131 -0
  111. package/src/components/cnpg/actions/CNPGDestroyInstanceDialog.tsx +185 -0
  112. package/src/components/cnpg/actions/CNPGInstanceActions.test.tsx +38 -0
  113. package/src/components/cnpg/actions/CNPGInstanceActions.tsx +74 -0
  114. package/src/components/cnpg/actions/CNPGMaintenanceBanner.tsx +53 -0
  115. package/src/components/cnpg/actions/CNPGPoolerActions.test.tsx +35 -0
  116. package/src/components/cnpg/actions/CNPGPoolerActions.tsx +79 -0
  117. package/src/components/cnpg/actions/CNPGReportDialog.test.tsx +12 -0
  118. package/src/components/cnpg/actions/CNPGReportDialog.tsx +91 -0
  119. package/src/components/cnpg/actions/CNPGScheduleActions.run.test.tsx +58 -0
  120. package/src/components/cnpg/actions/CNPGScheduleActions.test.tsx +54 -0
  121. package/src/components/cnpg/actions/CNPGScheduleActions.tsx +208 -0
  122. package/src/components/cnpg/actions/actionModel.test.ts +111 -0
  123. package/src/components/cnpg/actions/actionModel.ts +127 -0
  124. package/src/components/cnpg/actions/renderCNPGHeaderActions.tsx +60 -0
  125. package/src/components/cnpg/actions/useCNPGWriteGuard.tsx +58 -0
  126. package/src/components/cnpg/actions/useOpenCNPGPsql.ts +22 -0
  127. package/src/components/cnpg/archivingRepair.test.ts +104 -0
  128. package/src/components/cnpg/archivingRepair.ts +145 -0
  129. package/src/components/cnpg/baseBackup.test.ts +36 -0
  130. package/src/components/cnpg/baseBackup.ts +36 -0
  131. package/src/components/cnpg/blocking.test.ts +59 -0
  132. package/src/components/cnpg/blocking.ts +131 -0
  133. package/src/components/cnpg/coverageText.test.ts +27 -0
  134. package/src/components/cnpg/creation/CNPGTargetFields.tsx +110 -0
  135. package/src/components/cnpg/creation/targetModel.test.ts +137 -0
  136. package/src/components/cnpg/creation/targetModel.ts +149 -0
  137. package/src/components/cnpg/fleetStatus.test.ts +46 -0
  138. package/src/components/cnpg/fleetStatus.ts +43 -0
  139. package/src/components/cnpg/formFields.ts +2 -0
  140. package/src/components/cnpg/grantText.test.tsx +11 -0
  141. package/src/components/cnpg/host.test.tsx +24 -0
  142. package/src/components/cnpg/host.tsx +101 -0
  143. package/src/components/cnpg/inspectModel.test.ts +120 -0
  144. package/src/components/cnpg/inspectModel.ts +140 -0
  145. package/src/components/cnpg/logicalSlots.test.ts +30 -0
  146. package/src/components/cnpg/logicalSlots.ts +29 -0
  147. package/src/components/cnpg/lsn.test.ts +40 -0
  148. package/src/components/cnpg/lsn.ts +43 -0
  149. package/src/components/cnpg/operations/CNPGFleetOperation.tsx +30 -0
  150. package/src/components/cnpg/operations/CNPGOperationTracker.test.tsx +247 -0
  151. package/src/components/cnpg/operations/CNPGOperationTracker.tsx +255 -0
  152. package/src/components/cnpg/operations/model.test.ts +510 -0
  153. package/src/components/cnpg/operations/model.ts +499 -0
  154. package/src/components/cnpg/operations/presentation.test.ts +80 -0
  155. package/src/components/cnpg/operations/presentation.ts +84 -0
  156. package/src/components/cnpg/operations/store.ts +98 -0
  157. package/src/components/cnpg/operatorStatus.test.ts +134 -0
  158. package/src/components/cnpg/operatorStatus.ts +194 -0
  159. package/src/components/cnpg/paths.test.ts +54 -0
  160. package/src/components/cnpg/paths.ts +47 -4
  161. package/src/components/cnpg/protection/CNPGAttachArchiveDialog.tsx +185 -0
  162. package/src/components/cnpg/protection/CNPGCreateScheduleDialog.tsx +196 -0
  163. package/src/components/cnpg/protection/CNPGProtectionSetup.test.tsx +121 -0
  164. package/src/components/cnpg/protection/CNPGProtectionSetup.tsx +452 -0
  165. package/src/components/cnpg/protection/CNPGScheduleInput.tsx +115 -0
  166. package/src/components/cnpg/protection/CNPGScheduleRepairButton.tsx +120 -0
  167. package/src/components/cnpg/protection/scheduleModel.test.ts +19 -0
  168. package/src/components/cnpg/protection/scheduleModel.ts +16 -0
  169. package/src/components/cnpg/recovery/CNPGRecoveryPoint.tsx +220 -0
  170. package/src/components/cnpg/recovery/CNPGRestoreButton.test.tsx +29 -0
  171. package/src/components/cnpg/recovery/CNPGRestoreButton.tsx +26 -0
  172. package/src/components/cnpg/recovery/CNPGRestoreChecks.test.tsx +23 -0
  173. package/src/components/cnpg/recovery/CNPGRestoreChecks.tsx +153 -0
  174. package/src/components/cnpg/recovery/CNPGRestoreDialog.back.test.tsx +149 -0
  175. package/src/components/cnpg/recovery/CNPGRestoreDialog.test.tsx +46 -0
  176. package/src/components/cnpg/recovery/CNPGRestoreDialog.tsx +333 -0
  177. package/src/components/cnpg/recovery/CNPGRestoreProgress.tsx +182 -0
  178. package/src/components/cnpg/recovery/CNPGRestoreValidation.tsx +216 -0
  179. package/src/components/cnpg/recovery/restoreModel.test.ts +709 -0
  180. package/src/components/cnpg/recovery/restoreModel.ts +685 -0
  181. package/src/components/cnpg/recovery/restoreOperation.ts +56 -0
  182. package/src/components/cnpg/refresh.test.ts +38 -0
  183. package/src/components/cnpg/refresh.ts +17 -0
  184. package/src/components/cnpg/refreshNotice.test.tsx +29 -0
  185. package/src/components/cnpg/routes.test.ts +34 -3
  186. package/src/components/cnpg/routes.ts +60 -48
  187. package/src/components/cnpg/runtimeAssessment.ts +198 -0
  188. package/src/components/cnpg/runtimeModel.test.ts +175 -0
  189. package/src/components/cnpg/runtimeModel.ts +196 -0
  190. package/src/components/cnpg/runtimeParts.test.tsx +17 -0
  191. package/src/components/cnpg/runtimeParts.tsx +79 -0
  192. package/src/components/cnpg/shared.test.tsx +59 -0
  193. package/src/components/cnpg/shared.tsx +100 -182
  194. package/src/components/cnpg/storageModel.test.ts +107 -0
  195. package/src/components/cnpg/storageModel.ts +101 -0
  196. package/src/components/cnpg/trendSamples.test.ts +148 -0
  197. package/src/components/cnpg/trendSamples.ts +222 -0
  198. package/src/components/cnpg/useCNPGClusterAssessment.ts +30 -0
  199. package/src/components/cnpg/useCNPGKubectlContext.test.tsx +45 -0
  200. package/src/components/cnpg/useCNPGKubectlContext.ts +13 -0
  201. package/src/components/cnpg/useCNPGNavigate.test.tsx +58 -0
  202. package/src/components/cnpg/useCNPGNavigate.ts +21 -0
  203. package/src/components/cnpg/useCNPGPoolerLive.ts +46 -0
  204. package/src/components/cnpg/useCNPGScreenParams.ts +24 -0
  205. package/src/components/cnpg/useCNPGSidebarWorkspace.ts +66 -16
  206. package/src/components/cnpg/workspaceRoute.ts +23 -0
  207. package/src/components/cost/ApplicationCostTab.test.ts +7 -0
  208. package/src/components/cost/ApplicationCostTab.tsx +7 -7
  209. package/src/components/cost/CostView.test.ts +2 -1
  210. package/src/components/cost/CostView.tsx +0 -2
  211. package/src/components/cost/WorkloadCostTab.tsx +2 -4
  212. package/src/components/cost/errors.ts +3 -1
  213. package/src/components/cost/source.test.ts +10 -0
  214. package/src/components/cost/source.ts +5 -1
  215. package/src/components/diagnose/AgentSetupNotice.test.tsx +58 -0
  216. package/src/components/diagnose/AgentSetupNotice.tsx +142 -51
  217. package/src/components/diagnose/ApplyDialog.ack.test.tsx +47 -0
  218. package/src/components/diagnose/ApplyDialog.test.tsx +59 -6
  219. package/src/components/diagnose/ApplyDialog.tsx +26 -28
  220. package/src/components/diagnose/DiagnoseContext.test.ts +43 -0
  221. package/src/components/diagnose/DiagnoseContext.tsx +136 -54
  222. package/src/components/diagnose/DiagnoseSurface.tsx +16 -3
  223. package/src/components/diagnose/InvestigationView.tsx +39 -2
  224. package/src/components/diagnose/LocalDiagnoseAction.tsx +40 -20
  225. package/src/components/diagnose/agentCatalog.ts +2 -2
  226. package/src/components/diagnose/diagnoseEvidenceTypes.ts +2 -0
  227. package/src/components/diagnose/investigationEvidence/adapters/issues.ts +8 -0
  228. package/src/components/diagnose/investigationEvidence/projection.test.ts +16 -0
  229. package/src/components/dock/BottomDock.tsx +2 -0
  230. package/src/components/dock/TerminalTab.test.tsx +17 -0
  231. package/src/components/dock/TerminalTab.tsx +7 -3
  232. package/src/components/dock/TrafficFlowListTab.tsx +27 -6
  233. package/src/components/execution/BatchExecutionView.tsx +11 -11
  234. package/src/components/gitops/ArgoResourceDiffLoader.tsx +4 -2
  235. package/src/components/gitops/GitOpsView.tsx +16 -23
  236. package/src/components/helm/ChartBrowser.tsx +3 -3
  237. package/src/components/helm/HelmCompareRoute.test.tsx +59 -0
  238. package/src/components/helm/HelmCompareRoute.tsx +57 -26
  239. package/src/components/helm/HelmReleaseDrawer.test.ts +55 -3
  240. package/src/components/helm/HelmReleaseDrawer.tsx +50 -12
  241. package/src/components/helm/RevisionHistory.tsx +3 -2
  242. package/src/components/helm/TrackChartSourceDialog.render.test.tsx +120 -0
  243. package/src/components/helm/TrackChartSourceDialog.test.ts +50 -0
  244. package/src/components/helm/TrackChartSourceDialog.tsx +153 -176
  245. package/src/components/helm/ValuesViewer.test.tsx +301 -0
  246. package/src/components/helm/ValuesViewer.tsx +169 -78
  247. package/src/components/home/ActivitySummary.tsx +2 -1
  248. package/src/components/home/ClusterHealthCard.tsx +7 -0
  249. package/src/components/home/HomeView.tsx +20 -9
  250. package/src/components/home/ProblemsPanel.test.tsx +81 -0
  251. package/src/components/home/TopologyPreview.tsx +2 -2
  252. package/src/components/home/mcpToolCatalog.ts +1 -1
  253. package/src/components/issues/IssuesPane.render.test.tsx +122 -0
  254. package/src/components/issues/IssuesPane.test.ts +65 -1
  255. package/src/components/issues/IssuesPane.tsx +188 -25
  256. package/src/components/issues/uncheckedSources.ts +21 -0
  257. package/src/components/resource/PVCUsageBar.render.test.tsx +32 -0
  258. package/src/components/resource/PVCUsageBar.test.tsx +24 -0
  259. package/src/components/resource/PVCUsageBar.tsx +15 -6
  260. package/src/components/resource/WorkloadMetricsHelpDialog.test.tsx +3 -1
  261. package/src/components/resource/WorkloadMetricsHelpDialog.tsx +1 -1
  262. package/src/components/resources/CompositeRenderer.tsx +4 -4
  263. package/src/components/resources/ResourceDetailDrawer.tsx +2 -2
  264. package/src/components/resources/ResourcesView.tsx +26 -12
  265. package/src/components/resources/renderers/KyvernoPolicyQueued.tsx +2 -1
  266. package/src/components/resources/renderers/NodeRenderer.tsx +7 -1
  267. package/src/components/resources/renderers/ServiceRenderer.test.tsx +81 -0
  268. package/src/components/resources/renderers/ServiceRenderer.tsx +19 -14
  269. package/src/components/settings/LocalIntegrationStatus.test.tsx +52 -0
  270. package/src/components/settings/LocalIntegrationStatus.tsx +3 -4
  271. package/src/components/settings/SettingsDialog.tsx +150 -34
  272. package/src/components/shared/LargeClusterNamespacePicker.tsx +11 -5
  273. package/src/components/timeline/TimelineList.tsx +25 -24
  274. package/src/components/timeline/TimelineLoadError.test.tsx +62 -0
  275. package/src/components/timeline/TimelineLoadError.tsx +96 -0
  276. package/src/components/timeline/TimelineRetentionNote.test.tsx +49 -0
  277. package/src/components/timeline/TimelineRetentionNote.tsx +20 -0
  278. package/src/components/timeline/TimelineView.tsx +25 -45
  279. package/src/components/timeline/ViewModeToggle.tsx +20 -0
  280. package/src/components/timeline/useStickyError.test.tsx +47 -0
  281. package/src/components/timeline/useStickyError.ts +15 -0
  282. package/src/components/traffic/TrafficConnectionsTable.tsx +144 -0
  283. package/src/components/traffic/TrafficFilterSidebar.tsx +22 -1
  284. package/src/components/traffic/TrafficFlowListContext.tsx +12 -4
  285. package/src/components/traffic/TrafficFocusSearch.tsx +172 -0
  286. package/src/components/traffic/TrafficGraph.tsx +109 -26
  287. package/src/components/traffic/TrafficGraphTooLarge.tsx +150 -0
  288. package/src/components/traffic/TrafficView.tsx +582 -149
  289. package/src/components/traffic/trafficFilters.test.ts +398 -1
  290. package/src/components/traffic/trafficFilters.ts +408 -0
  291. package/src/components/ui/DiagnosticsOverlay.tsx +34 -2
  292. package/src/components/ui/Markdown.test.tsx +65 -0
  293. package/src/components/ui/Markdown.tsx +144 -127
  294. package/src/components/whats-new/releaseNotes.ts +63 -1
  295. package/src/components/workload/WorkloadView.test.ts +11 -1
  296. package/src/components/workload/WorkloadView.tsx +162 -529
  297. package/src/components/workspace/layout.test.tsx +29 -0
  298. package/src/components/workspace/layout.tsx +259 -0
  299. package/src/components/workspace/table.ts +7 -0
  300. package/src/context/TimelineCustomization.tsx +15 -0
  301. package/src/contexts/CapabilitiesContext.tsx +1 -1
  302. package/src/hooks/useGitOpsWriteGuard.test.tsx +457 -0
  303. package/src/hooks/useGitOpsWriteGuard.ts +193 -0
  304. package/src/hooks/useResolvedGitOpsOwner.ts +256 -0
  305. package/src/integrations/resourceHost.test.tsx +55 -0
  306. package/src/integrations/resourceHost.ts +99 -0
  307. package/src/integrations/resourceHosts.test.tsx +141 -0
  308. package/src/integrations/resourceHosts.tsx +119 -0
  309. package/src/integrations/workspaceRoute.ts +20 -0
  310. package/src/integrations/workspaceRoutes.test.ts +40 -0
  311. package/src/integrations/workspaceRoutes.ts +22 -0
  312. package/src/integrations/workspaceScreens.tsx +14 -0
  313. package/src/utils/drawer-trail.ts +35 -0
  314. package/src/utils/kindFilterStore.test.ts +147 -0
  315. package/src/utils/kindFilterStore.ts +91 -0
  316. package/src/utils/navigation.test.ts +13 -1
  317. package/src/utils/navigation.ts +39 -2
  318. package/src/utils/page-links.ts +21 -0
  319. package/src/utils/tabStorage.ts +40 -0
  320. package/src/utils/viewMemory.test.ts +78 -0
  321. package/src/utils/viewMemory.ts +54 -0
@@ -0,0 +1,301 @@
1
+ // @vitest-environment jsdom
2
+ import { act, useState, type ReactNode } from 'react'
3
+ import { createRoot, type Root } from 'react-dom/client'
4
+ import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
5
+ import type { HelmValues } from '../../types'
6
+ import { ValuesViewer } from './ValuesViewer'
7
+
8
+ const mutations = vi.hoisted(() => ({
9
+ apply: vi.fn(async () => undefined),
10
+ preview: vi.fn(async () => undefined),
11
+ }))
12
+
13
+ const overridesQuery = vi.hoisted(() => ({
14
+ current: {} as { data?: { userSupplied: Record<string, unknown> | null }; error: unknown; isFetching: boolean; refetch?: () => void },
15
+ refetch: vi.fn(),
16
+ }))
17
+
18
+ vi.mock('@skyhook-io/k8s-ui', () => ({
19
+ PaneLoader: ({ label }: { label: string }) => <div>{label}</div>,
20
+ }))
21
+ vi.mock('../../api/client', () => ({
22
+ useCanHelmAct: () => ({ allowed: true }),
23
+ useHelmValues: () => ({ refetch: overridesQuery.refetch, ...overridesQuery.current }),
24
+ useHelmApplyValues: () => ({
25
+ mutateAsync: mutations.apply,
26
+ isPending: false,
27
+ error: null,
28
+ }),
29
+ useHelmPreviewValues: () => ({
30
+ mutateAsync: mutations.preview,
31
+ isPending: false,
32
+ error: null,
33
+ }),
34
+ }))
35
+ vi.mock('../ui/CodeViewer', () => ({
36
+ CodeViewer: ({ code }: { code: string }) => (
37
+ <pre data-testid="values-code">{code}</pre>
38
+ ),
39
+ }))
40
+ vi.mock('../ui/YamlEditor', () => ({
41
+ YamlEditor: ({ value }: { value: string }) => (
42
+ <textarea aria-label="User Overrides YAML" value={value} readOnly />
43
+ ),
44
+ }))
45
+ vi.mock('./ValuesDiffPreview', () => ({ ValuesDiffPreview: () => null }))
46
+ vi.mock('../ui/Tooltip', () => ({
47
+ Tooltip: ({ children }: { children: ReactNode }) => children,
48
+ }))
49
+
50
+ const values: HelmValues = {
51
+ userSupplied: { image: { tag: 'pinned' } },
52
+ computed: {
53
+ image: { repository: 'example/app', tag: 'pinned' },
54
+ replicaCount: 2,
55
+ },
56
+ }
57
+
58
+ Object.assign(globalThis, { IS_REACT_ACT_ENVIRONMENT: true })
59
+
60
+ let root: Root
61
+
62
+ beforeEach(() => {
63
+ overridesQuery.current = {
64
+ data: { userSupplied: { image: { tag: 'pinned' } } },
65
+ error: null,
66
+ isFetching: false,
67
+ }
68
+ mutations.apply.mockClear()
69
+ mutations.preview.mockClear()
70
+ const element = document.createElement('div')
71
+ document.body.appendChild(element)
72
+ root = createRoot(element)
73
+ })
74
+
75
+ afterEach(async () => {
76
+ await act(async () => root.unmount())
77
+ document.body.replaceChildren()
78
+ })
79
+
80
+ function Harness({
81
+ helmValues = values,
82
+ error,
83
+ onRetry,
84
+ }: {
85
+ helmValues?: HelmValues | null
86
+ error?: unknown
87
+ onRetry?: () => void
88
+ }) {
89
+ const [showEffectiveValues, setShowEffectiveValues] = useState(true)
90
+ return (
91
+ <ValuesViewer
92
+ values={helmValues ?? undefined}
93
+ isLoading={false}
94
+ error={error}
95
+ showEffectiveValues={showEffectiveValues}
96
+ onToggleEffectiveValues={setShowEffectiveValues}
97
+ onCopy={() => undefined}
98
+ copied={false}
99
+ namespace="demo"
100
+ name="example"
101
+ onRetry={onRetry}
102
+ />
103
+ )
104
+ }
105
+
106
+ async function renderViewer(node: ReactNode = <Harness />) {
107
+ await act(async () => root.render(node))
108
+ }
109
+
110
+ function button(label: string) {
111
+ return Array.from(document.querySelectorAll('button')).find(
112
+ (element) => element.textContent?.trim() === label,
113
+ )
114
+ }
115
+
116
+ describe('ValuesViewer', () => {
117
+ it('shows read-only effective values by default and switches to user overrides', async () => {
118
+ await renderViewer()
119
+
120
+ expect(document.body.textContent).toContain('Effective Values')
121
+ expect(
122
+ document.querySelector('[data-testid="values-code"]')?.textContent,
123
+ ).toContain('replicaCount: 2')
124
+ expect(document.querySelector('textarea')).toBeNull()
125
+
126
+ await act(async () => button('User Overrides')!.click())
127
+
128
+ expect(document.body.textContent).toContain('User Overrides')
129
+ expect(
130
+ document.querySelector('[data-testid="values-code"]')?.textContent,
131
+ ).toContain('tag: pinned')
132
+ expect(
133
+ document.querySelector('[data-testid="values-code"]')?.textContent,
134
+ ).not.toContain('replicaCount')
135
+ })
136
+
137
+ it('labels editing explicitly and applies only user overrides', async () => {
138
+ await renderViewer()
139
+
140
+ await act(async () => button('Edit Overrides')!.click())
141
+
142
+ expect(document.body.textContent).toContain('Editing User Overrides')
143
+ expect(
144
+ (document.querySelector('textarea') as HTMLTextAreaElement).value,
145
+ ).toContain('tag: pinned')
146
+ expect(
147
+ (document.querySelector('textarea') as HTMLTextAreaElement).value,
148
+ ).not.toContain('replicaCount')
149
+
150
+ await act(async () => button('Apply')!.click())
151
+
152
+ expect(mutations.apply).toHaveBeenCalledWith({
153
+ namespace: 'demo',
154
+ name: 'example',
155
+ values: { image: { tag: 'pinned' } },
156
+ })
157
+ })
158
+
159
+ it('keeps user overrides and editing usable when only effective values fail to load', async () => {
160
+ const onRetry = vi.fn()
161
+ await renderViewer(<Harness helmValues={null} error={new Error('forbidden')} onRetry={onRetry} />)
162
+
163
+ expect(document.body.textContent).toContain("Couldn't load effective values.")
164
+ expect(document.querySelector('[data-testid="values-code"]')).toBeNull()
165
+ await act(async () => button('Try again')!.click())
166
+ expect(onRetry).toHaveBeenCalledTimes(1)
167
+
168
+ await act(async () => button('User Overrides')!.click())
169
+ expect(document.body.textContent).not.toContain("Couldn't load effective values")
170
+ expect(document.querySelector('[data-testid="values-code"]')?.textContent).toContain('tag: pinned')
171
+
172
+ await act(async () => button('Edit Overrides')!.click())
173
+ expect((document.querySelector('textarea') as HTMLTextAreaElement).value).toContain('tag: pinned')
174
+ await act(async () => button('Apply')!.click())
175
+ expect(mutations.apply).toHaveBeenCalledWith({ namespace: 'demo', name: 'example', values: { image: { tag: 'pinned' } } })
176
+ })
177
+
178
+ it('blocks editing when both reads fail, and says so in each view', async () => {
179
+ overridesQuery.current = { data: undefined, error: new Error('forbidden'), isFetching: false }
180
+ await renderViewer(<Harness helmValues={null} error={new Error('forbidden')} />)
181
+
182
+ expect(document.body.textContent).toContain("Couldn't load effective values.")
183
+ expect(document.body.textContent).toContain("Couldn't load your overrides, so editing is off.")
184
+ expect((button('Edit Overrides') as HTMLButtonElement).disabled).toBe(true)
185
+
186
+ await act(async () => button('User Overrides')!.click())
187
+ expect(document.body.textContent).toContain("Couldn't load your overrides. Editing is off until they load.")
188
+ expect(document.body.textContent).not.toContain('Showing the last ones that loaded')
189
+ expect((button('Edit Overrides') as HTMLButtonElement).disabled).toBe(true)
190
+ expect(document.querySelector('textarea')).toBeNull()
191
+ expect(mutations.apply).not.toHaveBeenCalled()
192
+ })
193
+
194
+ it('keeps showing loaded values when a later refetch fails, but blocks editing', async () => {
195
+ overridesQuery.current = { ...overridesQuery.current, error: new Error('refetch failed') }
196
+ await renderViewer(<Harness error={new Error('refetch failed')} />)
197
+
198
+ expect(document.body.textContent).toContain("Couldn't refresh effective values. Showing the last ones that loaded.")
199
+ expect(document.body.textContent).toContain("Couldn't refresh your overrides, so editing is off.")
200
+ expect(
201
+ document.querySelector('[data-testid="values-code"]')?.textContent,
202
+ ).toContain('replicaCount: 2')
203
+ expect((button('Edit Overrides') as HTMLButtonElement).disabled).toBe(true)
204
+
205
+ await act(async () => button('User Overrides')!.click())
206
+ expect(document.body.textContent).toContain("Couldn't refresh your overrides. Showing the last ones that loaded. Editing is off until they load again.")
207
+ expect(document.querySelector('[data-testid="values-code"]')?.textContent).toContain('tag: pinned')
208
+ })
209
+
210
+ it('seeds the editor from the overrides-only read, not the all-values response', async () => {
211
+ // An older Radar can answer all=true with empty overrides when its second read fails.
212
+ await renderViewer(<Harness helmValues={{ ...values, userSupplied: {} }} />)
213
+
214
+ await act(async () => button('Edit Overrides')!.click())
215
+
216
+ expect((document.querySelector('textarea') as HTMLTextAreaElement).value).toContain('tag: pinned')
217
+ })
218
+
219
+ it('shows overrides from the overrides-only read', async () => {
220
+ await renderViewer(<Harness helmValues={{ ...values, userSupplied: {} }} />)
221
+
222
+ await act(async () => button('User Overrides')!.click())
223
+
224
+ expect(document.querySelector('[data-testid="values-code"]')?.textContent).toContain('tag: pinned')
225
+ })
226
+
227
+ it('lets an older Radar edit a release with no overrides (null userSupplied)', async () => {
228
+ overridesQuery.current = { ...overridesQuery.current, data: { userSupplied: null } }
229
+ await renderViewer()
230
+
231
+ await act(async () => button('Edit Overrides')!.click())
232
+
233
+ expect((document.querySelector('textarea') as HTMLTextAreaElement).value).toBe('')
234
+ })
235
+
236
+ it('explains a failed refresh in the empty state', async () => {
237
+ overridesQuery.current = { ...overridesQuery.current, data: { userSupplied: {} }, error: new Error('refetch failed') }
238
+ await renderViewer(<Harness helmValues={{ ...values, userSupplied: {} }} />)
239
+
240
+ await act(async () => button('User Overrides')!.click())
241
+
242
+ expect(document.body.textContent).toContain('No user overrides')
243
+ expect(document.body.textContent).toContain("Couldn't refresh your overrides. Showing the last ones that loaded.")
244
+ })
245
+
246
+ it('never shows the all-values copy of overrides when the overrides-only read failed', async () => {
247
+ overridesQuery.current = { data: undefined, error: new Error('forbidden'), isFetching: false }
248
+ await renderViewer(<Harness helmValues={{ ...values, userSupplied: {} }} />)
249
+
250
+ await act(async () => button('User Overrides')!.click())
251
+
252
+ expect(document.body.textContent).toContain("Couldn't load your overrides. Editing is off until they load.")
253
+ expect(document.body.textContent).not.toContain('No user overrides')
254
+ expect(document.body.textContent).not.toContain('Showing the last ones that loaded')
255
+ expect((button('Edit Overrides') as HTMLButtonElement).disabled).toBe(true)
256
+
257
+ overridesQuery.refetch.mockClear()
258
+ await act(async () => button('Try again')!.click())
259
+ expect(overridesQuery.refetch).toHaveBeenCalledTimes(1)
260
+ })
261
+
262
+ it('keeps successfully loaded empty overrides editable', async () => {
263
+ overridesQuery.current = { ...overridesQuery.current, data: { userSupplied: {} } }
264
+ await renderViewer(
265
+ <Harness helmValues={{ ...values, userSupplied: {} }} />,
266
+ )
267
+
268
+ await act(async () => button('Edit Overrides')!.click())
269
+
270
+ expect((document.querySelector('textarea') as HTMLTextAreaElement).value).toBe('')
271
+ await act(async () => button('Apply')!.click())
272
+ expect(mutations.apply).toHaveBeenCalledWith({
273
+ namespace: 'demo',
274
+ name: 'example',
275
+ values: {},
276
+ })
277
+ })
278
+
279
+ it('keeps the edit and blocks apply while overrides reload or fail to reload', async () => {
280
+ await renderViewer()
281
+ await act(async () => button('Edit Overrides')!.click())
282
+
283
+ overridesQuery.current = { ...overridesQuery.current, isFetching: true }
284
+ await renderViewer()
285
+ expect(document.body.textContent).toContain('Editing User Overrides')
286
+ expect(document.body.textContent).toContain('Reloading user overrides. Your edits are kept.')
287
+ expect(document.querySelector('textarea')).not.toBeNull()
288
+ expect((button('Apply') as HTMLButtonElement).disabled).toBe(true)
289
+
290
+ overridesQuery.current = { ...overridesQuery.current, isFetching: false, error: new Error('refetch failed') }
291
+ await renderViewer()
292
+ expect(document.body.textContent).toContain("Couldn't reload user overrides, so Apply is off.")
293
+ expect(document.querySelector('textarea')).not.toBeNull()
294
+ expect((button('Apply') as HTMLButtonElement).disabled).toBe(true)
295
+
296
+ overridesQuery.refetch.mockClear()
297
+ await act(async () => button('Try again')!.click())
298
+ expect(overridesQuery.refetch).toHaveBeenCalledTimes(1)
299
+ expect(document.querySelector('textarea')).not.toBeNull()
300
+ })
301
+ })