1
0
mirror of https://github.com/lensapp/lens.git synced 2025-05-20 05:10:56 +00:00

Add support for custom columns on catalog categories

Signed-off-by: Sebastian Malton <sebastian@malton.name>
This commit is contained in:
Sebastian Malton 2022-01-17 19:50:50 -05:00
parent d31ab690c2
commit ec31105681
9 changed files with 581 additions and 67 deletions

View File

@ -0,0 +1,91 @@
/**
* Copyright (c) 2021 OpenLens Authors
*
* Permission is hereby granted, free of charge, to any person obtaining a copy of
* this software and associated documentation files (the "Software"), to deal in
* the Software without restriction, including without limitation the rights to
* use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
* the Software, and to permit persons to whom the Software is furnished to do so,
* subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
* IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
/**
* Get the value behind `key`. If it was not pressent, first insert `value`
* @param map The map to interact with
* @param key The key to insert into the map with
* @param value The value to optional add to the map
* @returns The value in the map
*/
export function getOrInsert<K, V>(map: Map<K, V>, key: K, value: V): V {
if (map.has(key)) {
return map.get(key);
}
return map.set(key, value).get(key);
}
/**
* Get the value behind `key`. If it was not pressent, first insert the value returned by `getVal`
*
* This function should be used over `getInsertWith` if it is expensive to build a `V`
* @param map The map to interact with
* @param key The key to insert into the map with
* @param builder A function that returns a new instance of `V`.
* @returns The value in the map
*/
export function getOrInsertWith<K, V>(map: Map<K, V>, key: K, builder: () => V): V {
if (map.has(key)) {
return map.get(key);
}
return map.set(key, builder()).get(key);
}
/**
* Set the value associated with `key` iff there was not a previous value
* @param map The map to interact with
* @throws if `key` already in map
* @returns `this` so that `strictSet` can be chained
*/
export function strictSet<K, V>(map: Map<K, V>, key: K, val: V): typeof map {
if (map.has(key)) {
throw new TypeError("Duplicate key in map");
}
return map.set(key, val);
}
/**
* Get the value associated with `key`
* @param map The map to interact with
* @throws if `key` did not a value associated with it
*/
export function strictGet<K, V>(map: Map<K, V>, key: K): V {
if (!map.has(key)) {
throw new TypeError("key not in map");
}
return map.get(key);
}
/**
* Toggles the "has" in `set` of `value`
* @param set The set to interact with
* @param value The value to either add if not present or remove if present from `set`
*/
export function toggle<T>(set: Set<T>, value: T): void {
if (!set.delete(value)) {
// Set.prototype.delete returns false if `value` was not in the set
set.add(value);
}
}

View File

@ -10,11 +10,19 @@ export function noop<T extends any[]>(...args: T): void {
return void args; return void args;
} }
/**
* A typecorrect version of <function>.bind()
*/
export function bind<BoundArgs extends any[], NonBoundArgs extends any[], ReturnType>(fn: (...args: [...BoundArgs, ...NonBoundArgs]) => ReturnType, thisArg: any, ...boundArgs: BoundArgs): (...args: NonBoundArgs) => ReturnType {
return fn.bind(thisArg, ...boundArgs);
}
export * from "./app-version"; export * from "./app-version";
export * from "./autobind"; export * from "./autobind";
export * from "./camelCase"; export * from "./camelCase";
export * from "./cloneJson"; export * from "./cloneJson";
export * from "./cluster-id-url-parsing"; export * from "./cluster-id-url-parsing";
export * from "./collection-functions";
export * from "./convertCpu"; export * from "./convertCpu";
export * from "./convertMemory"; export * from "./convertMemory";
export * from "./debouncePromise"; export * from "./debouncePromise";

View File

@ -16,6 +16,7 @@ import type { WelcomeMenuRegistration } from "../renderer/components/+welcome/we
import type { WelcomeBannerRegistration } from "../renderer/components/+welcome/welcome-banner-items/welcome-banner-registration"; import type { WelcomeBannerRegistration } from "../renderer/components/+welcome/welcome-banner-items/welcome-banner-registration";
import type { CommandRegistration } from "../renderer/components/command-palette/registered-commands/commands"; import type { CommandRegistration } from "../renderer/components/command-palette/registered-commands/commands";
import type { AppPreferenceRegistration } from "../renderer/components/+preferences/app-preferences/app-preference-registration"; import type { AppPreferenceRegistration } from "../renderer/components/+preferences/app-preferences/app-preference-registration";
import type { AdditionalCategoryColumnRegistration } from "../renderer/components/+catalog/custom-category-columns";
export class LensRendererExtension extends LensExtension { export class LensRendererExtension extends LensExtension {
globalPages: registries.PageRegistration[] = []; globalPages: registries.PageRegistration[] = [];
@ -33,6 +34,7 @@ export class LensRendererExtension extends LensExtension {
welcomeBanners: WelcomeBannerRegistration[] = []; welcomeBanners: WelcomeBannerRegistration[] = [];
catalogEntityDetailItems: registries.CatalogEntityDetailRegistration<CatalogEntity>[] = []; catalogEntityDetailItems: registries.CatalogEntityDetailRegistration<CatalogEntity>[] = [];
topBarItems: TopBarRegistration[] = []; topBarItems: TopBarRegistration[] = [];
additionalCategoryColumns: AdditionalCategoryColumnRegistration[] = [];
async navigate<P extends object>(pageId?: string, params?: P) { async navigate<P extends object>(pageId?: string, params?: P) {
const { navigate } = await import("../renderer/navigation"); const { navigate } = await import("../renderer/navigation");

View File

@ -28,25 +28,18 @@ import { RenderDelay } from "../render-delay/render-delay";
import { Icon } from "../icon"; import { Icon } from "../icon";
import { HotbarToggleMenuItem } from "./hotbar-toggle-menu-item"; import { HotbarToggleMenuItem } from "./hotbar-toggle-menu-item";
import { Avatar } from "../avatar"; import { Avatar } from "../avatar";
import { KubeObject } from "../../../common/k8s-api/kube-object";
import { getLabelBadges } from "./helpers";
import { withInjectables } from "@ogre-tools/injectable-react"; import { withInjectables } from "@ogre-tools/injectable-react";
import catalogPreviousActiveTabStorageInjectable import catalogPreviousActiveTabStorageInjectable from "./catalog-previous-active-tab-storage/catalog-previous-active-tab-storage.injectable";
from "./catalog-previous-active-tab-storage/catalog-previous-active-tab-storage.injectable";
import catalogEntityStoreInjectable from "./catalog-entity-store/catalog-entity-store.injectable"; import catalogEntityStoreInjectable from "./catalog-entity-store/catalog-entity-store.injectable";
import type { GetCategoryColumnsParams, CategoryColumns } from "./get-category-columns.injectable";
enum sortBy { import getCategoryColumnsInjectable from "./get-category-columns.injectable";
name = "name",
kind = "kind",
source = "source",
status = "status",
}
interface Props extends RouteComponentProps<CatalogViewRouteParam> {} interface Props extends RouteComponentProps<CatalogViewRouteParam> {}
interface Dependencies { interface Dependencies {
catalogPreviousActiveTabStorage: { set: (value: string ) => void } catalogPreviousActiveTabStorage: { set: (value: string ) => void };
catalogEntityStore: CatalogEntityStore catalogEntityStore: CatalogEntityStore;
getCategoryColumns: (params: GetCategoryColumnsParams) => CategoryColumns;
} }
@observer @observer
@ -228,46 +221,23 @@ class NonInjectedCatalog extends React.Component<Props & Dependencies> {
return null; return null;
} }
const { sortingCallbacks, searchFilters, renderTableContents, renderTableHeader } = this.props.getCategoryColumns({ activeCategory });
return ( return (
<ItemListLayout <ItemListLayout
className={styles.Catalog} className={styles.Catalog}
tableId={tableId} tableId={tableId}
renderHeaderTitle={activeCategory?.metadata.name || "Browse All"} renderHeaderTitle={activeCategory?.metadata.name ?? "Browse All"}
isSelectable={false} isSelectable={false}
isConfigurable={true} isConfigurable={true}
store={this.props.catalogEntityStore} store={this.props.catalogEntityStore}
sortingCallbacks={{ sortingCallbacks={sortingCallbacks}
[sortBy.name]: entity => entity.getName(), searchFilters={searchFilters}
[sortBy.source]: entity => entity.getSource(), renderTableHeader={renderTableHeader}
[sortBy.status]: entity => entity.status.phase,
[sortBy.kind]: entity => entity.kind,
}}
searchFilters={[
entity => [
entity.getName(),
entity.getId(),
entity.status.phase,
`source=${entity.getSource()}`,
...KubeObject.stringifyLabels(entity.metadata.labels),
],
]}
renderTableHeader={[
{ title: "Name", className: styles.entityName, sortBy: sortBy.name, id: "name" },
!activeCategory && { title: "Kind", sortBy: sortBy.kind, id: "kind" },
{ title: "Source", className: styles.sourceCell, sortBy: sortBy.source, id: "source" },
{ title: "Labels", className: `${styles.labelsCell} scrollable`, id: "labels" },
{ title: "Status", className: styles.statusCell, sortBy: sortBy.status, id: "status" },
].filter(Boolean)}
customizeTableRowProps={entity => ({ customizeTableRowProps={entity => ({
disabled: !entity.isEnabled(), disabled: !entity.isEnabled(),
})} })}
renderTableContents={entity => [ renderTableContents={renderTableContents}
this.renderName(entity),
!activeCategory && entity.kind,
entity.getSource(),
getLabelBadges(entity),
<span key="phase" className={entity.status.phase}>{entity.status.phase}</span>,
].filter(Boolean)}
onDetails={this.onDetails} onDetails={this.onDetails}
renderItemMenu={this.renderItemMenu} renderItemMenu={this.renderItemMenu}
/> />
@ -306,17 +276,11 @@ class NonInjectedCatalog extends React.Component<Props & Dependencies> {
} }
} }
export const Catalog = withInjectables<Dependencies, Props>( export const Catalog = withInjectables<Dependencies, Props>( NonInjectedCatalog, {
NonInjectedCatalog, getProps: (di, props) => ({
{ catalogEntityStore: di.inject(catalogEntityStoreInjectable),
getProps: (di, props) => ({ catalogPreviousActiveTabStorage: di.inject(catalogPreviousActiveTabStorageInjectable),
catalogEntityStore: di.inject(catalogEntityStoreInjectable), getCategoryColumns: di.inject(getCategoryColumnsInjectable),
...props,
catalogPreviousActiveTabStorage: di.inject( }),
catalogPreviousActiveTabStorageInjectable, });
),
...props,
}),
},
);

View File

@ -0,0 +1,70 @@
/**
* Copyright (c) 2021 OpenLens Authors
*
* Permission is hereby granted, free of charge, to any person obtaining a copy of
* this software and associated documentation files (the "Software"), to deal in
* the Software without restriction, including without limitation the rights to
* use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
* the Software, and to permit persons to whom the Software is furnished to do so,
* subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
* IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
import { getInjectable, lifecycleEnum } from "@ogre-tools/injectable";
import { computed, IComputedValue } from "mobx";
import type { LensRendererExtension } from "../../../extensions/lens-renderer-extension";
import rendererExtensionsInjectable from "../../../extensions/renderer-extensions.injectable";
import { getOrInsert } from "../../utils";
import type { RegisteredAdditionalCategoryColumn } from "./custom-category-columns";
interface Dependencies {
extensions: IComputedValue<LensRendererExtension[]>;
}
function getAdditionCategoryColumns({ extensions }: Dependencies): IComputedValue<Map<string, Map<string, RegisteredAdditionalCategoryColumn[]>>> {
return computed(() => {
const res = new Map<string, Map<string, RegisteredAdditionalCategoryColumn[]>>();
for (const ext of extensions.get()) {
for (const { renderCell, titleProps, priority = 50, searchFilter, sortCallback, ...registration } of ext.additionalCategoryColumns) {
const byGroup = getOrInsert(res, registration.group, new Map<string, RegisteredAdditionalCategoryColumn[]>());
const byKind = getOrInsert(byGroup, registration.kind, []);
const id = `${ext.name}:${registration.id}`;
byKind.push({
renderCell,
priority,
id,
titleProps: {
id,
...titleProps,
sortBy: sortCallback
? id
: undefined,
},
searchFilter,
sortCallback,
});
}
}
return res;
});
}
const categoryColumnsInjectable = getInjectable({
instantiate: (di) => getAdditionCategoryColumns({
extensions: di.inject(rendererExtensionsInjectable),
}),
lifecycle: lifecycleEnum.singleton,
});
export default categoryColumnsInjectable;

View File

@ -0,0 +1,99 @@
/**
* Copyright (c) 2021 OpenLens Authors
*
* Permission is hereby granted, free of charge, to any person obtaining a copy of
* this software and associated documentation files (the "Software"), to deal in
* the Software without restriction, including without limitation the rights to
* use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
* the Software, and to permit persons to whom the Software is furnished to do so,
* subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
* IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
import type React from "react";
import type { CatalogEntity } from "../../../common/catalog";
import type { TableCellProps } from "../table";
/**
* These are the supported props for the title cell
*/
export interface TitleCellProps {
className?: string;
title?: React.ReactNode;
}
/**
* This is the type used to declare new catalog category columns
*/
export interface AdditionalCategoryColumnRegistration {
/**
* The catalog entity kind that is declared by the category for this registration
*
* e.g.
* - `"KubernetesCluster"`
*/
kind: string;
/**
* The catalog entity group that is declared by the category for this registration
*
* e.g.
* - `"entity.k8slens.dev"`
*/
group: string;
/**
* The sorting order value.
*
* @default 50
*/
priority?: number;
/**
* This value MUST to be unique to your extension
*/
id: string;
/**
* This function will be called to generate the cells (on demand) for the column
*/
renderCell: (entity: CatalogEntity) => React.ReactNode;
/**
* This function will be used to generate the columns title cell.
*/
titleProps: TitleCellProps;
/**
* If provided then the column will support sorting and this function will be called to
* determine a row's ordering.
*
* strings are sorted ahead of numbers, and arrays determine ordering between equal
* elements of the previous index.
*/
sortCallback?: (entity: CatalogEntity) => string | number | (string | number)[];
/**
* If provided then searching is supported on this column and this function will be called
* to determine if the current search string matches for this row.
*/
searchFilter?: (entity: CatalogEntity) => string | string[];
}
export interface RegisteredAdditionalCategoryColumn {
id: string;
priority: number;
renderCell: (entity: CatalogEntity) => React.ReactNode;
titleProps: TableCellProps;
sortCallback?: (entity: CatalogEntity) => string | number | (string | number)[];
searchFilter?: (entity: CatalogEntity) => string | string[];
}

View File

@ -0,0 +1,96 @@
/**
* Copyright (c) 2021 OpenLens Authors
*
* Permission is hereby granted, free of charge, to any person obtaining a copy of
* this software and associated documentation files (the "Software"), to deal in
* the Software without restriction, including without limitation the rights to
* use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
* the Software, and to permit persons to whom the Software is furnished to do so,
* subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
* IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
import { getInjectable, lifecycleEnum } from "@ogre-tools/injectable";
import { orderBy } from "lodash";
import type { IComputedValue } from "mobx";
import type { CatalogCategory, CatalogEntity } from "../../../common/catalog";
import { bind } from "../../utils";
import type { ItemListLayoutProps } from "../item-object-list";
import type { RegisteredAdditionalCategoryColumn } from "./custom-category-columns";
import categoryColumnsInjectable from "./custom-category-columns.injectable";
import { anyCategoryColumns, browseAllColumns } from "./internal-category-columns";
interface Dependencies {
extensionColumns: IComputedValue<Map<string, Map<string, RegisteredAdditionalCategoryColumn[]>>>;
}
export interface GetCategoryColumnsParams {
activeCategory: CatalogCategory | null | undefined;
}
export type CategoryColumns = Required<Pick<ItemListLayoutProps<CatalogEntity>, "sortingCallbacks" | "searchFilters" | "renderTableContents" | "renderTableHeader">>;
function getNonGlobalColums(activeCategory: CatalogCategory | null | undefined, extensionColumns: IComputedValue<Map<string, Map<string, RegisteredAdditionalCategoryColumn[]>>>) {
if (activeCategory) {
return extensionColumns
.get()
.get(activeCategory.spec.group)
?.get(activeCategory.spec.names.kind)
?? [];
}
return browseAllColumns;
}
function getCategoryColumns({ extensionColumns }: Dependencies, { activeCategory }: GetCategoryColumnsParams): CategoryColumns {
const allRegistrations = orderBy(
[
...getNonGlobalColums(activeCategory, extensionColumns),
...anyCategoryColumns,
],
"priority",
"asc",
);
const sortingCallbacks: CategoryColumns["sortingCallbacks"] = {};
const searchFilters: CategoryColumns["searchFilters"] = [];
const renderTableHeader: CategoryColumns["renderTableHeader"] = [];
const tableRowRenderers: ((entity: CatalogEntity) => React.ReactNode)[] = [];
for (const registration of allRegistrations) {
if (registration.sortCallback) {
sortingCallbacks[registration.id] = registration.sortCallback;
}
if (registration.searchFilter) {
searchFilters.push(registration.searchFilter);
}
tableRowRenderers.push(registration.renderCell);
renderTableHeader.push(registration.titleProps);
}
return {
sortingCallbacks,
renderTableHeader,
renderTableContents: entity => tableRowRenderers.map(fn => fn(entity)),
searchFilters,
};
}
const getCategoryColumnsInjectable = getInjectable({
instantiate: (di) => bind(getCategoryColumns, null, {
extensionColumns: di.inject(categoryColumnsInjectable),
}),
lifecycle: lifecycleEnum.singleton,
});
export default getCategoryColumnsInjectable;

View File

@ -0,0 +1,136 @@
/**
* Copyright (c) 2021 OpenLens Authors
*
* Permission is hereby granted, free of charge, to any person obtaining a copy of
* this software and associated documentation files (the "Software"), to deal in
* the Software without restriction, including without limitation the rights to
* use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
* the Software, and to permit persons to whom the Software is furnished to do so,
* subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
* IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
import styles from "./catalog.module.scss";
import React from "react";
import { HotbarStore } from "../../../common/hotbar-store";
import type { CatalogEntity } from "../../api/catalog-entity";
import { Avatar } from "../avatar";
import type { RegisteredAdditionalCategoryColumn } from "./custom-category-columns";
import { Icon } from "../icon";
import { prevDefault } from "../../utils";
import { getLabelBadges } from "./helpers";
import { KubeObject } from "../../../common/k8s-api/kube-object";
function renderEntityName(entity: CatalogEntity) {
const hotbarStore = HotbarStore.getInstance();
const isItemInHotbar = hotbarStore.isAddedToActive(entity);
const onClick = prevDefault(
isItemInHotbar
? () => hotbarStore.removeFromHotbar(entity.getId())
: () => hotbarStore.addToHotbar(entity),
);
return (
<>
<Avatar
title={entity.getName()}
colorHash={`${entity.getName()}-${entity.getSource()}`}
src={entity.spec.icon?.src}
background={entity.spec.icon?.background}
className={styles.catalogAvatar}
size={24}
>
{entity.spec.icon?.material && <Icon material={entity.spec.icon?.material} small/>}
</Avatar>
<span>{entity.getName()}</span>
<Icon
small
className={styles.pinIcon}
material={!isItemInHotbar && "push_pin"}
svg={isItemInHotbar ? "push_off" : "push_pin"}
tooltip={isItemInHotbar ? "Remove from Hotbar" : "Add to Hotbar"}
onClick={onClick}
/>
</>
);
}
export const browseAllColumns: RegisteredAdditionalCategoryColumn[] = [
{
id: "kind",
priority: 5,
renderCell: entity => entity.kind,
titleProps: {
id: "kind",
sortBy: "kind",
title: "Kind",
},
sortCallback: entity => entity.kind,
},
];
export const anyCategoryColumns: RegisteredAdditionalCategoryColumn[] = [
{
id: "name",
priority: 0,
renderCell: renderEntityName,
titleProps: {
title: "Name",
className: styles.entityName,
id: "name",
sortBy: "name",
},
searchFilter: entity => entity.getName(),
sortCallback: entity => `name=${entity.getName()}`,
},
{
id: "source",
priority: 10,
renderCell: entity => entity.getSource(),
titleProps: {
title: "Source",
className: styles.sourceCell,
id: "source",
sortBy: "source",
},
sortCallback: entity => entity.getSource(),
searchFilter: entity => `source=${entity.getSource()}`,
},
{
id: "labels",
priority: 20,
renderCell: getLabelBadges,
titleProps: {
id: "labels",
title: "Labels",
className: `${styles.labelsCell} scrollable`,
},
searchFilter: entity => KubeObject.stringifyLabels(entity.metadata.labels),
},
{
id: "status",
priority: 30,
renderCell: entity => (
<span key="phase" className={entity.status.phase}>
{entity.status.phase}
</span>
),
titleProps: {
title: "Status",
className: styles.statusCell,
id: "status",
sortBy: "status",
},
searchFilter: entity => entity.status.phase,
},
];

View File

@ -14,18 +14,66 @@ import { Checkbox } from "../checkbox";
export type TableCellElem = React.ReactElement<TableCellProps>; export type TableCellElem = React.ReactElement<TableCellProps>;
export interface TableCellProps extends React.DOMAttributes<HTMLDivElement> { export interface TableCellProps extends React.DOMAttributes<HTMLDivElement> {
id?: string; // used for configuration visibility of columns /**
* used for configuration visibility of columns
*/
id?: string;
/**
* Any css class names for this table cell. Only used if `title` is a "simple" react node
*/
className?: string; className?: string;
/**
* The actual value of the cell
*/
title?: ReactNode; title?: ReactNode;
scrollable?: boolean; // content inside could be scrolled
checkbox?: boolean; // render cell with a checkbox /**
isChecked?: boolean; // mark checkbox as checked or not * content inside could be scrolled
renderBoolean?: boolean; // show "true" or "false" for all of the children elements are "typeof boolean" */
sortBy?: TableSortBy; // column name, must be same as key in sortable object <Table sortable={}/> scrollable?: boolean;
showWithColumn?: string // id of the column which follow same visibility rules
_sorting?: Partial<TableSortParams>; // <Table> sorting state, don't use this prop outside (!) /**
_sort?(sortBy: TableSortBy): void; // <Table> sort function, don't use this prop outside (!) * render cell with a checkbox
_nowrap?: boolean; // indicator, might come from parent <TableHead>, don't use this prop outside (!) */
checkbox?: boolean;
/**
* mark checkbox as checked or not
*/
isChecked?: boolean;
/**
* show "true" or "false" for all of the children elements are "typeof boolean"
*/
renderBoolean?: boolean;
/**
* column name, must be same as key in sortable object <Table sortable={}/>
*/
sortBy?: TableSortBy;
/**
* id of the column which follow same visibility rules
*/
showWithColumn?: string
/**
* @internal
*/
_sorting?: Partial<TableSortParams>;
/**
* @internal
*/
_sort?(sortBy: TableSortBy): void;
/**
* @internal
* indicator, might come from parent <TableHead>, don't use this prop outside (!)
*/
_nowrap?: boolean;
} }
export class TableCell extends React.Component<TableCellProps> { export class TableCell extends React.Component<TableCellProps> {