Skip to content

Commit

Permalink
Fix rendering of boolean values in CRDs (#1087)
Browse files Browse the repository at this point in the history
* Fix rendering of boolean values in CRDs

- add optional special casing for boolean values in DrawerItems and
  TableRows since React (imo annoying fashion) does not render boolean
  values by default.
- add a spinner on the sidebar for when the CRD menu is expeanded but
  the entries have not been loaded yet.
- Add ability to double click a Badge to expand, also make it so that
  Badges highligh all text on first click.

Signed-off-by: Sebastian Malton <[email protected]>
  • Loading branch information
Nokel81 authored Nov 10, 2020
1 parent dd90dcb commit a78bbb5
Show file tree
Hide file tree
Showing 15 changed files with 167 additions and 101 deletions.
3 changes: 2 additions & 1 deletion src/common/utils/saveToAppFiles.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
// Save file to electron app directory (e.g. "/Users/$USER/Library/Application Support/Lens" for MacOS)
import path from "path";
import { app, remote } from "electron";
import { ensureDirSync, writeFileSync, WriteFileOptions } from "fs-extra";
import { ensureDirSync, writeFileSync } from "fs-extra";
import { WriteFileOptions } from "fs"

export function saveToAppFiles(filePath: string, contents: any, options?: WriteFileOptions): string {
const absPath = path.resolve((app || remote.app).getPath("userData"), filePath);
Expand Down
2 changes: 1 addition & 1 deletion src/renderer/api/api-manager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ export class ApiManager {
return this.apis.get(pathOrCallback) || this.apis.get(KubeApi.parseApi(pathOrCallback).apiBase);
}

return Array.from(this.apis.values()).find(pathOrCallback);
return Array.from(this.apis.values()).find(pathOrCallback ?? ((api: KubeApi) => true));
}

registerApi(apiBase: string, api: KubeApi) {
Expand Down
7 changes: 3 additions & 4 deletions src/renderer/api/endpoints/crd.api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ type AdditionalPrinterColumnsCommon = {
description: string;
}

type AdditionalPrinterColumnsV1 = AdditionalPrinterColumnsCommon & {
export type AdditionalPrinterColumnsV1 = AdditionalPrinterColumnsCommon & {
jsonPath: string;
}

Expand Down Expand Up @@ -120,9 +120,9 @@ export class CustomResourceDefinition extends KubeObject {
return JSON.stringify(this.spec.conversion);
}

getPrinterColumns(ignorePriority = true) {
getPrinterColumns(ignorePriority = true): AdditionalPrinterColumnsV1[] {
const columns = this.spec.versions.find(a => this.getVersion() == a.name)?.additionalPrinterColumns
?? this.spec.additionalPrinterColumns?.map(({JSONPath, ...rest}) => ({ ...rest, jsonPath: JSONPath })) // map to V1 shape
?? this.spec.additionalPrinterColumns?.map(({ JSONPath, ...rest }) => ({ ...rest, jsonPath: JSONPath })) // map to V1 shape
?? [];
return columns
.filter(column => column.name != "Age")
Expand All @@ -149,4 +149,3 @@ export class CustomResourceDefinition extends KubeObject {
export const crdApi = new VersionedKubeApi<CustomResourceDefinition>({
objectConstructor: CustomResourceDefinition
});

102 changes: 59 additions & 43 deletions src/renderer/components/+custom-resources/crd-resource-details.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,67 +12,83 @@ import { KubeObjectDetailsProps } from "../kube-object";
import { crdStore } from "./crd.store";
import { KubeObjectMeta } from "../kube-object/kube-object-meta";
import { Input } from "../input";
import { CustomResourceDefinition } from "../../api/endpoints/crd.api";
import { AdditionalPrinterColumnsV1, CustomResourceDefinition } from "../../api/endpoints/crd.api";

interface Props extends KubeObjectDetailsProps<CustomResourceDefinition> {
}

function CrdColumnValue({ value }: { value: any[] | {} | string }) {
function convertSpecValue(value: any): any {
if (Array.isArray(value)) {
return <>{value.map((item, index) => <CrdColumnValue key={index} value={item} />)}</>
return value.map(convertSpecValue)
}
if (typeof(value) === 'object') return (
<Input
readOnly
multiLine
theme="round-black"
className="box grow"
value={JSON.stringify(value, null, 2)}
/>
);
return <span>{value}</span>;

if (typeof value === "object") {
return (
<Input
readOnly
multiLine
theme="round-black"
className="box grow"
value={JSON.stringify(value, null, 2)}
/>
)
}

return value
}

@observer
export class CrdResourceDetails extends React.Component<Props> {
@computed get crd() {
return crdStore.getByObject(this.props.object);
}

renderAdditionalColumns(crd: CustomResourceDefinition, columns: AdditionalPrinterColumnsV1[]) {
return columns.map(({ name, jsonPath: jp }) => (
<DrawerItem key={name} name={name} renderBoolean>
{convertSpecValue(jsonPath.value(crd, jp.slice(1)))}
</DrawerItem>
))
}

renderStatus(crd: CustomResourceDefinition, columns: AdditionalPrinterColumnsV1[]) {
const showStatus = !columns.find(column => column.name == "Status") && crd.status?.conditions;
if (!showStatus) {
return null
}

const conditions = crd.status.conditions
.filter(({ type, reason }) => type || reason)
.map(({ type, reason, message, status }) => ({ kind: type || reason, message, status }))
.map(({ kind, message, status }, index) => (
<Badge
key={kind + index} label={kind}
className={cssNames({ disabled: status === "False" }, kind.toLowerCase())}
tooltip={message}
/>
))

return (
<DrawerItem name={<Trans>Status</Trans>} className="status" labelsOnly>
{conditions}
</DrawerItem>
)
}

render() {
const { object } = this.props;
const { crd } = this;
if (!object || !crd) return null;
const { props: { object }, crd } = this;
if (!object || !crd) {
return null;
}

const className = cssNames("CrdResourceDetails", crd.getResourceKind());
const extraColumns = crd.getPrinterColumns();
const showStatus = !extraColumns.find(column => column.name == "Status") && object.status?.conditions;

return (
<div className={className}>
<KubeObjectMeta object={object}/>
{extraColumns.map(column => {
const { name } = column;
const value = jsonPath.query(object, (column.jsonPath).slice(1));
return (
<DrawerItem key={name} name={name}>
<CrdColumnValue value={value} />
</DrawerItem>
)
})}
{showStatus && (
<DrawerItem name={<Trans>Status</Trans>} className="status" labelsOnly>
{object.status.conditions.map((condition, index) => {
const { type, reason, message, status } = condition;
const kind = type || reason;
if (!kind) return null;
return (
<Badge
key={kind + index} label={kind}
className={cssNames({ disabled: status === "False" }, kind.toLowerCase())}
tooltip={message}
/>
);
})}
</DrawerItem>
)}
<KubeObjectMeta object={object} />
{this.renderAdditionalColumns(object, extraColumns)}
{this.renderStatus(object, extraColumns)}
</div>
)
}
Expand Down
13 changes: 7 additions & 6 deletions src/renderer/components/+custom-resources/crd-resources.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -56,11 +56,11 @@ export class CrdResources extends React.Component<Props> {
[sortBy.age]: (item: KubeObject) => item.metadata.creationTimestamp,
}
extraColumns.forEach(column => {
sortingCallbacks[column.name] = (item: KubeObject) => jsonPath.query(item, column.jsonPath.slice(1))
sortingCallbacks[column.name] = (item: KubeObject) => jsonPath.value(item, column.jsonPath.slice(1))
})
const ListView = KubeObjectListLayout;

return (
<ListView
<KubeObjectListLayout
className="CrdResources"
isClusterScoped={!isNamespaced}
store={store}
Expand All @@ -85,9 +85,10 @@ export class CrdResources extends React.Component<Props> {
renderTableContents={(crdInstance: KubeObject) => [
crdInstance.getName(),
isNamespaced && crdInstance.getNs(),
...extraColumns.map(column => {
return jsonPath.query(crdInstance, (column.jsonPath).slice(1))
}),
...extraColumns.map(column => ({
renderBoolean: true,
children: jsonPath.value(crdInstance, column.jsonPath.slice(1)),
})),
crdInstance.getAge(),
]}
/>
Expand Down
15 changes: 9 additions & 6 deletions src/renderer/components/drawer/drawer-item.tsx
Original file line number Diff line number Diff line change
@@ -1,25 +1,28 @@
import "./drawer-item.scss";
import React from "react";
import { cssNames } from "../../utils";
import { cssNames, displayBooleans } from "../../utils";

export interface DrawerItemProps extends React.HTMLAttributes<any> {
name: React.ReactNode;
className?: string;
title?: string;
labelsOnly?: boolean;
hidden?: boolean;
renderBoolean?: boolean; // show "true" or "false" for all of the children elements are "typeof boolean"
}

export class DrawerItem extends React.Component<DrawerItemProps> {
render() {
const { name, title, labelsOnly, children, hidden, ...elemProps } = this.props
let { className } = this.props;
const { name, title, labelsOnly, children, hidden, className, renderBoolean, ...elemProps } = this.props
if (hidden) return null
className = cssNames("DrawerItem", className, { labelsOnly });

const classNames = cssNames("DrawerItem", className, { labelsOnly });
const content = displayBooleans(renderBoolean, children)

return (
<div {...elemProps} className={className} title={title}>
<div {...elemProps} className={classNames} title={title}>
<span className="name">{name}</span>
<span className="value">{children}</span>
<span className="value">{content}</span>
</div>
)
}
Expand Down
18 changes: 9 additions & 9 deletions src/renderer/components/item-object-list/item-list-layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -235,7 +235,7 @@ export class ItemListLayout extends React.Component<ItemListLayoutProps> {
cellProps.className = cssNames(cellProps.className, headCell.className);
}
}
return <TableCell key={index} {...cellProps}/>
return <TableCell key={index} {...cellProps} />
})
}
{renderItemMenu && (
Expand Down Expand Up @@ -277,7 +277,7 @@ export class ItemListLayout extends React.Component<ItemListLayoutProps> {
if (!isReady || !filters.length || hideFilters || !userSettings.showAppliedFilters) {
return;
}
return <PageFiltersList filters={filters}/>
return <PageFiltersList filters={filters} />
}

renderNoItems() {
Expand All @@ -297,7 +297,7 @@ export class ItemListLayout extends React.Component<ItemListLayoutProps> {
</NoItems>
)
}
return <NoItems/>
return <NoItems />
}

renderHeaderContent(placeholders: IHeaderPlaceholders): ReactNode {
Expand Down Expand Up @@ -344,12 +344,12 @@ export class ItemListLayout extends React.Component<ItemListLayoutProps> {
title: <h5 className="title">{title}</h5>,
info: this.renderInfo(),
filters: <>
{!isClusterScoped && <NamespaceSelectFilter/>}
{!isClusterScoped && <NamespaceSelectFilter />}
<PageFiltersSelect allowEmpty disableFilters={{
[FilterType.NAMESPACE]: true, // namespace-select used instead
}}/>
}} />
</>,
search: <SearchInputUrl/>,
search: <SearchInputUrl />,
}
let header = this.renderHeaderContent(placeholders);
if (customizeHeader) {
Expand Down Expand Up @@ -381,7 +381,7 @@ export class ItemListLayout extends React.Component<ItemListLayoutProps> {
return (
<div className="items box grow flex column">
{!isReady && (
<Spinner center/>
<Spinner center />
)}
{isReady && (
<Table
Expand All @@ -406,8 +406,8 @@ export class ItemListLayout extends React.Component<ItemListLayoutProps> {
onClick={prevDefault(() => store.toggleSelectionAll(items))}
/>
)}
{renderTableHeader.map((cellProps, index) => <TableCell key={index} {...cellProps}/>)}
{renderItemMenu && <TableCell className="menu"/>}
{renderTableHeader.map((cellProps, index) => <TableCell key={index} {...cellProps} />)}
{renderItemMenu && <TableCell className="menu" />}
</TableHead>
)}
{
Expand Down
Loading

0 comments on commit a78bbb5

Please sign in to comment.