@skyhook-io/radar-app 1.13.4 → 1.14.0

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 (230) hide show
  1. package/package.json +8 -8
  2. package/src/App.tsx +60 -24
  3. package/src/RadarApp.tsx +5 -0
  4. package/src/api/client.authRedirect.test.ts +12 -1
  5. package/src/api/client.prometheusStatus.test.ts +24 -0
  6. package/src/api/client.ts +325 -16
  7. package/src/api/diagnose.ts +62 -15
  8. package/src/api/drain-plan.test.ts +86 -0
  9. package/src/api/quotas.test.ts +26 -0
  10. package/src/api/quotas.ts +20 -0
  11. package/src/api/workloadMetrics.test.ts +40 -0
  12. package/src/api/workloadMetrics.ts +68 -0
  13. package/src/components/CloudConnectFlow.tsx +179 -42
  14. package/src/components/CloudFunnelButton.tsx +107 -36
  15. package/src/components/ConnectionErrorView.test.tsx +30 -0
  16. package/src/components/ConnectionErrorView.tsx +31 -19
  17. package/src/components/SyncProgressPanel.tsx +73 -0
  18. package/src/components/audit/AuditView.tsx +2 -0
  19. package/src/components/cloudConnectHandoff.test.ts +140 -0
  20. package/src/components/cloudConnectHandoff.ts +124 -0
  21. package/src/components/diagnose/AgentCase.tsx +131 -0
  22. package/src/components/diagnose/DiagnoseContext.tsx +2 -1
  23. package/src/components/diagnose/DiagnoseSurface.test.tsx +0 -20
  24. package/src/components/diagnose/DiagnoseSurface.tsx +32 -195
  25. package/src/components/diagnose/InvestigationEvidencePane.test.tsx +783 -7
  26. package/src/components/diagnose/InvestigationEvidencePane.tsx +774 -133
  27. package/src/components/diagnose/InvestigationView.tsx +222 -5
  28. package/src/components/diagnose/diagnoseEvidenceTypes.ts +22 -1
  29. package/src/components/diagnose/investigationCase.test.tsx +1568 -0
  30. package/src/components/diagnose/investigationCase.ts +439 -0
  31. package/src/components/diagnose/investigationEvidence/adapters/changes.ts +60 -0
  32. package/src/components/diagnose/investigationEvidence/adapters/diagnose.test.ts +573 -0
  33. package/src/components/diagnose/investigationEvidence/adapters/diagnose.ts +935 -0
  34. package/src/components/diagnose/investigationEvidence/adapters/events.ts +96 -0
  35. package/src/components/diagnose/investigationEvidence/adapters/helm.test.ts +242 -0
  36. package/src/components/diagnose/investigationEvidence/adapters/helm.ts +208 -0
  37. package/src/components/diagnose/investigationEvidence/adapters/issues.ts +172 -0
  38. package/src/components/diagnose/investigationEvidence/adapters/logs.test.ts +283 -0
  39. package/src/components/diagnose/investigationEvidence/adapters/logs.ts +183 -0
  40. package/src/components/diagnose/investigationEvidence/adapters/permissions.test.ts +528 -0
  41. package/src/components/diagnose/investigationEvidence/adapters/permissions.ts +292 -0
  42. package/src/components/diagnose/investigationEvidence/adapters/prometheus.test.ts +1641 -0
  43. package/src/components/diagnose/investigationEvidence/adapters/prometheus.ts +769 -0
  44. package/src/components/diagnose/investigationEvidence/adapters/resource.test.ts +54 -0
  45. package/src/components/diagnose/investigationEvidence/adapters/resource.ts +257 -0
  46. package/src/components/diagnose/investigationEvidence/adapters/topology.ts +207 -0
  47. package/src/components/diagnose/investigationEvidence/builder.ts +418 -0
  48. package/src/components/diagnose/investigationEvidence/evidenceFixtures.ts +179 -0
  49. package/src/components/diagnose/investigationEvidence/identity.test.ts +614 -0
  50. package/src/components/diagnose/investigationEvidence/identity.ts +261 -0
  51. package/src/components/diagnose/investigationEvidence/index.ts +55 -0
  52. package/src/components/diagnose/investigationEvidence/observations.ts +954 -0
  53. package/src/components/diagnose/investigationEvidence/parse.ts +467 -0
  54. package/src/components/diagnose/investigationEvidence/projection.test.ts +2703 -0
  55. package/src/components/diagnose/investigationEvidence/types.ts +512 -0
  56. package/src/components/diagnose/investigationEvidenceKinds.ts +218 -0
  57. package/src/components/diagnose/investigationEvidencePresentation.test.ts +31 -0
  58. package/src/components/diagnose/investigationEvidencePresentation.ts +1 -0
  59. package/src/components/diagnose/investigationMetrics.test.ts +712 -0
  60. package/src/components/diagnose/investigationMetrics.ts +393 -0
  61. package/src/components/diagnose/investigationSourceFocus.ts +2 -0
  62. package/src/components/diagnose/investigationState.test.ts +322 -0
  63. package/src/components/diagnose/investigationState.ts +147 -25
  64. package/src/components/diagnose/parts.test.tsx +482 -0
  65. package/src/components/diagnose/parts.tsx +418 -41
  66. package/src/components/diagnose/target.test.ts +19 -0
  67. package/src/components/diagnose/target.ts +3 -2
  68. package/src/components/gitops/GitOpsView.tsx +7 -0
  69. package/src/components/gitops/RemoteDestinationCloudHint.tsx +26 -0
  70. package/src/components/resource/PrometheusCharts.overlays.test.ts +22 -0
  71. package/src/components/resource/PrometheusCharts.tsx +4 -3
  72. package/src/components/resource/PrometheusChartsGrid.render.test.tsx +99 -0
  73. package/src/components/resource/PrometheusChartsGrid.tsx +245 -103
  74. package/src/components/resource/RightsizingStrip.test.ts +38 -0
  75. package/src/components/resource/RightsizingStrip.tsx +15 -3
  76. package/src/components/resource/WorkloadMetricsHelpDialog.test.tsx +79 -0
  77. package/src/components/resource/WorkloadMetricsHelpDialog.tsx +145 -0
  78. package/src/components/resource/WorkloadMetricsSection.render.test.tsx +351 -0
  79. package/src/components/resource/WorkloadMetricsSection.tsx +486 -0
  80. package/src/components/resource/workloadMetricValues.test.ts +65 -0
  81. package/src/components/resource/workloadMetricValues.ts +30 -0
  82. package/src/components/resources/PodFilePreview.test.tsx +131 -0
  83. package/src/components/resources/PodFilePreview.tsx +394 -0
  84. package/src/components/resources/PodFilesystemModal.tsx +157 -67
  85. package/src/components/resources/ResourcesView.tsx +44 -6
  86. package/src/components/resources/renderers/NamespaceRenderer.tsx +7 -1
  87. package/src/components/resources/renderers/PodRenderer.tsx +4 -0
  88. package/src/components/resources/renderers/RolloutRenderer.tsx +15 -1
  89. package/src/components/resources/renderers/WorkloadRenderer.tsx +3 -0
  90. package/src/components/rightsizing/RightsizingScanView.tsx +21 -0
  91. package/src/components/settings/SettingsDialog.tsx +94 -14
  92. package/src/components/traffic/TrafficFlowList.tsx +229 -70
  93. package/src/components/traffic/TrafficGraph.tsx +6 -6
  94. package/src/components/traffic/TrafficView.tsx +10 -9
  95. package/src/components/traffic/trafficFilters.test.ts +28 -1
  96. package/src/components/traffic/trafficFilters.ts +31 -0
  97. package/src/components/workload/WorkloadView.tsx +19 -8
  98. package/src/context/ConnectionContext.tsx +48 -3
  99. package/src/context/DiagnoseCustomization.test.tsx +26 -0
  100. package/src/context/DiagnoseCustomization.tsx +15 -2
  101. package/src/index.css +17 -0
  102. package/src/index.ts +1 -0
  103. package/src/utils/navigation.ts +1 -1
  104. package/src/utils/shell-safe.test.ts +25 -1
  105. package/src/utils/shell-safe.ts +16 -0
  106. package/src/utils/topology-namespace.test.ts +64 -0
  107. package/src/utils/topology-namespace.ts +18 -0
  108. package/src/components/diagnose/investigationEvidence.test.ts +0 -5202
  109. package/src/components/diagnose/investigationEvidence.ts +0 -4784
  110. package/src/components/gitops/GitOpsActions.tsx +0 -1
  111. package/src/components/gitops/GitOpsStatusBadge.tsx +0 -1
  112. package/src/components/gitops/ManagedResourcesList.tsx +0 -1
  113. package/src/components/gitops/SyncCountdown.tsx +0 -1
  114. package/src/components/gitops/index.ts +0 -4
  115. package/src/components/helm/ManifestDiffViewer.tsx +0 -69
  116. package/src/components/logs/JsonLogLine.tsx +0 -1
  117. package/src/components/logs/LogCore.tsx +0 -2
  118. package/src/components/logs/useLogBuffer.ts +0 -2
  119. package/src/components/logs/useLogSearch.ts +0 -1
  120. package/src/components/resource-drawer/ResourceDrawer.tsx +0 -216
  121. package/src/components/resources/drawer-components.tsx +0 -1
  122. package/src/components/resources/renderers/AlertRenderer.tsx +0 -1
  123. package/src/components/resources/renderers/CNPGBackupRenderer.tsx +0 -1
  124. package/src/components/resources/renderers/CNPGPoolerRenderer.tsx +0 -1
  125. package/src/components/resources/renderers/CNPGScheduledBackupRenderer.tsx +0 -1
  126. package/src/components/resources/renderers/CertificateRenderer.tsx +0 -1
  127. package/src/components/resources/renderers/CertificateRequestRenderer.tsx +0 -1
  128. package/src/components/resources/renderers/ChallengeRenderer.tsx +0 -1
  129. package/src/components/resources/renderers/ClusterComplianceReportRenderer.tsx +0 -1
  130. package/src/components/resources/renderers/ClusterExternalSecretRenderer.tsx +0 -1
  131. package/src/components/resources/renderers/ClusterIssuerRenderer.tsx +0 -1
  132. package/src/components/resources/renderers/ConfigAuditReportRenderer.tsx +0 -1
  133. package/src/components/resources/renderers/ConfigMapRenderer.tsx +0 -1
  134. package/src/components/resources/renderers/CronJobRenderer.tsx +0 -1
  135. package/src/components/resources/renderers/CronWorkflowRenderer.tsx +0 -1
  136. package/src/components/resources/renderers/EventRenderer.tsx +0 -1
  137. package/src/components/resources/renderers/ExposedSecretReportRenderer.tsx +0 -1
  138. package/src/components/resources/renderers/ExternalSecretRenderer.tsx +0 -1
  139. package/src/components/resources/renderers/FluxHelmReleaseRenderer.tsx +0 -1
  140. package/src/components/resources/renderers/GRPCRouteRenderer.tsx +0 -1
  141. package/src/components/resources/renderers/GatewayClassRenderer.tsx +0 -1
  142. package/src/components/resources/renderers/GatewayRenderer.tsx +0 -1
  143. package/src/components/resources/renderers/GenericRenderer.tsx +0 -1
  144. package/src/components/resources/renderers/GitRepositoryRenderer.tsx +0 -1
  145. package/src/components/resources/renderers/HTTPRouteRenderer.tsx +0 -1
  146. package/src/components/resources/renderers/HelmRepositoryRenderer.tsx +0 -1
  147. package/src/components/resources/renderers/IngressClassRenderer.tsx +0 -1
  148. package/src/components/resources/renderers/IngressRenderer.tsx +0 -1
  149. package/src/components/resources/renderers/IstioAuthorizationPolicyRenderer.tsx +0 -1
  150. package/src/components/resources/renderers/IstioDestinationRuleRenderer.tsx +0 -1
  151. package/src/components/resources/renderers/IstioGatewayRenderer.tsx +0 -1
  152. package/src/components/resources/renderers/IstioPeerAuthenticationRenderer.tsx +0 -1
  153. package/src/components/resources/renderers/IstioServiceEntryRenderer.tsx +0 -1
  154. package/src/components/resources/renderers/IstioVirtualServiceRenderer.tsx +0 -1
  155. package/src/components/resources/renderers/JobRenderer.tsx +0 -1
  156. package/src/components/resources/renderers/KarpenterEC2NodeClassRenderer.tsx +0 -1
  157. package/src/components/resources/renderers/KarpenterNodeClaimRenderer.tsx +0 -1
  158. package/src/components/resources/renderers/KedaScaledJobRenderer.tsx +0 -1
  159. package/src/components/resources/renderers/KedaScaledObjectRenderer.tsx +0 -1
  160. package/src/components/resources/renderers/KedaTriggerAuthRenderer.tsx +0 -1
  161. package/src/components/resources/renderers/KnativeConfigurationRenderer.tsx +0 -1
  162. package/src/components/resources/renderers/KnativeEventingRenderer.tsx +0 -1
  163. package/src/components/resources/renderers/KnativeFlowRenderer.tsx +0 -1
  164. package/src/components/resources/renderers/KnativeNetworkingRenderer.tsx +0 -1
  165. package/src/components/resources/renderers/KnativeRevisionRenderer.tsx +0 -1
  166. package/src/components/resources/renderers/KnativeRouteRenderer.tsx +0 -1
  167. package/src/components/resources/renderers/KnativeServiceRenderer.tsx +0 -1
  168. package/src/components/resources/renderers/KnativeSourceRenderer.tsx +0 -1
  169. package/src/components/resources/renderers/KustomizationRenderer.tsx +0 -1
  170. package/src/components/resources/renderers/KyvernoPolicyReportRenderer.tsx +0 -1
  171. package/src/components/resources/renderers/LeaseRenderer.tsx +0 -1
  172. package/src/components/resources/renderers/NetworkPolicyRenderer.tsx +0 -1
  173. package/src/components/resources/renderers/OCIRepositoryRenderer.tsx +0 -1
  174. package/src/components/resources/renderers/OrderRenderer.tsx +0 -1
  175. package/src/components/resources/renderers/PersistentVolumeRenderer.tsx +0 -1
  176. package/src/components/resources/renderers/PodDisruptionBudgetRenderer.tsx +0 -1
  177. package/src/components/resources/renderers/PodMonitorRenderer.tsx +0 -1
  178. package/src/components/resources/renderers/PriorityClassRenderer.tsx +0 -1
  179. package/src/components/resources/renderers/PrometheusRuleRenderer.tsx +0 -1
  180. package/src/components/resources/renderers/ReplicaSetRenderer.tsx +0 -1
  181. package/src/components/resources/renderers/RuntimeClassRenderer.tsx +0 -1
  182. package/src/components/resources/renderers/SbomReportRenderer.tsx +0 -1
  183. package/src/components/resources/renderers/SealedSecretRenderer.tsx +0 -1
  184. package/src/components/resources/renderers/SecretRenderer.tsx +0 -1
  185. package/src/components/resources/renderers/SecretStoreRenderer.tsx +0 -1
  186. package/src/components/resources/renderers/ServiceMonitorRenderer.tsx +0 -1
  187. package/src/components/resources/renderers/SimpleRouteRenderer.tsx +0 -1
  188. package/src/components/resources/renderers/StorageClassRenderer.tsx +0 -1
  189. package/src/components/resources/renderers/TraefikIngressRouteRenderer.tsx +0 -1
  190. package/src/components/resources/renderers/VPARenderer.tsx +0 -1
  191. package/src/components/resources/renderers/VeleroScheduleRenderer.tsx +0 -1
  192. package/src/components/resources/renderers/VeleroVSLRenderer.tsx +0 -1
  193. package/src/components/resources/renderers/VulnerabilityReportRenderer.tsx +0 -1
  194. package/src/components/resources/renderers/WebhookConfigRenderer.tsx +0 -1
  195. package/src/components/resources/renderers/WorkflowRenderer.tsx +0 -1
  196. package/src/components/resources/renderers/WorkflowTemplateRenderer.tsx +0 -1
  197. package/src/components/resources/renderers/argo-cells.tsx +0 -1
  198. package/src/components/resources/renderers/certmanager-cells.tsx +0 -1
  199. package/src/components/resources/renderers/cnpg-cells.tsx +0 -1
  200. package/src/components/resources/renderers/eso-cells.tsx +0 -1
  201. package/src/components/resources/renderers/flux-cells.tsx +0 -1
  202. package/src/components/resources/renderers/index.ts +0 -94
  203. package/src/components/resources/renderers/istio-cells.tsx +0 -1
  204. package/src/components/resources/renderers/karpenter-cells.tsx +0 -1
  205. package/src/components/resources/renderers/keda-cells.tsx +0 -1
  206. package/src/components/resources/renderers/knative-cells.tsx +0 -1
  207. package/src/components/resources/renderers/kyverno-cells.tsx +0 -1
  208. package/src/components/resources/renderers/prometheus-cells.tsx +0 -1
  209. package/src/components/resources/renderers/traefik-cells.tsx +0 -1
  210. package/src/components/resources/renderers/trivy-cells.tsx +0 -1
  211. package/src/components/resources/renderers/trivy-shared.tsx +0 -1
  212. package/src/components/resources/renderers/velero-cells.tsx +0 -1
  213. package/src/components/resources/resource-utils-argo.ts +0 -2
  214. package/src/components/resources/resource-utils-certmanager.ts +0 -2
  215. package/src/components/resources/resource-utils-eso.ts +0 -2
  216. package/src/components/resources/resource-utils-flux.ts +0 -2
  217. package/src/components/resources/resource-utils-istio.ts +0 -2
  218. package/src/components/resources/resource-utils-karpenter.ts +0 -2
  219. package/src/components/resources/resource-utils-knative.ts +0 -2
  220. package/src/components/resources/resource-utils-kyverno.ts +0 -2
  221. package/src/components/resources/resource-utils-prometheus.ts +0 -2
  222. package/src/components/resources/resource-utils-traefik.ts +0 -1
  223. package/src/components/resources/resource-utils-trivy.ts +0 -2
  224. package/src/components/resources/resource-utils-velero.ts +0 -2
  225. package/src/components/shared/EditableYamlView.tsx +0 -24
  226. package/src/components/timeline/DiffViewer.tsx +0 -1
  227. package/src/components/traffic/index.ts +0 -3
  228. package/src/components/ui/ForceDeleteConfirmDialog.tsx +0 -1
  229. package/src/components/ui/MetricsChart.tsx +0 -1
  230. package/src/components/ui/ResourceBar.tsx +0 -1
@@ -26,6 +26,25 @@ describe("investigation target identity", () => {
26
26
  ).toBe("Rollout.argoproj.io prod/checkout");
27
27
  });
28
28
 
29
+ it("formats saved plural targets as Kubernetes Kinds", () => {
30
+ expect(
31
+ formatInvestigationTarget({
32
+ kind: "deployments",
33
+ group: "apps",
34
+ namespace: "prod",
35
+ name: "api",
36
+ }),
37
+ ).toBe("Deployment.apps prod/api");
38
+ expect(
39
+ formatInvestigationTarget({
40
+ kind: "pods",
41
+ group: "metrics.k8s.io",
42
+ namespace: "prod",
43
+ name: "api",
44
+ }),
45
+ ).toBe("PodMetrics.metrics.k8s.io prod/api");
46
+ });
47
+
29
48
  it("keeps the core target label compact", () => {
30
49
  expect(
31
50
  formatInvestigationTarget({
@@ -1,6 +1,6 @@
1
1
  import { resourceKey } from "@skyhook-io/k8s-ui";
2
2
 
3
- import { kindToPluralWithGroup } from "../../utils/navigation";
3
+ import { kindToPluralWithGroup, knownKindForPluralWithGroup } from "../../utils/navigation";
4
4
 
5
5
  export interface InvestigationTargetIdentity {
6
6
  kind: string;
@@ -31,6 +31,7 @@ export function runTargetKey(
31
31
  export function formatInvestigationTarget(
32
32
  target: InvestigationTargetIdentity,
33
33
  ): string {
34
- const kind = target.group ? `${target.kind}.${target.group}` : target.kind;
34
+ const canonicalKind = knownKindForPluralWithGroup(target.kind, target.group) ?? target.kind;
35
+ const kind = target.group ? `${canonicalKind}.${target.group}` : canonicalKind;
35
36
  return `${kind} ${target.namespace ? `${target.namespace}/` : ""}${target.name}`;
36
37
  }
@@ -72,6 +72,9 @@ import { useRegisterShortcut } from '../../hooks/useKeyboardShortcuts'
72
72
  import { CodeViewer } from '../ui/CodeViewer'
73
73
  import { ArgoResourceDiffLoader } from './ArgoResourceDiffLoader'
74
74
  import { RevisionMetaChip } from './RevisionMetaChip'
75
+ import { RemoteDestinationCloudHint } from './RemoteDestinationCloudHint'
76
+
77
+ const GITOPS_HEALTH_DOCS_URL = 'https://radarhq.io/docs/features/gitops#per-resource-health'
75
78
  import type { GitOpsHistoryItem } from '@skyhook-io/k8s-ui'
76
79
 
77
80
  const GITOPS_KINDS: APIResource[] = [
@@ -634,6 +637,9 @@ function GitOpsDetailView({ namespaces, onOpenResource, onOpenSettings }: GitOps
634
637
  detail={detail}
635
638
  insight={insightsQ.data ?? null}
636
639
  insightLoading={insightsQ.isLoading}
640
+ healthDocsUrl={GITOPS_HEALTH_DOCS_URL}
641
+ remoteDestinationHint={<RemoteDestinationCloudHint />}
642
+ onOpenSettings={onOpenSettings}
637
643
  renderRevisionMeta={
638
644
  isArgoApp && insightsQ.data?.capabilities?.revisionMetadataAvailable
639
645
  ? (revision) => (
@@ -808,6 +814,7 @@ function GitOpsDetailView({ namespaces, onOpenResource, onOpenSettings }: GitOps
808
814
  onQueryChange={setGraphSearch}
809
815
  filters={graphFilters}
810
816
  showToolbar={false}
817
+ issues={insightsQ.data?.issues}
811
818
  />
812
819
  </div>
813
820
  </div>
@@ -0,0 +1,26 @@
1
+ import { useNavCustomization } from '../../context/NavCustomization'
2
+ import { useCapabilities } from '../../api/client'
3
+ import { openCloudFunnel } from '../CloudFunnelButton'
4
+
5
+ // The one sentence a standalone Radar adds after "this application deploys
6
+ // to another cluster": the thing the user just ran into is what Radar Cloud
7
+ // does. "Radar Cloud" opens the same dialog as the top-right button, so
8
+ // there is one pitch and one connect flow. That dialog connects THIS
9
+ // cluster; the destination is a second connect from Cloud, which is why the
10
+ // copy says "both clusters" and promises the view in Cloud, not here. Rendered only when that button
11
+ // is: the server decides whether there is anything to pitch (cloudConnect
12
+ // capability), and embedded hosts (Radar Cloud itself) mount neither.
13
+ export function RemoteDestinationCloudHint() {
14
+ const { embedded } = useNavCustomization()
15
+ const capabilities = useCapabilities()
16
+ if (embedded || !capabilities.data?.cloudConnect) return null
17
+ return (
18
+ <>
19
+ Connect both clusters to{' '}
20
+ <button type="button" onClick={openCloudFunnel} className="underline decoration-theme-border underline-offset-2 hover:text-theme-text-primary">
21
+ Radar Cloud
22
+ </button>{' '}
23
+ to see this app's resources together.
24
+ </>
25
+ )
26
+ }
@@ -0,0 +1,22 @@
1
+ import { describe, expect, it } from 'vitest'
2
+ import { computeRequestLimitLines } from './PrometheusCharts'
3
+
4
+ describe('Pod request and limit overlays', () => {
5
+ it('does not draw a Pod limit when one runtime container is unlimited', () => {
6
+ const resource = { spec: { containers: [
7
+ { resources: { requests: { cpu: '100m' }, limits: { cpu: '1' } } },
8
+ { resources: { requests: { cpu: '200m' } } },
9
+ ] } }
10
+ const lines = computeRequestLimitLines(resource, 'Pod', 'cpu')!
11
+ expect(lines).toHaveLength(1)
12
+ expect(lines[0].kind).toBe('request')
13
+ expect(lines[0].value).toBeCloseTo(0.3)
14
+ })
15
+ it('sums fully limited runtime containers at per-Pod scale', () => {
16
+ const resource = { spec: { replicas: 5, template: { spec: {
17
+ containers: [{ resources: { limits: { cpu: '1' } } }],
18
+ initContainers: [{ restartPolicy: 'Always', resources: { limits: { cpu: '200m' } } }],
19
+ } } } }
20
+ expect(computeRequestLimitLines(resource, 'Deployment', 'cpu')![0].value).toBeCloseTo(1.2)
21
+ })
22
+ })
@@ -121,6 +121,7 @@ export function PrometheusCharts({ kind, namespace, name, showEmptyState = false
121
121
  statusError={status?.error}
122
122
  onConnect={() => connectMutation.mutate()}
123
123
  connecting={connectMutation.isPending}
124
+ discovering={status?.discovering === true}
124
125
  category={activeCategory}
125
126
  onCategoryChange={setActiveCategory}
126
127
  range={timeRange}
@@ -161,12 +162,12 @@ export function computeRequestLimitLines(
161
162
  if (runtimeContainers.length === 0) return undefined
162
163
 
163
164
  let reqSum = 0, reqAny = false
164
- let limSum = 0, limAny = false
165
+ let limSum = 0, limAll = true
165
166
  for (const c of runtimeContainers) {
166
167
  const req = readQuantity(c.resources?.requests?.[category], category)
167
168
  const lim = readQuantity(c.resources?.limits?.[category], category)
168
169
  if (req != null) { reqSum += req; reqAny = true }
169
- if (lim != null) { limSum += lim; limAny = true }
170
+ if (lim != null && lim > 0) { limSum += lim } else { limAll = false }
170
171
  }
171
172
 
172
173
  const lines: ReferenceLine[] = []
@@ -177,7 +178,7 @@ export function computeRequestLimitLines(
177
178
  kind: 'request',
178
179
  })
179
180
  }
180
- if (limAny) {
181
+ if (limAll) {
181
182
  lines.push({
182
183
  value: limSum,
183
184
  label: `limit ${formatRequestLimitLabel(limSum, category)}`,
@@ -0,0 +1,99 @@
1
+ import { beforeEach, describe, expect, it, vi } from 'vitest'
2
+ import { renderToString } from 'react-dom/server'
3
+ import { MemoryRouter } from 'react-router-dom'
4
+ import { NavCustomizationProvider } from '../../context/NavCustomization'
5
+ import type { WorkloadMetrics } from '../../api/workloadMetrics'
6
+
7
+ let connected = false
8
+ const ranges: string[] = []
9
+ const charts: { seriesLabels?: string[]; referenceLines?: unknown[] }[] = []
10
+ let workload: WorkloadMetrics
11
+
12
+ vi.mock('../../api/client', async (importActual) => ({
13
+ ...(await importActual<typeof import('../../api/client')>()),
14
+ useAutoPromConnect: () => undefined,
15
+ usePrometheusStatus: () => ({ data: { connected, error: 'Backend query failed' } }),
16
+ usePrometheusConnect: () => ({ mutate: vi.fn(), isPending: false }),
17
+ usePrometheusResourceMetrics: (_kind: string, _ns: string, _name: string, _category: string, range: string) => {
18
+ ranges.push(range)
19
+ return { data: { unit: 'cores', result: { series: [{ labels: { pod: 'web-0' }, dataPoints: [{ timestamp: 100, value: 1 }, { timestamp: 160, value: 2 }] }] } } }
20
+ },
21
+ }))
22
+ vi.mock('../../api/workloadMetrics', () => ({ useWorkloadMetrics: () => ({ data: workload }) }))
23
+ vi.mock('./RestartChart', () => ({ RestartEventLane: () => null }))
24
+ vi.mock('./RightsizingStrip', () => ({ RightsizingStrip: () => null }))
25
+ vi.mock('@skyhook-io/k8s-ui/components/charts', async (importActual) => ({
26
+ ...(await importActual<typeof import('@skyhook-io/k8s-ui/components/charts')>()),
27
+ AreaChart: (props: typeof charts[number]) => { charts.push(props); return <span>Chart</span> },
28
+ }))
29
+
30
+ const { PrometheusChartsGrid } = await import('./PrometheusChartsGrid')
31
+ const resource = { spec: { template: { spec: { containers: [{ resources: { requests: { cpu: '100m' }, limits: { cpu: '200m' } } }] } } } }
32
+ const render = (url = '/workloads/Deployment/demo/web?tab=metrics', embedded = false) => renderToString(
33
+ <MemoryRouter initialEntries={[url]}><NavCustomizationProvider value={embedded ? { embedded: true, rightExtras: null } : {}}>
34
+ <PrometheusChartsGrid kind="Deployment" namespace="demo" name="web" resource={resource} />
35
+ </NavCustomizationProvider></MemoryRouter>,
36
+ )
37
+
38
+ beforeEach(() => {
39
+ connected = false
40
+ ranges.length = 0
41
+ charts.length = 0
42
+ workload = { state: 'available', sources: [], pods: 1, podsTotal: 1, start: 100, end: 160, stepSeconds: 60, rateWindowSeconds: 300, history: {}, comparison: {}, panels: {} }
43
+ })
44
+
45
+ describe('Expanded metrics recovery and scope', () => {
46
+ it('offers standalone setup without losing the backend error or retry', () => {
47
+ const html = render()
48
+ expect(html).toContain('Backend query failed')
49
+ expect(html).toContain('Discover Prometheus')
50
+ expect(html).toContain('Configure metrics')
51
+ expect(html).toContain('workload-metrics.md#what-each-chart-needs')
52
+ })
53
+ it('uses operator guidance instead of standalone settings when embedded', () => {
54
+ const html = render(undefined, true)
55
+ expect(html).toContain('Ask your operator')
56
+ expect(html).not.toContain('Configure metrics')
57
+ expect(html).toContain('Discover Prometheus')
58
+ })
59
+ it.each([
60
+ ['/workloads/Deployment/demo/web?tab=metrics&metricsRange=3h', '3h'],
61
+ ['/applications?app=demo&workload=Deployment%2Fdemo%2Fweb&tab=metrics&metricsRange=6h', '6h'],
62
+ ['/applications?tab=metrics&metricsRange=invalid', '1h'],
63
+ ['/applications?tab=metrics', '1h'],
64
+ ])('reads and validates the URL range: %s', (url, expected) => {
65
+ connected = true
66
+ const html = render(url)
67
+ expect(html).toContain(`value="${expected}" selected=""`)
68
+ expect(ranges.length).toBeGreaterThan(0)
69
+ expect(ranges.every((range) => range === expected)).toBe(true)
70
+ })
71
+ it('leaves unverified charts accessible without template overlays or saturation', () => {
72
+ connected = true
73
+ const html = render()
74
+ expect(html).toContain('<details aria-label="Name-matched resource metrics">')
75
+ expect(html).toContain('Network and storage')
76
+ expect(html).not.toContain('% of limit')
77
+ expect(charts.length).toBeGreaterThan(0)
78
+ expect(charts.every((chart) => !chart.referenceLines?.length)).toBe(true)
79
+ })
80
+ it('labels unlabeled request totals without changing aggregate and quantile labels', () => {
81
+ connected = true
82
+ const series = [{ labels: {}, dataPoints: [{ timestamp: 160, value: 1 }] }]
83
+ workload.panels.requests = { state: 'available', unit: 'requests/s', series }
84
+ workload.panels.p50 = { state: 'available', unit: 'seconds', series }
85
+ workload.panels.p95 = { state: 'available', unit: 'seconds', series }
86
+ workload.panels.cpu = { state: 'available', unit: 'cores', series: [{ ...series[0], labels: { aggregation: 'Workload' } }, { ...series[0], labels: { aggregation: 'Maximum Pod' } }] }
87
+ render()
88
+ expect(charts.map((chart) => chart.seriesLabels)).toContainEqual(['Requests / sec'])
89
+ expect(charts.map((chart) => chart.seriesLabels)).toContainEqual(['p50', 'p95'])
90
+ expect(charts.map((chart) => chart.seriesLabels)).toContainEqual(['Workload', 'Maximum Pod'])
91
+ })
92
+ it('retains template references on verified current-Pod charts', () => {
93
+ connected = true
94
+ workload.history.cpu = { mode: 'current-pods' }
95
+ workload.panels.cpu = { state: 'available', unit: 'cores', series: [{ labels: { pod: 'web-0' }, dataPoints: [{ timestamp: 160, value: 0.1 }] }] }
96
+ render()
97
+ expect(charts.some((chart) => chart.referenceLines?.some((line) => (line as { label: string }).label === 'Template request 100m'))).toBe(true)
98
+ })
99
+ })