Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { PodsPage } from '@console/internal/components/pod-list';
import type { PageComponentProps } from '@console/internal/components/utils';
import { NodeSubNavPage } from './NodeSubNavPage';

const WORKLOAD_PAGE_ID = 'workload';
export const WORKLOAD_PAGE_ID = 'workload';

const NodePodsPage: FC<PageComponentProps<NodeKind>> = ({ obj }) => (
<PodsPage
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,6 @@ import type {
} from '@console/app/src/components/data-view/types';
import { useDataViewSelection } from '@console/app/src/components/data-view/useDataViewSelection';
import { useColumnWidthSettings } from '@console/app/src/components/data-view/useResizableColumnProps';
import NodeGroupEditButton from '@console/app/src/components/nodes/NodeGroupEditButton';
import { FLAG_NODE_MGMT_V1 } from '@console/app/src/consts';
import type { K8sModel } from '@console/dynamic-plugin-sdk/src/api/core-api';
import {
Expand Down Expand Up @@ -100,6 +99,7 @@ import { nodeStatus } from '../../status/node';
import { useIsKubevirtPluginActive } from '../../utils/kubevirt';
import { getNodeClientCSRs, isCSRResource } from './csr';
import NodeUptime from './node-dashboard/NodeUptime';
import NodeGroupEditButton from './NodeGroupEditButton';
import NodeRoles from './NodeRoles';
import { NodeStatusWithExtensions } from './NodeStatus';
import ClientCSRStatus from './status/CSRStatus';
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,10 @@ import { useMemo, useContext } from 'react';
import { useResolvedExtensions } from '@openshift/dynamic-plugin-sdk';
import { Card, CardBody, CardHeader, CardTitle, Stack, StackItem } from '@patternfly/react-core';
import { useTranslation } from 'react-i18next';
import { FLAG_NODE_MGMT_V1 } from '@console/app/src/consts';
import type { NodeInventoryExtensionItem } from '@console/dynamic-plugin-sdk/src/extensions/node';
import { isNodeInventoryItem } from '@console/dynamic-plugin-sdk/src/extensions/node';
import { useFlag } from '@console/dynamic-plugin-sdk/src/utils/flags';
import { useK8sWatchResource } from '@console/internal/components/utils/k8s-watch-hook';
import { resourcePathFromModel } from '@console/internal/components/utils/resource-link';
import { PodModel, NodeModel } from '@console/internal/models';
Expand All @@ -15,10 +17,12 @@ import {
} from '@console/shared/src/components/dashboard/inventory-card/InventoryItem';
import { getPodStatusGroups } from '@console/shared/src/components/dashboard/inventory-card/utils';
import { getName } from '@console/shared/src/selectors/common';
import { WORKLOAD_PAGE_ID } from '../NodeWorkload';
import { NodeDashboardContext } from './NodeDashboardContext';

const NodePodInventoryItem: ComponentType<{ obj: NodeKind }> = ({ obj }) => {
const nodeName = getName(obj);
const nodeMgmtV1Enabled = useFlag(FLAG_NODE_MGMT_V1);

const podResource = useMemo(
() =>
Expand All @@ -38,7 +42,9 @@ const NodePodInventoryItem: ComponentType<{ obj: NodeKind }> = ({ obj }) => {
return <InventoryItem title={PodModel.label} count={0} isLoading={!podsLoaded} />;
}

const basePath = `${resourcePathFromModel(NodeModel, nodeName)}/pods`;
const basePath = nodeMgmtV1Enabled
? `${resourcePathFromModel(NodeModel, nodeName)}/${WORKLOAD_PAGE_ID}/pods`
: `${resourcePathFromModel(NodeModel, nodeName)}/pods`;

return (
<StackItem>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import type { FC } from 'react';
import { useReducer, useCallback, useEffect } from 'react';
import { useMemo, useReducer, useCallback, useEffect } from 'react';
import * as _ from 'lodash';
import { FLAG_NODE_MGMT_V1 } from '@console/app/src/consts';
import { useFlag } from '@console/dynamic-plugin-sdk/src/utils/flags';
import type { NodeKind } from '@console/internal/module/k8s';
import Dashboard from '@console/shared/src/components/dashboard/Dashboard';
import DashboardGrid from '@console/shared/src/components/dashboard/DashboardGrid';
Expand Down Expand Up @@ -75,6 +77,7 @@ const reducer = (state: NodeDashboardState, action: NodeDashboardAction) => {
};

const NodeDashboard: FC<NodeDashboardProps> = ({ obj }) => {
const nodeMgmtV1Enabled = useFlag(FLAG_NODE_MGMT_V1);
const [state, dispatch] = useReducer(reducer, initialState(obj));

useEffect(() => {
Expand All @@ -94,20 +97,35 @@ const NodeDashboard: FC<NodeDashboardProps> = ({ obj }) => {
[],
);

const context = {
obj,
cpuLimit: state.cpuLimit,
memoryLimit: state.memoryLimit,
healthCheck: state.healthCheck,
setCPULimit,
setMemoryLimit,
setHealthCheck,
};
const context = useMemo(
() => ({
obj,
cpuLimit: state.cpuLimit,
memoryLimit: state.memoryLimit,
healthCheck: state.healthCheck,
setCPULimit,
setMemoryLimit,
setHealthCheck,
}),
[
obj,
setCPULimit,
setHealthCheck,
setMemoryLimit,
state.cpuLimit,
state.healthCheck,
state.memoryLimit,
],
);

return (
<NodeDashboardContext.Provider value={context}>
<Dashboard>
<DashboardGrid mainCards={mainCards} leftCards={leftCards} rightCards={rightCards} />
<DashboardGrid
mainCards={mainCards}
leftCards={leftCards}
rightCards={nodeMgmtV1Enabled ? undefined : rightCards}
/>
</Dashboard>
</NodeDashboardContext.Provider>
);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { useResolvedExtensions } from '@openshift/dynamic-plugin-sdk';
import { render, screen } from '@testing-library/react';
import { useFlag } from '@console/dynamic-plugin-sdk/src/utils/flags';
import { useK8sWatchResource } from '@console/internal/components/utils/k8s-watch-hook';
import type { NodeKind } from '@console/internal/module/k8s';
import InventoryCard from '../InventoryCard';
Expand All @@ -10,15 +11,24 @@ jest.mock('@openshift/dynamic-plugin-sdk', () => ({
useResolvedExtensions: jest.fn(),
}));

jest.mock('@console/dynamic-plugin-sdk/src/utils/flags', () => ({
useFlag: jest.fn(() => false),
}));

jest.mock('@console/internal/components/utils/k8s-watch-hook', () => ({
useK8sWatchResource: jest.fn(),
}));

const mockResourceInventoryItem = jest.fn();

jest.mock('@console/shared/src/components/dashboard/inventory-card/InventoryItem', () => ({
InventoryItem: ({ title, count }: { title: string; count?: number }) => (
<div data-test-inventory-item={`${title}:${count ?? 0}`}>{`${title}: ${count ?? 0}`}</div>
),
ResourceInventoryItem: () => <div data-test-inventory-item="pods">Pod Inventory</div>,
ResourceInventoryItem: (props: { basePath: string }) => {
mockResourceInventoryItem(props);
return <div data-test-inventory-item="pods">Pod Inventory</div>;
},
}));

const MockExtensionInventoryItem = jest.fn(() => (
Expand All @@ -27,6 +37,7 @@ const MockExtensionInventoryItem = jest.fn(() => (

const useResolvedExtensionsMock = useResolvedExtensions as jest.Mock;
const useK8sWatchResourceMock = useK8sWatchResource as jest.Mock;
const useFlagMock = useFlag as jest.Mock;

describe('InventoryCard', () => {
const mockNode: NodeKind = {
Expand Down Expand Up @@ -59,6 +70,7 @@ describe('InventoryCard', () => {

beforeEach(() => {
jest.clearAllMocks();
useFlagMock.mockReturnValue(false);
useResolvedExtensionsMock.mockReturnValue([[], true]);
useK8sWatchResourceMock.mockReturnValue([[], true, undefined]);
});
Expand Down Expand Up @@ -126,6 +138,30 @@ describe('InventoryCard', () => {
expect(screen.getByText('Extension Inventory Item')).toBeVisible();
});

it('should use legacy pods path when FLAG_NODE_MGMT_V1 is disabled', () => {
useFlagMock.mockReturnValue(false);

renderWithContext();

expect(mockResourceInventoryItem).toHaveBeenCalledWith(
expect.objectContaining({
basePath: '/k8s/cluster/nodes/test-node/pods',
}),
);
});

it('should use workload pods path when FLAG_NODE_MGMT_V1 is enabled', () => {
useFlagMock.mockReturnValue(true);

renderWithContext();

expect(mockResourceInventoryItem).toHaveBeenCalledWith(
expect.objectContaining({
basePath: '/k8s/cluster/nodes/test-node/workload/pods',
}),
);
});

it('should sort inventory items by priority from highest to lowest', () => {
useResolvedExtensionsMock.mockReturnValue([
[
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
import { render } from '@testing-library/react';
import * as flagsModule from '@console/dynamic-plugin-sdk/src/utils/flags';
import type { NodeKind } from '@console/internal/module/k8s';
import * as DashboardGridModule from '@console/shared/src/components/dashboard/DashboardGrid';
import ActivityCard from '../ActivityCard';
import NodeDashboard from '../NodeDashboard';

jest.mock('@console/dynamic-plugin-sdk/src/utils/flags', () => ({
useFlag: jest.fn(),
}));

jest.mock('@console/shared/src/components/dashboard/Dashboard', () => ({
__esModule: true,
default: ({ children }) => <div>{children}</div>,
}));

jest.mock('@console/shared/src/components/dashboard/DashboardGrid', () => ({
__esModule: true,
default: jest.fn(() => null),
}));

const mockNode: NodeKind = {
apiVersion: 'v1',
kind: 'Node',
metadata: {
name: 'test-node',
uid: 'test-node-uid',
resourceVersion: '12345',
creationTimestamp: '2024-01-01T00:00:00Z',
},
spec: {},
status: {
conditions: [],
addresses: [],
},
};

describe('NodeDashboard', () => {
beforeEach(() => {
jest.spyOn(flagsModule, 'useFlag').mockReturnValue(false);
jest.spyOn(DashboardGridModule, 'default').mockReturnValue(null);
});

afterEach(() => {
jest.restoreAllMocks();
});

describe('when rendering with node object', () => {
it('should render Dashboard with DashboardGrid', () => {
render(<NodeDashboard obj={mockNode} />);

expect(DashboardGridModule.default).toHaveBeenCalled();
});

it('should pass mainCards and leftCards to DashboardGrid', () => {
render(<NodeDashboard obj={mockNode} />);

expect(DashboardGridModule.default).toHaveBeenCalledWith(
expect.objectContaining({
mainCards: expect.arrayContaining([
expect.objectContaining({ Card: expect.any(Function) }),
]),
leftCards: expect.arrayContaining([
expect.objectContaining({ Card: expect.any(Function) }),
]),
}),
expect.any(Object),
);
});
});

describe('when NODE_MGMT_V1 flag is disabled', () => {
it('should pass rightCards, mainCards, and leftCards to DashboardGrid', () => {
jest.spyOn(flagsModule, 'useFlag').mockReturnValue(false);

render(<NodeDashboard obj={mockNode} />);

expect(DashboardGridModule.default).toHaveBeenCalledWith(
expect.objectContaining({
rightCards: [{ Card: ActivityCard }],
mainCards: expect.arrayContaining([
expect.objectContaining({ Card: expect.any(Function) }),
]),
leftCards: expect.arrayContaining([
expect.objectContaining({ Card: expect.any(Function) }),
]),
}),
expect.any(Object),
);
});
});

describe('when NODE_MGMT_V1 flag is enabled', () => {
it('should not pass rightCards to DashboardGrid', () => {
jest.spyOn(flagsModule, 'useFlag').mockReturnValue(true);

render(<NodeDashboard obj={mockNode} />);

expect(DashboardGridModule.default).toHaveBeenCalledWith(
expect.objectContaining({
rightCards: undefined,
mainCards: expect.arrayContaining([
expect.objectContaining({ Card: expect.any(Function) }),
]),
leftCards: expect.arrayContaining([
expect.objectContaining({ Card: expect.any(Function) }),
]),
}),
expect.any(Object),
);
});
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,9 @@ const DashboardGrid: FC<OverviewGridProps> = ({ mainCards, leftCards, rightCards
smallGrid,
]);

const mainGridSpan =
leftCards?.length && rightCards?.length ? 6 : leftCards?.length || rightCards?.length ? 9 : 12;

return (
<div ref={containerRef}>
{smallGrid ? (
Expand All @@ -51,15 +54,19 @@ const DashboardGrid: FC<OverviewGridProps> = ({ mainCards, leftCards, rightCards
</Grid>
) : (
<Grid className="co-dashboard-grid">
<GridItem lg={3} md={3} sm={3}>
<Grid className="co-dashboard-grid">{leftGridCards}</Grid>
</GridItem>
<GridItem lg={6} md={6} sm={6}>
{leftCards?.length ? (
<GridItem lg={3} md={3} sm={3}>
<Grid className="co-dashboard-grid">{leftGridCards}</Grid>
</GridItem>
) : null}
<GridItem lg={mainGridSpan} md={mainGridSpan} sm={mainGridSpan}>
<Grid className="co-dashboard-grid">{mainGridCards}</Grid>
</GridItem>
<GridItem lg={3} md={3} sm={3}>
<Grid className="co-dashboard-grid">{rightGridCards}</Grid>
</GridItem>
{rightCards?.length ? (
<GridItem lg={3} md={3} sm={3}>
<Grid className="co-dashboard-grid">{rightGridCards}</Grid>
</GridItem>
) : null}
</Grid>
)}
</div>
Expand Down
Loading