@skyhook-io/radar-app 1.15.0 → 1.16.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +2 -0
- package/package.json +6 -5
- package/src/App.tsx +14 -11
- package/src/RadarApp.tsx +51 -21
- package/src/api/apiResources.test.ts +19 -1
- package/src/api/apiResources.ts +18 -3
- package/src/api/client.capacity.test.ts +15 -0
- package/src/api/client.trace.test.ts +9 -1
- package/src/api/client.ts +232 -201
- package/src/api/diagnose.test.ts +102 -1
- package/src/api/diagnose.ts +42 -11
- package/src/api/drain-plan.test.ts +0 -10
- package/src/api/httpErrors.test.ts +95 -0
- package/src/api/httpErrors.ts +101 -0
- package/src/api/policy.ts +33 -5
- package/src/api/radarFeatures.render.test.tsx +190 -0
- package/src/api/radarFeatures.test.ts +133 -0
- package/src/api/radarFeatures.ts +147 -0
- package/src/api/timelineSource.test.ts +2 -10
- package/src/api/timelineSource.ts +3 -2
- package/src/api/traffic.ts +4 -3
- package/src/components/CloudFunnelButton.tsx +9 -9
- package/src/components/NamespaceSwitcher.test.tsx +47 -0
- package/src/components/NamespaceSwitcher.tsx +17 -1
- package/src/components/SelfManagedStart.tsx +5 -6
- package/src/components/applications/ApplicationsView.tsx +20 -6
- package/src/components/audit/UpgradeReadinessView.test.ts +14 -6
- package/src/components/audit/UpgradeReadinessView.tsx +9 -13
- package/src/components/capacity/CapacityView.test.tsx +13 -4
- package/src/components/capacity/shared.tsx +14 -13
- package/src/components/cost/ApplicationCostTab.tsx +9 -1
- package/src/components/cost/CostConnectionAction.tsx +30 -0
- package/src/components/cost/CostTrendChart.tsx +2 -0
- package/src/components/cost/CostView.tsx +4 -24
- package/src/components/cost/WorkloadCostTab.tsx +7 -1
- package/src/components/cost/source.test.ts +30 -3
- package/src/components/cost/source.ts +37 -2
- package/src/components/curl/ServiceCurlButton.tsx +7 -3
- package/src/components/dock/NodeTerminalTab.tsx +2 -1
- package/src/components/dock/TerminalTab.tsx +2 -1
- package/src/components/dock/TrafficFlowListTab.tsx +2 -2
- package/src/components/gitops/GitOpsView.tsx +12 -0
- package/src/components/home/HomeView.tsx +1 -1
- package/src/components/home/MCPSetupDialog.tsx +37 -37
- package/src/components/resource/PVCUsageBar.render.test.tsx +16 -0
- package/src/components/resource/PVCUsageBar.tsx +4 -1
- package/src/components/resource/PrometheusChartsGrid.render.test.tsx +1 -0
- package/src/components/resource/PrometheusChartsGrid.tsx +4 -0
- package/src/components/resources/ImageFilesystemModal.tsx +2 -1
- package/src/components/resources/PodFilePreview.tsx +4 -1
- package/src/components/resources/PodFilesystemModal.tsx +3 -2
- package/src/components/resources/ResourcesView.tsx +2 -1
- package/src/components/rightsizing/RightsizingScanView.tsx +4 -1
- package/src/components/settings/ConnectionHeadersEditor.tsx +109 -0
- package/src/components/settings/FormSaveActions.tsx +76 -0
- package/src/components/settings/IntegrationConnectionForms.tsx +391 -0
- package/src/components/settings/LocalConfigurationDetails.tsx +389 -0
- package/src/components/settings/LocalConnectionSettings.test.tsx +189 -0
- package/src/components/settings/LocalConnectionSettings.tsx +1015 -0
- package/src/components/settings/LocalIntegrationStatus.tsx +50 -0
- package/src/components/settings/PrometheusConfigField.tsx +30 -0
- package/src/components/settings/PrometheusConnectionForm.tsx +295 -0
- package/src/components/settings/SettingsDialog.tsx +382 -521
- package/src/components/settings/currency-options.test.ts +1 -1
- package/src/components/settings/currency-options.ts +1 -1
- package/src/components/settings/settings-state.test.ts +17 -10
- package/src/components/settings/settings-state.ts +20 -12
- package/src/components/timeline/TimelineList.test.ts +56 -0
- package/src/components/timeline/TimelineList.tsx +7 -1
- package/src/components/traffic/TrafficFlowList.tsx +5 -21
- package/src/components/traffic/TrafficFlowListContext.tsx +6 -4
- package/src/components/traffic/TrafficGraph.tsx +79 -48
- package/src/components/traffic/TrafficView.tsx +61 -37
- package/src/components/traffic/TrafficWizard.tsx +4 -2
- package/src/components/traffic/trafficFilters.test.ts +154 -1
- package/src/components/traffic/trafficFilters.ts +141 -0
- package/src/components/ui/Omnibar.tsx +1 -1
- package/src/components/ui/UpdateNotification.tsx +57 -12
- package/src/components/workload/WorkloadView.tsx +36 -13
- package/src/components/workload/historyPaging.test.ts +72 -0
- package/src/components/workload/historyPaging.ts +147 -0
- package/src/components/workload/useHistoryPaging.test.tsx +81 -0
- package/src/context/ConnectionContext.tsx +2 -1
- package/src/context/RadarUpgrade.tsx +30 -0
- package/src/context/RadarUpgradeHost.tsx +23 -0
- package/src/hooks/usePreviousIntegrationSettings.render.test.tsx +62 -0
- package/src/hooks/usePreviousIntegrationSettings.test.ts +62 -0
- package/src/hooks/usePreviousIntegrationSettings.ts +89 -0
- package/src/index.ts +1 -0
package/README.md
CHANGED
|
@@ -41,6 +41,8 @@ When `navSlots.embedded` is true, Radar sizes itself to the host container (`hei
|
|
|
41
41
|
|
|
42
42
|
Embedded mode renders only the active view. Radar Hub owns the sidebar, top bar, cluster selector, and navigation. `onClusterLoadStateChange` supplies the loading status for Hub's top bar.
|
|
43
43
|
|
|
44
|
+
The package can be newer than the Radar it talks to. Features that need a newer Radar show an inline "needs Radar vX or newer" note instead of an error. Radar decides this from the `features` block of `/api/capabilities`, or from the Radar's version when it predates a flag. Pass `radarVersion` so Radar can skip those calls before `/api/version-check` answers. Pass `onRequestRadarUpgrade` to run your own upgrade flow from the note's "Upgrade Radar" button. Without it, the note links to the upgrade instructions.
|
|
45
|
+
|
|
44
46
|
## Tailwind
|
|
45
47
|
|
|
46
48
|
Radar uses Tailwind v4 classes throughout. Your app's Tailwind config must scan Radar's source:
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@skyhook-io/radar-app",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.16.0",
|
|
4
4
|
"description": "Radar's full web UI as a reusable React component. Used by Radar's own binary and by external consumers like Radar Cloud.",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -25,6 +25,7 @@
|
|
|
25
25
|
"tsc": "node ../node_modules/typescript-7/bin/tsc --noEmit",
|
|
26
26
|
"tsc:ts6": "tsc --noEmit",
|
|
27
27
|
"test": "vitest run src",
|
|
28
|
+
"test:e2e:settings": "playwright test --config playwright.settings.config.ts",
|
|
28
29
|
"format": "prettier --write \"src/**/*.{ts,tsx}\"",
|
|
29
30
|
"lint": "eslint src && node scripts/check-no-native-title.mjs"
|
|
30
31
|
},
|
|
@@ -41,7 +42,7 @@
|
|
|
41
42
|
"yaml": "^2.9.0"
|
|
42
43
|
},
|
|
43
44
|
"peerDependencies": {
|
|
44
|
-
"@skyhook-io/k8s-ui": ">=1.
|
|
45
|
+
"@skyhook-io/k8s-ui": ">=1.16.0",
|
|
45
46
|
"@tanstack/react-query": ">=5",
|
|
46
47
|
"@xyflow/react": ">=12.0.0",
|
|
47
48
|
"clsx": ">=2",
|
|
@@ -66,11 +67,11 @@
|
|
|
66
67
|
"@xyflow/react": "^12.11.2",
|
|
67
68
|
"clsx": "^2.1.1",
|
|
68
69
|
"elkjs": "^0.11.1",
|
|
69
|
-
"eslint": "^10.
|
|
70
|
+
"eslint": "^10.11.0",
|
|
70
71
|
"eslint-plugin-react-hooks": "^7.1.1",
|
|
71
72
|
"eslint-plugin-react-refresh": "^0.5.3",
|
|
72
73
|
"globals": "^17.11.0",
|
|
73
|
-
"lucide-react": "^1.
|
|
74
|
+
"lucide-react": "^1.48.0",
|
|
74
75
|
"postcss": "^8.5.28",
|
|
75
76
|
"prettier": "^3.9.8",
|
|
76
77
|
"react": "^19.3.0",
|
|
@@ -80,7 +81,7 @@
|
|
|
80
81
|
"tailwindcss": "^4.3.3",
|
|
81
82
|
"typescript": "^6.0.2",
|
|
82
83
|
"typescript-eslint": "^8.70.0",
|
|
83
|
-
"vite": "^8.
|
|
84
|
+
"vite": "^8.3.1"
|
|
84
85
|
},
|
|
85
86
|
"sideEffects": [
|
|
86
87
|
"*.css"
|
package/src/App.tsx
CHANGED
|
@@ -46,6 +46,7 @@ import { ConnectionProvider, useConnection } from './context/ConnectionContext'
|
|
|
46
46
|
import { ConnectionErrorView } from './components/ConnectionErrorView'
|
|
47
47
|
import { SyncProgressPanel } from './components/SyncProgressPanel'
|
|
48
48
|
import { CapabilitiesProvider, useCapabilitiesContext } from './contexts/CapabilitiesContext'
|
|
49
|
+
import { RadarUpgradeProvider } from './context/RadarUpgrade'
|
|
49
50
|
import { UserMenu } from './components/UserMenu'
|
|
50
51
|
import { ErrorBoundary } from './components/ui/ErrorBoundary'
|
|
51
52
|
import { UpdateNotification } from './components/ui/UpdateNotification'
|
|
@@ -2533,17 +2534,19 @@ function App({ manageDocumentTitle = false, documentTitleSuffix, onClusterLoadSt
|
|
|
2533
2534
|
return (
|
|
2534
2535
|
<ConnectionProvider>
|
|
2535
2536
|
<CapabilitiesProvider>
|
|
2536
|
-
<
|
|
2537
|
-
<
|
|
2538
|
-
<
|
|
2539
|
-
<
|
|
2540
|
-
|
|
2541
|
-
|
|
2542
|
-
|
|
2543
|
-
|
|
2544
|
-
|
|
2545
|
-
|
|
2546
|
-
|
|
2537
|
+
<RadarUpgradeProvider>
|
|
2538
|
+
<ContextSwitchProvider>
|
|
2539
|
+
<DockProvider>
|
|
2540
|
+
<KeyboardShortcutProvider>
|
|
2541
|
+
<AppInner
|
|
2542
|
+
manageDocumentTitle={manageDocumentTitle}
|
|
2543
|
+
documentTitleSuffix={documentTitleSuffix}
|
|
2544
|
+
onClusterLoadStateChange={onClusterLoadStateChange}
|
|
2545
|
+
/>
|
|
2546
|
+
</KeyboardShortcutProvider>
|
|
2547
|
+
</DockProvider>
|
|
2548
|
+
</ContextSwitchProvider>
|
|
2549
|
+
</RadarUpgradeProvider>
|
|
2547
2550
|
</CapabilitiesProvider>
|
|
2548
2551
|
</ConnectionProvider>
|
|
2549
2552
|
)
|
package/src/RadarApp.tsx
CHANGED
|
@@ -33,9 +33,15 @@ import {
|
|
|
33
33
|
showApiSuccess,
|
|
34
34
|
} from "./components/ui/Toast";
|
|
35
35
|
import { setApiBase, setBasename } from "./api/config";
|
|
36
|
+
import { shouldRetryRadarQuery } from "./api/radarFeatures";
|
|
36
37
|
import { NavCustomizationProvider } from "./context/NavCustomization";
|
|
37
38
|
import { FilterLocationBridge } from "./filter/FilterLocationBridge";
|
|
38
39
|
import type { NavCustomization } from "./context/NavCustomization";
|
|
40
|
+
import { RadarUpgradeHostProvider } from "./context/RadarUpgradeHost";
|
|
41
|
+
import type {
|
|
42
|
+
RadarUpgradeHost,
|
|
43
|
+
RadarUpgradeRequest,
|
|
44
|
+
} from "./context/RadarUpgradeHost";
|
|
39
45
|
import type { ClusterLoadState } from "./types/clusterLoadState";
|
|
40
46
|
import { TimelineSourceProvider } from "./context/TimelineSource";
|
|
41
47
|
import type { TimelineSourceConfig } from "./api/timelineSource";
|
|
@@ -65,6 +71,21 @@ declare module "@tanstack/react-query" {
|
|
|
65
71
|
export interface RadarAppProps {
|
|
66
72
|
/** API base URL (REST + SSE + WS). Defaults to '/api' (same-origin). */
|
|
67
73
|
apiBase?: string;
|
|
74
|
+
/**
|
|
75
|
+
* The version of the Radar behind `apiBase`, as the host knows it (Radar
|
|
76
|
+
* Cloud: the cluster's `radar_version`). Radar prefers that Radar's own
|
|
77
|
+
* /version-check answer and uses this only until it arrives or when it
|
|
78
|
+
* fails, to decide up front that a feature needs a newer Radar instead of
|
|
79
|
+
* probing for it.
|
|
80
|
+
*/
|
|
81
|
+
radarVersion?: string;
|
|
82
|
+
/**
|
|
83
|
+
* Called when the user asks to upgrade from an inline "needs a newer Radar"
|
|
84
|
+
* note next to a feature the connected Radar is too old to serve. The host
|
|
85
|
+
* runs its own upgrade flow (Radar Cloud: one-click for owners, "ask an
|
|
86
|
+
* owner" otherwise). Omitted → the note links to the upgrade instructions.
|
|
87
|
+
*/
|
|
88
|
+
onRequestRadarUpgrade?: (request: RadarUpgradeRequest) => void;
|
|
68
89
|
/** React Router basename. Defaults to '' (mounted at root). */
|
|
69
90
|
basename?: string;
|
|
70
91
|
/**
|
|
@@ -163,7 +184,7 @@ function makeDefaultQueryClient(): QueryClient {
|
|
|
163
184
|
defaultOptions: {
|
|
164
185
|
queries: {
|
|
165
186
|
refetchOnWindowFocus: false,
|
|
166
|
-
retry:
|
|
187
|
+
retry: shouldRetryRadarQuery,
|
|
167
188
|
},
|
|
168
189
|
},
|
|
169
190
|
mutationCache: new MutationCache({
|
|
@@ -193,6 +214,8 @@ function makeDefaultQueryClient(): QueryClient {
|
|
|
193
214
|
|
|
194
215
|
export function RadarApp({
|
|
195
216
|
apiBase,
|
|
217
|
+
radarVersion,
|
|
218
|
+
onRequestRadarUpgrade,
|
|
196
219
|
basename,
|
|
197
220
|
router = "browser",
|
|
198
221
|
queryClient,
|
|
@@ -223,32 +246,39 @@ export function RadarApp({
|
|
|
223
246
|
[queryClient],
|
|
224
247
|
);
|
|
225
248
|
|
|
249
|
+
const upgradeHost = React.useMemo<RadarUpgradeHost>(
|
|
250
|
+
() => ({ radarVersion, onRequestRadarUpgrade }),
|
|
251
|
+
[radarVersion, onRequestRadarUpgrade],
|
|
252
|
+
);
|
|
253
|
+
|
|
226
254
|
const inner = (
|
|
227
255
|
<ThemeProvider>
|
|
228
256
|
<QueryClientProvider client={client}>
|
|
229
257
|
<ToastProvider>
|
|
230
258
|
<NavCustomizationProvider value={navSlots}>
|
|
231
|
-
<
|
|
232
|
-
<
|
|
233
|
-
<
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
<DiagnoseProvider
|
|
239
|
-
browserURLState={router !== "memory"}
|
|
240
|
-
forceRouterURLState={router === "memory"}
|
|
241
|
-
onFocusedRun={onInvestigationFocus}
|
|
259
|
+
<RadarUpgradeHostProvider value={upgradeHost}>
|
|
260
|
+
<FilterLocationBridge>
|
|
261
|
+
<TimelineSourceProvider config={timelineSource}>
|
|
262
|
+
<DiagnoseCustomizationProvider
|
|
263
|
+
value={renderDiagnoseAction ?? defaultDiagnoseAction}
|
|
264
|
+
consentCopy={diagnoseConsent}
|
|
265
|
+
renderRunActions={renderInvestigationRunActions}
|
|
242
266
|
>
|
|
243
|
-
<
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
267
|
+
<DiagnoseProvider
|
|
268
|
+
browserURLState={router !== "memory"}
|
|
269
|
+
forceRouterURLState={router === "memory"}
|
|
270
|
+
onFocusedRun={onInvestigationFocus}
|
|
271
|
+
>
|
|
272
|
+
<App
|
|
273
|
+
manageDocumentTitle={manageDocumentTitle}
|
|
274
|
+
documentTitleSuffix={documentTitleSuffix}
|
|
275
|
+
onClusterLoadStateChange={onClusterLoadStateChange}
|
|
276
|
+
/>
|
|
277
|
+
</DiagnoseProvider>
|
|
278
|
+
</DiagnoseCustomizationProvider>
|
|
279
|
+
</TimelineSourceProvider>
|
|
280
|
+
</FilterLocationBridge>
|
|
281
|
+
</RadarUpgradeHostProvider>
|
|
252
282
|
</NavCustomizationProvider>
|
|
253
283
|
</ToastProvider>
|
|
254
284
|
</QueryClientProvider>
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { describe, expect, it } from 'vitest'
|
|
2
|
-
import { hasKarpenterNodePools } from './apiResources'
|
|
2
|
+
import { hasKarpenterNodePools, hasPolicyReports } from './apiResources'
|
|
3
|
+
import type { APIResource } from '../types'
|
|
3
4
|
|
|
4
5
|
describe('hasKarpenterNodePools', () => {
|
|
5
6
|
it('uses exact API discovery rather than top-CRD counts or kind name alone', () => {
|
|
@@ -9,3 +10,20 @@ describe('hasKarpenterNodePools', () => {
|
|
|
9
10
|
expect(hasKarpenterNodePools(undefined)).toBe(false)
|
|
10
11
|
})
|
|
11
12
|
})
|
|
13
|
+
|
|
14
|
+
describe('hasPolicyReports', () => {
|
|
15
|
+
const resource = (name: string, group: string) => ({ name, group }) as APIResource
|
|
16
|
+
|
|
17
|
+
it('recognizes each report family by the resource names it actually serves', () => {
|
|
18
|
+
expect(hasPolicyReports([resource('policyreports', 'wgpolicyk8s.io')])).toBe(true)
|
|
19
|
+
expect(hasPolicyReports([resource('clusterpolicyreports', 'wgpolicyk8s.io')])).toBe(true)
|
|
20
|
+
expect(hasPolicyReports([resource('reports', 'openreports.io')])).toBe(true)
|
|
21
|
+
expect(hasPolicyReports([resource('clusterreports', 'openreports.io')])).toBe(true)
|
|
22
|
+
expect(hasPolicyReports([resource('policyreports', 'openreports.io')])).toBe(false)
|
|
23
|
+
})
|
|
24
|
+
|
|
25
|
+
it('reads a cluster without them as having no policy engine', () => {
|
|
26
|
+
expect(hasPolicyReports([resource('pods', ''), resource('policyreports', 'example.com')])).toBe(false)
|
|
27
|
+
expect(hasPolicyReports(undefined)).toBe(false)
|
|
28
|
+
})
|
|
29
|
+
})
|
package/src/api/apiResources.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { useQuery } from "@tanstack/react-query";
|
|
2
2
|
import type { APIResource } from "../types";
|
|
3
3
|
import { apiUrl, getAuthHeaders, getCredentialsMode } from "./config";
|
|
4
|
+
import { readErrorBody } from "./httpErrors";
|
|
4
5
|
|
|
5
6
|
// Re-export pure functions from package
|
|
6
7
|
export {
|
|
@@ -20,9 +21,7 @@ async function fetchJSON<T>(path: string): Promise<T> {
|
|
|
20
21
|
headers: getAuthHeaders(),
|
|
21
22
|
});
|
|
22
23
|
if (!response.ok) {
|
|
23
|
-
const error = await response
|
|
24
|
-
.json()
|
|
25
|
-
.catch(() => ({ error: "Unknown error" }));
|
|
24
|
+
const error = await readErrorBody(response);
|
|
26
25
|
throw new Error(error.error || `HTTP ${response.status}`);
|
|
27
26
|
}
|
|
28
27
|
return response.json();
|
|
@@ -37,6 +36,22 @@ export function useAPIResources() {
|
|
|
37
36
|
});
|
|
38
37
|
}
|
|
39
38
|
|
|
39
|
+
// The report families the server watches (reportGroups in
|
|
40
|
+
// internal/k8s/policy_reports.go): the wgpolicyk8s.io working-group API and
|
|
41
|
+
// its openreports.io successor, which names the same resources differently.
|
|
42
|
+
const POLICY_REPORT_RESOURCES: Record<string, readonly string[]> = {
|
|
43
|
+
"wgpolicyk8s.io": ["policyreports", "clusterpolicyreports"],
|
|
44
|
+
"openreports.io": ["reports", "clusterreports"],
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
export function hasPolicyReports(resources: APIResource[] | undefined): boolean {
|
|
48
|
+
return (
|
|
49
|
+
resources?.some((resource) =>
|
|
50
|
+
POLICY_REPORT_RESOURCES[resource.group]?.includes(resource.name),
|
|
51
|
+
) ?? false
|
|
52
|
+
);
|
|
53
|
+
}
|
|
54
|
+
|
|
40
55
|
export function hasKarpenterNodePools(
|
|
41
56
|
resources: APIResource[] | undefined,
|
|
42
57
|
): boolean {
|
|
@@ -5,6 +5,7 @@ import {
|
|
|
5
5
|
isCapacityCursorInvalidError,
|
|
6
6
|
shouldRetryCapacityQuery,
|
|
7
7
|
} from "./client";
|
|
8
|
+
import { RadarFeatureUnsupportedError } from "./radarFeatures";
|
|
8
9
|
|
|
9
10
|
describe("isCapacityCursorInvalidError", () => {
|
|
10
11
|
it("uses the structured capacity error code", () => {
|
|
@@ -42,6 +43,12 @@ describe("shouldRetryCapacityQuery", () => {
|
|
|
42
43
|
);
|
|
43
44
|
});
|
|
44
45
|
|
|
46
|
+
it("never retries a feature the connected Radar predates", () => {
|
|
47
|
+
expect(
|
|
48
|
+
shouldRetryCapacityQuery(0, new RadarFeatureUnsupportedError("capacity", {})),
|
|
49
|
+
).toBe(false);
|
|
50
|
+
});
|
|
51
|
+
|
|
45
52
|
it("bounds retries for transient failures", () => {
|
|
46
53
|
expect(shouldRetryCapacityQuery(0, new ApiError("unavailable", 503))).toBe(
|
|
47
54
|
true,
|
|
@@ -63,6 +70,14 @@ describe("capacityRefetchInterval", () => {
|
|
|
63
70
|
).toBe(false);
|
|
64
71
|
});
|
|
65
72
|
|
|
73
|
+
it("stops polling a Radar that predates Capacity", () => {
|
|
74
|
+
expect(
|
|
75
|
+
capacityRefetchInterval(undefined, true)({
|
|
76
|
+
state: { error: new RadarFeatureUnsupportedError("capacity", {}) },
|
|
77
|
+
}),
|
|
78
|
+
).toBe(false);
|
|
79
|
+
});
|
|
80
|
+
|
|
66
81
|
it("polls fast while the server reports syncing", () => {
|
|
67
82
|
expect(
|
|
68
83
|
capacityRefetchInterval(undefined, true)(withData({ state: "syncing" })),
|
|
@@ -25,9 +25,17 @@ describe('runInClusterMerged error surfacing', () => {
|
|
|
25
25
|
await expect(runInClusterMerged('Service', 'prod', 'web')).rejects.toThrow('no eligible routes')
|
|
26
26
|
})
|
|
27
27
|
|
|
28
|
+
// The banner around this message already says the in-cluster test couldn't run.
|
|
28
29
|
it('rejects a non-JSON gateway failure with the status, not a parse error', async () => {
|
|
29
30
|
respond(504, '<html>Gateway Timeout</html>', 'text/html')
|
|
30
|
-
await expect(runInClusterMerged('Service', 'prod', 'web')).rejects.toThrow('
|
|
31
|
+
await expect(runInClusterMerged('Service', 'prod', 'web')).rejects.toThrow('HTTP 504 (Gateway Timeout)')
|
|
32
|
+
})
|
|
33
|
+
|
|
34
|
+
it("keeps a proxy's plain-text reason", async () => {
|
|
35
|
+
respond(503, 'cluster "prod" not connected\n', 'text/plain; charset=utf-8')
|
|
36
|
+
await expect(runInClusterMerged('Service', 'prod', 'web')).rejects.toThrow(
|
|
37
|
+
'cluster "prod" not connected (HTTP 503)',
|
|
38
|
+
)
|
|
31
39
|
})
|
|
32
40
|
|
|
33
41
|
it('resolves a clean run with the finalized payload', async () => {
|