Compare commits

...

2 Commits

Author SHA1 Message Date
portainer-bot[bot]
862a80c69b fix(kubernetes): PersistentVolumeClaims datatable system resource filter [R8S-1031] (#2700)
Co-authored-by: nickl-portainer <nicholas.loomans@portainer.io>
2026-05-20 20:33:37 +00:00
RHCowan
5b5956574f fix(alerting): remove kube-scheduler and kube-controller-manager alert rules [R8S-1030] (#2695) (#2696)
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-20 09:01:38 +12:00
3 changed files with 74 additions and 9 deletions

View File

@@ -9,6 +9,8 @@ import { withUserProvider } from '@/react/test-utils/withUserProvider';
import { http, server } from '@/setup-tests/server';
import { createMockEnvironment } from '@/react-tools/test-mocks';
import { usePersistentVolumeClaims } from '@/react/kubernetes/volumes/queries/usePersistentVolumeClaims';
import { useNamespacesQuery } from '@/react/kubernetes/namespaces/queries/useNamespacesQuery';
import type { PortainerNamespace } from '@/react/kubernetes/namespaces/types';
import { PersistentVolumeClaimsDatatable } from './PersistentVolumeClaimsDatatable';
import type { PersistentVolumeClaim } from './types';
@@ -33,6 +35,10 @@ vi.mock(
})
);
vi.mock('@/react/kubernetes/namespaces/queries/useNamespacesQuery', () => ({
useNamespacesQuery: vi.fn(),
}));
vi.mock('@/react/kubernetes/volumes/ListView/ResizeClaimEditForm', () => ({
ResizeClaimEditForm: ({ claim }: { claim: PersistentVolumeClaim }) => (
<div data-cy="resize-form">Resize form for {claim.name}</div>
@@ -84,6 +90,11 @@ const mockPVCs: PersistentVolumeClaim[] = [
},
];
const mockNamespaces = [
{ Name: 'default', IsSystem: false },
{ Name: 'kube-system', IsSystem: true },
] as PortainerNamespace[];
function renderComponent() {
server.use(
http.get('/api/endpoints/:endpointId', () =>
@@ -100,11 +111,19 @@ function renderComponent() {
describe('PersistentVolumeClaimsDatatable', () => {
beforeEach(() => {
localStorage.clear();
mockUseEnvironmentId.mockReturnValue(3);
vi.mocked(usePersistentVolumeClaims).mockReturnValue({
data: mockPVCs,
vi.mocked(usePersistentVolumeClaims).mockImplementation(
(_envId, options) =>
({
data: options?.select ? options.select(mockPVCs) : mockPVCs,
isLoading: false,
}) as ReturnType<typeof usePersistentVolumeClaims>
);
vi.mocked(useNamespacesQuery).mockReturnValue({
data: [],
isLoading: false,
} as ReturnType<typeof usePersistentVolumeClaims>);
} as ReturnType<typeof useNamespacesQuery>);
});
it('renders the datatable with PVC names and title', async () => {
@@ -116,10 +135,15 @@ describe('PersistentVolumeClaimsDatatable', () => {
});
it('shows an empty table when there are no PVCs', async () => {
vi.mocked(usePersistentVolumeClaims).mockReturnValue({
data: [] as PersistentVolumeClaim[],
isLoading: false,
} as ReturnType<typeof usePersistentVolumeClaims>);
vi.mocked(usePersistentVolumeClaims).mockImplementation(
(_envId, options) =>
({
data: options?.select
? options.select([] as PersistentVolumeClaim[])
: [],
isLoading: false,
}) as ReturnType<typeof usePersistentVolumeClaims>
);
renderComponent();
@@ -163,4 +187,32 @@ describe('PersistentVolumeClaimsDatatable', () => {
expect(screen.queryByTestId('resize-form')).not.toBeInTheDocument();
});
it('hides system namespace PVCs by default', async () => {
vi.mocked(useNamespacesQuery).mockReturnValue({
data: mockNamespaces,
isLoading: false,
} as ReturnType<typeof useNamespacesQuery>);
renderComponent();
expect(await screen.findByText('test-pvc-1')).toBeVisible();
expect(screen.queryByText('test-pvc-2')).not.toBeInTheDocument();
});
it('shows system namespace PVCs when showSystemResources is enabled', async () => {
localStorage.setItem(
'portainer.datatable_settings_kube-volumes-pvc',
JSON.stringify({ state: { showSystemResources: true }, version: 1 })
);
vi.mocked(useNamespacesQuery).mockReturnValue({
data: mockNamespaces,
isLoading: false,
} as ReturnType<typeof useNamespacesQuery>);
renderComponent();
expect(await screen.findByText('test-pvc-1')).toBeVisible();
expect(screen.getByText('test-pvc-2')).toBeVisible();
});
});

View File

@@ -6,6 +6,8 @@ import { useEnvironmentId } from '@/react/hooks/useEnvironmentId';
import { usePersistentVolumeClaims } from '@/react/kubernetes/volumes/queries/usePersistentVolumeClaims';
import { useDeletePersistentVolumeClaims } from '@/react/kubernetes/volumes/queries/useDeletePersistentVolumeClaims';
import { ResizeClaimEditForm } from '@/react/kubernetes/volumes/ListView/ResizeClaimEditForm';
import { isSystemNamespace } from '@/react/kubernetes/namespaces/queries/useIsSystemNamespace';
import { useNamespacesQuery } from '@/react/kubernetes/namespaces/queries/useNamespacesQuery';
import { refreshableSettings } from '@@/datatables/types';
import { Datatable, TableSettingsMenu } from '@@/datatables';
@@ -43,9 +45,12 @@ export function PersistentVolumeClaimsDatatable() {
);
const envId = useEnvironmentId();
const namespacesQuery = useNamespacesQuery(envId);
const namespaces = namespacesQuery.data ?? [];
const deleteClaimsMutation = useDeletePersistentVolumeClaims(envId);
const claimsQuery = usePersistentVolumeClaims(envId, {
refetchInterval: tableState.autoRefreshRate * 1000,
select: filterVolumeClaims,
});
const claims = claimsQuery.data ?? [];
const columns = createPersistentVolumeClaimsColumns((claim) =>
@@ -98,4 +103,14 @@ export function PersistentVolumeClaimsDatatable() {
)}
</>
);
function filterVolumeClaims(
claims: PersistentVolumeClaim[]
): PersistentVolumeClaim[] {
return claims.filter(
(claim) =>
tableState.showSystemResources ||
!isSystemNamespace(claim.namespace, namespaces)
);
}
}

View File

@@ -20,8 +20,6 @@ const (
ClusterEtcdHealthyMetric = "portainer_edge_agent_etcd_healthy"
ClusterEtcdHealthValidMetric = "portainer_edge_agent_etcd_health_valid"
ClusterAPIServerTLSCertExpirySecondsMetric = "portainer_edge_agent_apiserver_tls_cert_expiry_seconds"
ClusterControlPlaneHealthyMetric = "portainer_edge_agent_control_plane_healthy"
ClusterControlPlaneHealthValidMetric = "portainer_edge_agent_control_plane_health_valid"
ClusterAPIServerHealthyMetric = "portainer_edge_agent_apiserver_healthy"
)