@skyhook-io/k8s-ui 1.17.0 → 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.
- package/README.md +41 -0
- package/package.json +3 -3
- package/src/components/charts/AreaChart.interaction.test.tsx +118 -0
- package/src/components/charts/AreaChart.test.tsx +25 -0
- package/src/components/charts/AreaChart.tsx +141 -12
- package/src/components/charts/index.ts +1 -0
- package/src/components/checks/ChecksView.tsx +18 -1
- package/src/components/cnpg/CNPGBackupSummary.tsx +57 -9
- package/src/components/cnpg/CNPGClusterHASection.test.tsx +90 -0
- package/src/components/cnpg/CNPGClusterHASection.tsx +331 -0
- package/src/components/cnpg/CNPGClusterSummary.test.tsx +275 -0
- package/src/components/cnpg/CNPGClusterSummary.tsx +310 -96
- package/src/components/cnpg/CNPGConnectSection.test.tsx +64 -0
- package/src/components/cnpg/CNPGConnectSection.tsx +178 -0
- package/src/components/cnpg/CNPGDeclarativeSummary.tsx +157 -26
- package/src/components/cnpg/CNPGImageCatalogSummary.tsx +7 -4
- package/src/components/cnpg/CNPGLogicalPath.test.tsx +22 -0
- package/src/components/cnpg/CNPGLogicalPath.tsx +150 -0
- package/src/components/cnpg/CNPGObjectStoreSummary.tsx +9 -6
- package/src/components/cnpg/CNPGObjectSummary.test.tsx +193 -2
- package/src/components/cnpg/CNPGPoolerScheduling.test.tsx +36 -0
- package/src/components/cnpg/CNPGPoolerSummary.tsx +241 -14
- package/src/components/cnpg/CNPGSharedSummary.tsx +15 -8
- package/src/components/cnpg/CNPGWALArchivingFact.test.tsx +39 -0
- package/src/components/cnpg/backupRuns.test.ts +17 -0
- package/src/components/cnpg/backupRuns.ts +19 -0
- package/src/components/cnpg/connect.test.ts +124 -0
- package/src/components/cnpg/connect.ts +155 -0
- package/src/components/cnpg/databaseRole.test.ts +38 -0
- package/src/components/cnpg/databaseRole.ts +80 -0
- package/src/components/cnpg/ha.test.ts +446 -0
- package/src/components/cnpg/ha.ts +621 -0
- package/src/components/cnpg/index.ts +18 -0
- package/src/components/cnpg/logicalReplication.test.ts +121 -0
- package/src/components/cnpg/logicalReplication.ts +252 -0
- package/src/components/cnpg/pooler.test.ts +84 -0
- package/src/components/cnpg/pooler.ts +225 -0
- package/src/components/cnpg/primitives.tsx +21 -117
- package/src/components/cnpg/problems.ts +12 -0
- package/src/components/cnpg/relations.test.ts +63 -13
- package/src/components/cnpg/relations.ts +61 -52
- package/src/components/cnpg/schedule.ts +35 -0
- package/src/components/cnpg/workspace-disk.test.ts +97 -0
- package/src/components/cnpg/workspace-fleet-metrics.test.ts +250 -0
- package/src/components/cnpg/workspace-problems.test.ts +175 -0
- package/src/components/cnpg/workspace.test.ts +380 -9
- package/src/components/cnpg/workspace.ts +1137 -136
- package/src/components/dock/DockContext.test.tsx +32 -0
- package/src/components/dock/DockContext.tsx +18 -4
- package/src/components/dock/LocalTerminalTab.test.tsx +40 -0
- package/src/components/dock/LocalTerminalTab.tsx +4 -3
- package/src/components/dock/TerminalTab.tsx +20 -8
- package/src/components/facts/certainty.tsx +36 -0
- package/src/components/facts/facts.test.tsx +24 -0
- package/src/components/facts/facts.tsx +62 -0
- package/src/components/facts/index.ts +6 -0
- package/src/components/facts/managed-by.test.tsx +17 -0
- package/src/components/facts/managed-by.tsx +36 -0
- package/src/components/facts/public-exports.test.tsx +35 -0
- package/src/components/gitops/GitOpsGraphFilterRail.tsx +6 -2
- package/src/components/gitops/GitOpsHealthSourceNotice.test.tsx +38 -0
- package/src/components/gitops/GitOpsHealthSourceNotice.tsx +13 -3
- package/src/components/gitops/insights/ArgoResourceDiff.test.tsx +115 -0
- package/src/components/gitops/insights/ArgoResourceDiff.tsx +22 -5
- package/src/components/gitops/insights/DriftPanel.tsx +62 -0
- package/src/components/gitops/insights/GitOpsInsightViews.tsx +24 -71
- package/src/components/gitops/tree/GitOpsTreeGraph.test.ts +26 -1
- package/src/components/gitops/tree/GitOpsTreeGraph.tsx +38 -55
- package/src/components/gitops/tree/layout.test.ts +111 -0
- package/src/components/gitops/tree/layout.ts +128 -0
- package/src/components/gitops/tree/tree-helpers.test.ts +17 -0
- package/src/components/gitops/tree/tree-helpers.ts +7 -1
- package/src/components/issues/ConditionalCleanup.test.tsx +82 -0
- package/src/components/issues/IssuesView.tsx +40 -5
- package/src/components/issues/ResourceIssuesSection.test.tsx +28 -0
- package/src/components/issues/ResourceIssuesSection.tsx +3 -0
- package/src/components/issues/index.ts +3 -1
- package/src/components/issues/issue-timing.ts +12 -1
- package/src/components/issues/issues.test.ts +49 -1
- package/src/components/issues/severity.ts +21 -0
- package/src/components/issues/types.ts +23 -0
- package/src/components/logs/LogCore.theme.test.tsx +81 -3
- package/src/components/logs/LogCore.tsx +25 -17
- package/src/components/logs/LogToolbarSelects.tsx +3 -1
- package/src/components/logs/LogsViewer.test.tsx +291 -0
- package/src/components/logs/LogsViewer.tsx +72 -11
- package/src/components/logs/StructuredLogLine.test.tsx +76 -0
- package/src/components/logs/StructuredLogLine.tsx +22 -30
- package/src/components/logs/WorkloadLogsViewer.test.tsx +65 -0
- package/src/components/logs/WorkloadLogsViewer.tsx +39 -10
- package/src/components/logs/useLogBuffer.test.ts +8 -0
- package/src/components/logs/useLogStream.ts +17 -1
- package/src/components/problems/index.ts +1 -0
- package/src/components/problems/problems.test.tsx +63 -0
- package/src/components/problems/problems.tsx +165 -0
- package/src/components/resources/ActiveFilterBar.test.tsx +78 -0
- package/src/components/resources/ActiveFilterBar.tsx +86 -0
- package/src/components/resources/ResourcesSidebar.keyboard.test.tsx +42 -0
- package/src/components/resources/ResourcesSidebar.test.tsx +36 -2
- package/src/components/resources/ResourcesSidebar.tsx +92 -41
- package/src/components/resources/ResourcesView.kind-filter-memory.test.ts +52 -0
- package/src/components/resources/ResourcesView.kind-view.test.tsx +149 -0
- package/src/components/resources/ResourcesView.per-container.test.ts +56 -1
- package/src/components/resources/ResourcesView.tsx +302 -109
- package/src/components/resources/curated-column-ownership.test.ts +3 -3
- package/src/components/resources/renderers/AutoscalingReferences.test.tsx +286 -0
- package/src/components/resources/renderers/CAPIClusterRenderer.tsx +3 -2
- package/src/components/resources/renderers/CAPIExactReferenceNavigation.test.tsx +27 -0
- package/src/components/resources/renderers/CNPGBackupRenderer.test.tsx +23 -0
- package/src/components/resources/renderers/CNPGBackupRenderer.tsx +8 -19
- package/src/components/resources/renderers/CNPGClusterRenderer.tsx +5 -7
- package/src/components/resources/renderers/CNPGDeclarativeRenderer.tsx +25 -0
- package/src/components/resources/renderers/CNPGScheduledBackupRenderer.test.tsx +32 -0
- package/src/components/resources/renderers/CNPGScheduledBackupRenderer.tsx +7 -15
- package/src/components/resources/renderers/CompositeRenderer.navigation.test.tsx +35 -0
- package/src/components/resources/renderers/CompositeRenderer.tsx +3 -3
- package/src/components/resources/renderers/CompositionRenderer.tsx +3 -3
- package/src/components/resources/renderers/ContourExactReferenceNavigation.test.tsx +34 -0
- package/src/components/resources/renderers/ContourHTTPProxyRenderer.tsx +11 -10
- package/src/components/resources/renderers/EndpointSliceRenderer.test.tsx +39 -1
- package/src/components/resources/renderers/EndpointSliceRenderer.tsx +14 -13
- package/src/components/resources/renderers/HPARenderer.tsx +56 -16
- package/src/components/resources/renderers/IngressRenderer.defaultBackend.test.tsx +33 -0
- package/src/components/resources/renderers/IngressRenderer.tsx +30 -3
- package/src/components/resources/renderers/IstioDestinationRuleRenderer.tsx +9 -9
- package/src/components/resources/renderers/IstioExactReferenceNavigation.test.tsx +34 -0
- package/src/components/resources/renderers/IstioVirtualServiceRenderer.tsx +5 -8
- package/src/components/resources/renderers/KedaScaledObjectRenderer.tsx +13 -11
- package/src/components/resources/renderers/KnativeEventingRenderer.tsx +24 -31
- package/src/components/resources/renderers/KnativeExactReferenceNavigation.test.tsx +47 -0
- package/src/components/resources/renderers/KnativeFlowRenderer.tsx +6 -7
- package/src/components/resources/renderers/KnativeNetworkingRenderer.tsx +7 -9
- package/src/components/resources/renderers/KnativeReferenceLink.tsx +14 -0
- package/src/components/resources/renderers/KnativeReferenceLinks.test.tsx +60 -0
- package/src/components/resources/renderers/KnativeSourceRenderer.tsx +9 -11
- package/src/components/resources/renderers/ManagedResourceRenderer.tsx +2 -2
- package/src/components/resources/renderers/MetricsUnavailableNotice.tsx +3 -2
- package/src/components/resources/renderers/PodRenderer.test.tsx +32 -0
- package/src/components/resources/renderers/PodRenderer.tsx +41 -29
- package/src/components/resources/renderers/SimpleRouteRenderer.tsx +1 -1
- package/src/components/resources/renderers/VPARenderer.tsx +17 -11
- package/src/components/resources/resource-utils-capi.ts +1 -1
- package/src/components/resources/resource-utils-cnpg.test.ts +35 -10
- package/src/components/resources/resource-utils-cnpg.ts +51 -20
- package/src/components/resources/resource-utils-contour.test.ts +44 -0
- package/src/components/resources/resource-utils-contour.ts +16 -0
- package/src/components/resources/resource-utils-istio.ts +12 -8
- package/src/components/resources/resource-utils-knative.ts +18 -0
- package/src/components/resources/resource-utils.ts +82 -6
- package/src/components/resources/rules-column-width.test.ts +1 -1
- package/src/components/resources/summarize-scheduler-message.test.ts +30 -0
- package/src/components/shared/ActionConfirmDialog.test.tsx +154 -0
- package/src/components/shared/ActionConfirmDialog.tsx +246 -0
- package/src/components/shared/CreateResourceDialog.test.tsx +330 -0
- package/src/components/shared/CreateResourceDialog.tsx +126 -80
- package/src/components/shared/DetailShell.test.tsx +80 -0
- package/src/components/shared/DetailShell.tsx +85 -10
- package/src/components/shared/EditableYamlView.test.tsx +7 -0
- package/src/components/shared/EditableYamlView.tsx +1 -1
- package/src/components/shared/ResourceActionsBar.tsx +11 -4
- package/src/components/shared/ResourceRendererDispatch.tsx +12 -9
- package/src/components/shared/index.ts +2 -0
- package/src/components/timeline/TimelineSwimlanes.tsx +1 -1
- package/src/components/timeline/timeline-filters.ts +1 -0
- package/src/components/topology/K8sResourceNode.test.ts +18 -1
- package/src/components/topology/K8sResourceNode.tsx +44 -2
- package/src/components/topology/TopologyFilterSidebar.tsx +1 -0
- package/src/components/topology/TopologyGraph.tsx +3 -71
- package/src/components/topology/TopologySearch.tsx +1 -0
- package/src/components/topology/internet-groups.test.ts +35 -0
- package/src/components/topology/internet-groups.ts +55 -0
- package/src/components/topology/layout.ts +1 -1
- package/src/components/ui/Badge.tsx +1 -0
- package/src/components/ui/FoldSection.tsx +68 -0
- package/src/components/ui/ForceDeleteConfirmDialog.test.tsx +62 -0
- package/src/components/ui/ForceDeleteConfirmDialog.tsx +24 -12
- package/src/components/ui/RefLink.tsx +20 -0
- package/src/components/ui/RowActionMenu.tsx +8 -3
- package/src/components/ui/crd-definition-navigation.test.tsx +160 -0
- package/src/components/ui/drawer-components.test.tsx +110 -1
- package/src/components/ui/drawer-components.tsx +62 -9
- package/src/components/ui/index.ts +3 -1
- package/src/components/ui/monacoEditorFeatures.ts +77 -0
- package/src/components/ui/monacoRuntime.ts +14 -19
- package/src/components/ui/status-tone.test.ts +10 -1
- package/src/components/ui/status-tone.tsx +29 -0
- package/src/components/workload/WorkloadView.deleted.test.tsx +43 -0
- package/src/components/workload/WorkloadView.relationships.test.tsx +37 -0
- package/src/components/workload/WorkloadView.spec.test.tsx +149 -0
- package/src/components/workload/WorkloadView.tsx +180 -64
- package/src/index.ts +14 -1
- package/src/monaco-deep.d.ts +19 -12
- package/src/theme/components.css +13 -0
- package/src/types/capacity.ts +11 -2
- package/src/types/core.ts +29 -2
- package/src/types/gitops-flux-status.test.ts +32 -1
- package/src/types/gitops-insights.ts +19 -2
- package/src/types/gitops-tree.ts +10 -0
- package/src/types/gitops.ts +26 -6
- package/src/utils/api-resources.ts +7 -0
- package/src/utils/application-history.ts +1 -0
- package/src/utils/cnpg-backup.test.ts +26 -0
- package/src/utils/cnpg-backup.ts +70 -0
- package/src/utils/format.test.ts +9 -0
- package/src/utils/format.ts +4 -0
- package/src/utils/gitops-owner.ts +3 -2
- package/src/utils/grant.test.ts +19 -0
- package/src/utils/grant.ts +35 -0
- package/src/utils/index.ts +4 -0
- package/src/utils/k8s-errors.ts +7 -7
- package/src/utils/log-format.test.ts +41 -0
- package/src/utils/log-format.ts +71 -15
- package/src/utils/log-level.ts +12 -8
- package/src/utils/navigation.test.ts +57 -0
- package/src/utils/navigation.ts +86 -8
- package/src/utils/object-reference-navigation.test.ts +43 -0
- package/src/utils/resource-hierarchy.test.ts +37 -0
- package/src/utils/resource-hierarchy.ts +15 -7
- package/src/utils/resource-icons.ts +2 -0
- package/src/utils/skeleton-yaml.test.ts +21 -0
- package/src/utils/skeleton-yaml.ts +16 -0
- package/src/utils/url-path.test.ts +35 -0
- package/src/utils/url-path.ts +21 -0
- package/src/utils/validators.test.ts +4 -13
- package/src/utils/validators.ts +5 -19
- package/src/utils/view-transition.test.ts +35 -1
- package/src/utils/view-transition.ts +16 -1
|
@@ -0,0 +1,275 @@
|
|
|
1
|
+
// @vitest-environment jsdom
|
|
2
|
+
import { act, type ReactNode } from 'react'
|
|
3
|
+
import { createRoot } from 'react-dom/client'
|
|
4
|
+
import { afterEach, describe, expect, it, vi } from 'vitest'
|
|
5
|
+
import { CNPGClusterSummary, CNPGDimensionMark, CNPGDimensionVerdict, CNPGServingStatus } from './CNPGClusterSummary'
|
|
6
|
+
import type { CNPGFleetRow, CNPGProblem } from './workspace'
|
|
7
|
+
import type { CNPGDimension } from './ha'
|
|
8
|
+
import { OpenIssueContext } from '../problems'
|
|
9
|
+
|
|
10
|
+
;(globalThis as { IS_REACT_ACT_ENVIRONMENT?: boolean }).IS_REACT_ACT_ENVIRONMENT = true
|
|
11
|
+
|
|
12
|
+
function row(over: Partial<CNPGFleetRow> = {}): CNPGFleetRow {
|
|
13
|
+
return {
|
|
14
|
+
key: 'db/pg',
|
|
15
|
+
namespace: 'db',
|
|
16
|
+
name: 'pg',
|
|
17
|
+
cluster: { status: { currentPrimary: 'pg-1' } },
|
|
18
|
+
controllerStatus: { text: 'Healthy', level: 'healthy' },
|
|
19
|
+
instances: { ready: 2, desired: 2 },
|
|
20
|
+
pods: [
|
|
21
|
+
{ name: 'pg-1', role: 'primary', ready: true },
|
|
22
|
+
{ name: 'pg-2', role: 'replica', ready: true },
|
|
23
|
+
],
|
|
24
|
+
replicaCluster: null,
|
|
25
|
+
hibernated: false,
|
|
26
|
+
pgVersion: '17',
|
|
27
|
+
catalog: null,
|
|
28
|
+
replication: { text: 'Lag unknown', tone: 'unknown' },
|
|
29
|
+
protection: {
|
|
30
|
+
schedule: { text: 'Active', tone: 'healthy', names: [] },
|
|
31
|
+
destination: { text: 'ObjectStore', tone: 'healthy', method: 'plugin' },
|
|
32
|
+
lastSuccessfulBackup: { text: '1 h ago', tone: 'healthy' },
|
|
33
|
+
walArchiving: { text: 'Archiving', tone: 'healthy' },
|
|
34
|
+
recoveryWindow: { text: 'x', tone: 'healthy' },
|
|
35
|
+
restoreValidation: { text: 'None recorded', tone: 'unknown' },
|
|
36
|
+
summary: { text: 'ok', tone: 'healthy' },
|
|
37
|
+
},
|
|
38
|
+
declarations: { summary: { text: 'None', tone: 'neutral' }, total: 0, failed: 0, pending: 0 },
|
|
39
|
+
poolers: [],
|
|
40
|
+
poolersKnown: true,
|
|
41
|
+
problems: [],
|
|
42
|
+
attention: false,
|
|
43
|
+
categories: new Set(),
|
|
44
|
+
...over,
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
const problem = (id: string, severity: CNPGProblem['severity'], title: string): Extract<CNPGProblem, { source: 'issue' }> => ({
|
|
49
|
+
id,
|
|
50
|
+
severity,
|
|
51
|
+
category: 'availability',
|
|
52
|
+
title,
|
|
53
|
+
subject: { kind: 'Backup', group: 'postgresql.cnpg.io', namespace: 'db', name: `b-${id}` },
|
|
54
|
+
source: 'issue',
|
|
55
|
+
})
|
|
56
|
+
|
|
57
|
+
function render(node: ReactNode) {
|
|
58
|
+
const host = document.createElement('div')
|
|
59
|
+
document.body.appendChild(host)
|
|
60
|
+
const root = createRoot(host)
|
|
61
|
+
act(() => root.render(node))
|
|
62
|
+
return root
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
afterEach(() => {
|
|
66
|
+
document.body.innerHTML = ''
|
|
67
|
+
})
|
|
68
|
+
|
|
69
|
+
describe('CNPGClusterSummary', () => {
|
|
70
|
+
it('keeps flat sections by default and frames only At a glance and About when asked', () => {
|
|
71
|
+
for (const framed of [false, true]) {
|
|
72
|
+
const root = render(<CNPGClusterSummary row={row({ problems: [problem('a', 'warning', 'Backup failed')] })} framed={framed} lead={<p>Maintenance notice</p>} operationalFacts={<div>Base backup progress</div>} stateFacts={<div>Existing host facts</div>} />)
|
|
73
|
+
const headings = [...document.querySelectorAll('h3')].filter((h) => ['At a glance', 'About'].includes(h.textContent!))
|
|
74
|
+
expect(headings).toHaveLength(2)
|
|
75
|
+
for (const heading of headings) expect(heading.closest('section') !== null).toBe(framed)
|
|
76
|
+
const glance = headings[0].parentElement!.parentElement!
|
|
77
|
+
const about = headings[1].parentElement!.parentElement!
|
|
78
|
+
if (framed) {
|
|
79
|
+
expect(glance.textContent).toContain('Base backup progress')
|
|
80
|
+
expect(about.textContent).not.toContain('Base backup progress')
|
|
81
|
+
expect(about.textContent).toContain('Existing host facts')
|
|
82
|
+
expect(glance.textContent).not.toContain('Existing host facts')
|
|
83
|
+
expect(glance.textContent).not.toContain('Maintenance notice')
|
|
84
|
+
expect(glance.textContent).not.toContain('Backup failed')
|
|
85
|
+
}
|
|
86
|
+
expect(document.body.textContent!.indexOf('Base backup progress')).toBeLessThan(document.body.textContent!.indexOf('About'))
|
|
87
|
+
expect(document.body.textContent!.indexOf('Existing host facts')).toBeGreaterThan(document.body.textContent!.indexOf('About'))
|
|
88
|
+
act(() => root.unmount())
|
|
89
|
+
}
|
|
90
|
+
const root = render(<CNPGClusterSummary row={row()} />)
|
|
91
|
+
expect(document.querySelector('section')).toBeNull()
|
|
92
|
+
act(() => root.unmount())
|
|
93
|
+
})
|
|
94
|
+
it('opens the other problems in place when the host links nowhere else', () => {
|
|
95
|
+
const onNavigate = vi.fn()
|
|
96
|
+
const r = row({ problems: [problem('a', 'critical', 'WAL archiving failing'), problem('b', 'warning', 'Backup failed'), problem('c', 'posture', 'No schedule')], attention: true })
|
|
97
|
+
const root = render(<CNPGClusterSummary row={r} onNavigate={onNavigate} />)
|
|
98
|
+
const more = [...document.querySelectorAll('button')].find((b) => b.textContent?.includes('+2 more'))!
|
|
99
|
+
expect(more.getAttribute('aria-expanded')).toBe('false')
|
|
100
|
+
act(() => more.click())
|
|
101
|
+
expect(more.getAttribute('aria-expanded')).toBe('true')
|
|
102
|
+
expect(document.body.textContent).toContain('Backup failed')
|
|
103
|
+
expect(document.body.textContent).toContain('No schedule')
|
|
104
|
+
act(() => [...document.querySelectorAll('button')].find((b) => b.textContent === 'b-b')!.click())
|
|
105
|
+
expect(onNavigate).toHaveBeenCalledWith(expect.objectContaining({ kind: 'Backup', name: 'b-b' }))
|
|
106
|
+
act(() => root.unmount())
|
|
107
|
+
})
|
|
108
|
+
it('can open with every problem listed', () => {
|
|
109
|
+
const r = row({ problems: [problem('a', 'critical', 'WAL archiving failing'), problem('b', 'warning', 'Backup failed')], attention: true })
|
|
110
|
+
const root = render(<CNPGClusterSummary row={r} initialProblemsExpanded />)
|
|
111
|
+
const more = [...document.querySelectorAll('button')].find((b) => b.getAttribute('aria-expanded') !== null)!
|
|
112
|
+
expect(more.getAttribute('aria-expanded')).toBe('true')
|
|
113
|
+
act(() => root.unmount())
|
|
114
|
+
})
|
|
115
|
+
it('keeps a host link as the override', () => {
|
|
116
|
+
const r = row({ problems: [problem('a', 'warning', 'One'), problem('b', 'warning', 'Two')], attention: true })
|
|
117
|
+
const root = render(<CNPGClusterSummary row={r} problemsLink={(n) => <a href="#all">All {n}</a>} />)
|
|
118
|
+
expect(document.body.textContent).toContain('All 2')
|
|
119
|
+
expect([...document.querySelectorAll('button')].some((b) => b.textContent?.includes('+1 more'))).toBe(false)
|
|
120
|
+
act(() => root.unmount())
|
|
121
|
+
})
|
|
122
|
+
it('marks a tab only when its dimension needs a look or could not be assessed', () => {
|
|
123
|
+
const dim = (tone: CNPGDimension['tone']): CNPGDimension => ({ id: 'protection', label: 'Backups', tone, text: 'verdict', source: 's' })
|
|
124
|
+
for (const tone of ['healthy', 'neutral'] as const) {
|
|
125
|
+
const root = render(<CNPGDimensionMark dimension={dim(tone)} />)
|
|
126
|
+
expect(document.querySelector('[aria-label="Backups: verdict"]')).toBeNull()
|
|
127
|
+
act(() => root.unmount())
|
|
128
|
+
}
|
|
129
|
+
for (const tone of ['degraded', 'unhealthy', 'unknown'] as const) {
|
|
130
|
+
const root = render(<CNPGDimensionMark dimension={dim(tone)} />)
|
|
131
|
+
expect(document.querySelector('[aria-label="Backups: verdict"]')).not.toBeNull()
|
|
132
|
+
act(() => root.unmount())
|
|
133
|
+
}
|
|
134
|
+
})
|
|
135
|
+
it("says on the tab why it is marked, and nothing when it is not", () => {
|
|
136
|
+
const backups = (tone: CNPGDimension['tone']): CNPGDimension => ({ id: 'protection', label: 'Backups', tone, text: 'no backup destination', source: 'Cluster spec' })
|
|
137
|
+
let root = render(<CNPGDimensionVerdict dimension={backups('degraded')} />)
|
|
138
|
+
expect(document.body.textContent).toContain('no backup destination')
|
|
139
|
+
expect(document.body.textContent).toContain('Cluster spec')
|
|
140
|
+
act(() => root.unmount())
|
|
141
|
+
root = render(<CNPGDimensionVerdict dimension={backups('healthy')} />)
|
|
142
|
+
expect(document.body.textContent).toBe('')
|
|
143
|
+
act(() => root.unmount())
|
|
144
|
+
})
|
|
145
|
+
it('draws an unassessed dimension as a ring, never a calm dot', () => {
|
|
146
|
+
const root = render(<CNPGDimensionMark dimension={{ id: 'storage', label: 'Storage', tone: 'unknown', text: 'unassessed', source: 's' }} />)
|
|
147
|
+
const mark = document.querySelector('[aria-label="Storage: unassessed"]')!
|
|
148
|
+
expect(mark.querySelector('.rounded-full.border')).not.toBeNull()
|
|
149
|
+
expect(mark.querySelector('.rounded-full:not(.border)')).toBeNull()
|
|
150
|
+
act(() => root.unmount())
|
|
151
|
+
})
|
|
152
|
+
it('makes the Serving status a button only when the host can open its details', () => {
|
|
153
|
+
const serving: CNPGDimension = { id: 'serving', label: 'Serving', tone: 'healthy', text: 'primary ready', source: 's' }
|
|
154
|
+
const onSelect = vi.fn()
|
|
155
|
+
let root = render(<CNPGServingStatus dimension={serving} />)
|
|
156
|
+
expect(document.querySelector('button')).toBeNull()
|
|
157
|
+
expect(document.body.textContent).toContain('primary ready')
|
|
158
|
+
act(() => root.unmount())
|
|
159
|
+
root = render(<CNPGServingStatus dimension={serving} onSelect={onSelect} />)
|
|
160
|
+
act(() => document.querySelector<HTMLButtonElement>('[aria-label="Serving: primary ready. Open its details"]')!.click())
|
|
161
|
+
expect(onSelect).toHaveBeenCalled()
|
|
162
|
+
act(() => root.unmount())
|
|
163
|
+
})
|
|
164
|
+
it('lists each dimension at a glance, opening its tab', () => {
|
|
165
|
+
const dims: CNPGDimension[] = [{ id: 'storage', label: 'Storage', tone: 'degraded', text: 'WAL held by an inactive slot', source: 'slot' }]
|
|
166
|
+
const onSelect = vi.fn()
|
|
167
|
+
const root = render(<CNPGClusterSummary row={row()} dimensions={dims} onSelectDimension={onSelect} />)
|
|
168
|
+
expect(document.body.textContent).toContain('WAL held by an inactive slot')
|
|
169
|
+
const open = [...document.querySelectorAll('button')].find((b) => b.textContent === 'Storage →')!
|
|
170
|
+
act(() => open.click())
|
|
171
|
+
expect(onSelect).toHaveBeenCalledWith('storage')
|
|
172
|
+
act(() => root.unmount())
|
|
173
|
+
})
|
|
174
|
+
})
|
|
175
|
+
|
|
176
|
+
describe('problem meta', () => {
|
|
177
|
+
it('keeps a space between the Backup and "and N more"', () => {
|
|
178
|
+
const r = row({
|
|
179
|
+
problems: [{ ...problem('a', 'warning', '3 backups failed'), alsoAbout: [{ kind: 'Backup', name: 'b-2' }, { kind: 'Backup', name: 'b-1' }] }],
|
|
180
|
+
attention: true,
|
|
181
|
+
})
|
|
182
|
+
const root = render(<CNPGClusterSummary row={r} onNavigate={() => {}} />)
|
|
183
|
+
expect(document.body.textContent).toContain('b-a and 2 more')
|
|
184
|
+
act(() => root.unmount())
|
|
185
|
+
})
|
|
186
|
+
})
|
|
187
|
+
|
|
188
|
+
describe('problem provenance', () => {
|
|
189
|
+
it('names the origin instead of "Radar issue" and links to Issues when the host can', () => {
|
|
190
|
+
const open = vi.fn()
|
|
191
|
+
const r = row({
|
|
192
|
+
problems: [{ ...problem('a', 'critical', 'WAL archiving is failing'), subject: { kind: 'Cluster', group: 'postgresql.cnpg.io', namespace: 'db', name: 'pg' }, origin: { label: 'Reported by CNPG', detail: 'ContinuousArchiving condition' } }],
|
|
193
|
+
attention: true,
|
|
194
|
+
})
|
|
195
|
+
const root = render(
|
|
196
|
+
<OpenIssueContext.Provider value={open}>
|
|
197
|
+
<CNPGClusterSummary row={r} />
|
|
198
|
+
</OpenIssueContext.Provider>,
|
|
199
|
+
)
|
|
200
|
+
expect(document.body.textContent).toContain('Reported by CNPG')
|
|
201
|
+
expect(document.body.textContent).not.toContain('Radar issue')
|
|
202
|
+
act(() => [...document.querySelectorAll('button')].find((b) => b.textContent === 'See in Issues →')!.click())
|
|
203
|
+
expect(open).toHaveBeenCalledWith(expect.objectContaining({ id: 'a' }))
|
|
204
|
+
act(() => root.unmount())
|
|
205
|
+
})
|
|
206
|
+
it('offers no Issues link without a host handler', () => {
|
|
207
|
+
const r = row({ problems: [problem('a', 'warning', 'Backup failed')], attention: true })
|
|
208
|
+
const root = render(<CNPGClusterSummary row={r} />)
|
|
209
|
+
expect(document.body.textContent).toContain('Detected by Radar')
|
|
210
|
+
expect(document.body.textContent).not.toContain('See in Issues')
|
|
211
|
+
act(() => root.unmount())
|
|
212
|
+
})
|
|
213
|
+
})
|
|
214
|
+
|
|
215
|
+
it('shows the declaration read limitation inline on Overview', () => {
|
|
216
|
+
const r = row({ declarations: { total: 1, failed: 0, pending: 0, summary: { text: '≥1 reconciled; Databases not read', tone: 'unknown' } } })
|
|
217
|
+
const root = render(<CNPGClusterSummary row={r} />)
|
|
218
|
+
expect(document.body.textContent).toContain('≥1 reconciled; Databases not read')
|
|
219
|
+
act(() => root.unmount())
|
|
220
|
+
})
|
|
221
|
+
|
|
222
|
+
it('shows neutral literal operator phases for every CNPG caller', () => {
|
|
223
|
+
const r = row({ cluster: { status: { phase: 'Cluster in healthy state' } } })
|
|
224
|
+
const root = render(<CNPGClusterSummary row={r} />)
|
|
225
|
+
expect(document.body.textContent).toContain('Cluster in healthy state')
|
|
226
|
+
const badge = [...document.querySelectorAll('span')].find((el) => el.textContent === 'Cluster in healthy state')!
|
|
227
|
+
expect(badge.className).not.toContain('emerald')
|
|
228
|
+
act(() => root.unmount())
|
|
229
|
+
const expanded = render(<CNPGClusterSummary row={r} framed />)
|
|
230
|
+
expect(document.body.textContent).toContain('Cluster in healthy state')
|
|
231
|
+
expect(document.body.textContent).not.toContain('Healthy')
|
|
232
|
+
act(() => expanded.unmount())
|
|
233
|
+
})
|
|
234
|
+
it('shows healthy Serving only when requested and keeps its tab mark absent', () => {
|
|
235
|
+
const dimension: CNPGDimension = { id: 'serving', label: 'Serving', tone: 'healthy', text: 'primary ready', source: 'Pod pg-1 and Service pg-rw' }
|
|
236
|
+
const root = render(<><CNPGDimensionVerdict dimension={dimension} alwaysShow /><CNPGDimensionMark dimension={dimension} /></>)
|
|
237
|
+
expect(document.body.textContent).toContain('primary ready')
|
|
238
|
+
expect(document.querySelector('[role="img"]')).toBeNull()
|
|
239
|
+
act(() => root.unmount())
|
|
240
|
+
const old = render(<CNPGDimensionVerdict dimension={dimension} />)
|
|
241
|
+
expect(document.body.textContent).not.toContain('primary ready')
|
|
242
|
+
act(() => old.unmount())
|
|
243
|
+
})
|
|
244
|
+
|
|
245
|
+
it('keeps literal blocked phases neutral with their explanation and Operator action', () => {
|
|
246
|
+
const phase = 'Cluster cannot proceed to reconciliation due to an unknown plugin being required'
|
|
247
|
+
const r = row({ cluster: { status: { phase } }, controllerStatus: { text: 'Unknown Plugin', level: 'unhealthy' } })
|
|
248
|
+
const open = vi.fn()
|
|
249
|
+
const root = render(<CNPGClusterSummary row={r} onOpenOperator={open} />)
|
|
250
|
+
expect(document.body.textContent).toContain(phase)
|
|
251
|
+
expect(document.body.textContent).toContain('reported by CNPG')
|
|
252
|
+
expect(document.body.textContent).toContain('Plugins this cluster uses')
|
|
253
|
+
const badge = [...document.querySelectorAll('span')].find((el) => el.textContent === phase)!
|
|
254
|
+
expect(badge.className).not.toMatch(/red|amber|emerald/)
|
|
255
|
+
act(() => [...document.querySelectorAll('button')].find((b) => b.textContent === 'Operator and plugins →')!.click())
|
|
256
|
+
expect(open).toHaveBeenCalled()
|
|
257
|
+
act(() => root.unmount())
|
|
258
|
+
})
|
|
259
|
+
|
|
260
|
+
it('separates absent operator readiness from observed zero ready Pods', () => {
|
|
261
|
+
const root = render(<CNPGClusterSummary row={row({ instances: { ready: null, desired: 1 }, pods: [], podReadiness: { ready: 0, total: 0 } })} />)
|
|
262
|
+
expect(document.body.textContent).toContain('Not reported by the operator')
|
|
263
|
+
expect(document.body.textContent).toContain('0 of 1 instance Pods ready')
|
|
264
|
+
expect(document.body.textContent).not.toContain('—/1')
|
|
265
|
+
act(() => root.unmount())
|
|
266
|
+
})
|
|
267
|
+
|
|
268
|
+
it('compacts only the scheduler disclosure inside the CNPG problem callout', () => {
|
|
269
|
+
const root = render(<CNPGClusterSummary row={row({ problems: [{ ...problem('schedule', 'warning', 'Instance cannot be scheduled'), rawDetail: '0/2 nodes are available: 2 Too many pods.', rawDetailLabel: 'Scheduler message' }] })} />)
|
|
270
|
+
const disclosure = [...document.body.querySelectorAll('button')].find((b) => b.textContent?.includes('Scheduler message'))!
|
|
271
|
+
const callout = [...document.body.querySelectorAll('div')].find((d) => d.classList.contains('[&_.mt-5]:mt-2'))
|
|
272
|
+
expect(callout?.contains(disclosure)).toBe(true)
|
|
273
|
+
expect(disclosure.getAttribute('aria-expanded')).toBe('false')
|
|
274
|
+
act(() => root.unmount())
|
|
275
|
+
})
|