Skip to content

Commit

Permalink
frontend: Refactor KubeObject class, deprecate makeKubeObject
Browse files Browse the repository at this point in the history
Signed-off-by: Oleksandr Dubenko <oldubenko@microsoft.com>
  • Loading branch information
sniok committed Aug 2, 2024
1 parent 90a38ac commit dcafa9a
Show file tree
Hide file tree
Showing 110 changed files with 1,131 additions and 1,062 deletions.
3 changes: 2 additions & 1 deletion frontend/src/components/App/Home/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -213,7 +213,8 @@ function HomeComponent(props: HomeComponentProps) {
/>
}
>
<ResourceTable
{/* ResourceTable is only taking KubeObject types and Cluster is not a KubeObject */}
<ResourceTable<any>
defaultSortingColumn={{ id: 'name', desc: false }}
columns={[
{
Expand Down
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
11 changes: 4 additions & 7 deletions frontend/src/components/common/ErrorBoundary/ErrorBoundary.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { eventAction, HeadlampEventType } from '../../../redux/headlampEventSlic
import store from '../../../redux/stores/store';

export interface ErrorBoundaryProps {
fallback?: ComponentType<{ error: Error }> | ReactElement | null;
fallback?: ComponentType<{ error: Error }> | ReactElement<{ error: Error }> | null;
}

interface State {
Expand Down Expand Up @@ -49,13 +49,10 @@ export default class ErrorBoundary extends Component<ErrorBoundaryProps, State>
if (!error) {
return this.props.children;
}
if (isValidElement(this.props.fallback)) {
return this.props.fallback;
const FallbackComponent = this.props.fallback;
if (isValidElement(FallbackComponent)) {
return FallbackComponent;
}
const FallbackComponent = this.props.fallback as
| ComponentType<{ error: Error }>
| undefined
| null;
return FallbackComponent ? <FallbackComponent error={error} /> : null;
}
}
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
8 changes: 4 additions & 4 deletions frontend/src/components/common/Link.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import MuiLink from '@mui/material/Link';
import React from 'react';
import { Link as RouterLink } from 'react-router-dom';
import { makeKubeObject } from '../../lib/k8s/cluster';
import { KubeObject } from '../../lib/k8s/cluster';
import { createRouteURL, RouteURLProps } from '../../lib/router';
import { LightTooltip } from './Tooltip';

Expand All @@ -24,16 +24,16 @@ export interface LinkProps extends LinkBaseProps {
}

export interface LinkObjectProps extends LinkBaseProps {
kubeObject: InstanceType<ReturnType<typeof makeKubeObject>>;
kubeObject?: KubeObject | 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 @@ -6,7 +6,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 | boolean | 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 @@ -24,7 +24,7 @@ export interface NameValueTableProps {
function Value({
value,
}: {
value: string | JSX.Element | JSX.Element[] | undefined;
value: string | null | number | boolean | JSX.Element | JSX.Element[] | undefined;
}): JSX.Element | null {
if (typeof value === 'undefined') {
return null;
Expand All @@ -39,7 +39,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 @@ -12,7 +12,7 @@ export default {
argTypes: {},
} as Meta;

const Template: Story<MainInfoSectionProps> = (args: MainInfoSectionProps) => (
const Template: Story<MainInfoSectionProps<any>> = args => (
<TestContext>
<MainInfoSection {...args} />
</TestContext>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,16 +13,13 @@ 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 KubeObject> {
resource: T | null;
headerSection?: ((resource: T | null) => React.ReactNode) | React.ReactNode;
title?: string;
extraInfo?:
| ((resource: KubeObject | null) => NameValueTableRow[] | null)
| NameValueTableRow[]
| null;
extraInfo?: ((resource: T | null) => NameValueTableRow[] | null) | NameValueTableRow[] | null;
actions?:
| ((resource: KubeObject | null) => React.ReactNode[] | null)
| ((resource: T | null) => React.ReactNode[] | null)
| React.ReactNode[]
| null
| HeaderAction[];
Expand All @@ -33,7 +30,7 @@ export interface MainInfoSectionProps {
error?: string | Error | null;
}

export function MainInfoSection(props: MainInfoSectionProps) {
export function MainInfoSection<T extends KubeObject>(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
16 changes: 8 additions & 8 deletions frontend/src/components/common/Resource/MetadataDisplay.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,14 +6,14 @@ import Typography, { TypographyProps } from '@mui/material/Typography';
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 Theme from '../../../lib/themes';
import { localeDate } from '../../../lib/util';
import { NameValueTable, NameValueTableRow } from '../../common/SimpleTable';
import Link from '../Link';
import { LightTooltip } from '../Tooltip';

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

export const metadataStyles = (theme: typeof Theme.light) => ({
color: theme.palette.text.primary,
Expand All @@ -29,15 +29,15 @@ export const metadataStyles = (theme: typeof Theme.light) => ({
textOverflow: 'ellipsis',
});

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 @@ -54,7 +54,7 @@ export function MetadataDisplay(props: MetadataDisplayProps) {
if (ownerRef.kind in ResourceClasses) {
let routeName;
try {
routeName = new ResourceClasses[ownerRef.kind]().detailsRoute;
routeName = ResourceClasses[ownerRef.kind as keyof typeof ResourceClasses].detailsRoute;
} catch (e) {
console.error(`Error getting routeName for {ownerRef.kind}`, e);
return null;
Expand Down
Loading

0 comments on commit dcafa9a

Please sign in to comment.