@skyhook-io/radar-app 1.8.6 → 1.8.8
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 +7 -1
- package/package.json +12 -10
- package/src/App.tsx +278 -230
- package/src/RadarApp.tsx +91 -20
- package/src/api/client.argoResourceSync.test.ts +69 -0
- package/src/api/client.delta.test.ts +89 -0
- package/src/api/client.deltaSync.test.ts +216 -0
- package/src/api/client.rightsizing.test.ts +32 -0
- package/src/api/client.ts +1462 -246
- package/src/api/diagnose.ts +288 -0
- package/src/api/timelineSource.test.ts +217 -0
- package/src/api/timelineSource.ts +582 -0
- package/src/components/ConnectionErrorView.tsx +174 -70
- package/src/components/ContextSwitcher.tsx +13 -5
- package/src/components/applications/ApplicationsView.tsx +775 -80
- package/src/components/audit/AuditSettingsDialog.tsx +2 -2
- package/src/components/cost/ApplicationCostTab.test.ts +204 -0
- package/src/components/cost/ApplicationCostTab.tsx +571 -0
- package/src/components/cost/CostTrendChart.tsx +103 -72
- package/src/components/cost/CostView.test.ts +12 -0
- package/src/components/cost/CostView.tsx +494 -229
- package/src/components/cost/CostViewTabs.test.tsx +21 -0
- package/src/components/cost/CostViewTabs.tsx +40 -0
- package/src/components/cost/CurrentAllocationUse.test.ts +21 -0
- package/src/components/cost/CurrentAllocationUse.tsx +126 -0
- package/src/components/cost/WorkloadCostTab.test.ts +153 -0
- package/src/components/cost/WorkloadCostTab.tsx +372 -0
- package/src/components/cost/cloud-console.test.ts +39 -0
- package/src/components/cost/cloud-console.ts +81 -0
- package/src/components/cost/errors.ts +8 -0
- package/src/components/cost/format.test.ts +27 -0
- package/src/components/cost/format.ts +46 -0
- package/src/components/cost/kinds.ts +5 -0
- package/src/components/curl/ServiceCurlButton.tsx +2 -2
- package/src/components/diagnose/AISettings.tsx +116 -0
- package/src/components/diagnose/DiagnoseContext.tsx +491 -0
- package/src/components/diagnose/DiagnoseSurface.tsx +385 -0
- package/src/components/diagnose/Home.tsx +163 -0
- package/src/components/diagnose/InvestigationView.tsx +604 -0
- package/src/components/diagnose/LocalDiagnoseAction.tsx +150 -0
- package/src/components/diagnose/launch.ts +65 -0
- package/src/components/diagnose/parts.tsx +1689 -0
- package/src/components/dock/BottomDock.tsx +2 -3
- package/src/components/dock/WorkloadLogsTab.tsx +21 -5
- package/src/components/execution/BatchExecutionView.test.ts +170 -0
- package/src/components/execution/BatchExecutionView.tsx +1329 -0
- package/src/components/execution/batch-run-actions.test.ts +48 -0
- package/src/components/execution/batch-run-actions.ts +24 -0
- package/src/components/execution/batch-timeline.test.ts +57 -0
- package/src/components/execution/batch-timeline.ts +46 -0
- package/src/components/execution/execution-definition.test.ts +208 -0
- package/src/components/execution/execution-definition.ts +245 -0
- package/src/components/gitops/ArgoResourceDiffLoader.tsx +23 -0
- package/src/components/gitops/GitOpsView.tsx +81 -14
- package/src/components/gitops/RevisionMetaChip.tsx +63 -0
- package/src/components/helm/ChartBrowser.tsx +2 -3
- package/src/components/helm/HelmCompareRoute.tsx +1 -2
- package/src/components/helm/HelmReleaseDrawer.test.ts +17 -0
- package/src/components/helm/HelmReleaseDrawer.tsx +376 -43
- package/src/components/helm/HelmView.tsx +2 -3
- package/src/components/helm/InstallWizard.tsx +3 -5
- package/src/components/helm/ManifestDiffViewer.tsx +1 -31
- package/src/components/helm/TrackChartSourceDialog.tsx +48 -4
- package/src/components/helm/ValuesDiffPreview.tsx +17 -7
- package/src/components/home/CostCard.tsx +21 -36
- package/src/components/home/HomeView.tsx +17 -15
- package/src/components/home/MCPSetupDialog.tsx +1 -1
- package/src/components/home/mcpToolCatalog.ts +2 -2
- package/src/components/issues/IssuesPane.tsx +9 -1
- package/src/components/logs/ScheduledWorkloadLogsViewer.tsx +135 -0
- package/src/components/portforward/PortForwardButton.tsx +2 -2
- package/src/components/portforward/PortForwardManager.tsx +19 -14
- package/src/components/resource/PrometheusChartsGrid.tsx +6 -80
- package/src/components/resource/RightsizingStrip.test.ts +109 -0
- package/src/components/resource/RightsizingStrip.tsx +320 -127
- package/src/components/resources/ImageFilesystemModal.tsx +2 -2
- package/src/components/resources/PodFilesystemModal.tsx +2 -3
- package/src/components/resources/ResourceDetailDrawer.tsx +2 -0
- package/src/components/resources/ResourcesView.tsx +13 -4
- package/src/components/resources/renderers/CronWorkflowRenderer.tsx +1 -0
- package/src/components/resources/renderers/index.ts +1 -0
- package/src/components/rightsizing/RightsizingScanView.tsx +938 -0
- package/src/components/rightsizing/copy.test.ts +56 -0
- package/src/components/rightsizing/model.test.ts +227 -0
- package/src/components/rightsizing/model.ts +158 -0
- package/src/components/rightsizing/presentation.test.ts +104 -0
- package/src/components/rightsizing/presentation.ts +94 -0
- package/src/components/settings/MyPermissionsDialog.tsx +66 -116
- package/src/components/settings/SettingsDialog.tsx +1290 -283
- package/src/components/shared/LargeClusterNamespacePicker.tsx +2 -2
- package/src/components/timeline/LocalTimelineScrubber.tsx +212 -0
- package/src/components/timeline/RetainedTimelineScrubber.tsx +311 -0
- package/src/components/timeline/TimelineList.tsx +81 -13
- package/src/components/timeline/TimelineView.tsx +842 -26
- package/src/components/timeline/TimelineView.urlparams.test.ts +335 -0
- package/src/components/traffic/TrafficFilterSidebar.tsx +2 -2
- package/src/components/traffic/TrafficView.tsx +8 -4
- package/src/components/ui/CommandPalette.tsx +2 -2
- package/src/components/workload/WorkloadView.tsx +912 -245
- package/src/context/ConnectionContext.tsx +109 -11
- package/src/context/DiagnoseCustomization.tsx +42 -0
- package/src/context/TimelineSource.tsx +50 -0
- package/src/hooks/useClusterLoadState.ts +73 -0
- package/src/hooks/useEventSource.ts +6 -0
- package/src/index.css +162 -1
- package/src/index.ts +12 -0
- package/src/main.tsx +1 -1
- package/src/types/clusterLoadState.ts +33 -0
- package/src/utils/navigation.ts +10 -0
|
@@ -644,7 +644,7 @@ export function PortForwardPanel() {
|
|
|
644
644
|
onMouseEnter={onPanelHoverEnter}
|
|
645
645
|
onMouseLeave={onPanelHoverLeave}
|
|
646
646
|
className={clsx(
|
|
647
|
-
'fixed z-[51] w-
|
|
647
|
+
'fixed z-[51] w-[26rem] max-w-[calc(100vw-2rem)]',
|
|
648
648
|
'transition-opacity duration-150 ease-out',
|
|
649
649
|
isPanelOpen
|
|
650
650
|
? 'opacity-100 pointer-events-auto'
|
|
@@ -726,14 +726,16 @@ export function PortForwardPanel() {
|
|
|
726
726
|
</div>
|
|
727
727
|
) : (
|
|
728
728
|
<div className="divide-y divide-theme-border">
|
|
729
|
-
{activeSessions.map((session) =>
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
729
|
+
{activeSessions.map((session) => {
|
|
730
|
+
const resourceName = session.serviceName || session.podName
|
|
731
|
+
return (
|
|
732
|
+
<div
|
|
733
|
+
key={session.id}
|
|
734
|
+
className={clsx(
|
|
735
|
+
'p-3 space-y-1',
|
|
736
|
+
session.status === 'error' ? 'bg-red-500/10' : 'hover:bg-theme-elevated'
|
|
737
|
+
)}
|
|
738
|
+
>
|
|
737
739
|
{/* Row 1: status dot + name | stop button */}
|
|
738
740
|
<div className="flex items-start justify-between gap-2">
|
|
739
741
|
<div className="flex items-start gap-2 min-w-0 flex-1">
|
|
@@ -743,9 +745,11 @@ export function PortForwardPanel() {
|
|
|
743
745
|
session.status === 'running' ? 'bg-green-500' : 'bg-red-500'
|
|
744
746
|
)}
|
|
745
747
|
/>
|
|
746
|
-
<
|
|
747
|
-
|
|
748
|
-
|
|
748
|
+
<Tooltip content={resourceName} delay={300} position="bottom" disabled={!isPanelOpen} wrapperClassName="min-w-0 flex-1">
|
|
749
|
+
<span className="block min-w-0 truncate text-sm font-medium text-theme-text-primary">
|
|
750
|
+
{resourceName}
|
|
751
|
+
</span>
|
|
752
|
+
</Tooltip>
|
|
749
753
|
{session.status === 'error' && (
|
|
750
754
|
<span className={clsx('badge-sm shrink-0', SEVERITY_BADGE.error)}>Failed</span>
|
|
751
755
|
)}
|
|
@@ -916,8 +920,9 @@ export function PortForwardPanel() {
|
|
|
916
920
|
</div>
|
|
917
921
|
</div>
|
|
918
922
|
)}
|
|
919
|
-
|
|
920
|
-
|
|
923
|
+
</div>
|
|
924
|
+
)
|
|
925
|
+
})}
|
|
921
926
|
</div>
|
|
922
927
|
)}
|
|
923
928
|
</div>
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { useMemo, useState } from 'react'
|
|
2
|
-
import {
|
|
2
|
+
import { Loader2, Wifi, WifiOff } from 'lucide-react'
|
|
3
3
|
import {
|
|
4
4
|
AreaChart,
|
|
5
5
|
SeriesLegend,
|
|
@@ -11,11 +11,9 @@ import {
|
|
|
11
11
|
usePrometheusStatus,
|
|
12
12
|
usePrometheusConnect,
|
|
13
13
|
usePrometheusResourceMetrics,
|
|
14
|
-
usePrometheusRightsizing,
|
|
15
14
|
useAutoPromConnect,
|
|
16
15
|
type PrometheusMetricCategory,
|
|
17
16
|
type PrometheusTimeRange,
|
|
18
|
-
type RightsizingTone,
|
|
19
17
|
} from '../../api/client'
|
|
20
18
|
import {
|
|
21
19
|
MetricsSummary,
|
|
@@ -57,7 +55,6 @@ export function PrometheusChartsGrid({
|
|
|
57
55
|
const showRestartLane = isSupported && kind !== 'Node'
|
|
58
56
|
|
|
59
57
|
const [timeRange, setTimeRange] = useState<PrometheusTimeRange>('1h')
|
|
60
|
-
const [diskExpanded, setDiskExpanded] = useState(false)
|
|
61
58
|
|
|
62
59
|
const categories = kind === 'Node' ? NODE_CATEGORIES : WORKLOAD_CATEGORIES
|
|
63
60
|
|
|
@@ -112,7 +109,6 @@ export function PrometheusChartsGrid({
|
|
|
112
109
|
const findCategory = (key: PrometheusMetricCategory): CategoryDef | undefined =>
|
|
113
110
|
categories.find(c => c.key === key)
|
|
114
111
|
|
|
115
|
-
// Disk I/O is collapsed by default — niche metric.
|
|
116
112
|
const primaryCats: { def: CategoryDef; refLines?: ReferenceLine[] }[] = []
|
|
117
113
|
const cpu = findCategory('cpu')
|
|
118
114
|
if (cpu) primaryCats.push({ def: cpu, refLines: cpuRefLines })
|
|
@@ -125,19 +121,16 @@ export function PrometheusChartsGrid({
|
|
|
125
121
|
if (tx) primaryCats.push({ def: tx })
|
|
126
122
|
}
|
|
127
123
|
const disk = findCategory('filesystem')
|
|
124
|
+
const chartPanels = disk ? [...primaryCats, { def: disk }] : primaryCats
|
|
128
125
|
|
|
129
126
|
return (
|
|
130
127
|
<div className="flex flex-col h-full overflow-auto">
|
|
131
|
-
<div className="shrink-0
|
|
132
|
-
<div className="flex items-center gap-2 text-sm font-medium text-theme-text-secondary">
|
|
133
|
-
<BarChart3 className="w-4 h-4 text-theme-text-tertiary" />
|
|
134
|
-
Metrics
|
|
135
|
-
<WorkloadHealthBadge kind={kind} namespace={namespace} name={name} />
|
|
136
|
-
</div>
|
|
128
|
+
<div className="flex shrink-0 justify-end px-4 pt-3">
|
|
137
129
|
<select
|
|
130
|
+
aria-label="Metrics time range"
|
|
138
131
|
value={timeRange}
|
|
139
132
|
onChange={e => setTimeRange(e.target.value as PrometheusTimeRange)}
|
|
140
|
-
className="
|
|
133
|
+
className="rounded-md border border-theme-border bg-theme-elevated px-2 py-1 text-xs text-theme-text-secondary shadow-theme-sm focus:outline-none focus:ring-1 focus:ring-accent/50"
|
|
141
134
|
>
|
|
142
135
|
{TIME_RANGES.map(tr => (
|
|
143
136
|
<option key={tr.value} value={tr.value}>{tr.label}</option>
|
|
@@ -154,7 +147,7 @@ export function PrometheusChartsGrid({
|
|
|
154
147
|
)}
|
|
155
148
|
|
|
156
149
|
<div className="grid grid-cols-1 md:grid-cols-2 gap-3 p-4">
|
|
157
|
-
{
|
|
150
|
+
{chartPanels.map(({ def, refLines }) => (
|
|
158
151
|
<MetricsPanel
|
|
159
152
|
key={def.key}
|
|
160
153
|
category={def}
|
|
@@ -166,30 +159,6 @@ export function PrometheusChartsGrid({
|
|
|
166
159
|
/>
|
|
167
160
|
))}
|
|
168
161
|
</div>
|
|
169
|
-
|
|
170
|
-
{disk && (
|
|
171
|
-
<div className="px-4 pb-4">
|
|
172
|
-
<button
|
|
173
|
-
type="button"
|
|
174
|
-
onClick={() => setDiskExpanded(v => !v)}
|
|
175
|
-
className="flex items-center gap-1.5 text-xs font-medium text-theme-text-tertiary hover:text-theme-text-secondary py-1"
|
|
176
|
-
>
|
|
177
|
-
{diskExpanded ? <ChevronDown className="w-3.5 h-3.5" /> : <ChevronRight className="w-3.5 h-3.5" />}
|
|
178
|
-
Disk I/O
|
|
179
|
-
</button>
|
|
180
|
-
{diskExpanded && (
|
|
181
|
-
<div className="mt-2">
|
|
182
|
-
<MetricsPanel
|
|
183
|
-
category={disk}
|
|
184
|
-
kind={kind}
|
|
185
|
-
namespace={namespace}
|
|
186
|
-
name={name}
|
|
187
|
-
timeRange={timeRange}
|
|
188
|
-
/>
|
|
189
|
-
</div>
|
|
190
|
-
)}
|
|
191
|
-
</div>
|
|
192
|
-
)}
|
|
193
162
|
</div>
|
|
194
163
|
)
|
|
195
164
|
}
|
|
@@ -269,49 +238,6 @@ function SaturationChip({ ratio, against }: { ratio: number; against: 'limit' |
|
|
|
269
238
|
return <span className={`badge badge-sm ${SEVERITY_BADGE[tone]}`}>{label}</span>
|
|
270
239
|
}
|
|
271
240
|
|
|
272
|
-
// Severity ordering for rightsizing tones. Aligned with `Tone` constants in
|
|
273
|
-
// `internal/prometheus/rightsizing.go` — adding a new tone there triggers a
|
|
274
|
-
// TypeScript exhaustiveness error on the Record key set here.
|
|
275
|
-
const TONE_RANK: Record<RightsizingTone, number> = {
|
|
276
|
-
ok: 0,
|
|
277
|
-
info: 1,
|
|
278
|
-
warning: 2,
|
|
279
|
-
alert: 3,
|
|
280
|
-
critical: 4,
|
|
281
|
-
}
|
|
282
|
-
|
|
283
|
-
function WorkloadHealthBadge({ kind, namespace, name }: { kind: string; namespace: string; name: string }) {
|
|
284
|
-
const supported = kind === 'Deployment' || kind === 'StatefulSet' || kind === 'DaemonSet'
|
|
285
|
-
const { data, error } = usePrometheusRightsizing(kind, namespace, name, supported)
|
|
286
|
-
if (!supported) return null
|
|
287
|
-
// Surface a neutral "Health unknown" pill when the rightsizing endpoint
|
|
288
|
-
// errors — otherwise an actually-throttled or OOM-risk workload would
|
|
289
|
-
// silently render as fine while we have no signal to display.
|
|
290
|
-
if (error && !data) {
|
|
291
|
-
const msg = error instanceof Error ? error.message : String(error)
|
|
292
|
-
return (
|
|
293
|
-
<Tooltip content={`Health check failed: ${msg}`}>
|
|
294
|
-
<span className={`badge badge-sm ${SEVERITY_BADGE.neutral}`}>Health unknown</span>
|
|
295
|
-
</Tooltip>
|
|
296
|
-
)
|
|
297
|
-
}
|
|
298
|
-
if (!data?.sampleAvailable || data.rows.length === 0) return null
|
|
299
|
-
|
|
300
|
-
const worst = data.rows.reduce<RightsizingTone>(
|
|
301
|
-
(acc, r) => (TONE_RANK[r.tone] > TONE_RANK[acc] ? r.tone : acc),
|
|
302
|
-
'ok',
|
|
303
|
-
)
|
|
304
|
-
// Skip the chip for the steady-state tones to avoid badge-blindness — we
|
|
305
|
-
// only want to draw the eye when there's something to address.
|
|
306
|
-
if (worst === 'ok' || worst === 'info') return null
|
|
307
|
-
|
|
308
|
-
const { label, severity }: { label: string; severity: Severity } =
|
|
309
|
-
worst === 'critical' ? { label: 'OOM risk', severity: 'error' } :
|
|
310
|
-
worst === 'alert' ? { label: 'CPU throttling', severity: 'alert' } :
|
|
311
|
-
/* warning */ { label: 'Needs review', severity: 'warning' }
|
|
312
|
-
return <span className={`badge badge-sm ${SEVERITY_BADGE[severity]}`}>{label}</span>
|
|
313
|
-
}
|
|
314
|
-
|
|
315
241
|
function PanelLoading() {
|
|
316
242
|
return (
|
|
317
243
|
<div className="flex items-center justify-center h-full min-h-[160px] text-theme-text-tertiary text-xs">
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
import { describe, expect, it } from 'vitest'
|
|
2
|
+
import type { RightsizingRow } from '../../api/client'
|
|
3
|
+
import {
|
|
4
|
+
getRightsizingExplanation,
|
|
5
|
+
getRightsizingPresentation,
|
|
6
|
+
RIGHTSIZING_METHODOLOGY,
|
|
7
|
+
RIGHTSIZING_DOCS_URL,
|
|
8
|
+
RIGHTSIZING_SUMMARY,
|
|
9
|
+
} from './RightsizingStrip'
|
|
10
|
+
|
|
11
|
+
const row = (overrides: Partial<RightsizingRow> = {}): RightsizingRow => ({
|
|
12
|
+
container: 'server',
|
|
13
|
+
resource: 'cpu',
|
|
14
|
+
fit: 'balanced',
|
|
15
|
+
confidence: 'high',
|
|
16
|
+
sampleCount: 2016,
|
|
17
|
+
expectedSamples: 2016,
|
|
18
|
+
coverage: 1,
|
|
19
|
+
hpaManaged: false,
|
|
20
|
+
hpaEvidenceAvailable: true,
|
|
21
|
+
oomEvidenceAvailable: true,
|
|
22
|
+
throttleAvailable: true,
|
|
23
|
+
...overrides,
|
|
24
|
+
})
|
|
25
|
+
|
|
26
|
+
describe('rightsizing presentation', () => {
|
|
27
|
+
it('keeps fit, confidence, and runtime risk as separate concepts', () => {
|
|
28
|
+
expect(getRightsizingPresentation('oversized')).toEqual({
|
|
29
|
+
label: 'Oversized',
|
|
30
|
+
severity: 'info',
|
|
31
|
+
})
|
|
32
|
+
expect(getRightsizingPresentation('under_requested')).toEqual({
|
|
33
|
+
label: 'Under-requested',
|
|
34
|
+
severity: 'warning',
|
|
35
|
+
})
|
|
36
|
+
expect(getRightsizingPresentation('insufficient_history')).toEqual({
|
|
37
|
+
label: 'Insufficient history',
|
|
38
|
+
severity: 'neutral',
|
|
39
|
+
})
|
|
40
|
+
})
|
|
41
|
+
|
|
42
|
+
it('labels query failures independently from insufficient history', () => {
|
|
43
|
+
expect(getRightsizingPresentation('insufficient_history', 'usage query failed')).toEqual({
|
|
44
|
+
label: 'Query failed',
|
|
45
|
+
severity: 'error',
|
|
46
|
+
})
|
|
47
|
+
})
|
|
48
|
+
|
|
49
|
+
it('explains why recommendations are withheld without inventing a zero-risk result', () => {
|
|
50
|
+
expect(getRightsizingExplanation(row({ recommendationReason: 'hpa_managed' }))).toContain(
|
|
51
|
+
'HPA manages cpu',
|
|
52
|
+
)
|
|
53
|
+
expect(
|
|
54
|
+
getRightsizingExplanation(row({ resource: 'memory', recommendationReason: 'oom_evidence' })),
|
|
55
|
+
).toContain('OOM evidence')
|
|
56
|
+
expect(
|
|
57
|
+
getRightsizingExplanation(row({ recommendationReason: 'hpa_evidence_unavailable' })),
|
|
58
|
+
).toContain('could not verify HPA')
|
|
59
|
+
expect(
|
|
60
|
+
getRightsizingExplanation(
|
|
61
|
+
row({
|
|
62
|
+
resource: 'memory',
|
|
63
|
+
recommendationReason: 'oom_evidence_unavailable',
|
|
64
|
+
}),
|
|
65
|
+
),
|
|
66
|
+
).toContain('could not verify recent OOM')
|
|
67
|
+
expect(getRightsizingExplanation(row({ throttleAvailable: false }))).toContain(
|
|
68
|
+
'throttling metrics are unavailable',
|
|
69
|
+
)
|
|
70
|
+
})
|
|
71
|
+
|
|
72
|
+
it('separates the demand target from a conservative reduction step', () => {
|
|
73
|
+
const explanation = getRightsizingExplanation(
|
|
74
|
+
row({
|
|
75
|
+
fit: 'oversized',
|
|
76
|
+
currentRequest: '1',
|
|
77
|
+
recommendedRequest: '500m',
|
|
78
|
+
calculatedRequest: '10m',
|
|
79
|
+
reductionLimited: true,
|
|
80
|
+
bursty: true,
|
|
81
|
+
peak: { name: 'P99', value: 0.2, formatted: '200m' },
|
|
82
|
+
}),
|
|
83
|
+
)
|
|
84
|
+
expect(explanation).toContain('Demand-based target: 10m')
|
|
85
|
+
expect(explanation).toContain('conservative next step')
|
|
86
|
+
expect(explanation).toContain('CPU P99 reached 200m')
|
|
87
|
+
})
|
|
88
|
+
|
|
89
|
+
it('does not revive the misleading efficiency vocabulary', () => {
|
|
90
|
+
const copy = [
|
|
91
|
+
getRightsizingPresentation('balanced').label,
|
|
92
|
+
getRightsizingPresentation('oversized').label,
|
|
93
|
+
getRightsizingExplanation(row({ recommendationReason: 'request_within_fit_range' })),
|
|
94
|
+
]
|
|
95
|
+
.join(' ')
|
|
96
|
+
.toLowerCase()
|
|
97
|
+
expect(copy).not.toContain('efficien')
|
|
98
|
+
expect(copy).not.toContain('waste')
|
|
99
|
+
})
|
|
100
|
+
|
|
101
|
+
it('sets the workload-level scope and methodology without promising savings or changes', () => {
|
|
102
|
+
expect(RIGHTSIZING_SUMMARY).toContain('this workload')
|
|
103
|
+
expect(RIGHTSIZING_SUMMARY).toContain('not a savings estimate or automatic change')
|
|
104
|
+
expect(RIGHTSIZING_METHODOLOGY).toContain('CPU P95 and memory maximum')
|
|
105
|
+
expect(RIGHTSIZING_METHODOLOGY).toContain('Reductions are staged')
|
|
106
|
+
expect(RIGHTSIZING_METHODOLOGY).toContain('Radar does not change requests')
|
|
107
|
+
expect(RIGHTSIZING_DOCS_URL).toContain('/features/rightsizing')
|
|
108
|
+
})
|
|
109
|
+
})
|