@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.
- package/package.json +8 -8
- package/src/App.tsx +60 -24
- package/src/RadarApp.tsx +5 -0
- package/src/api/client.authRedirect.test.ts +12 -1
- package/src/api/client.prometheusStatus.test.ts +24 -0
- package/src/api/client.ts +325 -16
- package/src/api/diagnose.ts +62 -15
- package/src/api/drain-plan.test.ts +86 -0
- package/src/api/quotas.test.ts +26 -0
- package/src/api/quotas.ts +20 -0
- package/src/api/workloadMetrics.test.ts +40 -0
- package/src/api/workloadMetrics.ts +68 -0
- package/src/components/CloudConnectFlow.tsx +179 -42
- package/src/components/CloudFunnelButton.tsx +107 -36
- package/src/components/ConnectionErrorView.test.tsx +30 -0
- package/src/components/ConnectionErrorView.tsx +31 -19
- package/src/components/SyncProgressPanel.tsx +73 -0
- package/src/components/audit/AuditView.tsx +2 -0
- package/src/components/cloudConnectHandoff.test.ts +140 -0
- package/src/components/cloudConnectHandoff.ts +124 -0
- package/src/components/diagnose/AgentCase.tsx +131 -0
- package/src/components/diagnose/DiagnoseContext.tsx +2 -1
- package/src/components/diagnose/DiagnoseSurface.test.tsx +0 -20
- package/src/components/diagnose/DiagnoseSurface.tsx +32 -195
- package/src/components/diagnose/InvestigationEvidencePane.test.tsx +783 -7
- package/src/components/diagnose/InvestigationEvidencePane.tsx +774 -133
- package/src/components/diagnose/InvestigationView.tsx +222 -5
- package/src/components/diagnose/diagnoseEvidenceTypes.ts +22 -1
- package/src/components/diagnose/investigationCase.test.tsx +1568 -0
- package/src/components/diagnose/investigationCase.ts +439 -0
- package/src/components/diagnose/investigationEvidence/adapters/changes.ts +60 -0
- package/src/components/diagnose/investigationEvidence/adapters/diagnose.test.ts +573 -0
- package/src/components/diagnose/investigationEvidence/adapters/diagnose.ts +935 -0
- package/src/components/diagnose/investigationEvidence/adapters/events.ts +96 -0
- package/src/components/diagnose/investigationEvidence/adapters/helm.test.ts +242 -0
- package/src/components/diagnose/investigationEvidence/adapters/helm.ts +208 -0
- package/src/components/diagnose/investigationEvidence/adapters/issues.ts +172 -0
- package/src/components/diagnose/investigationEvidence/adapters/logs.test.ts +283 -0
- package/src/components/diagnose/investigationEvidence/adapters/logs.ts +183 -0
- package/src/components/diagnose/investigationEvidence/adapters/permissions.test.ts +528 -0
- package/src/components/diagnose/investigationEvidence/adapters/permissions.ts +292 -0
- package/src/components/diagnose/investigationEvidence/adapters/prometheus.test.ts +1641 -0
- package/src/components/diagnose/investigationEvidence/adapters/prometheus.ts +769 -0
- package/src/components/diagnose/investigationEvidence/adapters/resource.test.ts +54 -0
- package/src/components/diagnose/investigationEvidence/adapters/resource.ts +257 -0
- package/src/components/diagnose/investigationEvidence/adapters/topology.ts +207 -0
- package/src/components/diagnose/investigationEvidence/builder.ts +418 -0
- package/src/components/diagnose/investigationEvidence/evidenceFixtures.ts +179 -0
- package/src/components/diagnose/investigationEvidence/identity.test.ts +614 -0
- package/src/components/diagnose/investigationEvidence/identity.ts +261 -0
- package/src/components/diagnose/investigationEvidence/index.ts +55 -0
- package/src/components/diagnose/investigationEvidence/observations.ts +954 -0
- package/src/components/diagnose/investigationEvidence/parse.ts +467 -0
- package/src/components/diagnose/investigationEvidence/projection.test.ts +2703 -0
- package/src/components/diagnose/investigationEvidence/types.ts +512 -0
- package/src/components/diagnose/investigationEvidenceKinds.ts +218 -0
- package/src/components/diagnose/investigationEvidencePresentation.test.ts +31 -0
- package/src/components/diagnose/investigationEvidencePresentation.ts +1 -0
- package/src/components/diagnose/investigationMetrics.test.ts +712 -0
- package/src/components/diagnose/investigationMetrics.ts +393 -0
- package/src/components/diagnose/investigationSourceFocus.ts +2 -0
- package/src/components/diagnose/investigationState.test.ts +322 -0
- package/src/components/diagnose/investigationState.ts +147 -25
- package/src/components/diagnose/parts.test.tsx +482 -0
- package/src/components/diagnose/parts.tsx +418 -41
- package/src/components/diagnose/target.test.ts +19 -0
- package/src/components/diagnose/target.ts +3 -2
- package/src/components/gitops/GitOpsView.tsx +7 -0
- package/src/components/gitops/RemoteDestinationCloudHint.tsx +26 -0
- package/src/components/resource/PrometheusCharts.overlays.test.ts +22 -0
- package/src/components/resource/PrometheusCharts.tsx +4 -3
- package/src/components/resource/PrometheusChartsGrid.render.test.tsx +99 -0
- package/src/components/resource/PrometheusChartsGrid.tsx +245 -103
- package/src/components/resource/RightsizingStrip.test.ts +38 -0
- package/src/components/resource/RightsizingStrip.tsx +15 -3
- package/src/components/resource/WorkloadMetricsHelpDialog.test.tsx +79 -0
- package/src/components/resource/WorkloadMetricsHelpDialog.tsx +145 -0
- package/src/components/resource/WorkloadMetricsSection.render.test.tsx +351 -0
- package/src/components/resource/WorkloadMetricsSection.tsx +486 -0
- package/src/components/resource/workloadMetricValues.test.ts +65 -0
- package/src/components/resource/workloadMetricValues.ts +30 -0
- package/src/components/resources/PodFilePreview.test.tsx +131 -0
- package/src/components/resources/PodFilePreview.tsx +394 -0
- package/src/components/resources/PodFilesystemModal.tsx +157 -67
- package/src/components/resources/ResourcesView.tsx +44 -6
- package/src/components/resources/renderers/NamespaceRenderer.tsx +7 -1
- package/src/components/resources/renderers/PodRenderer.tsx +4 -0
- package/src/components/resources/renderers/RolloutRenderer.tsx +15 -1
- package/src/components/resources/renderers/WorkloadRenderer.tsx +3 -0
- package/src/components/rightsizing/RightsizingScanView.tsx +21 -0
- package/src/components/settings/SettingsDialog.tsx +94 -14
- package/src/components/traffic/TrafficFlowList.tsx +229 -70
- package/src/components/traffic/TrafficGraph.tsx +6 -6
- package/src/components/traffic/TrafficView.tsx +10 -9
- package/src/components/traffic/trafficFilters.test.ts +28 -1
- package/src/components/traffic/trafficFilters.ts +31 -0
- package/src/components/workload/WorkloadView.tsx +19 -8
- package/src/context/ConnectionContext.tsx +48 -3
- package/src/context/DiagnoseCustomization.test.tsx +26 -0
- package/src/context/DiagnoseCustomization.tsx +15 -2
- package/src/index.css +17 -0
- package/src/index.ts +1 -0
- package/src/utils/navigation.ts +1 -1
- package/src/utils/shell-safe.test.ts +25 -1
- package/src/utils/shell-safe.ts +16 -0
- package/src/utils/topology-namespace.test.ts +64 -0
- package/src/utils/topology-namespace.ts +18 -0
- package/src/components/diagnose/investigationEvidence.test.ts +0 -5202
- package/src/components/diagnose/investigationEvidence.ts +0 -4784
- package/src/components/gitops/GitOpsActions.tsx +0 -1
- package/src/components/gitops/GitOpsStatusBadge.tsx +0 -1
- package/src/components/gitops/ManagedResourcesList.tsx +0 -1
- package/src/components/gitops/SyncCountdown.tsx +0 -1
- package/src/components/gitops/index.ts +0 -4
- package/src/components/helm/ManifestDiffViewer.tsx +0 -69
- package/src/components/logs/JsonLogLine.tsx +0 -1
- package/src/components/logs/LogCore.tsx +0 -2
- package/src/components/logs/useLogBuffer.ts +0 -2
- package/src/components/logs/useLogSearch.ts +0 -1
- package/src/components/resource-drawer/ResourceDrawer.tsx +0 -216
- package/src/components/resources/drawer-components.tsx +0 -1
- package/src/components/resources/renderers/AlertRenderer.tsx +0 -1
- package/src/components/resources/renderers/CNPGBackupRenderer.tsx +0 -1
- package/src/components/resources/renderers/CNPGPoolerRenderer.tsx +0 -1
- package/src/components/resources/renderers/CNPGScheduledBackupRenderer.tsx +0 -1
- package/src/components/resources/renderers/CertificateRenderer.tsx +0 -1
- package/src/components/resources/renderers/CertificateRequestRenderer.tsx +0 -1
- package/src/components/resources/renderers/ChallengeRenderer.tsx +0 -1
- package/src/components/resources/renderers/ClusterComplianceReportRenderer.tsx +0 -1
- package/src/components/resources/renderers/ClusterExternalSecretRenderer.tsx +0 -1
- package/src/components/resources/renderers/ClusterIssuerRenderer.tsx +0 -1
- package/src/components/resources/renderers/ConfigAuditReportRenderer.tsx +0 -1
- package/src/components/resources/renderers/ConfigMapRenderer.tsx +0 -1
- package/src/components/resources/renderers/CronJobRenderer.tsx +0 -1
- package/src/components/resources/renderers/CronWorkflowRenderer.tsx +0 -1
- package/src/components/resources/renderers/EventRenderer.tsx +0 -1
- package/src/components/resources/renderers/ExposedSecretReportRenderer.tsx +0 -1
- package/src/components/resources/renderers/ExternalSecretRenderer.tsx +0 -1
- package/src/components/resources/renderers/FluxHelmReleaseRenderer.tsx +0 -1
- package/src/components/resources/renderers/GRPCRouteRenderer.tsx +0 -1
- package/src/components/resources/renderers/GatewayClassRenderer.tsx +0 -1
- package/src/components/resources/renderers/GatewayRenderer.tsx +0 -1
- package/src/components/resources/renderers/GenericRenderer.tsx +0 -1
- package/src/components/resources/renderers/GitRepositoryRenderer.tsx +0 -1
- package/src/components/resources/renderers/HTTPRouteRenderer.tsx +0 -1
- package/src/components/resources/renderers/HelmRepositoryRenderer.tsx +0 -1
- package/src/components/resources/renderers/IngressClassRenderer.tsx +0 -1
- package/src/components/resources/renderers/IngressRenderer.tsx +0 -1
- package/src/components/resources/renderers/IstioAuthorizationPolicyRenderer.tsx +0 -1
- package/src/components/resources/renderers/IstioDestinationRuleRenderer.tsx +0 -1
- package/src/components/resources/renderers/IstioGatewayRenderer.tsx +0 -1
- package/src/components/resources/renderers/IstioPeerAuthenticationRenderer.tsx +0 -1
- package/src/components/resources/renderers/IstioServiceEntryRenderer.tsx +0 -1
- package/src/components/resources/renderers/IstioVirtualServiceRenderer.tsx +0 -1
- package/src/components/resources/renderers/JobRenderer.tsx +0 -1
- package/src/components/resources/renderers/KarpenterEC2NodeClassRenderer.tsx +0 -1
- package/src/components/resources/renderers/KarpenterNodeClaimRenderer.tsx +0 -1
- package/src/components/resources/renderers/KedaScaledJobRenderer.tsx +0 -1
- package/src/components/resources/renderers/KedaScaledObjectRenderer.tsx +0 -1
- package/src/components/resources/renderers/KedaTriggerAuthRenderer.tsx +0 -1
- package/src/components/resources/renderers/KnativeConfigurationRenderer.tsx +0 -1
- package/src/components/resources/renderers/KnativeEventingRenderer.tsx +0 -1
- package/src/components/resources/renderers/KnativeFlowRenderer.tsx +0 -1
- package/src/components/resources/renderers/KnativeNetworkingRenderer.tsx +0 -1
- package/src/components/resources/renderers/KnativeRevisionRenderer.tsx +0 -1
- package/src/components/resources/renderers/KnativeRouteRenderer.tsx +0 -1
- package/src/components/resources/renderers/KnativeServiceRenderer.tsx +0 -1
- package/src/components/resources/renderers/KnativeSourceRenderer.tsx +0 -1
- package/src/components/resources/renderers/KustomizationRenderer.tsx +0 -1
- package/src/components/resources/renderers/KyvernoPolicyReportRenderer.tsx +0 -1
- package/src/components/resources/renderers/LeaseRenderer.tsx +0 -1
- package/src/components/resources/renderers/NetworkPolicyRenderer.tsx +0 -1
- package/src/components/resources/renderers/OCIRepositoryRenderer.tsx +0 -1
- package/src/components/resources/renderers/OrderRenderer.tsx +0 -1
- package/src/components/resources/renderers/PersistentVolumeRenderer.tsx +0 -1
- package/src/components/resources/renderers/PodDisruptionBudgetRenderer.tsx +0 -1
- package/src/components/resources/renderers/PodMonitorRenderer.tsx +0 -1
- package/src/components/resources/renderers/PriorityClassRenderer.tsx +0 -1
- package/src/components/resources/renderers/PrometheusRuleRenderer.tsx +0 -1
- package/src/components/resources/renderers/ReplicaSetRenderer.tsx +0 -1
- package/src/components/resources/renderers/RuntimeClassRenderer.tsx +0 -1
- package/src/components/resources/renderers/SbomReportRenderer.tsx +0 -1
- package/src/components/resources/renderers/SealedSecretRenderer.tsx +0 -1
- package/src/components/resources/renderers/SecretRenderer.tsx +0 -1
- package/src/components/resources/renderers/SecretStoreRenderer.tsx +0 -1
- package/src/components/resources/renderers/ServiceMonitorRenderer.tsx +0 -1
- package/src/components/resources/renderers/SimpleRouteRenderer.tsx +0 -1
- package/src/components/resources/renderers/StorageClassRenderer.tsx +0 -1
- package/src/components/resources/renderers/TraefikIngressRouteRenderer.tsx +0 -1
- package/src/components/resources/renderers/VPARenderer.tsx +0 -1
- package/src/components/resources/renderers/VeleroScheduleRenderer.tsx +0 -1
- package/src/components/resources/renderers/VeleroVSLRenderer.tsx +0 -1
- package/src/components/resources/renderers/VulnerabilityReportRenderer.tsx +0 -1
- package/src/components/resources/renderers/WebhookConfigRenderer.tsx +0 -1
- package/src/components/resources/renderers/WorkflowRenderer.tsx +0 -1
- package/src/components/resources/renderers/WorkflowTemplateRenderer.tsx +0 -1
- package/src/components/resources/renderers/argo-cells.tsx +0 -1
- package/src/components/resources/renderers/certmanager-cells.tsx +0 -1
- package/src/components/resources/renderers/cnpg-cells.tsx +0 -1
- package/src/components/resources/renderers/eso-cells.tsx +0 -1
- package/src/components/resources/renderers/flux-cells.tsx +0 -1
- package/src/components/resources/renderers/index.ts +0 -94
- package/src/components/resources/renderers/istio-cells.tsx +0 -1
- package/src/components/resources/renderers/karpenter-cells.tsx +0 -1
- package/src/components/resources/renderers/keda-cells.tsx +0 -1
- package/src/components/resources/renderers/knative-cells.tsx +0 -1
- package/src/components/resources/renderers/kyverno-cells.tsx +0 -1
- package/src/components/resources/renderers/prometheus-cells.tsx +0 -1
- package/src/components/resources/renderers/traefik-cells.tsx +0 -1
- package/src/components/resources/renderers/trivy-cells.tsx +0 -1
- package/src/components/resources/renderers/trivy-shared.tsx +0 -1
- package/src/components/resources/renderers/velero-cells.tsx +0 -1
- package/src/components/resources/resource-utils-argo.ts +0 -2
- package/src/components/resources/resource-utils-certmanager.ts +0 -2
- package/src/components/resources/resource-utils-eso.ts +0 -2
- package/src/components/resources/resource-utils-flux.ts +0 -2
- package/src/components/resources/resource-utils-istio.ts +0 -2
- package/src/components/resources/resource-utils-karpenter.ts +0 -2
- package/src/components/resources/resource-utils-knative.ts +0 -2
- package/src/components/resources/resource-utils-kyverno.ts +0 -2
- package/src/components/resources/resource-utils-prometheus.ts +0 -2
- package/src/components/resources/resource-utils-traefik.ts +0 -1
- package/src/components/resources/resource-utils-trivy.ts +0 -2
- package/src/components/resources/resource-utils-velero.ts +0 -2
- package/src/components/shared/EditableYamlView.tsx +0 -24
- package/src/components/timeline/DiffViewer.tsx +0 -1
- package/src/components/traffic/index.ts +0 -3
- package/src/components/ui/ForceDeleteConfirmDialog.tsx +0 -1
- package/src/components/ui/MetricsChart.tsx +0 -1
- 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
|
|
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,
|
|
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
|
|
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 (
|
|
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
|
+
})
|