@skyhook-io/k8s-ui 1.16.3 → 1.17.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +41 -0
- package/package.json +3 -3
- package/src/components/applications/ApplicationDetail.test.tsx +15 -0
- package/src/components/applications/ApplicationDetail.tsx +17 -15
- 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/checks/types.ts +2 -13
- 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/GitOpsTableView.tsx +9 -24
- package/src/components/gitops/detail-helpers.ts +1 -1
- 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 +29 -83
- package/src/components/gitops/tree/GitOpsTreeGraph.test.ts +26 -1
- package/src/components/gitops/tree/GitOpsTreeGraph.tsx +40 -60
- 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 +14 -3
- package/src/components/issues/issues.test.ts +50 -2
- 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.notice.test.tsx +59 -0
- package/src/components/logs/WorkloadLogsViewer.test.tsx +65 -0
- package/src/components/logs/WorkloadLogsViewer.tsx +44 -10
- package/src/components/logs/useLogBuffer.test.ts +8 -0
- package/src/components/logs/useLogStream.ts +28 -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 +309 -117
- package/src/components/resources/curated-column-ownership.test.ts +3 -3
- package/src/components/resources/renderers/AnalysisRunRenderer.tsx +3 -3
- package/src/components/resources/renderers/ArgoApplicationRenderer.tsx +6 -5
- package/src/components/resources/renderers/AutoscalingReferences.test.tsx +286 -0
- package/src/components/resources/renderers/CAPIClusterRenderer.tsx +5 -4
- package/src/components/resources/renderers/CAPIExactReferenceNavigation.test.tsx +27 -0
- package/src/components/resources/renderers/CAPIKubeadmControlPlaneRenderer.tsx +2 -2
- package/src/components/resources/renderers/CAPIMachineDeploymentRenderer.tsx +2 -2
- package/src/components/resources/renderers/CAPIMachinePoolRenderer.tsx +2 -2
- package/src/components/resources/renderers/CAPIMachineRenderer.tsx +2 -2
- package/src/components/resources/renderers/CAPIMachineSetRenderer.tsx +2 -2
- 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 +7 -8
- 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 +9 -17
- package/src/components/resources/renderers/CNPGShared.tsx +2 -2
- package/src/components/resources/renderers/ClusterComplianceReportRenderer.tsx +2 -2
- 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/ConfigAuditReportRenderer.tsx +2 -2
- package/src/components/resources/renderers/ContourExactReferenceNavigation.test.tsx +34 -0
- package/src/components/resources/renderers/ContourHTTPProxyRenderer.tsx +11 -10
- package/src/components/resources/renderers/CronJobRenderer.tsx +6 -4
- package/src/components/resources/renderers/CronWorkflowRenderer.tsx +3 -2
- package/src/components/resources/renderers/EndpointSliceRenderer.test.tsx +62 -0
- package/src/components/resources/renderers/EndpointSliceRenderer.tsx +15 -14
- package/src/components/resources/renderers/EventRenderer.tsx +3 -3
- package/src/components/resources/renderers/ExperimentRenderer.tsx +2 -2
- package/src/components/resources/renderers/ExposedSecretReportRenderer.tsx +2 -2
- package/src/components/resources/renderers/ExternalSecretRenderer.tsx +2 -2
- package/src/components/resources/renderers/FluxHelmReleaseRenderer.tsx +4 -4
- package/src/components/resources/renderers/GRPCRouteRenderer.tsx +11 -24
- package/src/components/resources/renderers/GatewayRouteReferences.test.tsx +63 -0
- package/src/components/resources/renderers/GitRepositoryRenderer.tsx +3 -3
- package/src/components/resources/renderers/HPARenderer.tsx +57 -17
- package/src/components/resources/renderers/HTTPRouteRenderer.tsx +12 -25
- package/src/components/resources/renderers/HelmRepositoryRenderer.tsx +3 -3
- package/src/components/resources/renderers/IngressClassReferences.test.tsx +52 -0
- package/src/components/resources/renderers/IngressClassRenderer.tsx +10 -3
- package/src/components/resources/renderers/IngressRenderer.defaultBackend.test.tsx +33 -0
- package/src/components/resources/renderers/IngressRenderer.tsx +36 -5
- 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/KarpenterNodeClaimRenderer.tsx +2 -14
- package/src/components/resources/renderers/KedaCredentials.test.tsx +85 -0
- package/src/components/resources/renderers/KedaScaledObjectRenderer.tsx +15 -13
- package/src/components/resources/renderers/KedaTriggerAuthRenderer.tsx +6 -3
- 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/KustomizationRenderer.tsx +2 -2
- package/src/components/resources/renderers/KyvernoGlobalContextRenderer.tsx +3 -2
- package/src/components/resources/renderers/KyvernoQueueRenderers.tsx +4 -3
- package/src/components/resources/renderers/LeaseRenderer.tsx +3 -15
- package/src/components/resources/renderers/ManagedResourceRenderer.tsx +2 -2
- package/src/components/resources/renderers/MetricsUnavailableNotice.tsx +3 -2
- package/src/components/resources/renderers/NamespaceRenderer.test.ts +69 -2
- package/src/components/resources/renderers/NamespaceRenderer.tsx +6 -1
- package/src/components/resources/renderers/NodeRenderer.test.tsx +81 -1
- package/src/components/resources/renderers/NodeRenderer.tsx +56 -15
- package/src/components/resources/renderers/OCIRepositoryRenderer.tsx +3 -3
- package/src/components/resources/renderers/PodClassReferences.test.tsx +42 -0
- package/src/components/resources/renderers/PodNomination.test.tsx +57 -0
- package/src/components/resources/renderers/PodRenderer.test.tsx +32 -0
- package/src/components/resources/renderers/PodRenderer.tsx +62 -36
- package/src/components/resources/renderers/RolloutRenderer.test.tsx +1 -1
- package/src/components/resources/renderers/RolloutRenderer.tsx +3 -2
- package/src/components/resources/renderers/SbomReportRenderer.tsx +2 -2
- package/src/components/resources/renderers/SecretRenderer.test.tsx +88 -0
- package/src/components/resources/renderers/SecretRenderer.tsx +34 -5
- package/src/components/resources/renderers/ServiceRenderer.test.tsx +43 -0
- package/src/components/resources/renderers/ServiceRenderer.tsx +11 -3
- package/src/components/resources/renderers/SimpleRouteRenderer.tsx +9 -22
- package/src/components/resources/renderers/VPARenderer.tsx +17 -11
- package/src/components/resources/renderers/VeleroBSLRenderer.tsx +5 -7
- package/src/components/resources/renderers/VeleroBackupRenderer.tsx +3 -3
- package/src/components/resources/renderers/VeleroBackupRepositoryRenderer.tsx +2 -2
- package/src/components/resources/renderers/VeleroRestoreRenderer.tsx +3 -3
- package/src/components/resources/renderers/VeleroScheduleRenderer.tsx +2 -2
- package/src/components/resources/renderers/VulnerabilityReportRenderer.tsx +2 -2
- package/src/components/resources/renderers/WorkflowRenderer.tsx +4 -3
- package/src/components/resources/renderers/kyverno-modern-cells.tsx +1 -1
- package/src/components/resources/renderers/rollout/ReplicaSetProgression.tsx +2 -2
- 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 -24
- 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-keda.ts +1 -7
- package/src/components/resources/resource-utils-knative.ts +18 -0
- package/src/components/resources/resource-utils-kyverno.ts +1 -7
- package/src/components/resources/resource-utils-velero.ts +0 -12
- package/src/components/resources/resource-utils.ts +99 -39
- 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/DrainPlanDialog.tsx +1 -0
- package/src/components/shared/EditableYamlView.test.tsx +7 -0
- package/src/components/shared/EditableYamlView.tsx +1 -1
- package/src/components/shared/GitOpsWriteWarning.test.tsx +86 -0
- package/src/components/shared/GitOpsWriteWarning.tsx +135 -0
- package/src/components/shared/ResourceActionsBar.tsx +20 -24
- package/src/components/shared/ResourceRendererDispatch.tsx +13 -10
- package/src/components/shared/SetImageDialog.test.ts +75 -6
- package/src/components/shared/SetImageDialog.tsx +92 -84
- package/src/components/shared/index.ts +4 -1
- package/src/components/timeline/TimelineList.tsx +2 -15
- package/src/components/timeline/TimelineSwimlanes.tsx +3 -11
- 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 +8 -74
- 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/ScheduleValue.test.tsx +5 -0
- package/src/components/ui/ScheduleValue.tsx +3 -2
- package/src/components/ui/crd-definition-navigation.test.tsx +160 -0
- package/src/components/ui/drawer-components.test.tsx +175 -1
- package/src/components/ui/drawer-components.tsx +103 -36
- 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 +101 -0
- package/src/components/workload/WorkloadView.deletedDialog.test.tsx +45 -0
- package/src/components/workload/WorkloadView.relationships.test.tsx +37 -0
- package/src/components/workload/WorkloadView.secretSave.test.tsx +62 -0
- package/src/components/workload/WorkloadView.spec.test.tsx +149 -0
- package/src/components/workload/WorkloadView.tsx +297 -104
- 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 +21 -2
- package/src/types/core.ts +60 -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 +49 -2
- package/src/utils/format.ts +9 -1
- package/src/utils/gateway-references.test.ts +17 -0
- package/src/utils/gateway-references.ts +29 -0
- package/src/utils/gitops-owner.ts +3 -2
- package/src/utils/gitops-write-guard.test.ts +401 -0
- package/src/utils/gitops-write-guard.ts +509 -0
- package/src/utils/grant.test.ts +19 -0
- package/src/utils/grant.ts +35 -0
- package/src/utils/index.ts +6 -0
- package/src/utils/ingress-class-references.ts +22 -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 +70 -0
- package/src/utils/navigation.ts +91 -8
- package/src/utils/node-lifecycle.test.ts +36 -0
- package/src/utils/node-lifecycle.ts +77 -0
- package/src/utils/object-reference-navigation.test.ts +43 -0
- package/src/utils/resource-hierarchy.test.ts +69 -0
- package/src/utils/resource-hierarchy.ts +41 -7
- package/src/utils/resource-icons.ts +2 -0
- package/src/utils/resource-refs.test.ts +23 -0
- package/src/utils/resource-refs.ts +30 -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
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { describe, it, expect } from 'vitest'
|
|
2
|
-
import { mapHealthToTone } from './status-tone'
|
|
2
|
+
import { mapHealthToTone, worseTone } from './status-tone'
|
|
3
3
|
|
|
4
4
|
// Inputs flow in from heterogeneous sources (Problems API, Audit findings,
|
|
5
5
|
// multi-cluster aggregation). A regression here is a silent visual
|
|
@@ -51,3 +51,12 @@ describe('mapHealthToTone', () => {
|
|
|
51
51
|
expect(mapHealthToTone('HIGH')).toBe('alert')
|
|
52
52
|
})
|
|
53
53
|
})
|
|
54
|
+
|
|
55
|
+
describe('worseTone', () => {
|
|
56
|
+
it('never reads calmer than a part that could not be read', () => {
|
|
57
|
+
expect(worseTone('healthy', 'unknown')).toBe('unknown')
|
|
58
|
+
expect(worseTone('unknown', 'degraded')).toBe('degraded')
|
|
59
|
+
expect(worseTone('alert', 'unhealthy')).toBe('unhealthy')
|
|
60
|
+
expect(worseTone('neutral', 'healthy')).toBe('neutral')
|
|
61
|
+
})
|
|
62
|
+
})
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { type HealthLevel } from '../resources/resource-utils';
|
|
2
|
+
import { TONE_TEXT_CLASS } from './severity-tone';
|
|
2
3
|
|
|
3
4
|
// StatusDot + mapHealthToTone are typed helpers over the canonical OSS
|
|
4
5
|
// status vocabulary defined in `packages/k8s-ui/src/theme/components.css`.
|
|
@@ -48,6 +49,34 @@ const DOT_CLASS: Record<StatusTone, string> = {
|
|
|
48
49
|
unknown: 'bg-slate-400',
|
|
49
50
|
};
|
|
50
51
|
|
|
52
|
+
/** Solid fill for a tone, e.g. a bar or meter segment. */
|
|
53
|
+
export function toneFillClass(tone: StatusTone): string {
|
|
54
|
+
return DOT_CLASS[tone]
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
// Text for a value read in a tone. Healthy reads as plain primary text: only
|
|
58
|
+
// what needs a look is coloured.
|
|
59
|
+
const TEXT_CLASS: Record<StatusTone, string> = {
|
|
60
|
+
healthy: 'text-theme-text-primary',
|
|
61
|
+
degraded: TONE_TEXT_CLASS.amber,
|
|
62
|
+
alert: TONE_TEXT_CLASS.orange,
|
|
63
|
+
unhealthy: TONE_TEXT_CLASS.red,
|
|
64
|
+
unknown: 'text-theme-text-tertiary',
|
|
65
|
+
neutral: 'text-theme-text-secondary',
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
export function toneTextClass(tone: StatusTone): string {
|
|
69
|
+
return TEXT_CLASS[tone]
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
// Unknown ranks above healthy: a combined reading never looks calmer than a
|
|
73
|
+
// part that could not be read.
|
|
74
|
+
const TONE_RANK: Record<StatusTone, number> = { healthy: 0, neutral: 1, unknown: 2, degraded: 3, alert: 4, unhealthy: 5 }
|
|
75
|
+
|
|
76
|
+
export function worseTone(a: StatusTone, b: StatusTone): StatusTone {
|
|
77
|
+
return TONE_RANK[b] > TONE_RANK[a] ? b : a
|
|
78
|
+
}
|
|
79
|
+
|
|
51
80
|
// Normalize the variety of severity / health vocabularies that flow in
|
|
52
81
|
// from APIs (Problems, Audit, multi-cluster aggregation endpoints) onto
|
|
53
82
|
// a single tone. Inputs are case-insensitive. Returns 'unknown' for
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
import { renderToStaticMarkup } from 'react-dom/server'
|
|
2
|
+
import { describe, expect, it, vi } from 'vitest'
|
|
3
|
+
|
|
4
|
+
import { WorkloadView } from './WorkloadView'
|
|
5
|
+
|
|
6
|
+
const pod = {
|
|
7
|
+
apiVersion: 'v1',
|
|
8
|
+
kind: 'Pod',
|
|
9
|
+
metadata: { name: 'web-1', namespace: 'shop' },
|
|
10
|
+
spec: { containers: [{ name: 'app', image: 'nginx' }] },
|
|
11
|
+
status: { phase: 'Running', containerStatuses: [{ name: 'app', ready: true, state: { running: {} } }] },
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
function render(resourceError: unknown) {
|
|
15
|
+
return renderToStaticMarkup(
|
|
16
|
+
<WorkloadView
|
|
17
|
+
kind="pods"
|
|
18
|
+
namespace="shop"
|
|
19
|
+
name="web-1"
|
|
20
|
+
onBack={vi.fn()}
|
|
21
|
+
resource={pod}
|
|
22
|
+
resourceError={resourceError}
|
|
23
|
+
relationships={{ deployment: { kind: 'Deployment', namespace: 'shop', name: 'web' } }}
|
|
24
|
+
onNavigateToResource={vi.fn()}
|
|
25
|
+
actionsBarProps={{ canExec: true, canViewLogs: true, onOpenTerminal: vi.fn(), onOpenLogs: vi.fn(), onDelete: vi.fn(), renderDiagnose: () => 'Diagnose' }}
|
|
26
|
+
hasOperationalIssues
|
|
27
|
+
renderOverviewLead={() => 'Host issues panel'}
|
|
28
|
+
renderOverviewExtra={() => 'Host reachability panel'}
|
|
29
|
+
renderDiagnoseTab={() => 'Host reachability tab'}
|
|
30
|
+
reachableVia={[{ kind: 'Service', namespace: 'shop', name: 'web' }]}
|
|
31
|
+
/>,
|
|
32
|
+
)
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
describe('WorkloadView for an object deleted while open', () => {
|
|
36
|
+
it('says it is gone, names its owner, and offers nothing that acts on it', () => {
|
|
37
|
+
const html = render(Object.assign(new Error('pods "web-1" not found'), { status: 404 }))
|
|
38
|
+
expect(html).toContain('This Pod no longer exists in the cluster')
|
|
39
|
+
expect(html).toContain('Go to its Deployment web')
|
|
40
|
+
expect(html).toContain('>Deleted<')
|
|
41
|
+
expect(html).not.toContain('Terminal')
|
|
42
|
+
expect(html).not.toContain('Diagnose')
|
|
43
|
+
expect(html).not.toContain('Host issues panel')
|
|
44
|
+
expect(html).not.toContain('Host reachability panel')
|
|
45
|
+
expect(html).not.toContain('Reachability')
|
|
46
|
+
})
|
|
47
|
+
|
|
48
|
+
it('shows the live view when the fetch failed for another reason', () => {
|
|
49
|
+
const html = render(Object.assign(new Error('boom'), { status: 500 }))
|
|
50
|
+
expect(html).not.toContain('no longer exists')
|
|
51
|
+
expect(html).not.toContain('>Deleted<')
|
|
52
|
+
expect(html).toContain('Terminal')
|
|
53
|
+
expect(html).toContain('Diagnose')
|
|
54
|
+
expect(html).toContain('Host issues panel')
|
|
55
|
+
expect(html).toContain('Host reachability panel')
|
|
56
|
+
expect(html).toContain('Reachability')
|
|
57
|
+
})
|
|
58
|
+
})
|
|
59
|
+
|
|
60
|
+
function renderHosted(resourceError: unknown, activeTab: string) {
|
|
61
|
+
return renderToStaticMarkup(
|
|
62
|
+
<WorkloadView
|
|
63
|
+
kind="clusters"
|
|
64
|
+
group="postgresql.cnpg.io"
|
|
65
|
+
namespace="db"
|
|
66
|
+
name="pg"
|
|
67
|
+
expanded
|
|
68
|
+
activeTab={activeTab as never}
|
|
69
|
+
onBack={vi.fn()}
|
|
70
|
+
resource={{ apiVersion: 'postgresql.cnpg.io/v1', kind: 'Cluster', metadata: { name: 'pg', namespace: 'db' }, spec: {} }}
|
|
71
|
+
resourceError={resourceError}
|
|
72
|
+
renderSummary={() => 'Host summary action'}
|
|
73
|
+
extraTabs={[{ id: 'backups', label: 'Host backups', render: () => 'Host backups action' }]}
|
|
74
|
+
subheader="Host subheader action"
|
|
75
|
+
renderHeaderActions={() => 'Host header action'}
|
|
76
|
+
renderStatusBadge={() => 'Host phase'}
|
|
77
|
+
statusNote="Host verdict"
|
|
78
|
+
specTab={{ render: () => 'Host spec action' }}
|
|
79
|
+
/>,
|
|
80
|
+
)
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
describe('a host page for an object deleted while open', () => {
|
|
84
|
+
it('drops the host summary, tabs, subheader and actions, which act on the live object', () => {
|
|
85
|
+
const gone = Object.assign(new Error('clusters "pg" not found'), { status: 404 })
|
|
86
|
+
for (const tab of ['overview', 'backups', 'spec']) {
|
|
87
|
+
const html = renderHosted(gone, tab)
|
|
88
|
+
expect(html).toContain('>Deleted<')
|
|
89
|
+
for (const host of ['Host summary action', 'Host backups', 'Host subheader action', 'Host header action', 'Host phase', 'Host verdict', 'Host spec action']) {
|
|
90
|
+
expect(html, `${tab}: ${host}`).not.toContain(host)
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
})
|
|
94
|
+
|
|
95
|
+
it('keeps them while the object exists', () => {
|
|
96
|
+
const html = renderHosted(Object.assign(new Error('boom'), { status: 500 }), 'overview')
|
|
97
|
+
for (const host of ['Host summary action', 'Host backups', 'Host subheader action', 'Host header action', 'Host phase', 'Host verdict']) {
|
|
98
|
+
expect(html).toContain(host)
|
|
99
|
+
}
|
|
100
|
+
})
|
|
101
|
+
})
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
// @vitest-environment jsdom
|
|
2
|
+
import { act } from 'react'
|
|
3
|
+
import { createRoot } from 'react-dom/client'
|
|
4
|
+
import { expect, it, vi } from 'vitest'
|
|
5
|
+
|
|
6
|
+
import { WorkloadView } from './WorkloadView'
|
|
7
|
+
|
|
8
|
+
vi.stubGlobal('IS_REACT_ACT_ENVIRONMENT', true)
|
|
9
|
+
|
|
10
|
+
const deployment = {
|
|
11
|
+
apiVersion: 'apps/v1',
|
|
12
|
+
kind: 'Deployment',
|
|
13
|
+
metadata: { name: 'web', namespace: 'shop' },
|
|
14
|
+
spec: { replicas: 1, template: { spec: { containers: [{ name: 'app', image: 'nginx' }] } } },
|
|
15
|
+
status: { replicas: 1, readyReplicas: 1, availableReplicas: 1 },
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
it('closes an open Delete dialog when the object is deleted elsewhere', async () => {
|
|
19
|
+
const element = document.createElement('div')
|
|
20
|
+
document.body.append(element)
|
|
21
|
+
const root = createRoot(element)
|
|
22
|
+
const render = (resourceError: unknown) =>
|
|
23
|
+
root.render(
|
|
24
|
+
<WorkloadView
|
|
25
|
+
kind="deployments"
|
|
26
|
+
namespace="shop"
|
|
27
|
+
name="web"
|
|
28
|
+
onBack={vi.fn()}
|
|
29
|
+
resource={deployment}
|
|
30
|
+
resourceError={resourceError}
|
|
31
|
+
actionsBarProps={{ onDelete: vi.fn() }}
|
|
32
|
+
/>,
|
|
33
|
+
)
|
|
34
|
+
const dialogOpen = () => document.body.textContent?.includes('Are you sure you want to delete') ?? false
|
|
35
|
+
|
|
36
|
+
await act(async () => render(null))
|
|
37
|
+
await act(async () => { element.querySelector<HTMLButtonElement>('button:has(svg.lucide-trash-2)')!.click() })
|
|
38
|
+
expect(dialogOpen()).toBe(true)
|
|
39
|
+
|
|
40
|
+
await act(async () => render(Object.assign(new Error('deployments.apps "web" not found'), { status: 404 })))
|
|
41
|
+
expect(dialogOpen()).toBe(false)
|
|
42
|
+
|
|
43
|
+
await act(async () => root.unmount())
|
|
44
|
+
element.remove()
|
|
45
|
+
})
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { renderToStaticMarkup } from 'react-dom/server'
|
|
2
|
+
import { describe, expect, it } from 'vitest'
|
|
3
|
+
import { WorkloadView } from './WorkloadView'
|
|
4
|
+
|
|
5
|
+
const workload = {
|
|
6
|
+
apiVersion: 'apps/v1', kind: 'Deployment', metadata: { name: 'web', namespace: 'demo' },
|
|
7
|
+
spec: { replicas: 1, selector: { matchLabels: { app: 'web' } }, template: { metadata: { labels: { app: 'web' } }, spec: { containers: [{ name: 'app', image: 'nginx' }] } } },
|
|
8
|
+
status: { replicas: 1, availableReplicas: 1, readyReplicas: 1, updatedReplicas: 1 },
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
describe('workload monitoring projection', () => {
|
|
13
|
+
it('renders monitors in the related dependency card', () => {
|
|
14
|
+
const html = renderToStaticMarkup(<WorkloadView kind="deployments" namespace="demo" name="web" onBack={() => {}} resource={workload} relationships={{ monitors: [{ kind: 'PodMonitor', group: 'monitoring.coreos.com', namespace: 'demo', name: 'scraper' }] }} />)
|
|
15
|
+
expect(html).toContain('Monitored By')
|
|
16
|
+
expect(html).toContain('scraper')
|
|
17
|
+
})
|
|
18
|
+
})
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
describe('workload upstream projection', () => {
|
|
22
|
+
it('keeps other routing sources visible as entrypoints', () => {
|
|
23
|
+
const html = renderToStaticMarkup(<WorkloadView kind="deployments" namespace="demo" name="web" onBack={() => {}} resource={workload} relationships={{ routedFrom: [{ kind: 'Broker', group: 'eventing.knative.dev', namespace: 'demo', name: 'messages' }] }} />)
|
|
24
|
+
expect(html).toContain('Entry point resources')
|
|
25
|
+
expect(html).toContain('messages')
|
|
26
|
+
})
|
|
27
|
+
})
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
describe('workload staged-policy projection', () => {
|
|
31
|
+
it('shows staging without calling it an enforcing network policy', () => {
|
|
32
|
+
const html = renderToStaticMarkup(<WorkloadView kind="deployments" namespace="demo" name="web" onBack={() => {}} resource={workload} relationships={{ stagedPolicies: [{ kind: 'StagedNetworkPolicy', group: 'projectcalico.org', namespace: 'demo', name: 'preview' }] }} />)
|
|
33
|
+
expect(html).toContain('Staged Policies')
|
|
34
|
+
expect(html).toContain('preview')
|
|
35
|
+
expect(html).not.toContain('Network policies')
|
|
36
|
+
})
|
|
37
|
+
})
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
// @vitest-environment jsdom
|
|
2
|
+
import { act } from 'react'
|
|
3
|
+
import { createRoot } from 'react-dom/client'
|
|
4
|
+
import { expect, it, vi } from 'vitest'
|
|
5
|
+
|
|
6
|
+
import { WorkloadView } from './WorkloadView'
|
|
7
|
+
|
|
8
|
+
vi.stubGlobal('IS_REACT_ACT_ENVIRONMENT', true)
|
|
9
|
+
|
|
10
|
+
const secret = {
|
|
11
|
+
apiVersion: 'v1',
|
|
12
|
+
kind: 'Secret',
|
|
13
|
+
metadata: { name: 'db', namespace: 'shop', resourceVersion: '7' },
|
|
14
|
+
type: 'Opaque',
|
|
15
|
+
data: { password: btoa('old') },
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
function button(label: string): HTMLButtonElement | undefined {
|
|
19
|
+
return [...document.querySelectorAll('button')].find(b => b.textContent?.trim() === label || b.title === label)
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
it('saves a Secret value against the version on screen, and offers no edit once the Secret is gone', async () => {
|
|
23
|
+
const element = document.createElement('div')
|
|
24
|
+
document.body.append(element)
|
|
25
|
+
const root = createRoot(element)
|
|
26
|
+
const onUpdateResource = vi.fn().mockResolvedValue(undefined)
|
|
27
|
+
const render = (resourceError: unknown) =>
|
|
28
|
+
root.render(
|
|
29
|
+
<WorkloadView
|
|
30
|
+
kind="secrets"
|
|
31
|
+
namespace="shop"
|
|
32
|
+
name="db"
|
|
33
|
+
onBack={vi.fn()}
|
|
34
|
+
resource={secret}
|
|
35
|
+
resourceError={resourceError}
|
|
36
|
+
canUpdateSecrets
|
|
37
|
+
onUpdateResource={onUpdateResource}
|
|
38
|
+
/>,
|
|
39
|
+
)
|
|
40
|
+
|
|
41
|
+
await act(async () => render(null))
|
|
42
|
+
await act(async () => button('Reveal')!.click())
|
|
43
|
+
await act(async () => button('Edit value')!.click())
|
|
44
|
+
await act(async () => button('Save')!.click())
|
|
45
|
+
await act(async () => button('Update')!.click())
|
|
46
|
+
expect(onUpdateResource).toHaveBeenCalledWith(expect.objectContaining({
|
|
47
|
+
kind: 'secrets',
|
|
48
|
+
namespace: 'shop',
|
|
49
|
+
name: 'db',
|
|
50
|
+
reviewedResourceVersion: '7',
|
|
51
|
+
}))
|
|
52
|
+
|
|
53
|
+
await act(async () => button('Edit value')!.click())
|
|
54
|
+
expect(element.querySelector('textarea')).not.toBeNull()
|
|
55
|
+
await act(async () => render(Object.assign(new Error('secrets "db" not found'), { status: 404 })))
|
|
56
|
+
expect(element.textContent).toContain('This Secret no longer exists in the cluster')
|
|
57
|
+
expect(element.querySelector('textarea')).toBeNull()
|
|
58
|
+
expect(button('Edit value')).toBeUndefined()
|
|
59
|
+
|
|
60
|
+
await act(async () => root.unmount())
|
|
61
|
+
element.remove()
|
|
62
|
+
})
|
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
// @vitest-environment jsdom
|
|
2
|
+
import { act } from 'react'
|
|
3
|
+
import { createRoot } from 'react-dom/client'
|
|
4
|
+
import { renderToStaticMarkup } from 'react-dom/server'
|
|
5
|
+
import type { ReactNode } from 'react'
|
|
6
|
+
import { expect, it, vi } from 'vitest'
|
|
7
|
+
import { WorkloadView } from './WorkloadView'
|
|
8
|
+
|
|
9
|
+
const renderer = vi.hoisted(() => vi.fn(({ mainFooter }: { mainFooter?: ReactNode }) => <div>Generic renderer and Conditions{mainFooter}</div>))
|
|
10
|
+
vi.mock('../shared/ResourceRendererDispatch', async (original) => ({ ...await original<typeof import('../shared/ResourceRendererDispatch')>(), ResourceRendererDispatch: renderer }))
|
|
11
|
+
|
|
12
|
+
const resource = { apiVersion: 'postgresql.cnpg.io/v1', kind: 'Cluster', metadata: { name: 'pg', namespace: 'db', labels: { app: 'postgres' } }, spec: { instances: 2 }, status: {} }
|
|
13
|
+
const props = { kind: 'clusters', namespace: 'db', name: 'pg', group: 'postgresql.cnpg.io', resource, onBack: () => {}, activeTab: 'spec' as const, renderSummary: () => <div>Composed Overview</div>, renderOverviewExtra: () => <div>Audit Findings</div> }
|
|
14
|
+
|
|
15
|
+
;(globalThis as { IS_REACT_ACT_ENVIRONMENT?: boolean }).IS_REACT_ACT_ENVIRONMENT = true
|
|
16
|
+
|
|
17
|
+
it('replaces the entire expanded spec body, including its generic sections and lead', () => {
|
|
18
|
+
renderer.mockClear()
|
|
19
|
+
const html = renderToStaticMarkup(<WorkloadView {...props} specTab={{ label: 'Configuration', lead: <div>Old lead</div>, render: () => <div>Purpose-built configuration</div> }} />)
|
|
20
|
+
expect(html).toContain('Purpose-built configuration')
|
|
21
|
+
expect(html).toContain('Configuration')
|
|
22
|
+
for (const text of ['Generic renderer', 'Conditions', 'Audit Findings', 'Labels (1)', 'Metadata', 'Recent Events', 'Old lead']) expect(html).not.toContain(text)
|
|
23
|
+
expect(renderer).not.toHaveBeenCalled()
|
|
24
|
+
})
|
|
25
|
+
|
|
26
|
+
it('preserves the generic spec body and lead when no replacement is supplied', () => {
|
|
27
|
+
const html = renderToStaticMarkup(<WorkloadView {...props} specTab={{ label: 'Settings', lead: <div>Existing lead</div> }} />)
|
|
28
|
+
for (const text of ['Settings', 'Existing lead', 'Generic renderer and Conditions', 'Audit Findings']) expect(html).toContain(text)
|
|
29
|
+
})
|
|
30
|
+
|
|
31
|
+
it('does not use the spec replacement in a drawer or on Overview', () => {
|
|
32
|
+
const replacement = vi.fn(() => <div>Purpose-built configuration</div>)
|
|
33
|
+
let html = renderToStaticMarkup(<WorkloadView {...props} expanded={false} specTab={{ render: replacement }} />)
|
|
34
|
+
expect(html).toContain('Composed Overview')
|
|
35
|
+
html = renderToStaticMarkup(<WorkloadView {...props} activeTab="overview" specTab={{ render: replacement }} />)
|
|
36
|
+
expect(html).toContain('Composed Overview')
|
|
37
|
+
expect(replacement).not.toHaveBeenCalled()
|
|
38
|
+
})
|
|
39
|
+
|
|
40
|
+
it('still opens the original Spec & status in a drawer when a page replacement is provided', () => {
|
|
41
|
+
const replacement = vi.fn(() => <div>Purpose-built configuration</div>)
|
|
42
|
+
const host = document.createElement('div')
|
|
43
|
+
const root = createRoot(host)
|
|
44
|
+
act(() => root.render(<WorkloadView {...props} expanded={false} specTab={{ label: 'Configuration', render: replacement }} />))
|
|
45
|
+
const tab = [...host.querySelectorAll<HTMLButtonElement>('[role="tab"]')].find((b) => b.textContent === 'Spec & status')!
|
|
46
|
+
act(() => tab.click())
|
|
47
|
+
expect(tab.getAttribute('aria-selected')).toBe('true')
|
|
48
|
+
expect(host.textContent).toContain('Generic renderer and Conditions')
|
|
49
|
+
expect(host.textContent).toContain('Audit Findings')
|
|
50
|
+
expect(replacement).not.toHaveBeenCalled()
|
|
51
|
+
act(() => root.unmount())
|
|
52
|
+
})
|
|
53
|
+
|
|
54
|
+
it('lets a host render the reported phase while preserving the default derived badge', () => {
|
|
55
|
+
const instance = { ...resource, status: { phase: 'Cluster in healthy state', readyInstances: 1 } }
|
|
56
|
+
for (const expanded of [true, false]) {
|
|
57
|
+
const html = renderToStaticMarkup(<WorkloadView {...props} expanded={expanded} resource={instance} renderStatusBadge={(r) => <span>Phase: {r.status.phase}</span>} />)
|
|
58
|
+
expect(html).toContain('Phase: Cluster in healthy state')
|
|
59
|
+
expect(html).not.toMatch(/badge[^>]*>Degraded/)
|
|
60
|
+
expect(renderToStaticMarkup(<WorkloadView {...props} expanded={expanded} resource={instance} />)).toMatch(/badge[^>]*>Degraded/)
|
|
61
|
+
}
|
|
62
|
+
})
|
|
63
|
+
|
|
64
|
+
it('waits for the resource before asking a host for its status badge', () => {
|
|
65
|
+
const badge = vi.fn((r: any) => <span>Phase: {r.status.phase}</span>)
|
|
66
|
+
for (const expanded of [true, false]) {
|
|
67
|
+
expect(() => renderToStaticMarkup(<WorkloadView {...props} expanded={expanded} resource={undefined} renderStatusBadge={badge} />)).not.toThrow()
|
|
68
|
+
}
|
|
69
|
+
expect(badge).not.toHaveBeenCalled()
|
|
70
|
+
})
|
|
71
|
+
|
|
72
|
+
it.each(['ScheduledBackup', 'Database', 'Publication', 'ClusterImageCatalog', 'Deployment', 'Pod', 'Pooler'])('keeps %s drawer utilities apart from identity and object actions', (kind) => {
|
|
73
|
+
const host = document.createElement('div')
|
|
74
|
+
const root = createRoot(host)
|
|
75
|
+
const name = 'a-long-object-name-that-needs-the-full-drawer-width'
|
|
76
|
+
act(() => root.render(<WorkloadView {...props} name={name} resource={{ ...resource, kind }} expanded={false} onExpand={() => {}} onClose={() => {}} renderStatusBadge={() => <span>Reported status</span>} renderHeaderActions={() => <button>Object action</button>} />))
|
|
77
|
+
const expand = host.querySelector('[aria-label="Open full view"]')!
|
|
78
|
+
const utilityRow = expand.parentElement!.parentElement!
|
|
79
|
+
expect(utilityRow.classList.contains('shrink-0')).toBe(true)
|
|
80
|
+
expect(utilityRow.classList.contains('flex-nowrap')).toBe(true)
|
|
81
|
+
expect(utilityRow.querySelector('[aria-label="Refresh"]')).not.toBeNull()
|
|
82
|
+
expect(utilityRow.querySelector('[aria-label="Close"]')).not.toBeNull()
|
|
83
|
+
expect(utilityRow.textContent).not.toContain('Object action')
|
|
84
|
+
// The name has its own line under the badges and controls, so a long one gets the drawer's full width.
|
|
85
|
+
const title = host.querySelector('h2')!
|
|
86
|
+
expect(title.textContent).toBe(name)
|
|
87
|
+
const topRow = utilityRow.parentElement!
|
|
88
|
+
expect(topRow.contains(title)).toBe(false)
|
|
89
|
+
expect(topRow.textContent).toContain('Reported status')
|
|
90
|
+
const action = [...host.querySelectorAll('button')].find((b) => b.textContent === 'Object action')!
|
|
91
|
+
expect(topRow.contains(action)).toBe(false)
|
|
92
|
+
expect(title.parentElement!.contains(action)).toBe(false)
|
|
93
|
+
act(() => root.unmount())
|
|
94
|
+
})
|
|
95
|
+
|
|
96
|
+
it('preserves Diagnose before the resource loads while deferring resource-dependent actions', () => {
|
|
97
|
+
const header = vi.fn(() => <button>Resource action</button>)
|
|
98
|
+
const html = renderToStaticMarkup(<WorkloadView {...props} resource={undefined} expanded={false} renderHeaderActions={header} actionsBarProps={{ renderDiagnose: () => <button>Diagnose this object</button> }} />)
|
|
99
|
+
expect(html).toContain('Diagnose this object')
|
|
100
|
+
expect(header).not.toHaveBeenCalled()
|
|
101
|
+
})
|
|
102
|
+
|
|
103
|
+
it.each(['Deployment', 'Pod', 'ScheduledBackup', 'Database', 'Pooler'])('groups Diagnose with %s drawer utilities and omits empty object actions', (kind) => {
|
|
104
|
+
const host = document.createElement('div')
|
|
105
|
+
const root = createRoot(host)
|
|
106
|
+
const header = vi.fn(() => null)
|
|
107
|
+
const group = kind === 'Deployment' ? 'apps' : kind === 'Pod' ? '' : 'postgresql.cnpg.io'
|
|
108
|
+
const data = { ...resource, kind, apiVersion: group ? `${group}/v1` : 'v1' }
|
|
109
|
+
act(() => root.render(<WorkloadView {...props} kind={`${kind.toLowerCase()}s`} group={group} resource={data} expanded={false} onExpand={() => {}} onClose={() => {}} renderHeaderActions={header} actionsBarProps={{ renderDiagnose: () => <button>Diagnose</button> }} />))
|
|
110
|
+
const utility = host.querySelector('[aria-label="Open full view"]')!.parentElement!.parentElement!
|
|
111
|
+
expect(utility.textContent).toContain('Diagnose')
|
|
112
|
+
expect(host.querySelector('[data-action-group=object]')!.matches(':empty')).toBe(true)
|
|
113
|
+
expect(header).toHaveBeenCalledTimes(1)
|
|
114
|
+
act(() => root.render(<WorkloadView {...props} kind={`${kind.toLowerCase()}s`} group={group} resource={data} expanded renderHeaderActions={header} actionsBarProps={{ renderDiagnose: () => <button>Diagnose</button> }} />))
|
|
115
|
+
const refresh = host.querySelector('[aria-label="Refresh"]')!
|
|
116
|
+
expect(refresh.parentElement!.parentElement!.textContent).toContain('Diagnose')
|
|
117
|
+
act(() => root.unmount())
|
|
118
|
+
})
|
|
119
|
+
|
|
120
|
+
it('hides the object action band when a host action component has no content yet', () => {
|
|
121
|
+
const EmptyAction = () => null
|
|
122
|
+
const host = document.createElement('div'); const root = createRoot(host)
|
|
123
|
+
act(() => root.render(<WorkloadView {...props} expanded={false} renderHeaderActions={() => <EmptyAction />} />))
|
|
124
|
+
const row = host.querySelector('[data-action-group=object]')!
|
|
125
|
+
expect(row.matches(':empty')).toBe(true)
|
|
126
|
+
expect(row.classList.contains('empty:hidden')).toBe(true)
|
|
127
|
+
act(() => root.render(<WorkloadView {...props} expanded={false} renderHeaderActions={() => <button>Run now</button>} />))
|
|
128
|
+
expect(row.matches(':empty')).toBe(false)
|
|
129
|
+
act(() => root.unmount())
|
|
130
|
+
})
|
|
131
|
+
|
|
132
|
+
it.each(['Deployment', 'Pod', 'Pooler', 'ScheduledBackup'])('groups %s drawer actions with utilities in one toolbar', (kind) => {
|
|
133
|
+
const host = document.createElement('div'); const root = createRoot(host)
|
|
134
|
+
const apiKind = kind === 'Deployment' ? 'deployments' : kind === 'Pod' ? 'pods' : kind === 'Pooler' ? 'poolers' : 'scheduledbackups'
|
|
135
|
+
const group = kind === 'Deployment' ? 'apps' : kind === 'Pod' ? '' : 'postgresql.cnpg.io'
|
|
136
|
+
const data = { ...resource, kind, apiVersion: group ? `${group}/v1` : 'v1', spec: { containers: [{ name: 'postgres', image: 'postgres' }] }, status: { phase: 'Running' } }
|
|
137
|
+
act(() => root.render(<WorkloadView {...props} kind={apiKind} group={group} expanded={false} resource={data} actionsBarProps={{ canExec: true, onOpenTerminal: () => {}, onRestart: () => {} }} renderHeaderActions={() => kind === 'ScheduledBackup' ? <><div><button>Suspend</button><button>Run now</button><button>Edit schedule</button></div><div>Run blocked: no backup destination</div></> : <button>{kind === 'Pooler' ? 'Pause' : 'Object action'}</button>} />))
|
|
138
|
+
const object = host.querySelector('[data-action-group=object]')!
|
|
139
|
+
const utility = host.querySelector('[data-action-group=utility]')!
|
|
140
|
+
expect(object.parentElement).toBe(utility.parentElement)
|
|
141
|
+
expect(object.parentElement!.classList.contains('flex-wrap')).toBe(true)
|
|
142
|
+
expect(utility.classList.contains('ml-auto')).toBe(true)
|
|
143
|
+
expect(object.querySelector('button')).not.toBeNull()
|
|
144
|
+
expect(utility.textContent).toContain('YAML')
|
|
145
|
+
if (kind === 'Pod') expect(object.textContent).toContain('Terminal')
|
|
146
|
+
if (kind === 'Deployment') expect(object.textContent).toContain('Restart')
|
|
147
|
+
if (kind === 'ScheduledBackup') { expect(object.querySelectorAll('button')).toHaveLength(3); expect(object.textContent).toContain('Run blocked: no backup destination') }
|
|
148
|
+
act(() => root.unmount())
|
|
149
|
+
})
|