@skyhook-io/k8s-ui 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 (309) hide show
  1. package/README.md +41 -0
  2. package/package.json +3 -3
  3. package/src/components/applications/ApplicationDetail.test.tsx +15 -0
  4. package/src/components/applications/ApplicationDetail.tsx +17 -15
  5. package/src/components/charts/AreaChart.interaction.test.tsx +118 -0
  6. package/src/components/charts/AreaChart.test.tsx +25 -0
  7. package/src/components/charts/AreaChart.tsx +141 -12
  8. package/src/components/charts/index.ts +1 -0
  9. package/src/components/checks/ChecksView.tsx +18 -1
  10. package/src/components/checks/types.ts +2 -13
  11. package/src/components/cnpg/CNPGBackupSummary.tsx +57 -9
  12. package/src/components/cnpg/CNPGClusterHASection.test.tsx +90 -0
  13. package/src/components/cnpg/CNPGClusterHASection.tsx +331 -0
  14. package/src/components/cnpg/CNPGClusterSummary.test.tsx +275 -0
  15. package/src/components/cnpg/CNPGClusterSummary.tsx +310 -96
  16. package/src/components/cnpg/CNPGConnectSection.test.tsx +64 -0
  17. package/src/components/cnpg/CNPGConnectSection.tsx +178 -0
  18. package/src/components/cnpg/CNPGDeclarativeSummary.tsx +157 -26
  19. package/src/components/cnpg/CNPGImageCatalogSummary.tsx +7 -4
  20. package/src/components/cnpg/CNPGLogicalPath.test.tsx +22 -0
  21. package/src/components/cnpg/CNPGLogicalPath.tsx +150 -0
  22. package/src/components/cnpg/CNPGObjectStoreSummary.tsx +9 -6
  23. package/src/components/cnpg/CNPGObjectSummary.test.tsx +193 -2
  24. package/src/components/cnpg/CNPGPoolerScheduling.test.tsx +36 -0
  25. package/src/components/cnpg/CNPGPoolerSummary.tsx +241 -14
  26. package/src/components/cnpg/CNPGSharedSummary.tsx +15 -8
  27. package/src/components/cnpg/CNPGWALArchivingFact.test.tsx +39 -0
  28. package/src/components/cnpg/backupRuns.test.ts +17 -0
  29. package/src/components/cnpg/backupRuns.ts +19 -0
  30. package/src/components/cnpg/connect.test.ts +124 -0
  31. package/src/components/cnpg/connect.ts +155 -0
  32. package/src/components/cnpg/databaseRole.test.ts +38 -0
  33. package/src/components/cnpg/databaseRole.ts +80 -0
  34. package/src/components/cnpg/ha.test.ts +446 -0
  35. package/src/components/cnpg/ha.ts +621 -0
  36. package/src/components/cnpg/index.ts +18 -0
  37. package/src/components/cnpg/logicalReplication.test.ts +121 -0
  38. package/src/components/cnpg/logicalReplication.ts +252 -0
  39. package/src/components/cnpg/pooler.test.ts +84 -0
  40. package/src/components/cnpg/pooler.ts +225 -0
  41. package/src/components/cnpg/primitives.tsx +21 -117
  42. package/src/components/cnpg/problems.ts +12 -0
  43. package/src/components/cnpg/relations.test.ts +63 -13
  44. package/src/components/cnpg/relations.ts +61 -52
  45. package/src/components/cnpg/schedule.ts +35 -0
  46. package/src/components/cnpg/workspace-disk.test.ts +97 -0
  47. package/src/components/cnpg/workspace-fleet-metrics.test.ts +250 -0
  48. package/src/components/cnpg/workspace-problems.test.ts +175 -0
  49. package/src/components/cnpg/workspace.test.ts +380 -9
  50. package/src/components/cnpg/workspace.ts +1137 -136
  51. package/src/components/dock/DockContext.test.tsx +32 -0
  52. package/src/components/dock/DockContext.tsx +18 -4
  53. package/src/components/dock/LocalTerminalTab.test.tsx +40 -0
  54. package/src/components/dock/LocalTerminalTab.tsx +4 -3
  55. package/src/components/dock/TerminalTab.tsx +20 -8
  56. package/src/components/facts/certainty.tsx +36 -0
  57. package/src/components/facts/facts.test.tsx +24 -0
  58. package/src/components/facts/facts.tsx +62 -0
  59. package/src/components/facts/index.ts +6 -0
  60. package/src/components/facts/managed-by.test.tsx +17 -0
  61. package/src/components/facts/managed-by.tsx +36 -0
  62. package/src/components/facts/public-exports.test.tsx +35 -0
  63. package/src/components/gitops/GitOpsGraphFilterRail.tsx +6 -2
  64. package/src/components/gitops/GitOpsHealthSourceNotice.test.tsx +38 -0
  65. package/src/components/gitops/GitOpsHealthSourceNotice.tsx +13 -3
  66. package/src/components/gitops/GitOpsTableView.tsx +9 -24
  67. package/src/components/gitops/detail-helpers.ts +1 -1
  68. package/src/components/gitops/insights/ArgoResourceDiff.test.tsx +115 -0
  69. package/src/components/gitops/insights/ArgoResourceDiff.tsx +22 -5
  70. package/src/components/gitops/insights/DriftPanel.tsx +62 -0
  71. package/src/components/gitops/insights/GitOpsInsightViews.tsx +29 -83
  72. package/src/components/gitops/tree/GitOpsTreeGraph.test.ts +26 -1
  73. package/src/components/gitops/tree/GitOpsTreeGraph.tsx +40 -60
  74. package/src/components/gitops/tree/layout.test.ts +111 -0
  75. package/src/components/gitops/tree/layout.ts +128 -0
  76. package/src/components/gitops/tree/tree-helpers.test.ts +17 -0
  77. package/src/components/gitops/tree/tree-helpers.ts +7 -1
  78. package/src/components/issues/ConditionalCleanup.test.tsx +82 -0
  79. package/src/components/issues/IssuesView.tsx +40 -5
  80. package/src/components/issues/ResourceIssuesSection.test.tsx +28 -0
  81. package/src/components/issues/ResourceIssuesSection.tsx +3 -0
  82. package/src/components/issues/index.ts +3 -1
  83. package/src/components/issues/issue-timing.ts +14 -3
  84. package/src/components/issues/issues.test.ts +50 -2
  85. package/src/components/issues/severity.ts +21 -0
  86. package/src/components/issues/types.ts +23 -0
  87. package/src/components/logs/LogCore.theme.test.tsx +81 -3
  88. package/src/components/logs/LogCore.tsx +25 -17
  89. package/src/components/logs/LogToolbarSelects.tsx +3 -1
  90. package/src/components/logs/LogsViewer.test.tsx +291 -0
  91. package/src/components/logs/LogsViewer.tsx +72 -11
  92. package/src/components/logs/StructuredLogLine.test.tsx +76 -0
  93. package/src/components/logs/StructuredLogLine.tsx +22 -30
  94. package/src/components/logs/WorkloadLogsViewer.notice.test.tsx +59 -0
  95. package/src/components/logs/WorkloadLogsViewer.test.tsx +65 -0
  96. package/src/components/logs/WorkloadLogsViewer.tsx +44 -10
  97. package/src/components/logs/useLogBuffer.test.ts +8 -0
  98. package/src/components/logs/useLogStream.ts +28 -1
  99. package/src/components/problems/index.ts +1 -0
  100. package/src/components/problems/problems.test.tsx +63 -0
  101. package/src/components/problems/problems.tsx +165 -0
  102. package/src/components/resources/ActiveFilterBar.test.tsx +78 -0
  103. package/src/components/resources/ActiveFilterBar.tsx +86 -0
  104. package/src/components/resources/ResourcesSidebar.keyboard.test.tsx +42 -0
  105. package/src/components/resources/ResourcesSidebar.test.tsx +36 -2
  106. package/src/components/resources/ResourcesSidebar.tsx +92 -41
  107. package/src/components/resources/ResourcesView.kind-filter-memory.test.ts +52 -0
  108. package/src/components/resources/ResourcesView.kind-view.test.tsx +149 -0
  109. package/src/components/resources/ResourcesView.per-container.test.ts +56 -1
  110. package/src/components/resources/ResourcesView.tsx +309 -117
  111. package/src/components/resources/curated-column-ownership.test.ts +3 -3
  112. package/src/components/resources/renderers/AnalysisRunRenderer.tsx +3 -3
  113. package/src/components/resources/renderers/ArgoApplicationRenderer.tsx +6 -5
  114. package/src/components/resources/renderers/AutoscalingReferences.test.tsx +286 -0
  115. package/src/components/resources/renderers/CAPIClusterRenderer.tsx +5 -4
  116. package/src/components/resources/renderers/CAPIExactReferenceNavigation.test.tsx +27 -0
  117. package/src/components/resources/renderers/CAPIKubeadmControlPlaneRenderer.tsx +2 -2
  118. package/src/components/resources/renderers/CAPIMachineDeploymentRenderer.tsx +2 -2
  119. package/src/components/resources/renderers/CAPIMachinePoolRenderer.tsx +2 -2
  120. package/src/components/resources/renderers/CAPIMachineRenderer.tsx +2 -2
  121. package/src/components/resources/renderers/CAPIMachineSetRenderer.tsx +2 -2
  122. package/src/components/resources/renderers/CNPGBackupRenderer.test.tsx +23 -0
  123. package/src/components/resources/renderers/CNPGBackupRenderer.tsx +8 -19
  124. package/src/components/resources/renderers/CNPGClusterRenderer.tsx +7 -8
  125. package/src/components/resources/renderers/CNPGDeclarativeRenderer.tsx +25 -0
  126. package/src/components/resources/renderers/CNPGScheduledBackupRenderer.test.tsx +32 -0
  127. package/src/components/resources/renderers/CNPGScheduledBackupRenderer.tsx +9 -17
  128. package/src/components/resources/renderers/CNPGShared.tsx +2 -2
  129. package/src/components/resources/renderers/ClusterComplianceReportRenderer.tsx +2 -2
  130. package/src/components/resources/renderers/CompositeRenderer.navigation.test.tsx +35 -0
  131. package/src/components/resources/renderers/CompositeRenderer.tsx +3 -3
  132. package/src/components/resources/renderers/CompositionRenderer.tsx +3 -3
  133. package/src/components/resources/renderers/ConfigAuditReportRenderer.tsx +2 -2
  134. package/src/components/resources/renderers/ContourExactReferenceNavigation.test.tsx +34 -0
  135. package/src/components/resources/renderers/ContourHTTPProxyRenderer.tsx +11 -10
  136. package/src/components/resources/renderers/CronJobRenderer.tsx +6 -4
  137. package/src/components/resources/renderers/CronWorkflowRenderer.tsx +3 -2
  138. package/src/components/resources/renderers/EndpointSliceRenderer.test.tsx +62 -0
  139. package/src/components/resources/renderers/EndpointSliceRenderer.tsx +15 -14
  140. package/src/components/resources/renderers/EventRenderer.tsx +3 -3
  141. package/src/components/resources/renderers/ExperimentRenderer.tsx +2 -2
  142. package/src/components/resources/renderers/ExposedSecretReportRenderer.tsx +2 -2
  143. package/src/components/resources/renderers/ExternalSecretRenderer.tsx +2 -2
  144. package/src/components/resources/renderers/FluxHelmReleaseRenderer.tsx +4 -4
  145. package/src/components/resources/renderers/GRPCRouteRenderer.tsx +11 -24
  146. package/src/components/resources/renderers/GatewayRouteReferences.test.tsx +63 -0
  147. package/src/components/resources/renderers/GitRepositoryRenderer.tsx +3 -3
  148. package/src/components/resources/renderers/HPARenderer.tsx +57 -17
  149. package/src/components/resources/renderers/HTTPRouteRenderer.tsx +12 -25
  150. package/src/components/resources/renderers/HelmRepositoryRenderer.tsx +3 -3
  151. package/src/components/resources/renderers/IngressClassReferences.test.tsx +52 -0
  152. package/src/components/resources/renderers/IngressClassRenderer.tsx +10 -3
  153. package/src/components/resources/renderers/IngressRenderer.defaultBackend.test.tsx +33 -0
  154. package/src/components/resources/renderers/IngressRenderer.tsx +36 -5
  155. package/src/components/resources/renderers/IstioDestinationRuleRenderer.tsx +9 -9
  156. package/src/components/resources/renderers/IstioExactReferenceNavigation.test.tsx +34 -0
  157. package/src/components/resources/renderers/IstioVirtualServiceRenderer.tsx +5 -8
  158. package/src/components/resources/renderers/KarpenterNodeClaimRenderer.tsx +2 -14
  159. package/src/components/resources/renderers/KedaCredentials.test.tsx +85 -0
  160. package/src/components/resources/renderers/KedaScaledObjectRenderer.tsx +15 -13
  161. package/src/components/resources/renderers/KedaTriggerAuthRenderer.tsx +6 -3
  162. package/src/components/resources/renderers/KnativeEventingRenderer.tsx +24 -31
  163. package/src/components/resources/renderers/KnativeExactReferenceNavigation.test.tsx +47 -0
  164. package/src/components/resources/renderers/KnativeFlowRenderer.tsx +6 -7
  165. package/src/components/resources/renderers/KnativeNetworkingRenderer.tsx +7 -9
  166. package/src/components/resources/renderers/KnativeReferenceLink.tsx +14 -0
  167. package/src/components/resources/renderers/KnativeReferenceLinks.test.tsx +60 -0
  168. package/src/components/resources/renderers/KnativeSourceRenderer.tsx +9 -11
  169. package/src/components/resources/renderers/KustomizationRenderer.tsx +2 -2
  170. package/src/components/resources/renderers/KyvernoGlobalContextRenderer.tsx +3 -2
  171. package/src/components/resources/renderers/KyvernoQueueRenderers.tsx +4 -3
  172. package/src/components/resources/renderers/LeaseRenderer.tsx +3 -15
  173. package/src/components/resources/renderers/ManagedResourceRenderer.tsx +2 -2
  174. package/src/components/resources/renderers/MetricsUnavailableNotice.tsx +3 -2
  175. package/src/components/resources/renderers/NamespaceRenderer.test.ts +69 -2
  176. package/src/components/resources/renderers/NamespaceRenderer.tsx +6 -1
  177. package/src/components/resources/renderers/NodeRenderer.test.tsx +81 -1
  178. package/src/components/resources/renderers/NodeRenderer.tsx +56 -15
  179. package/src/components/resources/renderers/OCIRepositoryRenderer.tsx +3 -3
  180. package/src/components/resources/renderers/PodClassReferences.test.tsx +42 -0
  181. package/src/components/resources/renderers/PodNomination.test.tsx +57 -0
  182. package/src/components/resources/renderers/PodRenderer.test.tsx +32 -0
  183. package/src/components/resources/renderers/PodRenderer.tsx +62 -36
  184. package/src/components/resources/renderers/RolloutRenderer.test.tsx +1 -1
  185. package/src/components/resources/renderers/RolloutRenderer.tsx +3 -2
  186. package/src/components/resources/renderers/SbomReportRenderer.tsx +2 -2
  187. package/src/components/resources/renderers/SecretRenderer.test.tsx +88 -0
  188. package/src/components/resources/renderers/SecretRenderer.tsx +34 -5
  189. package/src/components/resources/renderers/ServiceRenderer.test.tsx +43 -0
  190. package/src/components/resources/renderers/ServiceRenderer.tsx +11 -3
  191. package/src/components/resources/renderers/SimpleRouteRenderer.tsx +9 -22
  192. package/src/components/resources/renderers/VPARenderer.tsx +17 -11
  193. package/src/components/resources/renderers/VeleroBSLRenderer.tsx +5 -7
  194. package/src/components/resources/renderers/VeleroBackupRenderer.tsx +3 -3
  195. package/src/components/resources/renderers/VeleroBackupRepositoryRenderer.tsx +2 -2
  196. package/src/components/resources/renderers/VeleroRestoreRenderer.tsx +3 -3
  197. package/src/components/resources/renderers/VeleroScheduleRenderer.tsx +2 -2
  198. package/src/components/resources/renderers/VulnerabilityReportRenderer.tsx +2 -2
  199. package/src/components/resources/renderers/WorkflowRenderer.tsx +4 -3
  200. package/src/components/resources/renderers/kyverno-modern-cells.tsx +1 -1
  201. package/src/components/resources/renderers/rollout/ReplicaSetProgression.tsx +2 -2
  202. package/src/components/resources/resource-utils-capi.ts +1 -1
  203. package/src/components/resources/resource-utils-cnpg.test.ts +35 -10
  204. package/src/components/resources/resource-utils-cnpg.ts +51 -24
  205. package/src/components/resources/resource-utils-contour.test.ts +44 -0
  206. package/src/components/resources/resource-utils-contour.ts +16 -0
  207. package/src/components/resources/resource-utils-istio.ts +12 -8
  208. package/src/components/resources/resource-utils-keda.ts +1 -7
  209. package/src/components/resources/resource-utils-knative.ts +18 -0
  210. package/src/components/resources/resource-utils-kyverno.ts +1 -7
  211. package/src/components/resources/resource-utils-velero.ts +0 -12
  212. package/src/components/resources/resource-utils.ts +99 -39
  213. package/src/components/resources/rules-column-width.test.ts +1 -1
  214. package/src/components/resources/summarize-scheduler-message.test.ts +30 -0
  215. package/src/components/shared/ActionConfirmDialog.test.tsx +154 -0
  216. package/src/components/shared/ActionConfirmDialog.tsx +246 -0
  217. package/src/components/shared/CreateResourceDialog.test.tsx +330 -0
  218. package/src/components/shared/CreateResourceDialog.tsx +126 -80
  219. package/src/components/shared/DetailShell.test.tsx +80 -0
  220. package/src/components/shared/DetailShell.tsx +85 -10
  221. package/src/components/shared/DrainPlanDialog.tsx +1 -0
  222. package/src/components/shared/EditableYamlView.test.tsx +7 -0
  223. package/src/components/shared/EditableYamlView.tsx +1 -1
  224. package/src/components/shared/GitOpsWriteWarning.test.tsx +86 -0
  225. package/src/components/shared/GitOpsWriteWarning.tsx +135 -0
  226. package/src/components/shared/ResourceActionsBar.tsx +20 -24
  227. package/src/components/shared/ResourceRendererDispatch.tsx +13 -10
  228. package/src/components/shared/SetImageDialog.test.ts +75 -6
  229. package/src/components/shared/SetImageDialog.tsx +92 -84
  230. package/src/components/shared/index.ts +4 -1
  231. package/src/components/timeline/TimelineList.tsx +2 -15
  232. package/src/components/timeline/TimelineSwimlanes.tsx +3 -11
  233. package/src/components/timeline/timeline-filters.ts +1 -0
  234. package/src/components/topology/K8sResourceNode.test.ts +18 -1
  235. package/src/components/topology/K8sResourceNode.tsx +44 -2
  236. package/src/components/topology/TopologyFilterSidebar.tsx +1 -0
  237. package/src/components/topology/TopologyGraph.tsx +8 -74
  238. package/src/components/topology/TopologySearch.tsx +1 -0
  239. package/src/components/topology/internet-groups.test.ts +35 -0
  240. package/src/components/topology/internet-groups.ts +55 -0
  241. package/src/components/topology/layout.ts +1 -1
  242. package/src/components/ui/Badge.tsx +1 -0
  243. package/src/components/ui/FoldSection.tsx +68 -0
  244. package/src/components/ui/ForceDeleteConfirmDialog.test.tsx +62 -0
  245. package/src/components/ui/ForceDeleteConfirmDialog.tsx +24 -12
  246. package/src/components/ui/RefLink.tsx +20 -0
  247. package/src/components/ui/RowActionMenu.tsx +8 -3
  248. package/src/components/ui/ScheduleValue.test.tsx +5 -0
  249. package/src/components/ui/ScheduleValue.tsx +3 -2
  250. package/src/components/ui/crd-definition-navigation.test.tsx +160 -0
  251. package/src/components/ui/drawer-components.test.tsx +175 -1
  252. package/src/components/ui/drawer-components.tsx +103 -36
  253. package/src/components/ui/index.ts +3 -1
  254. package/src/components/ui/monacoEditorFeatures.ts +77 -0
  255. package/src/components/ui/monacoRuntime.ts +14 -19
  256. package/src/components/ui/status-tone.test.ts +10 -1
  257. package/src/components/ui/status-tone.tsx +29 -0
  258. package/src/components/workload/WorkloadView.deleted.test.tsx +101 -0
  259. package/src/components/workload/WorkloadView.deletedDialog.test.tsx +45 -0
  260. package/src/components/workload/WorkloadView.relationships.test.tsx +37 -0
  261. package/src/components/workload/WorkloadView.secretSave.test.tsx +62 -0
  262. package/src/components/workload/WorkloadView.spec.test.tsx +149 -0
  263. package/src/components/workload/WorkloadView.tsx +297 -104
  264. package/src/index.ts +14 -1
  265. package/src/monaco-deep.d.ts +19 -12
  266. package/src/theme/components.css +13 -0
  267. package/src/types/capacity.ts +21 -2
  268. package/src/types/core.ts +60 -2
  269. package/src/types/gitops-flux-status.test.ts +32 -1
  270. package/src/types/gitops-insights.ts +19 -2
  271. package/src/types/gitops-tree.ts +10 -0
  272. package/src/types/gitops.ts +26 -6
  273. package/src/utils/api-resources.ts +7 -0
  274. package/src/utils/application-history.ts +1 -0
  275. package/src/utils/cnpg-backup.test.ts +26 -0
  276. package/src/utils/cnpg-backup.ts +70 -0
  277. package/src/utils/format.test.ts +49 -2
  278. package/src/utils/format.ts +9 -1
  279. package/src/utils/gateway-references.test.ts +17 -0
  280. package/src/utils/gateway-references.ts +29 -0
  281. package/src/utils/gitops-owner.ts +3 -2
  282. package/src/utils/gitops-write-guard.test.ts +401 -0
  283. package/src/utils/gitops-write-guard.ts +509 -0
  284. package/src/utils/grant.test.ts +19 -0
  285. package/src/utils/grant.ts +35 -0
  286. package/src/utils/index.ts +6 -0
  287. package/src/utils/ingress-class-references.ts +22 -0
  288. package/src/utils/k8s-errors.ts +7 -7
  289. package/src/utils/log-format.test.ts +41 -0
  290. package/src/utils/log-format.ts +71 -15
  291. package/src/utils/log-level.ts +12 -8
  292. package/src/utils/navigation.test.ts +70 -0
  293. package/src/utils/navigation.ts +91 -8
  294. package/src/utils/node-lifecycle.test.ts +36 -0
  295. package/src/utils/node-lifecycle.ts +77 -0
  296. package/src/utils/object-reference-navigation.test.ts +43 -0
  297. package/src/utils/resource-hierarchy.test.ts +69 -0
  298. package/src/utils/resource-hierarchy.ts +41 -7
  299. package/src/utils/resource-icons.ts +2 -0
  300. package/src/utils/resource-refs.test.ts +23 -0
  301. package/src/utils/resource-refs.ts +30 -0
  302. package/src/utils/skeleton-yaml.test.ts +21 -0
  303. package/src/utils/skeleton-yaml.ts +16 -0
  304. package/src/utils/url-path.test.ts +35 -0
  305. package/src/utils/url-path.ts +21 -0
  306. package/src/utils/validators.test.ts +4 -13
  307. package/src/utils/validators.ts +5 -19
  308. package/src/utils/view-transition.test.ts +35 -1
  309. package/src/utils/view-transition.ts +16 -1
@@ -0,0 +1,55 @@
1
+ import type { GroupingMode, TopologyEdge, TopologyNode } from '../../types'
2
+ import { getGroupKey } from './layout'
3
+
4
+ /**
5
+ * Splits the traffic view's single Internet node into one per group, so each
6
+ * namespace or app shows its own entry from outside. Every original Internet
7
+ * edge is kept, whatever kind its entrypoint is (Ingress, Gateway, Istio
8
+ * Gateway, Knative Service, Traefik route, Contour HTTPProxy); an entrypoint
9
+ * with no group stays connected to the original Internet node.
10
+ */
11
+ export function splitInternetByGroup(
12
+ nodes: TopologyNode[],
13
+ edges: TopologyEdge[],
14
+ groupMode: GroupingMode,
15
+ ): { nodes: TopologyNode[]; edges: TopologyEdge[] } {
16
+ if (groupMode === 'none') return { nodes, edges }
17
+ const internetNode = nodes.find(n => n.kind === 'Internet')
18
+ if (!internetNode) return { nodes, edges }
19
+
20
+ const nodeById = new Map(nodes.map(n => [n.id, n]))
21
+ const groups = new Map<string, { entry: TopologyNode; edges: TopologyEdge[] }>()
22
+ const regrouped = new Set<TopologyEdge>()
23
+ for (const edge of edges) {
24
+ if (edge.source !== internetNode.id) continue
25
+ const target = nodeById.get(edge.target)
26
+ const groupKey = target ? getGroupKey(target, groupMode) : null
27
+ if (!target || !groupKey) continue
28
+ if (!groups.has(groupKey)) groups.set(groupKey, { entry: target, edges: [] })
29
+ groups.get(groupKey)!.edges.push(edge)
30
+ regrouped.add(edge)
31
+ }
32
+ if (groups.size === 0) return { nodes, edges }
33
+
34
+ const keepOriginal = edges.some(e => e.source === internetNode.id && !regrouped.has(e))
35
+ const newNodes = keepOriginal ? [...nodes] : nodes.filter(n => n.id !== internetNode.id)
36
+ const newEdges = edges.filter(e => !regrouped.has(e))
37
+ for (const [groupKey, group] of groups) {
38
+ const internetId = `internet-${groupMode}-${groupKey}`
39
+ newNodes.push({
40
+ id: internetId,
41
+ kind: 'Internet',
42
+ name: 'Internet',
43
+ status: 'healthy',
44
+ data: {
45
+ // Group metadata so the node lands inside its entrypoints' group.
46
+ namespace: groupMode === 'namespace' ? groupKey : group.entry.data?.namespace,
47
+ labels: groupMode === 'app' ? { 'app.kubernetes.io/name': groupKey } : {},
48
+ },
49
+ })
50
+ for (const edge of group.edges) {
51
+ newEdges.push({ ...edge, id: `${internetId}-to-${edge.target}`, source: internetId })
52
+ }
53
+ }
54
+ return { nodes: newNodes, edges: newEdges }
55
+ }
@@ -864,7 +864,7 @@ function computeWorkloadCards(
864
864
  const KIND_PRIORITY: Record<string, number> = {
865
865
  'Deployment': 1, 'Rollout': 1, 'StatefulSet': 2, 'DaemonSet': 3,
866
866
  'CronJob': 4, 'CronWorkflow': 4, 'Job': 5, 'Workflow': 5, 'Service': 6, 'Gateway': 7,
867
- 'HTTPRoute': 6, 'GRPCRoute': 6, 'TCPRoute': 6, 'TLSRoute': 6, 'Ingress': 7,
867
+ 'HTTPRoute': 6, 'GRPCRoute': 6, 'TCPRoute': 6, 'TLSRoute': 6, 'UDPRoute': 6, 'Ingress': 7,
868
868
  'CalicoNetworkPolicy': 10, 'CalicoGlobalNetworkPolicy': 10,
869
869
  'CalicoStagedNetworkPolicy': 10, 'CalicoStagedGlobalNetworkPolicy': 10,
870
870
  'CalicoStagedKubernetesNetworkPolicy': 10,
@@ -85,6 +85,7 @@ const KIND: Record<string, string> = {
85
85
  HTTPRoute: 'bg-purple-100 text-purple-700 border-purple-300 dark:bg-purple-950/50 dark:text-purple-400 dark:border-purple-700/40',
86
86
  GRPCRoute: 'bg-purple-100 text-purple-700 border-purple-300 dark:bg-purple-950/50 dark:text-purple-400 dark:border-purple-700/40',
87
87
  TCPRoute: 'bg-purple-100 text-purple-700 border-purple-300 dark:bg-purple-950/50 dark:text-purple-400 dark:border-purple-700/40',
88
+ UDPRoute: 'bg-purple-100 text-purple-700 border-purple-300 dark:bg-purple-950/50 dark:text-purple-400 dark:border-purple-700/40',
88
89
  TLSRoute: 'bg-purple-100 text-purple-700 border-purple-300 dark:bg-purple-950/50 dark:text-purple-400 dark:border-purple-700/40',
89
90
 
90
91
  // Config
@@ -0,0 +1,68 @@
1
+ import { useEffect, useState, type ReactNode } from 'react'
2
+ import { clsx } from 'clsx'
3
+ import { toneTextClass } from './status-tone'
4
+ import { Collapse, CollapseChevron, useDisclosure } from './Collapse'
5
+
6
+ /** A folded section's one-line summary, and whether it opens on its own. */
7
+ export interface FoldSummary {
8
+ text: string
9
+ /** Something in the section needs a look: it opens itself. */
10
+ attention: boolean
11
+ }
12
+
13
+ export function SectionHeading({ children, hint }: { children: ReactNode; hint?: ReactNode }) {
14
+ return (
15
+ <div className="mb-2 mt-5 flex items-baseline gap-2 first:mt-0">
16
+ <h3 className="text-[11px] font-semibold uppercase tracking-wide text-theme-text-tertiary">{children}</h3>
17
+ {hint && <span className="text-[11px] text-theme-text-tertiary">{hint}</span>}
18
+ </div>
19
+ )
20
+ }
21
+
22
+ /**
23
+ * A section folded to one summary line. It opens itself when `attention`
24
+ * turns true (data arriving after the first render included), and stays as
25
+ * the reader left it otherwise.
26
+ */
27
+ export function FoldSection({
28
+ title,
29
+ hint,
30
+ summary,
31
+ attention,
32
+ children,
33
+ }: {
34
+ title: ReactNode
35
+ hint?: ReactNode
36
+ summary: ReactNode
37
+ attention: boolean
38
+ children: ReactNode
39
+ }) {
40
+ const [open, setOpen] = useState(attention)
41
+ useEffect(() => {
42
+ if (attention) setOpen(true)
43
+ }, [attention])
44
+ const d = useDisclosure(open)
45
+ return (
46
+ <div className="mt-5 first:mt-0">
47
+ <h3>
48
+ <button
49
+ type="button"
50
+ {...d.buttonProps}
51
+ onClick={() => setOpen((v) => !v)}
52
+ className="flex w-full items-start gap-2 rounded text-left focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-accent"
53
+ >
54
+ <CollapseChevron open={open} className="mt-1 h-3 w-3 shrink-0 text-theme-text-tertiary" />
55
+ {/* Wrapped parts line up under the title, not under the chevron. */}
56
+ <span className="flex min-w-0 flex-wrap items-baseline gap-x-2 gap-y-0.5">
57
+ <span className="text-[11px] font-semibold uppercase tracking-wide text-theme-text-tertiary">{title}</span>
58
+ {hint && <span className="text-[11px] text-theme-text-tertiary">{hint}</span>}
59
+ {!open && <span className={clsx('min-w-0 text-sm', attention ? toneTextClass('degraded') : 'text-theme-text-secondary')}>{summary}</span>}
60
+ </span>
61
+ </button>
62
+ </h3>
63
+ <Collapse open={open} id={d.panelId}>
64
+ <div className="pt-2">{children}</div>
65
+ </Collapse>
66
+ </div>
67
+ )
68
+ }
@@ -0,0 +1,62 @@
1
+ // @vitest-environment jsdom
2
+ import { act } from 'react'
3
+ import { createRoot, type Root } from 'react-dom/client'
4
+ import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
5
+ import { ForceDeleteConfirmDialog } from './ForceDeleteConfirmDialog'
6
+
7
+ let root: Root
8
+ const base = { open: true, onClose: vi.fn(), onConfirm: vi.fn(), resourceName: 'web', resourceKind: 'Deployment', namespaceName: 'demo', isLoading: false }
9
+ beforeEach(() => {
10
+ Object.assign(globalThis, { IS_REACT_ACT_ENVIRONMENT: true })
11
+ const host = document.createElement('div')
12
+ document.body.appendChild(host)
13
+ root = createRoot(host)
14
+ })
15
+ afterEach(async () => {
16
+ await act(async () => root.unmount())
17
+ document.body.replaceChildren()
18
+ vi.clearAllMocks()
19
+ Object.assign(globalThis, { IS_REACT_ACT_ENVIRONMENT: false })
20
+ })
21
+
22
+ describe('Deletion preview states', () => {
23
+ it('states the namespace-wide deletion consequence without relying on topology', async () => {
24
+ await act(async () => root.render(<ForceDeleteConfirmDialog {...base} resourceKind="Namespace" resourceName="demo" namespaceName="" cascadeRootResolved={false} />))
25
+ expect(document.body.textContent).toContain('delete the namespace "demo" and the resources in it')
26
+ expect(document.body.textContent).not.toContain("not in Radar's topology")
27
+ })
28
+ it('shows one advisory statement for a resolved preview', async () => {
29
+ await act(async () => root.render(<ForceDeleteConfirmDialog {...base} cascadeRootResolved cascadeDependents={[{ kind: 'Pod', namespace: 'demo', name: 'web-pod' }]} />))
30
+ const toggle = [...document.querySelectorAll('button')].find(b => b.textContent?.includes('May affect'))!
31
+ await act(async () => toggle.click())
32
+ expect(document.body.textContent).toContain('web-pod')
33
+ expect(document.body.textContent?.match(/does not predict what will be deleted/g)).toHaveLength(1)
34
+ expect(document.body.textContent).not.toContain('Other resources may be affected')
35
+ })
36
+ it.each([[], undefined])('omits the advisory statement for an empty resolved preview (%j)', async (dependents) => {
37
+ await act(async () => root.render(<ForceDeleteConfirmDialog {...base} cascadeRootResolved cascadeDependents={dependents} />))
38
+ expect(document.body.textContent).not.toContain('May affect')
39
+ expect(document.body.textContent).not.toContain('does not predict')
40
+ expect(document.body.textContent).not.toContain('unavailable')
41
+ expect(document.body.textContent).not.toContain('Other resources may be affected')
42
+ })
43
+ it('distinguishes an observed unresolved root from a fetch failure', async () => {
44
+ await act(async () => root.render(<ForceDeleteConfirmDialog {...base} cascadeRootResolved={false} />))
45
+ expect(document.body.textContent).toContain("This resource is not in Radar's topology")
46
+ await act(async () => root.render(<ForceDeleteConfirmDialog {...base} cascadeRootResolved={false} cascadePreviewFailed cascadeDependents={[{ kind: 'Pod', namespace: 'demo', name: 'stale' }]} />))
47
+ expect(document.body.textContent).toContain('Related-resource preview unavailable.')
48
+ expect(document.body.textContent).not.toContain('This resource is not in')
49
+ expect(document.body.textContent).not.toContain('Other resources may be affected')
50
+ expect(document.body.textContent).not.toContain('May affect')
51
+ const confirm = [...document.querySelectorAll('button')].find(b => b.textContent === 'Delete')!
52
+ expect(confirm.disabled).toBe(false)
53
+ await act(async () => confirm.click())
54
+ expect(base.onConfirm).toHaveBeenCalledWith(false)
55
+ })
56
+ it('shows loading without presenting stale failure or success as a result', async () => {
57
+ await act(async () => root.render(<ForceDeleteConfirmDialog {...base} cascadeLoading cascadePreviewFailed cascadeRootResolved />))
58
+ expect(document.body.textContent).toContain('Checking related resources')
59
+ expect(document.body.textContent).not.toContain('unavailable')
60
+ expect(document.body.textContent).not.toContain('does not predict')
61
+ })
62
+ })
@@ -1,5 +1,5 @@
1
1
  import { useState, useMemo } from 'react'
2
- import { AlertTriangle, Loader2 } from 'lucide-react'
2
+ import { Loader2 } from 'lucide-react'
3
3
  import { Collapse, CollapseChevron, useDisclosure } from './Collapse'
4
4
  import { ConfirmDialog } from './ConfirmDialog'
5
5
  import { pluralize } from '../../utils/pluralize'
@@ -22,6 +22,7 @@ interface ForceDeleteConfirmDialogProps {
22
22
  cascadeDependents?: CascadeDependent[]
23
23
  cascadeLoading?: boolean
24
24
  cascadeRootResolved?: boolean
25
+ cascadePreviewFailed?: boolean
25
26
  }
26
27
 
27
28
  export function ForceDeleteConfirmDialog({
@@ -35,8 +36,10 @@ export function ForceDeleteConfirmDialog({
35
36
  cascadeDependents,
36
37
  cascadeLoading,
37
38
  cascadeRootResolved,
39
+ cascadePreviewFailed,
38
40
  }: ForceDeleteConfirmDialogProps) {
39
41
  const [forceDelete, setForceDelete] = useState(false)
42
+ const isNamespace = resourceKind.toLowerCase() === 'namespace'
40
43
 
41
44
  function handleClose() {
42
45
  onClose()
@@ -54,7 +57,7 @@ export function ForceDeleteConfirmDialog({
54
57
  onConfirm={handleConfirm}
55
58
  title="Delete Resource"
56
59
  message={`Are you sure you want to delete "${resourceName}"?`}
57
- details={`This will permanently delete the ${resourceKind} "${resourceName}" from the "${namespaceName}" namespace.`}
60
+ details={isNamespace ? `This will permanently delete the namespace "${resourceName}" and the resources in it.` : `This will permanently delete the ${resourceKind} "${resourceName}" from the "${namespaceName}" namespace.`}
58
61
  confirmLabel={forceDelete ? 'Force Delete' : 'Delete'}
59
62
  variant="danger"
60
63
  isLoading={isLoading}
@@ -63,21 +66,30 @@ export function ForceDeleteConfirmDialog({
63
66
  {cascadeLoading && (
64
67
  <div className="flex items-center gap-2 text-xs text-theme-text-tertiary">
65
68
  <Loader2 className="w-3.5 h-3.5 animate-spin" />
66
- Checking for dependent resources...
69
+ Checking related resources...
67
70
  </div>
68
71
  )}
69
72
 
70
- {!cascadeLoading && cascadeDependents && cascadeDependents.length > 0 && (
73
+ {!cascadeLoading && !cascadePreviewFailed && cascadeDependents && cascadeDependents.length > 0 && (
71
74
  <CascadeDependentsList dependents={cascadeDependents} />
72
75
  )}
73
76
 
74
- {!cascadeLoading && cascadeRootResolved === false && (
75
- <div className="flex items-start gap-2 rounded border border-theme-border bg-theme-elevated px-3 py-2 text-xs text-warning-text">
76
- <AlertTriangle className="mt-0.5 h-3.5 w-3.5 shrink-0" />
77
- <span>
78
- Radar couldn&apos;t verify which dependent resources Kubernetes will also delete. Additional resources may be deleted but aren&apos;t shown here.
79
- </span>
80
- </div>
77
+ {!cascadeLoading && cascadePreviewFailed && (
78
+ <p className="text-xs text-theme-text-tertiary">
79
+ Related-resource preview unavailable.
80
+ </p>
81
+ )}
82
+
83
+ {!isNamespace && !cascadeLoading && !cascadePreviewFailed && cascadeRootResolved === false && (
84
+ <p className="text-xs text-theme-text-tertiary">
85
+ This resource is not in Radar&apos;s topology, so its related resources cannot be previewed.
86
+ </p>
87
+ )}
88
+
89
+ {!cascadeLoading && !cascadePreviewFailed && cascadeRootResolved === true && cascadeDependents && cascadeDependents.length > 0 && (
90
+ <p className="text-xs text-theme-text-secondary">
91
+ This list shows visible related resources; it does not predict what will be deleted.
92
+ </p>
81
93
  )}
82
94
 
83
95
  <label className="flex items-center gap-2 text-sm text-theme-text-secondary">
@@ -120,7 +132,7 @@ function CascadeDependentsList({ dependents }: { dependents: CascadeDependent[]
120
132
  >
121
133
  <CollapseChevron open={expanded} inheritColor className="w-3.5 h-3.5" />
122
134
  <span>
123
- Will also delete {pluralize(dependents.length, 'dependent resource')}
135
+ May affect {pluralize(dependents.length, 'related resource')}
124
136
  </span>
125
137
  </button>
126
138
 
@@ -0,0 +1,20 @@
1
+ import type { ReactNode } from 'react'
2
+ import { clsx } from 'clsx'
3
+ import type { ResourceRef } from '../../types/core'
4
+
5
+ export type NavigateToRef = (ref: ResourceRef) => void
6
+
7
+ /** A reference to another object: a link when the host can navigate, plain text otherwise. */
8
+ export function RefLink({ refTo, onNavigate, children, mono }: { refTo: ResourceRef; onNavigate?: NavigateToRef; children?: ReactNode; mono?: boolean }) {
9
+ const label = children ?? refTo.name
10
+ if (!onNavigate) return <span className={clsx(mono && 'font-mono')}>{label}</span>
11
+ return (
12
+ <button
13
+ type="button"
14
+ onClick={() => onNavigate(refTo)}
15
+ className={clsx('text-accent-text hover:underline text-left break-all', mono && 'font-mono')}
16
+ >
17
+ {label}
18
+ </button>
19
+ )
20
+ }
@@ -56,14 +56,19 @@ export function RowActionMenu({ items, ariaLabel = 'Row actions', compact = true
56
56
  const onDown = (e: MouseEvent) => {
57
57
  if (ref.current && !ref.current.contains(e.target as Node)) setOpen(false)
58
58
  }
59
+ // Capture phase, consumed: Escape that closes the menu must not also run
60
+ // the page's own Escape (close the drawer, leave the full view).
59
61
  const onKey = (e: KeyboardEvent) => {
60
- if (e.key === 'Escape') setOpen(false)
62
+ if (e.key !== 'Escape') return
63
+ e.preventDefault()
64
+ e.stopPropagation()
65
+ setOpen(false)
61
66
  }
62
67
  document.addEventListener('mousedown', onDown)
63
- document.addEventListener('keydown', onKey)
68
+ document.addEventListener('keydown', onKey, true)
64
69
  return () => {
65
70
  document.removeEventListener('mousedown', onDown)
66
- document.removeEventListener('keydown', onKey)
71
+ document.removeEventListener('keydown', onKey, true)
67
72
  }
68
73
  }, [open])
69
74
 
@@ -74,6 +74,11 @@ describe('TimeValue', () => {
74
74
  expect(html).not.toMatch(/\dT\d\d:\d\d:\d\dZ/)
75
75
  })
76
76
 
77
+ // A bare "6h" under "Started" or "Finished" reads as a duration.
78
+ it('says the moment was in the past', () => {
79
+ expect(render(<TimeValue timestamp={new Date(Date.now() - 6 * 3600_000).toISOString()} />)).toContain('6h ago')
80
+ })
81
+
77
82
  it('says so when the thing never happened', () => {
78
83
  expect(render(<TimeValue timestamp={undefined} fallback="Never" />)).toContain('Never')
79
84
  })
@@ -1,5 +1,6 @@
1
1
  import { Tooltip } from './Tooltip'
2
- import { cronToHuman, formatAge } from '../resources/resource-utils'
2
+ import { cronToHuman } from '../resources/resource-utils'
3
+ import { formatRelativeAgeTime } from '../../utils/format'
3
4
 
4
5
  /**
5
6
  * Hover affordance used across the drawer: without it a tooltip is a secret.
@@ -79,7 +80,7 @@ export function TimeValue({ timestamp, fallback = '-' }: TimeValueProps) {
79
80
  if (!timestamp) return <>{fallback}</>
80
81
  return (
81
82
  <Tooltip content={new Date(timestamp).toLocaleString()} position="right">
82
- <span className={HOVERABLE}>{formatAge(timestamp)}</span>
83
+ <span className={HOVERABLE}>{formatRelativeAgeTime(timestamp)}</span>
83
84
  </Tooltip>
84
85
  )
85
86
  }
@@ -0,0 +1,160 @@
1
+ // @vitest-environment jsdom
2
+ import { act } from "react";
3
+ import { createRoot } from "react-dom/client";
4
+ import { renderToStaticMarkup } from "react-dom/server";
5
+ import { afterEach, expect, it, vi } from "vitest";
6
+ import { MetadataSection } from "./drawer-components";
7
+ import {
8
+ customResourceDefinitionRef,
9
+ initNavigationMap,
10
+ resetNavigationMap,
11
+ } from "../../utils/navigation";
12
+
13
+ Object.assign(globalThis, { IS_REACT_ACT_ENVIRONMENT: true });
14
+ afterEach(resetNavigationMap);
15
+ const resources = [
16
+ {
17
+ group: "plants.example.io",
18
+ version: "v1",
19
+ kind: "Cactus",
20
+ name: "cacti",
21
+ definitionName: "cacti.plants.example.io",
22
+ namespaced: true,
23
+ isCrd: true,
24
+ verbs: ["get"],
25
+ },
26
+ {
27
+ group: "other.example.io",
28
+ version: "v1",
29
+ kind: "Cactus",
30
+ name: "cactuses",
31
+ definitionName: "cactuses.other.example.io",
32
+ namespaced: false,
33
+ isCrd: true,
34
+ verbs: ["get"],
35
+ },
36
+ {
37
+ group: "apps",
38
+ version: "v1",
39
+ kind: "Deployment",
40
+ name: "deployments",
41
+ namespaced: true,
42
+ isCrd: false,
43
+ verbs: ["get"],
44
+ },
45
+ ];
46
+
47
+ it("uses exact discovered CRD group and plural instead of guessing from the Kind", () => {
48
+ expect(
49
+ customResourceDefinitionRef("plants.example.io/v1", "Cactus"),
50
+ ).toBeNull();
51
+ initNavigationMap(resources);
52
+ expect(customResourceDefinitionRef("plants.example.io/v1", "Cactus")).toEqual(
53
+ {
54
+ kind: "CustomResourceDefinition",
55
+ group: "apiextensions.k8s.io",
56
+ namespace: "",
57
+ name: "cacti.plants.example.io",
58
+ },
59
+ );
60
+ expect(
61
+ customResourceDefinitionRef("other.example.io/v1", "Cactus")?.name,
62
+ ).toBe("cactuses.other.example.io");
63
+ expect(customResourceDefinitionRef("apps/v1", "Deployment")).toBeNull();
64
+ initNavigationMap([
65
+ ...resources,
66
+ {
67
+ group: "metrics.k8s.io",
68
+ version: "v1beta1",
69
+ kind: "PodMetrics",
70
+ name: "pods",
71
+ namespaced: true,
72
+ isCrd: true,
73
+ verbs: ["get"],
74
+ },
75
+ ]);
76
+ expect(
77
+ customResourceDefinitionRef("metrics.k8s.io/v1beta1", "PodMetrics"),
78
+ ).toBeNull();
79
+ resetNavigationMap();
80
+ expect(
81
+ customResourceDefinitionRef("plants.example.io/v1", "Cactus"),
82
+ ).toBeNull();
83
+ initNavigationMap([
84
+ {
85
+ ...resources[0],
86
+ name: "cactuses",
87
+ definitionName: "cactuses.plants.example.io",
88
+ },
89
+ ]);
90
+ expect(
91
+ customResourceDefinitionRef("plants.example.io/v1", "Cactus")?.name,
92
+ ).toBe("cactuses.plants.example.io");
93
+ expect(
94
+ customResourceDefinitionRef("unknown.example.io/v1", "Cactus"),
95
+ ).toBeNull();
96
+ expect(customResourceDefinitionRef(undefined, "Cactus")).toBeNull();
97
+ initNavigationMap([]);
98
+ expect(
99
+ customResourceDefinitionRef("plants.example.io/v1", "Cactus"),
100
+ ).toBeNull();
101
+ });
102
+
103
+ it("navigates from instance metadata to its cluster-scoped definition", async () => {
104
+ initNavigationMap(resources);
105
+ const element = document.createElement("div");
106
+ const root = createRoot(element);
107
+ const onNavigate = vi.fn();
108
+ try {
109
+ await act(async () =>
110
+ root.render(
111
+ <MetadataSection
112
+ data={{
113
+ apiVersion: "plants.example.io/v1",
114
+ kind: "Cactus",
115
+ metadata: { name: "live", namespace: "team" },
116
+ }}
117
+ onNavigate={onNavigate}
118
+ />,
119
+ ),
120
+ );
121
+ const button = [...element.querySelectorAll("button")].find(
122
+ (b) => b.textContent === "cacti.plants.example.io",
123
+ );
124
+ expect(button).toBeDefined();
125
+ await act(async () => button!.click());
126
+ expect(onNavigate).toHaveBeenCalledWith({
127
+ kind: "CustomResourceDefinition",
128
+ group: "apiextensions.k8s.io",
129
+ namespace: "",
130
+ name: "cacti.plants.example.io",
131
+ });
132
+ } finally {
133
+ await act(async () => root.unmount());
134
+ }
135
+ });
136
+
137
+ it("keeps exact Kind casing within one API group", () => {
138
+ initNavigationMap([
139
+ ...resources,
140
+ { ...resources[0], kind: "CACTUS", name: "uppercacti", definitionName: "uppercacti.plants.example.io" },
141
+ ]);
142
+ expect(customResourceDefinitionRef("plants.example.io/v1", "Cactus")?.name).toBe("cacti.plants.example.io");
143
+ expect(customResourceDefinitionRef("plants.example.io/v1", "CACTUS")?.name).toBe("uppercacti.plants.example.io");
144
+ expect(customResourceDefinitionRef("plants.example.io/v1", "cactus")).toBeNull();
145
+ });
146
+
147
+ it("does not add a guessed definition to built-in or undiscovered metadata", () => {
148
+ initNavigationMap(resources);
149
+ const html = renderToStaticMarkup(
150
+ <MetadataSection
151
+ data={{
152
+ apiVersion: "apps/v1",
153
+ kind: "Deployment",
154
+ metadata: { name: "live", uid: "native" },
155
+ }}
156
+ />,
157
+ );
158
+ expect(html).toContain("native");
159
+ expect(html).not.toContain("Definition");
160
+ });