@skyhook-io/radar-app 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 +1 -1
- package/package.json +3 -3
- package/src/App.tsx +125 -87
- package/src/api/actions.errorCode.test.ts +40 -0
- package/src/api/actions.test.ts +48 -0
- package/src/api/actions.ts +79 -0
- package/src/api/apiResources.navigation.test.tsx +232 -0
- package/src/api/apiResources.ts +46 -3
- package/src/api/client.helmSource.test.ts +39 -0
- package/src/api/client.namespaceSwitch.hook.test.tsx +54 -0
- package/src/api/client.namespaceSwitch.test.ts +107 -0
- package/src/api/client.ts +266 -39
- package/src/api/cnpg-ha.live.test.ts +140 -0
- package/src/api/cnpg-ha.test.ts +78 -0
- package/src/api/cnpg-ha.ts +19 -0
- package/src/api/cnpg-history.ts +123 -0
- package/src/api/cnpg-inspect.ts +102 -0
- package/src/api/cnpg-protection.ts +112 -0
- package/src/api/cnpg-recovery.test.tsx +60 -0
- package/src/api/cnpg-recovery.ts +262 -0
- package/src/api/cnpg-sessions.ts +175 -0
- package/src/api/cnpg-storage.ts +138 -0
- package/src/api/cnpg.legacy-reads.test.ts +43 -0
- package/src/api/cnpg.pooler-runtime.test.ts +15 -0
- package/src/api/cnpg.ts +428 -10
- package/src/api/radarFeatures.render.test.tsx +47 -3
- package/src/api/radarFeatures.ts +4 -1
- package/src/api/reviewedActionMutation.test.tsx +164 -0
- package/src/api/reviewedActionMutation.ts +66 -0
- package/src/api/timelineSource.test.ts +9 -0
- package/src/api/timelineSource.ts +8 -0
- package/src/components/audit/AuditView.tsx +9 -0
- package/src/components/capacity/CapacityActivity.tsx +3 -31
- package/src/components/capacity/CapacityDemand.tsx +4 -36
- package/src/components/capacity/CapacityOverview.tsx +217 -56
- package/src/components/capacity/CapacityPoolDetail.tsx +6 -55
- package/src/components/capacity/CapacityView.test.tsx +323 -0
- package/src/components/capacity/ClusterSchedulingCard.tsx +2 -10
- package/src/components/capacity/certaintyGlyph.test.tsx +2 -9
- package/src/components/capacity/routes.ts +42 -0
- package/src/components/capacity/shared.tsx +11 -129
- package/src/components/capacity/workspaceRoute.ts +18 -0
- package/src/components/cnpg/CNPGActionHost.compatibility.test.tsx +30 -0
- package/src/components/cnpg/CNPGArchivingRepair.tsx +191 -0
- package/src/components/cnpg/CNPGBlockingSessions.test.tsx +50 -0
- package/src/components/cnpg/CNPGBlockingSessions.tsx +333 -0
- package/src/components/cnpg/CNPGClusterActivity.test.tsx +11 -0
- package/src/components/cnpg/CNPGClusterActivity.tsx +18 -9
- package/src/components/cnpg/CNPGClusterLogs.compatibility.test.tsx +130 -0
- package/src/components/cnpg/CNPGClusterLogs.empty.test.tsx +29 -0
- package/src/components/cnpg/CNPGClusterLogs.test.ts +9 -0
- package/src/components/cnpg/CNPGClusterLogs.tsx +81 -24
- package/src/components/cnpg/CNPGClusterTabs.test.tsx +152 -0
- package/src/components/cnpg/CNPGClusterTabs.tsx +187 -0
- package/src/components/cnpg/CNPGClustersKindView.tsx +46 -0
- package/src/components/cnpg/CNPGConfiguration.test.tsx +223 -0
- package/src/components/cnpg/CNPGConfiguration.tsx +190 -0
- package/src/components/cnpg/CNPGCreateClusterDialog.test.tsx +113 -0
- package/src/components/cnpg/CNPGCreateClusterDialog.tsx +74 -0
- package/src/components/cnpg/CNPGDeclarations.test.ts +107 -0
- package/src/components/cnpg/CNPGDeclarations.tsx +236 -78
- package/src/components/cnpg/CNPGDetailPage.test.tsx +62 -0
- package/src/components/cnpg/CNPGDetailPage.tsx +159 -98
- package/src/components/cnpg/CNPGDrawerTrail.tsx +2 -2
- package/src/components/cnpg/CNPGInvestigationAction.test.tsx +30 -0
- package/src/components/cnpg/CNPGOperator.test.tsx +34 -0
- package/src/components/cnpg/CNPGOperator.tsx +78 -30
- package/src/components/cnpg/CNPGOperatorBanner.tsx +39 -0
- package/src/components/cnpg/CNPGOperatorConditions.test.tsx +45 -0
- package/src/components/cnpg/CNPGOperatorConditions.tsx +16 -0
- package/src/components/cnpg/CNPGOperatorConfig.test.tsx +27 -0
- package/src/components/cnpg/CNPGOperatorDiagnosis.tsx +247 -0
- package/src/components/cnpg/CNPGOverview.test.tsx +171 -0
- package/src/components/cnpg/CNPGOverview.tsx +238 -63
- package/src/components/cnpg/CNPGParametersInEffect.test.tsx +51 -0
- package/src/components/cnpg/CNPGParametersInEffect.tsx +120 -0
- package/src/components/cnpg/CNPGPerformance.prerequisite.test.tsx +48 -0
- package/src/components/cnpg/CNPGPerformance.test.tsx +73 -0
- package/src/components/cnpg/CNPGPerformance.tsx +477 -0
- package/src/components/cnpg/CNPGPooling.test.tsx +89 -0
- package/src/components/cnpg/CNPGPooling.tsx +97 -40
- package/src/components/cnpg/CNPGProtection.test.tsx +145 -0
- package/src/components/cnpg/CNPGProtection.tsx +228 -122
- package/src/components/cnpg/CNPGReplicationTab.test.tsx +87 -0
- package/src/components/cnpg/CNPGReplicationTab.tsx +212 -0
- package/src/components/cnpg/CNPGReplicationView.tsx +264 -0
- package/src/components/cnpg/CNPGStorage.test.tsx +140 -0
- package/src/components/cnpg/CNPGStorage.tsx +619 -0
- package/src/components/cnpg/CNPGSummaryHost.overview.test.tsx +44 -0
- package/src/components/cnpg/CNPGSummaryHost.test.tsx +23 -0
- package/src/components/cnpg/CNPGSummaryHost.tsx +228 -54
- package/src/components/cnpg/CNPGTrends.denied.test.tsx +33 -0
- package/src/components/cnpg/CNPGTrends.missing.test.tsx +69 -0
- package/src/components/cnpg/CNPGTrends.test.ts +38 -0
- package/src/components/cnpg/CNPGTrends.tsx +702 -0
- package/src/components/cnpg/CNPGView.tsx +34 -21
- package/src/components/cnpg/CNPGWorkspace.compatibility.test.tsx +90 -0
- package/src/components/cnpg/actions/CNPGClusterActions.dialog.test.tsx +79 -0
- package/src/components/cnpg/actions/CNPGClusterActions.test.tsx +142 -0
- package/src/components/cnpg/actions/CNPGClusterActions.tsx +853 -0
- package/src/components/cnpg/actions/CNPGConnectButton.read.test.tsx +40 -0
- package/src/components/cnpg/actions/CNPGConnectButton.test.ts +25 -0
- package/src/components/cnpg/actions/CNPGConnectButton.tsx +131 -0
- package/src/components/cnpg/actions/CNPGDestroyInstanceDialog.tsx +185 -0
- package/src/components/cnpg/actions/CNPGInstanceActions.test.tsx +38 -0
- package/src/components/cnpg/actions/CNPGInstanceActions.tsx +74 -0
- package/src/components/cnpg/actions/CNPGMaintenanceBanner.tsx +53 -0
- package/src/components/cnpg/actions/CNPGPoolerActions.test.tsx +35 -0
- package/src/components/cnpg/actions/CNPGPoolerActions.tsx +79 -0
- package/src/components/cnpg/actions/CNPGReportDialog.test.tsx +12 -0
- package/src/components/cnpg/actions/CNPGReportDialog.tsx +91 -0
- package/src/components/cnpg/actions/CNPGScheduleActions.run.test.tsx +58 -0
- package/src/components/cnpg/actions/CNPGScheduleActions.test.tsx +54 -0
- package/src/components/cnpg/actions/CNPGScheduleActions.tsx +208 -0
- package/src/components/cnpg/actions/actionModel.test.ts +111 -0
- package/src/components/cnpg/actions/actionModel.ts +127 -0
- package/src/components/cnpg/actions/renderCNPGHeaderActions.tsx +60 -0
- package/src/components/cnpg/actions/useCNPGWriteGuard.tsx +58 -0
- package/src/components/cnpg/actions/useOpenCNPGPsql.ts +22 -0
- package/src/components/cnpg/archivingRepair.test.ts +104 -0
- package/src/components/cnpg/archivingRepair.ts +145 -0
- package/src/components/cnpg/baseBackup.test.ts +36 -0
- package/src/components/cnpg/baseBackup.ts +36 -0
- package/src/components/cnpg/blocking.test.ts +59 -0
- package/src/components/cnpg/blocking.ts +131 -0
- package/src/components/cnpg/coverageText.test.ts +27 -0
- package/src/components/cnpg/creation/CNPGTargetFields.tsx +110 -0
- package/src/components/cnpg/creation/targetModel.test.ts +137 -0
- package/src/components/cnpg/creation/targetModel.ts +149 -0
- package/src/components/cnpg/fleetStatus.test.ts +46 -0
- package/src/components/cnpg/fleetStatus.ts +43 -0
- package/src/components/cnpg/formFields.ts +2 -0
- package/src/components/cnpg/grantText.test.tsx +11 -0
- package/src/components/cnpg/host.test.tsx +24 -0
- package/src/components/cnpg/host.tsx +101 -0
- package/src/components/cnpg/inspectModel.test.ts +120 -0
- package/src/components/cnpg/inspectModel.ts +140 -0
- package/src/components/cnpg/logicalSlots.test.ts +30 -0
- package/src/components/cnpg/logicalSlots.ts +29 -0
- package/src/components/cnpg/lsn.test.ts +40 -0
- package/src/components/cnpg/lsn.ts +43 -0
- package/src/components/cnpg/operations/CNPGFleetOperation.tsx +30 -0
- package/src/components/cnpg/operations/CNPGOperationTracker.test.tsx +247 -0
- package/src/components/cnpg/operations/CNPGOperationTracker.tsx +255 -0
- package/src/components/cnpg/operations/model.test.ts +510 -0
- package/src/components/cnpg/operations/model.ts +499 -0
- package/src/components/cnpg/operations/presentation.test.ts +80 -0
- package/src/components/cnpg/operations/presentation.ts +84 -0
- package/src/components/cnpg/operations/store.ts +98 -0
- package/src/components/cnpg/operatorStatus.test.ts +134 -0
- package/src/components/cnpg/operatorStatus.ts +194 -0
- package/src/components/cnpg/paths.test.ts +54 -0
- package/src/components/cnpg/paths.ts +47 -4
- package/src/components/cnpg/protection/CNPGAttachArchiveDialog.tsx +185 -0
- package/src/components/cnpg/protection/CNPGCreateScheduleDialog.tsx +196 -0
- package/src/components/cnpg/protection/CNPGProtectionSetup.test.tsx +121 -0
- package/src/components/cnpg/protection/CNPGProtectionSetup.tsx +452 -0
- package/src/components/cnpg/protection/CNPGScheduleInput.tsx +115 -0
- package/src/components/cnpg/protection/CNPGScheduleRepairButton.tsx +120 -0
- package/src/components/cnpg/protection/scheduleModel.test.ts +19 -0
- package/src/components/cnpg/protection/scheduleModel.ts +16 -0
- package/src/components/cnpg/recovery/CNPGRecoveryPoint.tsx +220 -0
- package/src/components/cnpg/recovery/CNPGRestoreButton.test.tsx +29 -0
- package/src/components/cnpg/recovery/CNPGRestoreButton.tsx +26 -0
- package/src/components/cnpg/recovery/CNPGRestoreChecks.test.tsx +23 -0
- package/src/components/cnpg/recovery/CNPGRestoreChecks.tsx +153 -0
- package/src/components/cnpg/recovery/CNPGRestoreDialog.back.test.tsx +149 -0
- package/src/components/cnpg/recovery/CNPGRestoreDialog.test.tsx +46 -0
- package/src/components/cnpg/recovery/CNPGRestoreDialog.tsx +333 -0
- package/src/components/cnpg/recovery/CNPGRestoreProgress.tsx +182 -0
- package/src/components/cnpg/recovery/CNPGRestoreValidation.tsx +216 -0
- package/src/components/cnpg/recovery/restoreModel.test.ts +709 -0
- package/src/components/cnpg/recovery/restoreModel.ts +685 -0
- package/src/components/cnpg/recovery/restoreOperation.ts +56 -0
- package/src/components/cnpg/refresh.test.ts +38 -0
- package/src/components/cnpg/refresh.ts +17 -0
- package/src/components/cnpg/refreshNotice.test.tsx +29 -0
- package/src/components/cnpg/routes.test.ts +34 -3
- package/src/components/cnpg/routes.ts +60 -48
- package/src/components/cnpg/runtimeAssessment.ts +198 -0
- package/src/components/cnpg/runtimeModel.test.ts +175 -0
- package/src/components/cnpg/runtimeModel.ts +196 -0
- package/src/components/cnpg/runtimeParts.test.tsx +17 -0
- package/src/components/cnpg/runtimeParts.tsx +79 -0
- package/src/components/cnpg/shared.test.tsx +59 -0
- package/src/components/cnpg/shared.tsx +100 -182
- package/src/components/cnpg/storageModel.test.ts +107 -0
- package/src/components/cnpg/storageModel.ts +101 -0
- package/src/components/cnpg/trendSamples.test.ts +148 -0
- package/src/components/cnpg/trendSamples.ts +222 -0
- package/src/components/cnpg/useCNPGClusterAssessment.ts +30 -0
- package/src/components/cnpg/useCNPGKubectlContext.test.tsx +45 -0
- package/src/components/cnpg/useCNPGKubectlContext.ts +13 -0
- package/src/components/cnpg/useCNPGNavigate.test.tsx +58 -0
- package/src/components/cnpg/useCNPGNavigate.ts +21 -0
- package/src/components/cnpg/useCNPGPoolerLive.ts +46 -0
- package/src/components/cnpg/useCNPGScreenParams.ts +24 -0
- package/src/components/cnpg/useCNPGSidebarWorkspace.ts +66 -16
- package/src/components/cnpg/workspaceRoute.ts +23 -0
- package/src/components/cost/ApplicationCostTab.test.ts +7 -0
- package/src/components/cost/ApplicationCostTab.tsx +7 -7
- package/src/components/cost/CostView.test.ts +2 -1
- package/src/components/cost/CostView.tsx +0 -2
- package/src/components/cost/WorkloadCostTab.tsx +2 -4
- package/src/components/cost/errors.ts +3 -1
- package/src/components/cost/source.test.ts +10 -0
- package/src/components/cost/source.ts +5 -1
- package/src/components/diagnose/investigationEvidence/adapters/issues.ts +8 -0
- package/src/components/diagnose/investigationEvidence/projection.test.ts +16 -0
- package/src/components/dock/BottomDock.tsx +2 -0
- package/src/components/dock/TerminalTab.test.tsx +17 -0
- package/src/components/dock/TerminalTab.tsx +7 -3
- package/src/components/gitops/ArgoResourceDiffLoader.tsx +4 -2
- package/src/components/gitops/GitOpsView.tsx +16 -23
- package/src/components/helm/HelmCompareRoute.test.tsx +59 -0
- package/src/components/helm/HelmCompareRoute.tsx +57 -26
- package/src/components/helm/HelmReleaseDrawer.test.ts +55 -3
- package/src/components/helm/HelmReleaseDrawer.tsx +50 -12
- package/src/components/helm/TrackChartSourceDialog.render.test.tsx +120 -0
- package/src/components/helm/TrackChartSourceDialog.test.ts +50 -0
- package/src/components/helm/TrackChartSourceDialog.tsx +153 -176
- package/src/components/helm/ValuesViewer.test.tsx +301 -0
- package/src/components/helm/ValuesViewer.tsx +169 -78
- package/src/components/home/ActivitySummary.tsx +2 -1
- package/src/components/home/HomeView.tsx +20 -9
- package/src/components/home/ProblemsPanel.test.tsx +81 -0
- package/src/components/home/TopologyPreview.tsx +2 -2
- package/src/components/issues/IssuesPane.render.test.tsx +122 -0
- package/src/components/issues/IssuesPane.test.ts +65 -1
- package/src/components/issues/IssuesPane.tsx +188 -25
- package/src/components/issues/uncheckedSources.ts +21 -0
- package/src/components/resource/PVCUsageBar.render.test.tsx +32 -0
- package/src/components/resource/PVCUsageBar.test.tsx +24 -0
- package/src/components/resource/PVCUsageBar.tsx +15 -6
- package/src/components/resource/WorkloadMetricsHelpDialog.test.tsx +3 -1
- package/src/components/resource/WorkloadMetricsHelpDialog.tsx +1 -1
- package/src/components/resources/CompositeRenderer.tsx +4 -4
- package/src/components/resources/ResourceDetailDrawer.tsx +2 -2
- package/src/components/resources/ResourcesView.tsx +26 -12
- package/src/components/settings/LocalIntegrationStatus.test.tsx +52 -0
- package/src/components/settings/LocalIntegrationStatus.tsx +3 -4
- package/src/components/settings/SettingsDialog.tsx +28 -15
- package/src/components/timeline/TimelineList.tsx +22 -24
- package/src/components/timeline/TimelineLoadError.test.tsx +62 -0
- package/src/components/timeline/TimelineLoadError.tsx +96 -0
- package/src/components/timeline/TimelineView.tsx +18 -45
- package/src/components/timeline/ViewModeToggle.tsx +20 -0
- package/src/components/timeline/useStickyError.test.tsx +47 -0
- package/src/components/timeline/useStickyError.ts +15 -0
- package/src/components/ui/DiagnosticsOverlay.tsx +34 -2
- package/src/components/ui/Markdown.test.tsx +65 -0
- package/src/components/ui/Markdown.tsx +144 -127
- package/src/components/whats-new/releaseNotes.ts +63 -1
- package/src/components/workload/WorkloadView.test.ts +10 -1
- package/src/components/workload/WorkloadView.tsx +27 -239
- package/src/components/workspace/layout.test.tsx +29 -0
- package/src/components/workspace/layout.tsx +259 -0
- package/src/components/workspace/table.ts +7 -0
- package/src/contexts/CapabilitiesContext.tsx +1 -1
- package/src/integrations/resourceHost.test.tsx +55 -0
- package/src/integrations/resourceHost.ts +99 -0
- package/src/integrations/resourceHosts.test.tsx +141 -0
- package/src/integrations/resourceHosts.tsx +119 -0
- package/src/integrations/workspaceRoute.ts +20 -0
- package/src/integrations/workspaceRoutes.test.ts +40 -0
- package/src/integrations/workspaceRoutes.ts +22 -0
- package/src/integrations/workspaceScreens.tsx +14 -0
- package/src/utils/drawer-trail.ts +35 -0
- package/src/utils/kindFilterStore.test.ts +147 -0
- package/src/utils/kindFilterStore.ts +91 -0
- package/src/utils/navigation.test.ts +13 -1
- package/src/utils/navigation.ts +39 -2
- package/src/utils/page-links.ts +21 -0
- package/src/utils/tabStorage.ts +40 -0
- package/src/utils/viewMemory.test.ts +78 -0
- package/src/utils/viewMemory.ts +54 -0
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { describe, expect, it } from 'vitest'
|
|
2
|
+
import { renderCNPGSummary } from './CNPGSummaryHost'
|
|
3
|
+
|
|
4
|
+
const ctx = (apiVersion: string, kind: string) => ({
|
|
5
|
+
apiKind: kind,
|
|
6
|
+
namespace: 'pg',
|
|
7
|
+
name: 'x',
|
|
8
|
+
resource: { apiVersion, kind, metadata: { namespace: 'pg', name: 'x' } },
|
|
9
|
+
context: 'drawer' as const,
|
|
10
|
+
})
|
|
11
|
+
|
|
12
|
+
describe('renderCNPGSummary', () => {
|
|
13
|
+
it('returns null for resources without a CNPG summary, so they keep the default Overview', () => {
|
|
14
|
+
expect(renderCNPGSummary(ctx('v1', 'Pod'))).toBeNull()
|
|
15
|
+
expect(renderCNPGSummary(ctx('apps/v1', 'Deployment'))).toBeNull()
|
|
16
|
+
expect(renderCNPGSummary(ctx('velero.io/v1', 'Backup'))).toBeNull()
|
|
17
|
+
expect(renderCNPGSummary(ctx('cluster.x-k8s.io/v1beta1', 'Cluster'))).toBeNull()
|
|
18
|
+
})
|
|
19
|
+
|
|
20
|
+
it('returns a summary for a CNPG Cluster', () => {
|
|
21
|
+
expect(renderCNPGSummary(ctx('postgresql.cnpg.io/v1', 'Cluster'))).not.toBeNull()
|
|
22
|
+
})
|
|
23
|
+
})
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import type { ReactNode } from 'react'
|
|
2
|
-
import {
|
|
2
|
+
import { useLocation, useSearchParams } from 'react-router-dom'
|
|
3
3
|
import {
|
|
4
4
|
CNPG_BARMAN_OBJECTSTORE_GROUP,
|
|
5
|
+
OpenIssueContext,
|
|
5
6
|
CNPG_GROUP,
|
|
6
7
|
CNPGBackupSummary,
|
|
7
8
|
CNPGClusterSummary,
|
|
@@ -12,17 +13,42 @@ import {
|
|
|
12
13
|
CNPGPublicationSummary,
|
|
13
14
|
CNPGScheduledBackupSummary,
|
|
14
15
|
CNPGSubscriptionSummary,
|
|
16
|
+
CNPGDatabaseRoleSummary,
|
|
17
|
+
relationUnavailable,
|
|
18
|
+
cnpgLogicalPaths,
|
|
19
|
+
cnpgLogicalSlotFact,
|
|
20
|
+
cnpgSubscriptionHostNamespace,
|
|
21
|
+
FactRow,
|
|
22
|
+
FactSource,
|
|
23
|
+
FactValue,
|
|
15
24
|
PaneLoader,
|
|
16
25
|
isApiGroup,
|
|
17
26
|
refToSelectedResource,
|
|
18
|
-
type
|
|
19
|
-
type
|
|
27
|
+
type NavigateToRef,
|
|
28
|
+
type ResourceRef,
|
|
29
|
+
type CNPGLogicalPath,
|
|
20
30
|
type CNPGWorkspaceResponse,
|
|
21
31
|
type NavigateToResource,
|
|
22
32
|
} from '@skyhook-io/k8s-ui'
|
|
23
33
|
import { useCNPGFleet } from './useCNPGSidebarWorkspace'
|
|
24
|
-
import {
|
|
34
|
+
import { useCNPGRuntime, useCNPGScheduleCapabilities, useCNPGWorkspace, type CNPGRuntimeResponse } from '../../api/cnpg'
|
|
35
|
+
import { cnpgPublisherSlotsFrom, useCNPGPublisherSlots } from './logicalSlots'
|
|
36
|
+
import { cnpgBaseBackupFacts, describeCNPGBaseBackup } from './baseBackup'
|
|
37
|
+
import { useCNPGPoolerLive } from './useCNPGPoolerLive'
|
|
38
|
+
import { useCNPGClusterAssessment } from './useCNPGClusterAssessment'
|
|
39
|
+
import { CNPGMaintenanceBanner } from './actions/CNPGMaintenanceBanner'
|
|
40
|
+
import { CNPG_CONNECT_PARAM, cnpgConnectParamValue } from './actions/CNPGConnectButton'
|
|
41
|
+
import { CNPGOperatorBanner } from './CNPGOperatorBanner'
|
|
42
|
+
import { cnpgClusterFullPath, cnpgDimensionPath, cnpgDimensionTabLabel, cnpgWithinDetail } from './paths'
|
|
43
|
+
import { cnpgScreenPath } from './routes'
|
|
44
|
+
import { currentPageLabel, issuesPathForSubject } from '../../utils/page-links'
|
|
45
|
+
import { CNPGRestoreProgress } from './recovery/CNPGRestoreProgress'
|
|
46
|
+
import { restoreBackupDeclared } from './recovery/restoreModel'
|
|
25
47
|
import { useConnection } from '../../context/ConnectionContext'
|
|
48
|
+
import { useNavCustomization } from '../../context/NavCustomization'
|
|
49
|
+
import { RefreshFailedNotice } from '../workspace/layout'
|
|
50
|
+
import { useCNPGNavigate } from './useCNPGNavigate'
|
|
51
|
+
import { CNPGOperatorConditions } from './CNPGOperatorConditions'
|
|
26
52
|
|
|
27
53
|
interface SummaryContext {
|
|
28
54
|
apiKind: string
|
|
@@ -34,70 +60,189 @@ interface SummaryContext {
|
|
|
34
60
|
onNavigate?: NavigateToResource
|
|
35
61
|
}
|
|
36
62
|
|
|
37
|
-
function
|
|
38
|
-
const
|
|
63
|
+
function BaseBackupFact({ runtime }: { runtime: CNPGRuntimeResponse | undefined }) {
|
|
64
|
+
const bb = cnpgBaseBackupFacts(runtime)
|
|
65
|
+
if (!bb) return null
|
|
66
|
+
return (
|
|
67
|
+
<FactRow label="Standby cloning">
|
|
68
|
+
{bb.rows.length > 1 ? (
|
|
69
|
+
<ul className="space-y-0.5">
|
|
70
|
+
{bb.rows.map((r) => (
|
|
71
|
+
<li key={r.applicationName}>{describeCNPGBaseBackup(r)}</li>
|
|
72
|
+
))}
|
|
73
|
+
</ul>
|
|
74
|
+
) : (
|
|
75
|
+
<FactValue fact={bb.fact} />
|
|
76
|
+
)}
|
|
77
|
+
<FactSource fact={bb.fact} />
|
|
78
|
+
</FactRow>
|
|
79
|
+
)
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
function ClusterSummaryHost({ namespace, name, resource, context, onNavigate }: SummaryContext) {
|
|
83
|
+
const navigate = useCNPGNavigate()
|
|
84
|
+
const location = useLocation()
|
|
39
85
|
const { connection } = useConnection()
|
|
40
86
|
// The workspace is read for the object's own namespace: an explicitly opened
|
|
41
87
|
// Cluster shows its facts whatever the namespace filter is.
|
|
42
|
-
const { query,
|
|
43
|
-
const
|
|
88
|
+
const { query, runtime, ha, row, dimensions } = useCNPGClusterAssessment(namespace, name)
|
|
89
|
+
const operatorConditions = context === 'expanded' ? <CNPGOperatorConditions conditions={resource.status?.conditions ?? []} /> : null
|
|
44
90
|
if (!row) {
|
|
45
|
-
if (query.isLoading) return
|
|
91
|
+
if (query.isLoading) return <><PaneLoader label="Loading summary…" className="h-40" />{operatorConditions}</>
|
|
46
92
|
return (
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
93
|
+
<>
|
|
94
|
+
<div className="px-4 py-4 text-sm text-theme-text-secondary">
|
|
95
|
+
{query.error instanceof Error
|
|
96
|
+
? `The CloudNativePG summary could not be loaded: ${query.error.message}`
|
|
97
|
+
: 'The CloudNativePG workspace summary could not be read.'}{' '}
|
|
98
|
+
{context === 'expanded' ? 'Configuration shows the declared settings; Operator conditions are below. YAML holds the full object.' : 'Spec & status still shows the object’s fields.'}
|
|
99
|
+
</div>
|
|
100
|
+
{operatorConditions}
|
|
101
|
+
</>
|
|
53
102
|
)
|
|
54
103
|
}
|
|
55
|
-
const
|
|
104
|
+
const goRef = onNavigate ? (ref: ResourceRef) => onNavigate(refToSelectedResource(ref)) : undefined
|
|
105
|
+
// On the Cluster's own page this is a tab change, applied like a tab click;
|
|
106
|
+
// anywhere else (the drawer, another page) it is a push to the full page
|
|
107
|
+
// with a return label.
|
|
108
|
+
const go = (path: string) => {
|
|
109
|
+
const inPlace = context === 'expanded' ? cnpgWithinDetail(location.pathname, location.search, path) : null
|
|
110
|
+
if (inPlace) navigate(inPlace, { replace: true, state: location.state })
|
|
111
|
+
else navigate(path, { state: { returnLabel: currentPageLabel(), returnCtx: connection.context } })
|
|
112
|
+
}
|
|
56
113
|
return (
|
|
57
|
-
<
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
114
|
+
<div>
|
|
115
|
+
<CNPGClusterSummary
|
|
116
|
+
row={row}
|
|
117
|
+
framed={context === 'expanded'}
|
|
118
|
+
onNavigate={goRef}
|
|
119
|
+
lead={
|
|
120
|
+
<>
|
|
121
|
+
<RefreshFailedNotice queries={[runtime, ha]} />
|
|
122
|
+
{context === 'drawer' && <CNPGOperatorBanner namespaces={[namespace]} />}
|
|
123
|
+
<CNPGMaintenanceBanner namespace={namespace} name={name} maintenance={ha.data?.maintenance} />
|
|
124
|
+
{row.cluster?.spec?.bootstrap?.recovery && (
|
|
125
|
+
<CNPGRestoreProgress
|
|
126
|
+
namespace={namespace}
|
|
127
|
+
name={name}
|
|
128
|
+
nextSteps={{
|
|
129
|
+
backup: row.cluster ? restoreBackupDeclared(row.cluster) : undefined,
|
|
130
|
+
onOpen: (step) => {
|
|
131
|
+
if (step === 'connect') {
|
|
132
|
+
const p = new URLSearchParams(location.search)
|
|
133
|
+
p.set(CNPG_CONNECT_PARAM, cnpgConnectParamValue(namespace, name, context === 'drawer' ? 'drawer' : 'page'))
|
|
134
|
+
navigate({ search: p.toString() }, { replace: true, state: location.state })
|
|
135
|
+
return
|
|
136
|
+
}
|
|
137
|
+
const protection = cnpgClusterFullPath(namespace, name, connection.context || undefined, 'backups')
|
|
138
|
+
go(step === 'validate' ? `${protection}&validate=1` : step === 'backup' ? `${protection}&protectionSetup=1` : protection)
|
|
139
|
+
},
|
|
140
|
+
}}
|
|
141
|
+
/>
|
|
142
|
+
)}
|
|
143
|
+
</>
|
|
144
|
+
}
|
|
145
|
+
onSelectDimension={(id) => go(cnpgDimensionPath(namespace, name, connection.context || undefined, id))}
|
|
146
|
+
dimensionLinkLabel={cnpgDimensionTabLabel}
|
|
147
|
+
onOpenOperator={() => navigate(cnpgScreenPath('operator'), { state: { returnLabel: currentPageLabel(), returnCtx: connection.context } })}
|
|
148
|
+
initialProblemsExpanded={context === 'expanded' && new URLSearchParams(location.search).get('problems') === 'all'}
|
|
149
|
+
dimensions={dimensions}
|
|
150
|
+
operationalFacts={<BaseBackupFact runtime={runtime.data} />}
|
|
151
|
+
actions={
|
|
152
|
+
context === 'drawer'
|
|
153
|
+
? [
|
|
154
|
+
{
|
|
155
|
+
label: 'Open cluster',
|
|
156
|
+
primary: true,
|
|
157
|
+
onClick: () =>
|
|
158
|
+
navigate(cnpgClusterFullPath(namespace, name, connection.context || undefined), {
|
|
159
|
+
state: { returnLabel: currentPageLabel(), returnCtx: connection.context },
|
|
160
|
+
}),
|
|
161
|
+
},
|
|
162
|
+
{
|
|
163
|
+
label: 'Logs',
|
|
164
|
+
onClick: () =>
|
|
165
|
+
navigate(cnpgClusterFullPath(namespace, name, connection.context || undefined, 'logs'), {
|
|
166
|
+
state: { returnLabel: currentPageLabel(), returnCtx: connection.context },
|
|
167
|
+
}),
|
|
168
|
+
},
|
|
169
|
+
{
|
|
170
|
+
label: 'Backups',
|
|
171
|
+
onClick: () =>
|
|
172
|
+
navigate(cnpgClusterFullPath(namespace, name, connection.context || undefined, 'backups'), {
|
|
173
|
+
state: { returnLabel: currentPageLabel(), returnCtx: connection.context },
|
|
174
|
+
}),
|
|
175
|
+
},
|
|
176
|
+
]
|
|
177
|
+
: undefined
|
|
178
|
+
}
|
|
179
|
+
/>
|
|
180
|
+
{operatorConditions}
|
|
181
|
+
</div>
|
|
89
182
|
)
|
|
90
183
|
}
|
|
91
184
|
|
|
92
|
-
type ObjectSummary = (props: { resource: any; workspace: CNPGWorkspaceResponse | null; onNavigate?:
|
|
185
|
+
type ObjectSummary = (props: { resource: any; workspace: CNPGWorkspaceResponse | null; onNavigate?: NavigateToRef }) => ReactNode
|
|
186
|
+
|
|
187
|
+
function ScheduledBackupSummaryHost(props: { resource: any; workspace: CNPGWorkspaceResponse | null; onNavigate?: NavigateToRef }) {
|
|
188
|
+
const ns = props.resource?.metadata?.namespace ?? ''
|
|
189
|
+
const name = props.resource?.metadata?.name ?? ''
|
|
190
|
+
const caps = useCNPGScheduleCapabilities(ns, name)
|
|
191
|
+
return <CNPGScheduledBackupSummary {...props} schedulePreview={caps.data?.facts.preview} />
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
// The publisher may live in another namespace than the subscriber: that
|
|
195
|
+
// namespace's Clusters, Publications and Poolers are read too.
|
|
196
|
+
function useLogicalWorkspace(ws: CNPGWorkspaceResponse | null, subscriptions: any[]) {
|
|
197
|
+
const hostNs = [...new Set(subscriptions.map((s) => cnpgSubscriptionHostNamespace(s, ws?.objects.clusters ?? [])).filter((n): n is string => !!n))]
|
|
198
|
+
const extra = ws?.namespaces === null ? [] : hostNs.filter((n) => !(ws?.namespaces ?? []).includes(n))
|
|
199
|
+
const other = useCNPGWorkspace(extra, { enabled: !!ws && extra.length > 0 })
|
|
200
|
+
const merged = (key: 'clusters' | 'publications' | 'poolers') => [...(ws?.objects[key] ?? []), ...(other.data?.installed ? other.data.objects[key] ?? [] : [])]
|
|
201
|
+
const publicationsUnavailable = (ns: string) =>
|
|
202
|
+
ws?.namespaces === null || ws?.namespaces?.includes(ns)
|
|
203
|
+
? relationUnavailable(ws, 'publications', ns, 'Publications')
|
|
204
|
+
: relationUnavailable(other.data?.installed ? other.data : null, 'publications', ns, 'Publications')
|
|
205
|
+
return { clusters: merged('clusters'), publications: merged('publications'), poolers: merged('poolers'), publicationsUnavailable }
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
function LogicalPathSlot({ path, children }: { path: CNPGLogicalPath; children: (slot: ReturnType<typeof cnpgLogicalSlotFact>, notice: ReactNode) => ReactNode }) {
|
|
209
|
+
const { observed, query } = useCNPGPublisherSlots(path.publisher)
|
|
210
|
+
return <>{children(cnpgLogicalSlotFact(path, observed), <RefreshFailedNotice queries={[query]} />)}</>
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
function SubscriptionSummaryHost(props: { resource: any; workspace: CNPGWorkspaceResponse | null; onNavigate?: NavigateToRef }) {
|
|
214
|
+
const lw = useLogicalWorkspace(props.workspace, [props.resource])
|
|
215
|
+
const path = props.workspace ? cnpgLogicalPaths([props.resource], lw.clusters, lw.publications, lw.poolers, lw.publicationsUnavailable)[0] : undefined
|
|
216
|
+
if (!path) return <CNPGSubscriptionSummary {...props} />
|
|
217
|
+
return <LogicalPathSlot path={path}>{(slot, notice) => <CNPGSubscriptionSummary {...props} logicalPath={{ path, slot, notice }} />}</LogicalPathSlot>
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
function PublicationSummaryHost(props: { resource: any; workspace: CNPGWorkspaceResponse | null; onNavigate?: NavigateToRef }) {
|
|
221
|
+
// Subscribers are the Subscriptions in view; the publisher's own runtime
|
|
222
|
+
// answers for every slot.
|
|
223
|
+
const pubCluster = props.resource?.spec?.cluster?.name
|
|
224
|
+
const ns = props.resource?.metadata?.namespace ?? ''
|
|
225
|
+
const runtime = useCNPGRuntime(ns, pubCluster ?? '', !!pubCluster)
|
|
226
|
+
const all = useCNPGWorkspace([], { enabled: !!props.workspace })
|
|
227
|
+
const ws = all.data?.installed ? all.data : props.workspace
|
|
228
|
+
const paths = ws
|
|
229
|
+
? cnpgLogicalPaths(ws.objects.subscriptions ?? [], ws.objects.clusters ?? [], ws.objects.publications ?? [], ws.objects.poolers ?? []).filter(
|
|
230
|
+
(p) => p.publication.object?.namespace === ns && p.publication.object?.name === props.resource?.metadata?.name,
|
|
231
|
+
)
|
|
232
|
+
: []
|
|
233
|
+
const observed = cnpgPublisherSlotsFrom(runtime.data, runtime.error, runtime.isRefetchError)
|
|
234
|
+
const notice = <RefreshFailedNotice queries={[runtime]} />
|
|
235
|
+
return <CNPGPublicationSummary {...props} subscribers={paths.map((path) => ({ path, slot: cnpgLogicalSlotFact(path, observed), notice }))} />
|
|
236
|
+
}
|
|
93
237
|
|
|
94
238
|
const OBJECT_SUMMARIES: Record<string, ObjectSummary> = {
|
|
95
239
|
Backup: CNPGBackupSummary,
|
|
96
|
-
ScheduledBackup:
|
|
240
|
+
ScheduledBackup: ScheduledBackupSummaryHost,
|
|
97
241
|
Pooler: CNPGPoolerSummary,
|
|
98
242
|
Database: CNPGDatabaseSummary,
|
|
99
|
-
Publication:
|
|
100
|
-
Subscription:
|
|
243
|
+
Publication: PublicationSummaryHost,
|
|
244
|
+
Subscription: SubscriptionSummaryHost,
|
|
245
|
+
DatabaseRole: CNPGDatabaseRoleSummary,
|
|
101
246
|
ImageCatalog: CNPGImageCatalogSummary,
|
|
102
247
|
ClusterImageCatalog: CNPGImageCatalogSummary,
|
|
103
248
|
}
|
|
@@ -109,10 +254,19 @@ function ObjectSummaryHost({ ctx, Summary }: { ctx: SummaryContext; Summary: Obj
|
|
|
109
254
|
const { query } = useCNPGFleet(clusterScoped ? [] : [ctx.namespace])
|
|
110
255
|
if (query.isLoading) return <PaneLoader label="Loading summary…" className="h-40" />
|
|
111
256
|
const workspace = query.data?.installed ? query.data : null
|
|
112
|
-
const go = ctx.onNavigate ? (ref:
|
|
257
|
+
const go = ctx.onNavigate ? (ref: ResourceRef) => ctx.onNavigate?.(refToSelectedResource(ref)) : undefined
|
|
113
258
|
return <Summary resource={ctx.resource} workspace={workspace} onNavigate={go} />
|
|
114
259
|
}
|
|
115
260
|
|
|
261
|
+
function PoolerSummaryHost({ ctx }: { ctx: SummaryContext }) {
|
|
262
|
+
const { query } = useCNPGFleet([ctx.namespace])
|
|
263
|
+
const { live, queries } = useCNPGPoolerLive(ctx.namespace, ctx.name)
|
|
264
|
+
if (query.isLoading) return <PaneLoader label="Loading summary…" className="h-40" />
|
|
265
|
+
const workspace = query.data?.installed ? query.data : null
|
|
266
|
+
const go = ctx.onNavigate ? (ref: ResourceRef) => ctx.onNavigate?.(refToSelectedResource(ref)) : undefined
|
|
267
|
+
return <CNPGPoolerSummary resource={ctx.resource} workspace={workspace} onNavigate={go} live={live} lead={<RefreshFailedNotice queries={queries} />} />
|
|
268
|
+
}
|
|
269
|
+
|
|
116
270
|
// The object's own apiVersion decides: Velero also ships a Backup kind.
|
|
117
271
|
function groupOf(ctx: SummaryContext): string | undefined {
|
|
118
272
|
const apiVersion = ctx.resource?.apiVersion
|
|
@@ -130,6 +284,7 @@ function renderSummaryFor(ctx: SummaryContext): ReactNode {
|
|
|
130
284
|
}
|
|
131
285
|
if (group !== CNPG_GROUP) return null
|
|
132
286
|
if (kind === 'Cluster') return <ClusterSummaryHost {...ctx} />
|
|
287
|
+
if (kind === 'Pooler') return <PoolerSummaryHost ctx={ctx} />
|
|
133
288
|
const Summary = OBJECT_SUMMARIES[kind]
|
|
134
289
|
return Summary ? <ObjectSummaryHost ctx={ctx} Summary={Summary} /> : null
|
|
135
290
|
}
|
|
@@ -139,5 +294,24 @@ function renderSummaryFor(ctx: SummaryContext): ReactNode {
|
|
|
139
294
|
* without one, which keeps the default Overview.
|
|
140
295
|
*/
|
|
141
296
|
export function renderCNPGSummary(ctx: SummaryContext): ReactNode {
|
|
142
|
-
|
|
297
|
+
const summary = renderSummaryFor(ctx)
|
|
298
|
+
return summary ? <IssueLinks>{summary}</IssueLinks> : null
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
// "See in Issues →" on every CNPG problem: the Issues page narrowed to the
|
|
302
|
+
// problem's subject (it has no link to a single issue). Omitted where the host
|
|
303
|
+
// takes Issues over, since its page does not read the subject.
|
|
304
|
+
function IssueLinks({ children }: { children: ReactNode }) {
|
|
305
|
+
const navigate = useCNPGNavigate()
|
|
306
|
+
const [searchParams] = useSearchParams()
|
|
307
|
+
const issuesTakenOver = !!useNavCustomization().fleetTakeoverHref?.('issues')
|
|
308
|
+
return (
|
|
309
|
+
<OpenIssueContext.Provider
|
|
310
|
+
value={issuesTakenOver ? undefined : (p) =>
|
|
311
|
+
navigate(issuesPathForSubject(p.subject, searchParams.get('namespaces')))
|
|
312
|
+
}
|
|
313
|
+
>
|
|
314
|
+
{children}
|
|
315
|
+
</OpenIssueContext.Provider>
|
|
316
|
+
)
|
|
143
317
|
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { renderToStaticMarkup } from 'react-dom/server'
|
|
2
|
+
import { MemoryRouter } from 'react-router-dom'
|
|
3
|
+
import { describe, expect, it, vi } from 'vitest'
|
|
4
|
+
|
|
5
|
+
vi.mock('../../api/cnpg-history', async (orig) => ({
|
|
6
|
+
...(await orig<typeof import('../../api/cnpg-history')>()),
|
|
7
|
+
useCNPGClusterHistory: () => ({ data: { source: 'none', reason: 'Radar is not connected to Prometheus' }, isLoading: false, error: null }),
|
|
8
|
+
}))
|
|
9
|
+
|
|
10
|
+
const { CNPGTrends } = await import('./CNPGTrends')
|
|
11
|
+
|
|
12
|
+
const render = (samplingDenied?: string) =>
|
|
13
|
+
renderToStaticMarkup(
|
|
14
|
+
<MemoryRouter>
|
|
15
|
+
<CNPGTrends namespace="db" name="pg" samples={[]} samplingDenied={samplingDenied} />
|
|
16
|
+
</MemoryRouter>,
|
|
17
|
+
)
|
|
18
|
+
|
|
19
|
+
describe('CNPGTrends without Prometheus', () => {
|
|
20
|
+
it('does not wait for samples that can never arrive when the proxy is denied', () => {
|
|
21
|
+
const html = render('get pods/proxy in db')
|
|
22
|
+
expect(html).toContain('History needs Prometheus')
|
|
23
|
+
expect(html).toContain('In-page samples need get pods/proxy in db too.')
|
|
24
|
+
expect(html).not.toContain('Collecting samples')
|
|
25
|
+
expect(html).not.toContain('since this page opened')
|
|
26
|
+
expect(html).not.toContain('Drag across a chart')
|
|
27
|
+
expect(html).toContain('History needs Prometheus. Radar is not connected to Prometheus.')
|
|
28
|
+
})
|
|
29
|
+
it('samples in the page when the proxy is allowed', () => {
|
|
30
|
+
const html = render()
|
|
31
|
+
expect(html).toContain('Collecting samples')
|
|
32
|
+
})
|
|
33
|
+
})
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import { renderToStaticMarkup } from 'react-dom/server'
|
|
2
|
+
import { MemoryRouter } from 'react-router-dom'
|
|
3
|
+
import { describe, expect, it, vi } from 'vitest'
|
|
4
|
+
|
|
5
|
+
const lag = (series: string[], state = 'ok') => ({
|
|
6
|
+
id: 'replicationLag',
|
|
7
|
+
title: 'Replay lag per standby',
|
|
8
|
+
unit: 'seconds',
|
|
9
|
+
source: 'cnpg_pg_replication_lag',
|
|
10
|
+
seriesBy: 'pod',
|
|
11
|
+
state,
|
|
12
|
+
reason: state === 'empty' ? 'no instance was a standby in this range' : undefined,
|
|
13
|
+
series: series.map((pod) => ({
|
|
14
|
+
labels: { pod },
|
|
15
|
+
dataPoints: [
|
|
16
|
+
{ timestamp: 60, value: 0 },
|
|
17
|
+
{ timestamp: 120, value: 0 },
|
|
18
|
+
],
|
|
19
|
+
})),
|
|
20
|
+
steps: 2,
|
|
21
|
+
covered: 2,
|
|
22
|
+
})
|
|
23
|
+
|
|
24
|
+
let chart = lag(['pg-2'])
|
|
25
|
+
vi.mock('../../api/cnpg-history', async (orig) => ({
|
|
26
|
+
...(await orig<typeof import('../../api/cnpg-history')>()),
|
|
27
|
+
useCNPGClusterHistory: () => ({
|
|
28
|
+
data: {
|
|
29
|
+
source: 'prometheus',
|
|
30
|
+
state: 'ok',
|
|
31
|
+
range: '1h',
|
|
32
|
+
start: '1970-01-01T00:01:00Z',
|
|
33
|
+
end: '1970-01-01T00:02:00Z',
|
|
34
|
+
stepSeconds: 60,
|
|
35
|
+
sampledAt: '',
|
|
36
|
+
cluster: { namespace: 'db', name: 'pg', uid: 'u' },
|
|
37
|
+
charts: [chart],
|
|
38
|
+
},
|
|
39
|
+
isLoading: false,
|
|
40
|
+
error: null,
|
|
41
|
+
}),
|
|
42
|
+
}))
|
|
43
|
+
|
|
44
|
+
const { CNPGTrends } = await import('./CNPGTrends')
|
|
45
|
+
|
|
46
|
+
const render = (standbys?: string[]) =>
|
|
47
|
+
renderToStaticMarkup(
|
|
48
|
+
<MemoryRouter>
|
|
49
|
+
<CNPGTrends namespace="db" name="pg" samples={[]} standbys={standbys} instancePods={['pg-1', 'pg-2', 'pg-3']} />
|
|
50
|
+
</MemoryRouter>,
|
|
51
|
+
)
|
|
52
|
+
|
|
53
|
+
describe('replay-lag history with a standby that does not report', () => {
|
|
54
|
+
it('names the standby instead of drawing only the calm one', () => {
|
|
55
|
+
chart = lag(['pg-2'])
|
|
56
|
+
expect(render(['pg-1', 'pg-2'])).toContain('No line for pg-1: Prometheus has no samples from it in this range.')
|
|
57
|
+
})
|
|
58
|
+
it('says nothing when every standby has a line, or when the standbys are unknown', () => {
|
|
59
|
+
chart = lag(['pg-2'])
|
|
60
|
+
expect(render(['pg-2'])).not.toContain('No line for')
|
|
61
|
+
expect(render()).not.toContain('No line for')
|
|
62
|
+
})
|
|
63
|
+
it('replaces "no instance was a standby" when standbys exist but none reported', () => {
|
|
64
|
+
chart = lag([], 'empty')
|
|
65
|
+
const html = render(['pg-1', 'pg-2'])
|
|
66
|
+
expect(html).toContain('No line for pg-1, pg-2: Prometheus has no samples from them in this range.')
|
|
67
|
+
expect(html).not.toContain('no instance was a standby')
|
|
68
|
+
})
|
|
69
|
+
})
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { describe, expect, it } from 'vitest'
|
|
2
|
+
import { historyGaps, sampleGaps, unchartedInstances } from './CNPGTrends'
|
|
3
|
+
|
|
4
|
+
const s = (points: [number, number | null][], label = 'a') => ({ labels: { pod: label }, dataPoints: points.map(([timestamp, value]) => ({ timestamp, value })) })
|
|
5
|
+
|
|
6
|
+
describe('historyGaps', () => {
|
|
7
|
+
it('hatches steps no series covers and says a leading run predates the series', () => {
|
|
8
|
+
const gaps = historyGaps([s([[120, 1], [150, 2]]), s([[240, 1]], 'b')], 60, 240, 30)
|
|
9
|
+
expect(gaps).toEqual([
|
|
10
|
+
{ start: 60, end: 105, label: expect.stringContaining('before this series existed') },
|
|
11
|
+
{ start: 165, end: 225, label: expect.stringContaining('not scraped') },
|
|
12
|
+
])
|
|
13
|
+
})
|
|
14
|
+
|
|
15
|
+
it('reports nothing when every step has a sample', () => {
|
|
16
|
+
expect(historyGaps([s([[0, 1], [30, 0], [60, 3]])], 0, 60, 30)).toEqual([])
|
|
17
|
+
})
|
|
18
|
+
})
|
|
19
|
+
|
|
20
|
+
describe('sampleGaps', () => {
|
|
21
|
+
it('spans from the last sample before a null run to the first after it', () => {
|
|
22
|
+
const gaps = sampleGaps([s([[0, 1], [5, null], [10, null], [15, 4]])])
|
|
23
|
+
expect(gaps).toHaveLength(1)
|
|
24
|
+
expect(gaps[0]).toMatchObject({ start: 0, end: 15 })
|
|
25
|
+
})
|
|
26
|
+
})
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
describe('unchartedInstances', () => {
|
|
30
|
+
it('names the expected instances a chart has no line for', () => {
|
|
31
|
+
expect(unchartedInstances(['pg-1', 'pg-2'], ['pg-2'])).toEqual(['pg-1'])
|
|
32
|
+
expect(unchartedInstances(['pg-1'], ['pg-1'])).toEqual([])
|
|
33
|
+
})
|
|
34
|
+
it('claims nothing when the expected set is unknown', () => {
|
|
35
|
+
expect(unchartedInstances(undefined, [])).toEqual([])
|
|
36
|
+
expect(unchartedInstances([], ['pg-1'])).toEqual([])
|
|
37
|
+
})
|
|
38
|
+
})
|