Skip to content

Commit

Permalink
frontend: Fix KubeObject type and all its uses
Browse files Browse the repository at this point in the history
Signed-off-by: Oleksandr Dubenko <[email protected]>
  • Loading branch information
sniok committed May 10, 2024
1 parent e3b1b7a commit f0ee0f1
Show file tree
Hide file tree
Showing 77 changed files with 331 additions and 283 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import React from 'react';
import { Provider } from 'react-redux';
import { useDispatch } from 'react-redux';
import { MemoryRouter } from 'react-router-dom';
import { KubeObject } from '../../lib/k8s/cluster';
import { SectionBox } from '../common';
import DetailsViewSection, { DetailsViewSectionProps } from './DetailsViewSection';
import { setDetailsView } from './detailsViewSectionSlice';
Expand Down Expand Up @@ -63,10 +64,10 @@ const Template: Story<DetailsViewSectionProps> = args => {

export const MatchRenderIt = Template.bind({});
MatchRenderIt.args = {
resource: { kind: 'Node' },
resource: { kind: 'Node' } as KubeObject,
};

export const NoMatchNoRender = Template.bind({});
NoMatchNoRender.args = {
resource: { kind: 'DoesNotExist' },
resource: { kind: 'DoesNotExist' } as KubeObject,
};
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ import detailsViewSectionReducer, {
addDetailsViewSectionsProcessor,
DefaultDetailsViewSection,
DetailsViewSection,
DetailsViewSectionProcessorType,
DetailsViewsSectionProcessor,
setDetailsView,
setDetailsViewSection,
Expand Down Expand Up @@ -67,7 +66,7 @@ describe('detailsViewSectionSlice', () => {
it('should add a new details view sections processor when provided as an object', () => {
const processor: DetailsViewsSectionProcessor = {
id: 'test-processor',
processor: info => info.actions,
processor: () => [{ id: 'test-section' }],
};
store.dispatch(addDetailsViewSectionsProcessor(processor));
expect(store.getState().detailsViewSection.detailsViewSectionsProcessors).toEqual([
Expand All @@ -76,7 +75,7 @@ describe('detailsViewSectionSlice', () => {
});

it('should add a new details view sections processor when provided as a function', () => {
const processorFunc: DetailsViewSectionProcessorType = info => info.actions;
const processorFunc = () => [{ id: 'test-section' }];
store.dispatch(addDetailsViewSectionsProcessor(processorFunc));
const processors = store.getState().detailsViewSection.detailsViewSectionsProcessors;
expect(processors).toHaveLength(1);
Expand Down
14 changes: 7 additions & 7 deletions frontend/src/components/cluster/Charts.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import '../../i18n/config';
import { useTheme } from '@mui/material/styles';
import { useTranslation } from 'react-i18next';
import { KubeObject } from '../../lib/k8s/cluster';
import { KubeMetrics } from '../../lib/k8s/cluster';
import Node from '../../lib/k8s/node';
import Pod from '../../lib/k8s/pod';
import { parseCpu, parseRam, TO_GB, TO_ONE_CPU } from '../../lib/units';
Expand All @@ -14,11 +14,11 @@ export function MemoryCircularChart(props: ResourceCircularChartProps) {
const { noMetrics } = props;
const { t } = useTranslation(['translation', 'glossary']);

function memoryUsedGetter(item: KubeObject) {
function memoryUsedGetter(item: KubeMetrics) {
return parseRam(item.usage.memory) / TO_GB;
}

function memoryAvailableGetter(item: KubeObject) {
function memoryAvailableGetter(item: Node | Pod) {
return parseRam(item.status!.capacity.memory) / TO_GB;
}

Expand Down Expand Up @@ -50,11 +50,11 @@ export function CpuCircularChart(props: ResourceCircularChartProps) {
const { noMetrics } = props;
const { t } = useTranslation(['translation', 'glossary']);

function cpuUsedGetter(item: KubeObject) {
function cpuUsedGetter(item: KubeMetrics) {
return parseCpu(item.usage.cpu) / TO_ONE_CPU;
}

function cpuAvailableGetter(item: KubeObject) {
function cpuAvailableGetter(item: Node | Pod) {
return parseCpu(item.status!.capacity.cpu) / TO_ONE_CPU;
}

Expand Down Expand Up @@ -82,7 +82,7 @@ export function CpuCircularChart(props: ResourceCircularChartProps) {
);
}

export function PodsStatusCircleChart(props: Pick<ResourceCircularChartProps, 'items'>) {
export function PodsStatusCircleChart(props: { items: Pod[] | null }) {
const theme = useTheme();
const { items } = props;
const { t } = useTranslation(['translation', 'glossary']);
Expand Down Expand Up @@ -143,7 +143,7 @@ export function PodsStatusCircleChart(props: Pick<ResourceCircularChartProps, 'i
);
}

export function NodesStatusCircleChart(props: Pick<ResourceCircularChartProps, 'items'>) {
export function NodesStatusCircleChart(props: { items: Node[] | null }) {
const theme = useTheme();
const { items } = props;
const { t } = useTranslation(['translation', 'glossary']);
Expand Down
4 changes: 2 additions & 2 deletions frontend/src/components/cluster/Overview.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -189,8 +189,8 @@ function EventsSection() {
cellProps: { style: { textAlign: 'right' } },
gridTemplate: 'minmax(150px, 0.5fr)',
sort: (e1: Event, e2: Event) => {
const date1 = e1.lastTimestamp || e1.metadata.creationTimestamp;
const date2 = e2.lastTimestamp || e2.metadata.creationTimestamp;
const date1 = e1.getValue('lastTimestamp') || e1.metadata.creationTimestamp;
const date2 = e2.getValue('lastTimestamp') || e2.metadata.creationTimestamp;
return new Date(date2).getTime() - new Date(date1).getTime();
},
},
Expand Down
2 changes: 1 addition & 1 deletion frontend/src/components/common/LabelListItem.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import React from 'react';
import { LightTooltip } from './Tooltip';

export interface LabelListItemProps {
labels: React.ReactNode[];
labels?: React.ReactNode[];
}

export default function LabelListItem(props: LabelListItemProps) {
Expand Down
6 changes: 3 additions & 3 deletions frontend/src/components/common/Link.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -24,16 +24,16 @@ export interface LinkProps extends LinkBaseProps {
}

export interface LinkObjectProps extends LinkBaseProps {
kubeObject: InstanceType<ReturnType<typeof makeKubeObject>>;
kubeObject: InstanceType<ReturnType<typeof makeKubeObject>> | null;
[prop: string]: any;
}

function PureLink(props: React.PropsWithChildren<LinkProps | LinkObjectProps>) {
if ((props as LinkObjectProps).kubeObject) {
const { kubeObject, ...otherProps } = props as LinkObjectProps;
return (
<MuiLink component={RouterLink} to={kubeObject.getDetailsLink()} {...otherProps}>
{props.children || kubeObject.getName()}
<MuiLink component={RouterLink} to={kubeObject!.getDetailsLink()} {...otherProps}>
{props.children || kubeObject!.getName()}
</MuiLink>
);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ export interface NameValueTableRow {
/** The name (key) for this row */
name: string | JSX.Element;
/** The value for this row */
value?: string | JSX.Element | JSX.Element[];
value?: string | number | null | JSX.Element | JSX.Element[];
/** Whether this row should be hidden (can be a boolean or a function that will take the
* @param value and return a boolean) */
hide?: boolean | ((value: NameValueTableRow['value']) => boolean);
Expand All @@ -81,7 +81,7 @@ export interface NameValueTableProps {
function Value({
value,
}: {
value: string | JSX.Element | JSX.Element[] | undefined;
value: string | null | number | JSX.Element | JSX.Element[] | undefined;
}): JSX.Element | null {
if (typeof value === 'undefined') {
return null;
Expand All @@ -96,7 +96,7 @@ function Value({
</>
);
} else {
return value;
return value as JSX.Element | null;
}
}

Expand Down
4 changes: 2 additions & 2 deletions frontend/src/components/common/Resource/AuthVisible.tsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
import React from 'react';
import { KubeObject } from '../../../lib/k8s/cluster';
import { KubeObject, KubeObjectClass } from '../../../lib/k8s/cluster';

export interface AuthVisibleProps extends React.PropsWithChildren<{}> {
/** The item for which auth will be checked or a resource class (e.g. Job). */
item: KubeObject;
item: KubeObject | KubeObjectClass | null;
/** The verb associated with the permissions being verifying. See https://kubernetes.io/docs/reference/access-authn-authz/authorization/#determine-the-request-verb . */
authVerb: string;
/** The subresource for which the permissions are being verifyied (e.g. "log" when checking for a pod's log). */
Expand Down
12 changes: 7 additions & 5 deletions frontend/src/components/common/Resource/CircularChart.tsx
Original file line number Diff line number Diff line change
@@ -1,21 +1,23 @@
import '../../../i18n/config';
import _ from 'lodash';
import _, { List } from 'lodash';
import { useTranslation } from 'react-i18next';
import { KubeMetrics, KubeObject } from '../../../lib/k8s/cluster';
import Node from '../../../lib/k8s/node';
import Pod from '../../../lib/k8s/pod';
import { PercentageCircleProps } from '../Chart';
import TileChart from '../TileChart';

export interface CircularChartProps extends Omit<PercentageCircleProps, 'data'> {
/** Items to display in the chart (should have a corresponding value in @param itemsMetrics) */
items: KubeObject[] | null;
items: Node[] | Pod[] | null;
/** Metrics to display in the chart (for items in @param items) */
itemsMetrics: KubeMetrics[] | null;
/** Whether no metrics are available. If true, then instead of a chart, a message will be displayed */
noMetrics?: boolean;
/** Function to get the "used" value for the metrics in question */
resourceUsedGetter?: (node: KubeObject) => number;
resourceUsedGetter?: (node: KubeMetrics) => number;
/** Function to get the "available" value for the metrics in question */
resourceAvailableGetter?: (node: KubeObject) => number;
resourceAvailableGetter?: (node: Node | Pod) => number;
/** Function to create a legend for the data */
getLegend?: (used: number, available: number) => string;
/** Tooltip to display when hovering over the chart */
Expand Down Expand Up @@ -56,7 +58,7 @@ export function CircularChart(props: CircularChartProps) {

const nodeMetrics = filterMetrics(items, itemsMetrics);
const usedValue = _.sumBy(nodeMetrics, resourceUsedGetter);
const availableValue = _.sumBy(items, resourceAvailableGetter);
const availableValue = _.sumBy(items as List<Node | Pod>, resourceAvailableGetter);

return [usedValue, availableValue];
}
Expand Down
2 changes: 1 addition & 1 deletion frontend/src/components/common/Resource/CreateButton.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@ export default function CreateButton(props: CreateButtonProps) {
if (massagedNewItemDefs[i].kind === 'List') {
// flatten this List kind with the items that it has which is a list of valid k8s resources
const deletedItem = massagedNewItemDefs.splice(i, 1);
massagedNewItemDefs = massagedNewItemDefs.concat(deletedItem[0].items);
massagedNewItemDefs = massagedNewItemDefs.concat(deletedItem[0].items!);
}
if (!massagedNewItemDefs[i].metadata?.name) {
setErrorMessage(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import Paper from '@mui/material/Paper';
import React from 'react';
import { useTranslation } from 'react-i18next';
import { useLocation } from 'react-router-dom';
import { KubeObject } from '../../../../lib/k8s/cluster';
import { KubeObjectClass } from '../../../../lib/k8s/cluster';
import { createRouteURL } from '../../../../lib/router';
import { HeaderAction } from '../../../../redux/actionButtonsSlice';
import Loader from '../../../common/Loader';
Expand All @@ -13,16 +13,16 @@ import SectionBox from '../../SectionBox';
import { MetadataDisplay } from '../MetadataDisplay';
import { MainInfoHeader } from './MainInfoSectionHeader';

export interface MainInfoSectionProps {
resource: KubeObject | null;
headerSection?: ((resource: KubeObject | null) => React.ReactNode) | React.ReactNode;
export interface MainInfoSectionProps<T extends KubeObjectClass> {
resource: InstanceType<T> | null;
headerSection?: ((resource: InstanceType<T> | null) => React.ReactNode) | React.ReactNode;
title?: string;
extraInfo?:
| ((resource: KubeObject | null) => NameValueTableRow[] | null)
| ((resource: InstanceType<T> | null) => NameValueTableRow[] | null)
| NameValueTableRow[]
| null;
actions?:
| ((resource: KubeObject | null) => React.ReactNode[] | null)
| ((resource: InstanceType<T> | null) => React.ReactNode[] | null)
| React.ReactNode[]
| null
| HeaderAction[];
Expand All @@ -33,7 +33,7 @@ export interface MainInfoSectionProps {
error?: string | Error | null;
}

export function MainInfoSection(props: MainInfoSectionProps) {
export function MainInfoSection<T extends KubeObjectClass>(props: MainInfoSectionProps<T>) {
const {
resource,
headerSection,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,12 +15,12 @@ import EditButton from '../EditButton';
import { RestartButton } from '../RestartButton';
import ScaleButton from '../ScaleButton';

export interface MainInfoHeaderProps {
resource: KubeObject | null;
headerSection?: ((resource: KubeObject | null) => React.ReactNode) | React.ReactNode;
export interface MainInfoHeaderProps<T extends KubeObject> {
resource: T | null;
headerSection?: ((resource: T | null) => React.ReactNode) | React.ReactNode;
title?: string;
actions?:
| ((resource: KubeObject | null) => React.ReactNode[] | null)
| ((resource: T | null) => React.ReactNode[] | null)
| React.ReactNode[]
| null
| HeaderAction[];
Expand All @@ -30,7 +30,7 @@ export interface MainInfoHeaderProps {
backLink?: string | ReturnType<typeof useLocation> | null;
}

export function MainInfoHeader(props: MainInfoHeaderProps) {
export function MainInfoHeader<T extends KubeObject>(props: MainInfoHeaderProps<T>) {
const { resource, title, actions = [], headerStyle = 'main', noDefaultActions = false } = props;
const headerActions = useTypedSelector(state => state.actionButtons.headerActions);
const headerActionsProcessors = useTypedSelector(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ export default {
],
} as Meta;

const Template: Story<MetadataDisplayProps> = args => <MetadataDisplayComponent {...args} />;
const Template: Story<MetadataDisplayProps<any>> = args => <MetadataDisplayComponent {...args} />;

const mockResource: KubeObjectInterface = {
kind: 'MyKind',
Expand Down
15 changes: 8 additions & 7 deletions frontend/src/components/common/Resource/MetadataDisplay.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import makeStyles from '@mui/styles/makeStyles';
import React from 'react';
import { useTranslation } from 'react-i18next';
import { ResourceClasses } from '../../../lib/k8s';
import { KubeObject, KubeObjectInterface, KubeOwnerReference } from '../../../lib/k8s/cluster';
import { KubeObject, KubeOwnerReference } from '../../../lib/k8s/cluster';
import { localeDate } from '../../../lib/util';
import { NameValueTable, NameValueTableRow } from '../../common/SimpleTable';
import Link from '../Link';
Expand All @@ -29,17 +29,17 @@ export const useMetadataDisplayStyles = makeStyles(theme => ({
},
}));

type ExtraRowsFunc = (resource: KubeObjectInterface) => NameValueTableRow[] | null;
type ExtraRowsFunc<T> = (resource: T | null) => NameValueTableRow[] | null;

export interface MetadataDisplayProps {
resource: KubeObject;
extraRows?: ExtraRowsFunc | NameValueTableRow[] | null;
export interface MetadataDisplayProps<T extends KubeObject> {
resource: T;
extraRows?: ExtraRowsFunc<T> | NameValueTableRow[] | null;
}

export function MetadataDisplay(props: MetadataDisplayProps) {
export function MetadataDisplay<T extends KubeObject>(props: MetadataDisplayProps<T>) {
const { resource, extraRows } = props;
const { t } = useTranslation();
let makeExtraRows: ExtraRowsFunc;
let makeExtraRows: ExtraRowsFunc<T>;

function makeOwnerReferences(ownerReferences: KubeOwnerReference[]) {
if (!resource || ownerReferences === undefined) {
Expand All @@ -56,6 +56,7 @@ export function MetadataDisplay(props: MetadataDisplayProps) {
if (ownerRef.kind in ResourceClasses) {
let routeName;
try {
// @ts-ignore -- dynamic property access
routeName = new ResourceClasses[ownerRef.kind]().detailsRoute;
} catch (e) {
console.error(`Error getting routeName for {ownerRef.kind}`, e);
Expand Down
10 changes: 5 additions & 5 deletions frontend/src/components/common/Resource/PortForward.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,15 +11,15 @@ import {
startPortForward,
stopOrDeletePortForward,
} from '../../../lib/k8s/apiProxy';
import { KubeContainer, KubeObject } from '../../../lib/k8s/cluster';
import { KubeContainer, KubeObjectInterface } from '../../../lib/k8s/cluster';
import Pod from '../../../lib/k8s/pod';
import Service from '../../../lib/k8s/service';
import { getCluster } from '../../../lib/util';
import ActionButton from '../ActionButton';

interface PortForwardProps {
containerPort: number | string;
resource?: KubeObject;
resource?: KubeObjectInterface;
}

export interface PortForwardState {
Expand Down Expand Up @@ -155,14 +155,14 @@ export default function PortForward(props: PortForwardProps) {
}

function handlePortForward() {
if (!namespace || !cluster) {
if (!namespace || !cluster || !pods) {
return;
}

setError(null);

const resourceName = name || '';
const podNamespace = isPod ? namespace : pods[0].metadata.namespace;
const podNamespace = isPod ? namespace : pods[0].metadata.namespace!;
const serviceNamespace = namespace;
const serviceName = !isPod ? resourceName : '';
const podName = isPod ? resourceName : pods[0].metadata.name;
Expand Down Expand Up @@ -265,7 +265,7 @@ export default function PortForward(props: PortForwardProps) {
});
}

if (isPod && (!resource || resource.status.phase === 'Failed')) {
if (isPod && (!resource || (resource as Pod).status.phase === 'Failed')) {
return null;
}

Expand Down
Loading

0 comments on commit f0ee0f1

Please sign in to comment.