@skyhook-io/k8s-ui 1.18.1 → 1.18.2
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 +8 -0
- package/package.json +1 -1
- package/src/components/applications/ApplicationsView.tsx +8 -7
- package/src/components/cluster-switcher/ClusterSwitcher.tsx +4 -0
- package/src/components/compare/ResourceCompareView.tsx +6 -2
- package/src/components/gitops/GitOpsTableView.tsx +13 -8
- package/src/components/issues/IssuesView.tsx +2 -2
- package/src/components/issues/diagnostic.ts +2 -0
- package/src/components/issues/severity.ts +1 -0
- package/src/components/logs/LogCore.tsx +13 -11
- package/src/components/logs/WorkloadLogsViewer.tsx +1 -1
- package/src/components/namespace-switcher/NamespacePicker.tsx +2 -0
- package/src/components/resources/ResourcesSidebar.keyboard.test.tsx +28 -0
- package/src/components/resources/ResourcesSidebar.tsx +26 -5
- package/src/components/resources/ResourcesView.crossplane-pause.test.tsx +133 -0
- package/src/components/resources/ResourcesView.flagger.test.tsx +31 -0
- package/src/components/resources/ResourcesView.kind-view.test.tsx +96 -1
- package/src/components/resources/ResourcesView.opentelemetry.test.tsx +29 -0
- package/src/components/resources/ResourcesView.tsx +238 -66
- package/src/components/resources/curated-column-ownership.test.ts +3 -3
- package/src/components/resources/generic-status.ts +15 -0
- package/src/components/resources/printer-columns.test.ts +36 -0
- package/src/components/resources/printer-columns.ts +12 -1
- package/src/components/resources/readiness-gates.test.ts +95 -0
- package/src/components/resources/renderers/CompositeRenderer.tsx +8 -12
- package/src/components/resources/renderers/CrossplanePauseContext.test.tsx +153 -0
- package/src/components/resources/renderers/CrossplanePauseContext.tsx +39 -0
- package/src/components/resources/renderers/FlaggerRenderers.tsx +96 -0
- package/src/components/resources/renderers/GenericRenderer.tsx +19 -11
- package/src/components/resources/renderers/ManagedResourceRenderer.tsx +5 -12
- package/src/components/resources/renderers/OpenTelemetryRenderers.tsx +73 -0
- package/src/components/resources/renderers/crossplane-cells.tsx +5 -5
- package/src/components/resources/renderers/index.ts +3 -0
- package/src/components/resources/resource-utils-crossplane.test.ts +45 -0
- package/src/components/resources/resource-utils-crossplane.ts +44 -8
- package/src/components/resources/resource-utils-flagger.test.tsx +82 -0
- package/src/components/resources/resource-utils-flagger.ts +76 -0
- package/src/components/resources/resource-utils-opentelemetry.test.tsx +74 -0
- package/src/components/resources/resource-utils-opentelemetry.ts +97 -0
- package/src/components/resources/resource-utils.ts +80 -2
- package/src/components/shared/CreateResourceDialog.test.tsx +14 -0
- package/src/components/shared/CreateResourceDialog.tsx +5 -0
- package/src/components/shared/DetailShell.phone.test.tsx +54 -0
- package/src/components/shared/DetailShell.tsx +14 -3
- package/src/components/shared/EditableYamlView.phone.test.tsx +44 -0
- package/src/components/shared/EditableYamlView.test.tsx +58 -0
- package/src/components/shared/EditableYamlView.tsx +18 -3
- package/src/components/shared/FilterRailPanel.test.tsx +39 -0
- package/src/components/shared/FilterRailPanel.tsx +62 -0
- package/src/components/shared/GitOpsWriteWarning.managers.test.tsx +85 -0
- package/src/components/shared/GitOpsWriteWarning.tsx +87 -5
- package/src/components/shared/ManagedByChip.manager.test.tsx +115 -0
- package/src/components/shared/ManagedByChip.menu.test.tsx +103 -0
- package/src/components/shared/ManagedByChip.tsx +135 -2
- package/src/components/shared/ResourceActionsBar.tsx +22 -6
- package/src/components/shared/ResourceRendererDispatch.tsx +23 -0
- package/src/components/shared/SetImageDialog.tsx +3 -0
- package/src/components/topology/TopologyControls.tsx +1 -1
- package/src/components/ui/CrossplaneOperationNotice.test.tsx +153 -0
- package/src/components/ui/CrossplaneOperationNotice.tsx +139 -0
- package/src/components/ui/ForceDeleteConfirmDialog.test.tsx +55 -6
- package/src/components/ui/ForceDeleteConfirmDialog.tsx +127 -22
- package/src/components/ui/PageHeader.tsx +3 -2
- package/src/components/ui/SummaryTile.tsx +13 -3
- package/src/components/ui/Tooltip.touch.test.tsx +55 -0
- package/src/components/ui/Tooltip.tsx +11 -2
- package/src/components/ui/YamlReview.test.tsx +52 -0
- package/src/components/ui/YamlReview.tsx +7 -0
- package/src/components/ui/drawer-components.test.tsx +27 -0
- package/src/components/ui/drawer-components.tsx +22 -42
- package/src/components/workload/WorkloadView.relationships.test.tsx +40 -0
- package/src/components/workload/WorkloadView.tsx +57 -34
- package/src/hooks/useKeepInViewport.mounted.test.tsx +72 -0
- package/src/hooks/useKeepInViewport.test.ts +27 -0
- package/src/hooks/useKeepInViewport.ts +87 -0
- package/src/types/core.ts +53 -1
- package/src/utils/api-resources.ts +1 -0
- package/src/utils/gitops-owner.ts +136 -8
- package/src/utils/gitops-write-guard.test.ts +332 -0
- package/src/utils/gitops-write-guard.ts +175 -11
- package/src/utils/index.ts +1 -0
- package/src/utils/read-actions.ts +34 -0
- package/src/utils/touch-intent.test.ts +39 -0
- package/src/utils/touch-intent.ts +35 -0
- package/vitest.config.ts +26 -0
|
@@ -3,6 +3,7 @@ import { act, type ComponentProps } from 'react'
|
|
|
3
3
|
import { createRoot } from 'react-dom/client'
|
|
4
4
|
import { afterEach, describe, expect, it, vi } from 'vitest'
|
|
5
5
|
import { ResourcesView } from './ResourcesView'
|
|
6
|
+
import { ResourceActionsBar } from '../shared/ResourceActionsBar'
|
|
6
7
|
import { KeyboardShortcutProvider } from '../../hooks/useKeyboardShortcuts'
|
|
7
8
|
import type { APIResource } from '../../types'
|
|
8
9
|
|
|
@@ -252,7 +253,7 @@ it('can show selection from compare without turning off the saved preference', (
|
|
|
252
253
|
expect(localStorage.getItem(checkboxKey)).toBe('true')
|
|
253
254
|
})
|
|
254
255
|
|
|
255
|
-
it('
|
|
256
|
+
it('confirms bulk deletion and clears successful selection without hiding checkboxes', () => {
|
|
256
257
|
const { remove } = renderBulkTable()
|
|
257
258
|
key('b')
|
|
258
259
|
act(() => required<HTMLInputElement>('input[title="Select all"]').click())
|
|
@@ -268,6 +269,32 @@ it('keeps delete confirmation and clears successful selection without hiding che
|
|
|
268
269
|
expect(localStorage.getItem(checkboxKey)).toBe('true')
|
|
269
270
|
})
|
|
270
271
|
|
|
272
|
+
it('requires a new bulk confirmation after submission without clearing uncertain selection', async () => {
|
|
273
|
+
const { remove, render } = renderBulkTable()
|
|
274
|
+
const other = { ...pod, metadata: { ...pod.metadata, name: 'remaining', uid: 'remaining-id' } }
|
|
275
|
+
render(pods, [pod, other])
|
|
276
|
+
key('b')
|
|
277
|
+
act(() => required<HTMLInputElement>('input[title="Select all"]').click())
|
|
278
|
+
clickButton('Delete')
|
|
279
|
+
const dialog = required<HTMLElement>('[role="dialog"]', document)
|
|
280
|
+
clickButton('Delete 2 resources', dialog)
|
|
281
|
+
expect(remove).toHaveBeenCalledTimes(1)
|
|
282
|
+
expect(remove.mock.calls[0]?.[0]).toHaveLength(2)
|
|
283
|
+
expect(dialog.closest('[inert]')).not.toBeNull()
|
|
284
|
+
expect(host.textContent).toContain('2 selected')
|
|
285
|
+
|
|
286
|
+
render(pods, [other])
|
|
287
|
+
await act(async () => { await new Promise(resolve => setTimeout(resolve, 300)) })
|
|
288
|
+
expect(document.querySelector('[role="dialog"]')).toBeNull()
|
|
289
|
+
expect(host.textContent).toContain('1 selected')
|
|
290
|
+
expect(remove).toHaveBeenCalledTimes(1)
|
|
291
|
+
clickButton('Delete')
|
|
292
|
+
const nextDialog = required<HTMLElement>('[role="dialog"]', document)
|
|
293
|
+
expect(nextDialog.textContent).toContain('Delete 1 Pod?')
|
|
294
|
+
clickButton('Delete 1 resource', nextDialog)
|
|
295
|
+
expect(remove.mock.calls[1]?.[0]).toEqual([{ kind: 'pods', group: '', namespace: 'test', name: 'remaining' }])
|
|
296
|
+
})
|
|
297
|
+
|
|
271
298
|
it.each(['false', 'garbage', 'null', '1'])('ignores invalid or disabled visibility value %s', value => {
|
|
272
299
|
localStorage.setItem(checkboxKey, value)
|
|
273
300
|
renderBulkTable()
|
|
@@ -294,3 +321,71 @@ it('does not offer selection without a bulk action callback even with saved visi
|
|
|
294
321
|
expect(host.querySelector('input[title="Select all"]')).toBeNull()
|
|
295
322
|
expect(localStorage.getItem(checkboxKey)).toBe('true')
|
|
296
323
|
})
|
|
324
|
+
|
|
325
|
+
it('closes single-delete confirmation on submission but keeps the inspected resource until known success', () => {
|
|
326
|
+
const remove = vi.fn<NonNullable<ComponentProps<typeof ResourceActionsBar>['onDelete']>>()
|
|
327
|
+
const close = vi.fn()
|
|
328
|
+
host = document.createElement('div'); document.body.append(host); root = createRoot(host)
|
|
329
|
+
act(() => root.render(<ResourceActionsBar
|
|
330
|
+
resource={{ kind: 'pods', namespace: 'test', name: 'example' }}
|
|
331
|
+
data={pod}
|
|
332
|
+
onDelete={remove}
|
|
333
|
+
onClose={close}
|
|
334
|
+
/>))
|
|
335
|
+
act(() => required<SVGElement>('svg[class*="trash"]').closest<HTMLButtonElement>('button')!.click())
|
|
336
|
+
const dialog = required<HTMLElement>('[role="dialog"]', document)
|
|
337
|
+
clickButton('Delete', dialog)
|
|
338
|
+
expect(remove).toHaveBeenCalledTimes(1)
|
|
339
|
+
expect(dialog.closest('[inert]')).not.toBeNull()
|
|
340
|
+
expect(close).not.toHaveBeenCalled()
|
|
341
|
+
act(() => remove.mock.calls[0]?.[1]?.onSuccess?.())
|
|
342
|
+
expect(close).toHaveBeenCalledTimes(1)
|
|
343
|
+
})
|
|
344
|
+
|
|
345
|
+
it('preserves rows selected after a bulk delete was submitted', () => {
|
|
346
|
+
const { remove, render } = renderBulkTable()
|
|
347
|
+
key('b')
|
|
348
|
+
act(() => required<HTMLInputElement>('input[title="Select all"]').click())
|
|
349
|
+
clickButton('Delete')
|
|
350
|
+
clickButton('Delete 1 resource', required<HTMLElement>('[role="dialog"]', document))
|
|
351
|
+
const other = { ...pod, metadata: { ...pod.metadata, name: 'remaining', uid: 'remaining-id' } }
|
|
352
|
+
render(pods, [pod, other])
|
|
353
|
+
act(() => required<HTMLInputElement>('input[title="Deselect all"]').click())
|
|
354
|
+
expect(host.textContent).toContain('2 selected')
|
|
355
|
+
act(() => remove.mock.calls[0]?.[1]?.onSuccess?.())
|
|
356
|
+
expect(host.textContent).toContain('1 selected')
|
|
357
|
+
render(pods, [other])
|
|
358
|
+
expect(host.textContent).toContain('1 selected')
|
|
359
|
+
})
|
|
360
|
+
|
|
361
|
+
it.each(['name', 'uid'])('does not close a newly inspected resource after an earlier delete succeeds (%s)', change => {
|
|
362
|
+
const remove = vi.fn<NonNullable<ComponentProps<typeof ResourceActionsBar>['onDelete']>>()
|
|
363
|
+
const close = vi.fn()
|
|
364
|
+
host = document.createElement('div'); document.body.append(host); root = createRoot(host)
|
|
365
|
+
const render = (name: string, uid: string, deleting = false) => act(() => root.render(<ResourceActionsBar
|
|
366
|
+
resource={{ kind: 'pods', namespace: 'test', name }}
|
|
367
|
+
data={{ ...pod, metadata: { ...pod.metadata, name, uid } }}
|
|
368
|
+
onDelete={remove}
|
|
369
|
+
onClose={close}
|
|
370
|
+
isDeleting={deleting}
|
|
371
|
+
/>))
|
|
372
|
+
render('example', 'original')
|
|
373
|
+
act(() => required<HTMLButtonElement>('button[aria-label="Delete resource"]').click())
|
|
374
|
+
clickButton('Delete', required<HTMLElement>('[role="dialog"]', document))
|
|
375
|
+
render(change === 'name' ? 'other' : 'example', 'replacement', true)
|
|
376
|
+
expect(required<HTMLButtonElement>('button[aria-label="Deleting resource"]').disabled).toBe(true)
|
|
377
|
+
act(() => remove.mock.calls[0]?.[1]?.onSuccess?.())
|
|
378
|
+
expect(close).not.toHaveBeenCalled()
|
|
379
|
+
})
|
|
380
|
+
|
|
381
|
+
it('ignores a delete success callback after its action bar is unmounted', () => {
|
|
382
|
+
const remove = vi.fn<NonNullable<ComponentProps<typeof ResourceActionsBar>['onDelete']>>()
|
|
383
|
+
const close = vi.fn()
|
|
384
|
+
host = document.createElement('div'); document.body.append(host); root = createRoot(host)
|
|
385
|
+
act(() => root.render(<ResourceActionsBar resource={{ kind: 'pods', namespace: 'test', name: 'example' }} data={pod} onDelete={remove} onClose={close} />))
|
|
386
|
+
act(() => required<HTMLButtonElement>('button[aria-label="Delete resource"]').click())
|
|
387
|
+
clickButton('Delete', required<HTMLElement>('[role="dialog"]', document))
|
|
388
|
+
act(() => root.render(<div>Another view</div>))
|
|
389
|
+
act(() => remove.mock.calls[0]?.[1]?.onSuccess?.())
|
|
390
|
+
expect(close).not.toHaveBeenCalled()
|
|
391
|
+
})
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
// @vitest-environment jsdom
|
|
2
|
+
import { act } from 'react'
|
|
3
|
+
import { createRoot } from 'react-dom/client'
|
|
4
|
+
import { VirtuosoMockContext } from 'react-virtuoso'
|
|
5
|
+
import { afterEach, expect, it, vi } from 'vitest'
|
|
6
|
+
import { ResourcesView } from './ResourcesView'
|
|
7
|
+
import { KeyboardShortcutProvider } from '../../hooks/useKeyboardShortcuts'
|
|
8
|
+
import type { APIResource } from '../../types'
|
|
9
|
+
|
|
10
|
+
;(globalThis as any).IS_REACT_ACT_ENVIRONMENT = true
|
|
11
|
+
window.matchMedia = vi.fn().mockReturnValue({ matches: false, addEventListener: vi.fn(), removeEventListener: vi.fn() })
|
|
12
|
+
Element.prototype.scrollIntoView = vi.fn()
|
|
13
|
+
let root: ReturnType<typeof createRoot>
|
|
14
|
+
let host: HTMLDivElement
|
|
15
|
+
afterEach(() => { act(() => root?.unmount()); host?.remove(); localStorage.clear() })
|
|
16
|
+
|
|
17
|
+
it('shows reconciliation and labeled Pod snapshots in the actual collector table', async () => {
|
|
18
|
+
host = document.createElement('div'); document.body.append(host); root = createRoot(host)
|
|
19
|
+
const kind: APIResource = { group: 'opentelemetry.io', version: 'v1beta1', kind: 'OpenTelemetryCollector', name: 'opentelemetrycollectors', namespaced: true, isCrd: true, verbs: ['get', 'list'] }
|
|
20
|
+
const data = { apiVersion: 'opentelemetry.io/v1beta1', kind: 'OpenTelemetryCollector', metadata: { name: 'collector', namespace: 'test' }, spec: { mode: 'deployment', managementState: 'managed' }, status: { conditions: [{ type: 'Ready', status: 'False', reason: 'ReconcileError' }], scale: { statusReplicas: '0/1' }, version: '0.162.0' } }
|
|
21
|
+
await act(async () => root.render(<VirtuosoMockContext.Provider value={{ viewportHeight: 600, itemHeight: 48 }}><KeyboardShortcutProvider><ResourcesView namespaces={['test']} apiResources={[kind]} resourceCounts={{ OpenTelemetryCollector: 1 }} selectedKindQuery={{ isLoading: false, data: [data], resourceName: kind.name, group: kind.group }} locationPathname="/resources/opentelemetrycollectors" locationSearch="?apiGroup=opentelemetry.io" onNavigate={() => {}} /></KeyboardShortcutProvider></VirtuosoMockContext.Provider>))
|
|
22
|
+
const table = host.querySelector('table')!
|
|
23
|
+
expect(table).not.toBeNull()
|
|
24
|
+
expect(table.textContent).toContain('Reconciliation')
|
|
25
|
+
expect(table.textContent).toContain('Reconcile failed')
|
|
26
|
+
expect(table.textContent).toContain('Reported Pods')
|
|
27
|
+
expect(table.textContent).toContain('0/1')
|
|
28
|
+
expect(table.textContent).toContain('0.162.0')
|
|
29
|
+
})
|