chore: change yaml rendering to be an option and enable for kubernetes plugin FE components

Signed-off-by: Chris Langhout <clanghout@bol.com>
This commit is contained in:
Chris Langhout
2025-01-04 16:01:35 +02:00
committed by blam
parent 58e1383241
commit b6c5ea2aba
9 changed files with 131 additions and 24 deletions
+2
View File
@@ -322,6 +322,8 @@ catalog:
# Backstage example templates
- type: file
target: ../../plugins/scaffolder-backend/sample-templates/all-templates.yaml
- type: file
target: ../../plugins/scaffolder-backend/sample-templates/big-review-step.yaml
rules:
- allow: [Template]
scaffolder:
@@ -14,50 +14,138 @@
* limitations under the License.
*/
import React, { Fragment } from 'react';
import React, { Fragment, ReactElement } from 'react';
import startCase from 'lodash/startCase';
import Typography from '@material-ui/core/Typography';
import { MetadataTable, MetadataTableItem } from './MetadataTable';
import {
MetadataList,
MetadataListItem,
MetadataTable,
MetadataTableItem,
} from './MetadataTable';
import { CodeSnippet } from '../CodeSnippet';
import jsyaml from 'js-yaml';
import {
Theme,
createStyles,
WithStyles,
withStyles,
} from '@material-ui/core/styles';
export type StructuredMetadataTableListClassKey = 'root';
export type StructuredMetadataTableNestedListClassKey = 'root';
const listStyle = createStyles({
root: {
margin: '0 0',
listStyleType: 'none',
},
});
function toValue(value: object | Array<any> | boolean | string) {
export type StructuredMetadataTableNestedListClassKey = 'root';
const nestedListStyle = (theme: Theme) =>
createStyles({
root: {
...listStyle.root,
paddingLeft: theme.spacing(1),
},
});
interface StyleProps extends WithStyles {
children?: React.ReactNode;
}
// Sub Components
const StyledList = withStyles(listStyle, {
name: 'BackstageStructuredMetadataTableList',
})(({ classes, children }: StyleProps) => (
<MetadataList classes={classes}>{children}</MetadataList>
));
const StyledNestedList = withStyles(nestedListStyle, {
name: 'BackstageStructuredMetadataTableNestedList',
})(({ classes, children }: StyleProps) => (
<MetadataList classes={classes}>{children}</MetadataList>
));
function renderList(list: Array<any>, options: Options, nested: boolean) {
const values = list.map((item: any, index: number) => (
<MetadataListItem key={index}>
{toValue(item, options, nested)}
</MetadataListItem>
));
return nested ? (
<StyledNestedList>{values}</StyledNestedList>
) : (
<StyledList>{values}</StyledList>
);
}
function renderMap(
map: { [key: string]: any },
options: Options,
nested: boolean,
) {
const values = Object.keys(map).map(key => {
const value = toValue(map[key], options, true);
return (
<MetadataListItem key={key}>
<Typography variant="body2" component="span">
{`${options.titleFormat(key)}: `}
</Typography>
{value}
</MetadataListItem>
);
});
return nested ? (
<StyledNestedList>{values}</StyledNestedList>
) : (
<StyledList>{values}</StyledList>
);
}
function toValue(
value: ReactElement | object | Array<any> | boolean,
options: Options,
nested: boolean,
) {
if (React.isValidElement(value)) {
return <Fragment>{value}</Fragment>;
}
if (value !== null && typeof value === 'object') {
return (
<CodeSnippet
language="yaml"
text={jsyaml.dump(value)}
customStyle={{
background: 'transparent',
lineHeight: '1.4',
padding: '0',
margin: 0,
}}
/>
);
if (options.nestedValuesAsYaml) {
return (
<CodeSnippet
language="yaml"
text={jsyaml.dump(value)}
customStyle={{
background: 'transparent',
lineHeight: '1.4',
padding: '0',
margin: 0,
}}
/>
);
}
if (!Array.isArray(value)) {
return renderMap(value, options, nested);
}
}
if (Array.isArray(value)) {
return renderList(value, options, nested);
}
if (typeof value === 'boolean') {
return <Fragment>{value ? '✅' : '❌'}</Fragment>;
}
return (
<Typography variant="body2" component="span">
{value}
</Typography>
);
}
const ItemValue = ({ value }: { value: any }) => (
<Fragment>{toValue(value)}</Fragment>
const ItemValue = ({ value, options }: { value: any; options: Options }) => (
<Fragment>{toValue(value, options, false)}</Fragment>
);
const TableItem = ({
@@ -71,7 +159,7 @@ const TableItem = ({
}) => {
return (
<MetadataTableItem title={options.titleFormat(title)}>
<ItemValue value={value} />
<ItemValue value={value} options={options} />
</MetadataTableItem>
);
};
@@ -94,6 +182,7 @@ export interface StructuredMetadataTableProps {
* @returns Formatted key
*/
titleFormat?: (key: string) => string;
nestedValuesAsYaml?: boolean;
};
}
@@ -101,9 +190,10 @@ type Options = Required<NonNullable<StructuredMetadataTableProps['options']>>;
/** @public */
export function StructuredMetadataTable(props: StructuredMetadataTableProps) {
const { metadata, dense = true, options = {} } = props;
const { metadata, dense = true, options } = props;
const metadataItems = mapToItems(metadata, {
titleFormat: startCase,
nestedValuesAsYaml: options?.nestedValuesAsYaml ?? false,
...options,
});
return <MetadataTable dense={dense}>{metadataItems}</MetadataTable>;
@@ -58,6 +58,7 @@ export const ClusterOverview = () => {
'OIDC Token Provider': value.oidcTokenProvider ?? 'N/A',
'Dashboard Link': value.dashboardUrl ?? 'N/A',
}}
options={{ nestedValuesAsYaml: true }}
/>
)}
</InfoCard>
@@ -50,7 +50,10 @@ const defaultColumns: TableColumn<INode>[] = [
<Grid container>
<Grid item xs={12}>
<Typography variant="h5">Node Info</Typography>
<StructuredMetadataTable metadata={node.status?.nodeInfo ?? {}} />
<StructuredMetadataTable
metadata={node.status?.nodeInfo ?? {}}
options={{ nestedValuesAsYaml: true }}
/>
</Grid>
<Grid item xs={12}>
<Typography variant="h5">Addresses</Typography>
@@ -61,6 +64,7 @@ const defaultColumns: TableColumn<INode>[] = [
return accum;
}, {} as any) ?? {}
}
options={{ nestedValuesAsYaml: true }}
/>
</Grid>
<Grid item xs={12}>
@@ -72,6 +76,7 @@ const defaultColumns: TableColumn<INode>[] = [
return accum;
}, {} as any) ?? {}
}
options={{ nestedValuesAsYaml: true }}
/>
</Grid>
</Grid>
@@ -84,7 +84,10 @@ const DefaultCustomResourceAccordion = ({
</AccordionSummary>
<AccordionDetails>
{Object.prototype.hasOwnProperty.call(customResource, 'status') && (
<StructuredMetadataTable metadata={customResource.status} />
<StructuredMetadataTable
metadata={customResource.status}
options={{ nestedValuesAsYaml: true }}
/>
)}
</AccordionDetails>
</Accordion>
@@ -65,6 +65,9 @@ const IngressCard = ({ ingress }: IngressCardProps) => {
metadata={{
...ingress.spec,
}}
options={{
nestedValuesAsYaml: true,
}}
/>
);
};
@@ -237,6 +237,7 @@ const KubernetesStructuredMetadataTableDrawerContent = <
{!isYaml && (
<StructuredMetadataTable
metadata={renderObject(replaceNullsWithUndefined(object))}
options={{ nestedValuesAsYaml: true }}
/>
)}
</div>
@@ -176,6 +176,7 @@ export const ContainerCard: React.FC<ContainerCardProps> = ({
containerSpec,
containerStatus,
)}
options={{ nestedValuesAsYaml: true }}
/>
</Grid>
{containerMetrics && (
@@ -77,6 +77,7 @@ const ServiceCard = ({ service }: ServiceCardProps) => {
ports: service.spec?.ports,
...metadata,
}}
options={{ nestedValuesAsYaml: true }}
/>
);
};