@skyhook-io/radar-app 1.9.3 → 1.9.5
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 +1 -1
- package/src/App.tsx +26 -6
- package/src/api/client.ts +251 -0
- package/src/api/config.test.ts +19 -0
- package/src/components/CloudConnectFlow.tsx +605 -0
- package/src/components/CloudFunnelButton.tsx +499 -0
- package/src/components/ConnectionErrorView.tsx +2 -1
- package/src/components/UserMenu.tsx +3 -2
- package/src/components/audit/AuditView.tsx +13 -0
- package/src/components/audit/ChecksViewTabs.test.tsx +20 -0
- package/src/components/audit/ChecksViewTabs.tsx +43 -0
- package/src/components/audit/UpgradeReadinessView.test.ts +130 -0
- package/src/components/audit/UpgradeReadinessView.tsx +699 -0
- package/src/components/capacity/shared.tsx +9 -2
- package/src/components/diagnose/DiagnoseSurface.tsx +2 -1
- package/src/components/home/ClusterHealthCard.tsx +10 -5
- package/src/components/nav/PrimaryNavRail.tsx +4 -1
- package/src/components/resources/ImageFilesystemModal.tsx +4 -1
- package/src/components/settings/SettingsDialog.tsx +15 -6
- package/src/components/traffic/TrafficWizard.tsx +4 -2
- package/src/main.tsx +7 -1
- package/src/vite-env.d.ts +8 -0
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
import { describe, expect, it } from 'vitest'
|
|
2
|
+
import { renderToStaticMarkup } from 'react-dom/server'
|
|
3
|
+
import { ApiError } from '../../api/client'
|
|
4
|
+
import { UPGRADE_IMPACT_DOCS_URL, UPGRADE_IMPACT_MIN_RADAR_VERSION, UpgradeReadinessError, groupFindings, incompleteUpgradeCheckCount, issueSpecificReferences, summaryMeta, upgradeEvaluationSummary } from './UpgradeReadinessView'
|
|
5
|
+
|
|
6
|
+
describe('UpgradeReadinessError', () => {
|
|
7
|
+
it('maps an unmatched endpoint 404 to the v1.9 upgrade message', () => {
|
|
8
|
+
const html = renderToStaticMarkup(UpgradeReadinessError({ error: new ApiError('Unknown error', 404) }))
|
|
9
|
+
|
|
10
|
+
expect(html).toContain('Upgrade impact needs a newer Radar')
|
|
11
|
+
expect(html).toContain(UPGRADE_IMPACT_MIN_RADAR_VERSION)
|
|
12
|
+
expect(html).toContain('Upgrade the in-cluster Radar to enable it')
|
|
13
|
+
expect(html).not.toContain('Unable to analyze upgrade impact')
|
|
14
|
+
})
|
|
15
|
+
|
|
16
|
+
it('keeps a real resource 404 on the generic error state', () => {
|
|
17
|
+
const html = renderToStaticMarkup(UpgradeReadinessError({ error: new ApiError('Cluster not found', 404) }))
|
|
18
|
+
|
|
19
|
+
expect(html).toContain('Unable to analyze upgrade impact')
|
|
20
|
+
expect(html).toContain('Cluster not found')
|
|
21
|
+
expect(html).not.toContain('needs a newer Radar')
|
|
22
|
+
})
|
|
23
|
+
|
|
24
|
+
it('keeps ordinary failures on the generic error state', () => {
|
|
25
|
+
const html = renderToStaticMarkup(UpgradeReadinessError({ error: new ApiError('boom', 500) }))
|
|
26
|
+
|
|
27
|
+
expect(html).toContain('Unable to analyze upgrade impact')
|
|
28
|
+
expect(html).toContain('boom')
|
|
29
|
+
expect(html).not.toContain('needs a newer Radar')
|
|
30
|
+
})
|
|
31
|
+
})
|
|
32
|
+
|
|
33
|
+
describe('upgradeEvaluationSummary', () => {
|
|
34
|
+
it('distinguishes applicable, incomplete, and not-applicable checks', () => {
|
|
35
|
+
expect(upgradeEvaluationSummary(18, {
|
|
36
|
+
blocked: 1,
|
|
37
|
+
warnings: 0,
|
|
38
|
+
reviews: 0,
|
|
39
|
+
passed: 14,
|
|
40
|
+
unknown: 1,
|
|
41
|
+
notApplicable: 2,
|
|
42
|
+
findings: 1,
|
|
43
|
+
})).toBe('18 evaluated · 16 applicable · 1 with partial evidence · 2 not applicable')
|
|
44
|
+
})
|
|
45
|
+
|
|
46
|
+
it('does not imply incomplete coverage when every applicable check completed', () => {
|
|
47
|
+
expect(upgradeEvaluationSummary(10, {
|
|
48
|
+
blocked: 0,
|
|
49
|
+
warnings: 0,
|
|
50
|
+
reviews: 0,
|
|
51
|
+
passed: 10,
|
|
52
|
+
unknown: 0,
|
|
53
|
+
notApplicable: 0,
|
|
54
|
+
findings: 0,
|
|
55
|
+
})).toBe('10 evaluated · 10 applicable · 0 not applicable')
|
|
56
|
+
})
|
|
57
|
+
|
|
58
|
+
it('links to the documented catalog rather than inventing a second catalog in the UI', () => {
|
|
59
|
+
expect(UPGRADE_IMPACT_DOCS_URL).toContain('/features/upgrade-impact')
|
|
60
|
+
})
|
|
61
|
+
|
|
62
|
+
it('counts incomplete evidence even when a blocker takes status precedence', () => {
|
|
63
|
+
expect(incompleteUpgradeCheckCount([
|
|
64
|
+
{ status: 'blocked', caveat: 'one PDB has stale status' },
|
|
65
|
+
{ status: 'unknown' },
|
|
66
|
+
{ status: 'passed' },
|
|
67
|
+
])).toBe(2)
|
|
68
|
+
})
|
|
69
|
+
})
|
|
70
|
+
|
|
71
|
+
describe('groupFindings', () => {
|
|
72
|
+
it('groups repeated remediation inside a check without merging distinct action levels', () => {
|
|
73
|
+
const base = {
|
|
74
|
+
ruleID: 'admission-webhook-readiness',
|
|
75
|
+
title: 'Backend unavailable',
|
|
76
|
+
resource: { kind: 'ValidatingWebhookConfiguration', namespace: '', name: 'policy' },
|
|
77
|
+
evidence: { source: 'live', path: 'webhooks[0].clientConfig.service' },
|
|
78
|
+
impact: 'Admission is unavailable.',
|
|
79
|
+
remediation: 'Restore the backend.',
|
|
80
|
+
references: [],
|
|
81
|
+
}
|
|
82
|
+
const groups = groupFindings([
|
|
83
|
+
{ ...base, level: 'blocker', evidence: { ...base.evidence, detail: 'default/a' } },
|
|
84
|
+
{ ...base, level: 'blocker', evidence: { ...base.evidence, detail: 'default/b' } },
|
|
85
|
+
{ ...base, level: 'warning', evidence: { ...base.evidence, detail: 'default/c' } },
|
|
86
|
+
])
|
|
87
|
+
expect(groups.map((group) => group.total)).toEqual([2, 1])
|
|
88
|
+
})
|
|
89
|
+
|
|
90
|
+
it('counts findings even when multiple findings do not map one-to-one to resources', () => {
|
|
91
|
+
const base = {
|
|
92
|
+
ruleID: 'renamed-control-plane-metrics',
|
|
93
|
+
title: 'Metric renamed',
|
|
94
|
+
level: 'review' as const,
|
|
95
|
+
evidence: { source: 'PrometheusRule', path: 'spec.groups' },
|
|
96
|
+
impact: 'A query stops matching.',
|
|
97
|
+
remediation: 'Update the query.',
|
|
98
|
+
references: [],
|
|
99
|
+
}
|
|
100
|
+
const groups = groupFindings([
|
|
101
|
+
{ ...base, evidence: { ...base.evidence, detail: 'old_a' } },
|
|
102
|
+
{ ...base, evidence: { ...base.evidence, detail: 'old_b' } },
|
|
103
|
+
])
|
|
104
|
+
expect(groups[0].total).toBe(2)
|
|
105
|
+
})
|
|
106
|
+
})
|
|
107
|
+
|
|
108
|
+
describe('summaryMeta', () => {
|
|
109
|
+
it('keeps partial coverage explicit even when a confirmed blocker takes precedence', () => {
|
|
110
|
+
const meta = summaryMeta('blocked', '1.36', '1.36', {
|
|
111
|
+
blocked: 1,
|
|
112
|
+
warnings: 0,
|
|
113
|
+
reviews: 0,
|
|
114
|
+
unknown: 2,
|
|
115
|
+
}, 'partial')
|
|
116
|
+
expect(meta.body).toContain('Evidence coverage is also incomplete')
|
|
117
|
+
})
|
|
118
|
+
})
|
|
119
|
+
|
|
120
|
+
describe('issueSpecificReferences', () => {
|
|
121
|
+
it('keeps check-wide sources at the parent while preserving issue-specific sources', () => {
|
|
122
|
+
const checkReferences = [{ title: 'Admission webhook good practices', url: 'https://example.com/admission' }]
|
|
123
|
+
const findingReferences = [
|
|
124
|
+
...checkReferences,
|
|
125
|
+
{ title: 'Matching requests: matchPolicy', url: 'https://example.com/admission#match-policy' },
|
|
126
|
+
]
|
|
127
|
+
|
|
128
|
+
expect(issueSpecificReferences(checkReferences, findingReferences)).toEqual([findingReferences[1]])
|
|
129
|
+
})
|
|
130
|
+
})
|