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

Remove unnecessary CatalogEntityItem (#4582)

* Remove unnecessary CatalogEntityItem

Signed-off-by: Sebastian Malton <sebastian@malton.name>

* Using simple notation to set search url params

Signed-off-by: Alex Andreev <alex.andreev.email@gmail.com>

* Fix styles file name

Signed-off-by: Alex Andreev <alex.andreev.email@gmail.com>

Co-authored-by: Alex Andreev <alex.andreev.email@gmail.com>
This commit is contained in:
Sebastian Malton 2021-12-24 05:49:34 -05:00 committed by GitHub
parent 2d279a6b99
commit 8082501bb3
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 219 additions and 110 deletions

View File

@ -20,9 +20,11 @@
*/ */
import { anyObject } from "jest-mock-extended"; import { anyObject } from "jest-mock-extended";
import { merge } from "lodash";
import mockFs from "mock-fs"; import mockFs from "mock-fs";
import logger from "../../main/logger"; import logger from "../../main/logger";
import { AppPaths } from "../app-paths"; import { AppPaths } from "../app-paths";
import type { CatalogEntity, CatalogEntityData, CatalogEntityKindData } from "../catalog";
import { ClusterStore } from "../cluster-store"; import { ClusterStore } from "../cluster-store";
import { HotbarStore } from "../hotbar-store"; import { HotbarStore } from "../hotbar-store";
@ -54,68 +56,58 @@ jest.mock("../../main/catalog/catalog-entity-registry", () => ({
}, },
})); }));
const testCluster = { function getMockCatalogEntity(data: Partial<CatalogEntityData> & CatalogEntityKindData): CatalogEntity {
uid: "test", return merge(data, {
name: "test", getName: jest.fn(() => data.metadata?.name),
getId: jest.fn(() => data.metadata?.uid),
getSource: jest.fn(() => data.metadata?.source ?? "unknown"),
isEnabled: jest.fn(() => data.status?.enabled ?? true),
onContextMenuOpen: jest.fn(),
onSettingsOpen: jest.fn(),
metadata: {},
spec: {},
status: {},
}) as CatalogEntity;
}
const testCluster = getMockCatalogEntity({
apiVersion: "v1", apiVersion: "v1",
kind: "Cluster", kind: "Cluster",
status: { status: {
phase: "Running", phase: "Running",
}, },
spec: {},
getName: jest.fn(),
getId: jest.fn(),
onDetailsOpen: jest.fn(),
onContextMenuOpen: jest.fn(),
onSettingsOpen: jest.fn(),
metadata: { metadata: {
uid: "test", uid: "test",
name: "test", name: "test",
labels: {}, labels: {},
}, },
}; });
const minikubeCluster = { const minikubeCluster = getMockCatalogEntity({
uid: "minikube",
name: "minikube",
apiVersion: "v1", apiVersion: "v1",
kind: "Cluster", kind: "Cluster",
status: { status: {
phase: "Running", phase: "Running",
}, },
spec: {},
getName: jest.fn(),
getId: jest.fn(),
onDetailsOpen: jest.fn(),
onContextMenuOpen: jest.fn(),
onSettingsOpen: jest.fn(),
metadata: { metadata: {
uid: "minikube", uid: "minikube",
name: "minikube", name: "minikube",
labels: {}, labels: {},
}, },
}; });
const awsCluster = { const awsCluster = getMockCatalogEntity({
uid: "aws",
name: "aws",
apiVersion: "v1", apiVersion: "v1",
kind: "Cluster", kind: "Cluster",
status: { status: {
phase: "Running", phase: "Running",
}, },
spec: {},
getName: jest.fn(),
getId: jest.fn(),
onDetailsOpen: jest.fn(),
onContextMenuOpen: jest.fn(),
onSettingsOpen: jest.fn(),
metadata: { metadata: {
uid: "aws", uid: "aws",
name: "aws", name: "aws",
labels: {}, labels: {},
}, },
}; });
jest.mock("electron", () => ({ jest.mock("electron", () => ({
app: { app: {

View File

@ -38,14 +38,44 @@ export type CatalogEntityConstructor<Entity extends CatalogEntity> = (
); );
export interface CatalogCategoryVersion<Entity extends CatalogEntity> { export interface CatalogCategoryVersion<Entity extends CatalogEntity> {
/**
* The specific version that the associated constructor is for. This MUST be
* a DNS label and SHOULD be of the form `vN`, `vNalphaY`, or `vNbetaY` where
* `N` and `Y` are both integers greater than 0.
*
* Examples: The following are valid values for this field.
* - `v1`
* - `v1beta1`
* - `v1alpha2`
* - `v3beta2`
*/
name: string; name: string;
/**
* The constructor for the entities.
*/
entityClass: CatalogEntityConstructor<Entity>; entityClass: CatalogEntityConstructor<Entity>;
} }
export interface CatalogCategorySpec { export interface CatalogCategorySpec {
/**
* The grouping for for the category. This MUST be a DNS label.
*/
group: string; group: string;
/**
* The specific versions of the constructors.
*
* NOTE: the field `.apiVersion` after construction MUST match `{.group}/{.versions.[] | .name}`.
* For example, if `group = "entity.k8slens.dev"` and there is an entry in `.versions` with
* `name = "v1alpha1"` then the resulting `.apiVersion` MUST be `entity.k8slens.dev/v1alpha1`
*/
versions: CatalogCategoryVersion<CatalogEntity>[]; versions: CatalogCategoryVersion<CatalogEntity>[];
names: { names: {
/**
* The kind of entity that this category is for. This value MUST be a DNS
* label and MUST be equal to the `kind` fields that are produced by the
* `.versions.[] | .entityClass` fields.
*/
kind: string; kind: string;
}; };
} }
@ -114,6 +144,7 @@ export abstract class CatalogCategory extends (EventEmitter as new () => TypedEm
export interface CatalogEntityMetadata { export interface CatalogEntityMetadata {
uid: string; uid: string;
name: string; name: string;
shortName?: string;
description?: string; description?: string;
source?: string; source?: string;
labels: Record<string, string>; labels: Record<string, string>;
@ -211,7 +242,14 @@ export abstract class CatalogEntity<
Status extends CatalogEntityStatus = CatalogEntityStatus, Status extends CatalogEntityStatus = CatalogEntityStatus,
Spec extends CatalogEntitySpec = CatalogEntitySpec, Spec extends CatalogEntitySpec = CatalogEntitySpec,
> implements CatalogEntityKindData { > implements CatalogEntityKindData {
/**
* The group and version of this class.
*/
public abstract readonly apiVersion: string; public abstract readonly apiVersion: string;
/**
* A DNS label name of the entity.
*/
public abstract readonly kind: string; public abstract readonly kind: string;
@observable metadata: Metadata; @observable metadata: Metadata;
@ -225,14 +263,35 @@ export abstract class CatalogEntity<
this.spec = data.spec; this.spec = data.spec;
} }
/**
* Get the UID of this entity
*/
public getId(): string { public getId(): string {
return this.metadata.uid; return this.metadata.uid;
} }
/**
* Get the name of this entity
*/
public getName(): string { public getName(): string {
return this.metadata.name; return this.metadata.name;
} }
/**
* Get the specified source of this entity, defaulting to `"unknown"` if not
* provided
*/
public getSource(): string {
return this.metadata.source ?? "unknown";
}
/**
* Get if this entity is enabled.
*/
public isEnabled(): boolean {
return this.status.enabled ?? true;
}
public abstract onRun?(context: CatalogEntityActionContext): void | Promise<void>; public abstract onRun?(context: CatalogEntityActionContext): void | Promise<void>;
public abstract onContextMenuOpen(context: CatalogEntityContextMenuContext): void | Promise<void>; public abstract onContextMenuOpen(context: CatalogEntityContextMenuContext): void | Promise<void>;
public abstract onSettingsOpen(context: CatalogEntitySettingsContext): void | Promise<void>; public abstract onSettingsOpen(context: CatalogEntitySettingsContext): void | Promise<void>;

View File

@ -27,14 +27,15 @@ import type { CatalogCategory, CatalogEntity } from "../../../common/catalog";
import { Icon } from "../icon"; import { Icon } from "../icon";
import { CatalogEntityDrawerMenu } from "./catalog-entity-drawer-menu"; import { CatalogEntityDrawerMenu } from "./catalog-entity-drawer-menu";
import { CatalogEntityDetailRegistry } from "../../../extensions/registries"; import { CatalogEntityDetailRegistry } from "../../../extensions/registries";
import type { CatalogEntityItem } from "./catalog-entity-item";
import { isDevelopment } from "../../../common/vars"; import { isDevelopment } from "../../../common/vars";
import { cssNames } from "../../utils"; import { cssNames } from "../../utils";
import { Avatar } from "../avatar"; import { Avatar } from "../avatar";
import { getLabelBadges } from "./helpers";
interface Props<T extends CatalogEntity> { interface Props<T extends CatalogEntity> {
item: CatalogEntityItem<T> | null | undefined; entity: T;
hideDetails(): void; hideDetails(): void;
onRun: () => void;
} }
@observer @observer
@ -47,32 +48,30 @@ export class CatalogEntityDetails<T extends CatalogEntity> extends Component<Pro
} }
} }
renderContent(item: CatalogEntityItem<T>) { renderContent(entity: T) {
const detailItems = CatalogEntityDetailRegistry.getInstance().getItemsForKind(item.kind, item.apiVersion); const { onRun, hideDetails } = this.props;
const details = detailItems.map(({ components }, index) => { const detailItems = CatalogEntityDetailRegistry.getInstance().getItemsForKind(entity.kind, entity.apiVersion);
return <components.Details entity={item.entity} key={index}/>; const details = detailItems.map(({ components }, index) => <components.Details entity={entity} key={index} />);
}); const showDefaultDetails = detailItems.find((item) => item.priority > 999) === undefined;
const showDetails = detailItems.find((item) => item.priority > 999) === undefined;
return ( return (
<> <>
{showDetails && ( {showDefaultDetails && (
<div className="flex"> <div className="flex">
<div className={styles.entityIcon}> <div className={styles.entityIcon}>
<Avatar <Avatar
title={item.name} title={entity.getName()}
colorHash={`${item.name}-${item.source}`} colorHash={`${entity.getName()}-${entity.getSource()}`}
size={128} size={128}
src={item.entity.spec.icon?.src} src={entity.spec.icon?.src}
data-testid="detail-panel-hot-bar-icon" data-testid="detail-panel-hot-bar-icon"
background={item.entity.spec.icon?.background} background={entity.spec.icon?.background}
onClick={() => item.onRun()} onClick={onRun}
className={styles.avatar} className={styles.avatar}
> >
{item.entity.spec.icon?.material && <Icon material={item.entity.spec.icon?.material}/>} {entity.spec.icon?.material && <Icon material={entity.spec.icon?.material}/>}
</Avatar> </Avatar>
{item?.enabled && ( {entity.isEnabled() && (
<div className={styles.hint}> <div className={styles.hint}>
Click to open Click to open
</div> </div>
@ -80,23 +79,23 @@ export class CatalogEntityDetails<T extends CatalogEntity> extends Component<Pro
</div> </div>
<div className={cssNames("box grow", styles.metadata)}> <div className={cssNames("box grow", styles.metadata)}>
<DrawerItem name="Name"> <DrawerItem name="Name">
{item.name} {entity.getName()}
</DrawerItem> </DrawerItem>
<DrawerItem name="Kind"> <DrawerItem name="Kind">
{item.kind} {entity.kind}
</DrawerItem> </DrawerItem>
<DrawerItem name="Source"> <DrawerItem name="Source">
{item.source} {entity.getSource()}
</DrawerItem> </DrawerItem>
<DrawerItem name="Status"> <DrawerItem name="Status">
{item.phase} {entity.status.phase}
</DrawerItem> </DrawerItem>
<DrawerItem name="Labels"> <DrawerItem name="Labels">
{...item.getLabelBadges(this.props.hideDetails)} {getLabelBadges(entity, hideDetails)}
</DrawerItem> </DrawerItem>
{isDevelopment && ( {isDevelopment && (
<DrawerItem name="Id"> <DrawerItem name="Id">
{item.getId()} {entity.getId()}
</DrawerItem> </DrawerItem>
)} )}
</div> </div>
@ -110,19 +109,18 @@ export class CatalogEntityDetails<T extends CatalogEntity> extends Component<Pro
} }
render() { render() {
const { item, hideDetails } = this.props; const { entity, hideDetails } = this.props;
const title = `${item.kind}: ${item.name}`;
return ( return (
<Drawer <Drawer
className={styles.entityDetails} className={styles.entityDetails}
usePortal={true} usePortal={true}
open={true} open={true}
title={title} title={`${entity.kind}: ${entity.getName()}`}
toolbar={<CatalogEntityDrawerMenu item={item} key={item.getId()} />} toolbar={<CatalogEntityDrawerMenu entity={entity} key={entity.getId()} />}
onClose={hideDetails} onClose={hideDetails}
> >
{item && this.renderContent(item)} {this.renderContent(entity)}
</Drawer> </Drawer>
); );
} }

View File

@ -29,11 +29,10 @@ import { navigate } from "../../navigation";
import { MenuItem } from "../menu"; import { MenuItem } from "../menu";
import { ConfirmDialog } from "../confirm-dialog"; import { ConfirmDialog } from "../confirm-dialog";
import { Icon } from "../icon"; import { Icon } from "../icon";
import type { CatalogEntityItem } from "./catalog-entity-item";
import { HotbarToggleMenuItem } from "./hotbar-toggle-menu-item"; import { HotbarToggleMenuItem } from "./hotbar-toggle-menu-item";
export interface CatalogEntityDrawerMenuProps<T extends CatalogEntity> extends MenuActionsProps { export interface CatalogEntityDrawerMenuProps<T extends CatalogEntity> extends MenuActionsProps {
item: CatalogEntityItem<T> | null | undefined; entity: T;
} }
@observer @observer
@ -50,7 +49,7 @@ export class CatalogEntityDrawerMenu<T extends CatalogEntity> extends React.Comp
menuItems: [], menuItems: [],
navigate: (url: string) => navigate(url), navigate: (url: string) => navigate(url),
}; };
this.props.item?.onContextMenuOpen(this.contextMenu); this.props.entity?.onContextMenuOpen(this.contextMenu);
} }
onMenuItemClick(menuItem: CatalogEntityContextMenu) { onMenuItemClick(menuItem: CatalogEntityContextMenu) {
@ -108,9 +107,9 @@ export class CatalogEntityDrawerMenu<T extends CatalogEntity> extends React.Comp
} }
render() { render() {
const { className, item: entity, ...menuProps } = this.props; const { className, entity, ...menuProps } = this.props;
if (!this.contextMenu || !entity.enabled) { if (!this.contextMenu || !entity.isEnabled()) {
return null; return null;
} }
@ -120,7 +119,7 @@ export class CatalogEntityDrawerMenu<T extends CatalogEntity> extends React.Comp
toolbar toolbar
{...menuProps} {...menuProps}
> >
{this.getMenuItems(entity.entity)} {this.getMenuItems(entity)}
</MenuActions> </MenuActions>
); );
} }

View File

@ -25,9 +25,8 @@ import type { CatalogEntity } from "../../api/catalog-entity";
import { ItemStore } from "../../../common/item.store"; import { ItemStore } from "../../../common/item.store";
import { CatalogCategory, catalogCategoryRegistry } from "../../../common/catalog"; import { CatalogCategory, catalogCategoryRegistry } from "../../../common/catalog";
import { autoBind, disposer } from "../../../common/utils"; import { autoBind, disposer } from "../../../common/utils";
import { CatalogEntityItem } from "./catalog-entity-item";
export class CatalogEntityStore extends ItemStore<CatalogEntityItem<CatalogEntity>> { export class CatalogEntityStore extends ItemStore<CatalogEntity> {
constructor(private registry: CatalogEntityRegistry = catalogEntityRegistry) { constructor(private registry: CatalogEntityRegistry = catalogEntityRegistry) {
super(); super();
makeObservable(this); makeObservable(this);
@ -39,10 +38,10 @@ export class CatalogEntityStore extends ItemStore<CatalogEntityItem<CatalogEntit
@computed get entities() { @computed get entities() {
if (!this.activeCategory) { if (!this.activeCategory) {
return this.registry.filteredItems.map(entity => new CatalogEntityItem(entity, this.registry)); return this.registry.filteredItems;
} }
return this.registry.getItemsForCategory(this.activeCategory, { filtered: true }).map(entity => new CatalogEntityItem(entity, this.registry)); return this.registry.getItemsForCategory(this.activeCategory, { filtered: true });
} }
@computed get selectedItem() { @computed get selectedItem() {
@ -68,4 +67,8 @@ export class CatalogEntityStore extends ItemStore<CatalogEntityItem<CatalogEntit
// concurrency is true to fix bug if catalog filter is removed and added at the same time // concurrency is true to fix bug if catalog filter is removed and added at the same time
return this.loadItems(() => this.entities, undefined, true); return this.loadItems(() => this.entities, undefined, true);
} }
onRun(entity: CatalogEntity): void {
this.registry.onRun(entity);
}
} }

View File

@ -29,7 +29,6 @@ import { kubernetesClusterCategory } from "../../../common/catalog-entities/kube
import { catalogCategoryRegistry, CatalogCategoryRegistry, CatalogEntity, CatalogEntityActionContext, CatalogEntityData } from "../../../common/catalog"; import { catalogCategoryRegistry, CatalogCategoryRegistry, CatalogEntity, CatalogEntityActionContext, CatalogEntityData } from "../../../common/catalog";
import { CatalogEntityRegistry } from "../../../renderer/api/catalog-entity-registry"; import { CatalogEntityRegistry } from "../../../renderer/api/catalog-entity-registry";
import { CatalogEntityDetailRegistry } from "../../../extensions/registries"; import { CatalogEntityDetailRegistry } from "../../../extensions/registries";
import { CatalogEntityItem } from "./catalog-entity-item";
import { CatalogEntityStore } from "./catalog-entity.store"; import { CatalogEntityStore } from "./catalog-entity.store";
import { AppPaths } from "../../../common/app-paths"; import { AppPaths } from "../../../common/app-paths";
@ -130,7 +129,7 @@ describe("<Catalog />", () => {
const catalogEntityRegistry = new CatalogEntityRegistry(catalogCategoryRegistry); const catalogEntityRegistry = new CatalogEntityRegistry(catalogCategoryRegistry);
const catalogEntityStore = new CatalogEntityStore(catalogEntityRegistry); const catalogEntityStore = new CatalogEntityStore(catalogEntityRegistry);
const onRun = jest.fn(); const onRun = jest.fn();
const catalogEntityItem = new CatalogEntityItem(createMockCatalogEntity(onRun), catalogEntityRegistry); const catalogEntityItem = createMockCatalogEntity(onRun);
// mock as if there is a selected item > the detail panel opens // mock as if there is a selected item > the detail panel opens
jest jest
@ -166,7 +165,7 @@ describe("<Catalog />", () => {
const catalogEntityRegistry = new CatalogEntityRegistry(catalogCategoryRegistry); const catalogEntityRegistry = new CatalogEntityRegistry(catalogCategoryRegistry);
const catalogEntityStore = new CatalogEntityStore(catalogEntityRegistry); const catalogEntityStore = new CatalogEntityStore(catalogEntityRegistry);
const onRun = jest.fn(); const onRun = jest.fn();
const catalogEntityItem = new CatalogEntityItem(createMockCatalogEntity(onRun), catalogEntityRegistry); const catalogEntityItem = createMockCatalogEntity(onRun);
// mock as if there is a selected item > the detail panel opens // mock as if there is a selected item > the detail panel opens
jest jest
@ -200,7 +199,7 @@ describe("<Catalog />", () => {
const catalogEntityRegistry = new CatalogEntityRegistry(catalogCategoryRegistry); const catalogEntityRegistry = new CatalogEntityRegistry(catalogCategoryRegistry);
const catalogEntityStore = new CatalogEntityStore(catalogEntityRegistry); const catalogEntityStore = new CatalogEntityStore(catalogEntityRegistry);
const onRun = jest.fn(); const onRun = jest.fn();
const catalogEntityItem = new CatalogEntityItem(createMockCatalogEntity(onRun), catalogEntityRegistry); const catalogEntityItem = createMockCatalogEntity(onRun);
// mock as if there is a selected item > the detail panel opens // mock as if there is a selected item > the detail panel opens
jest jest
@ -235,7 +234,7 @@ describe("<Catalog />", () => {
const catalogEntityRegistry = new CatalogEntityRegistry(catalogCategoryRegistry); const catalogEntityRegistry = new CatalogEntityRegistry(catalogCategoryRegistry);
const catalogEntityStore = new CatalogEntityStore(catalogEntityRegistry); const catalogEntityStore = new CatalogEntityStore(catalogEntityRegistry);
const onRun = jest.fn(() => done()); const onRun = jest.fn(() => done());
const catalogEntityItem = new CatalogEntityItem(createMockCatalogEntity(onRun), catalogEntityRegistry); const catalogEntityItem = createMockCatalogEntity(onRun);
// mock as if there is a selected item > the detail panel opens // mock as if there is a selected item > the detail panel opens
jest jest
@ -265,7 +264,7 @@ describe("<Catalog />", () => {
const catalogEntityRegistry = new CatalogEntityRegistry(catalogCategoryRegistry); const catalogEntityRegistry = new CatalogEntityRegistry(catalogCategoryRegistry);
const catalogEntityStore = new CatalogEntityStore(catalogEntityRegistry); const catalogEntityStore = new CatalogEntityStore(catalogEntityRegistry);
const onRun = jest.fn(); const onRun = jest.fn();
const catalogEntityItem = new CatalogEntityItem(createMockCatalogEntity(onRun), catalogEntityRegistry); const catalogEntityItem = createMockCatalogEntity(onRun);
// mock as if there is a selected item > the detail panel opens // mock as if there is a selected item > the detail panel opens
jest jest
@ -302,7 +301,7 @@ describe("<Catalog />", () => {
const catalogEntityRegistry = new CatalogEntityRegistry(catalogCategoryRegistry); const catalogEntityRegistry = new CatalogEntityRegistry(catalogCategoryRegistry);
const catalogEntityStore = new CatalogEntityStore(catalogEntityRegistry); const catalogEntityStore = new CatalogEntityStore(catalogEntityRegistry);
const onRun = jest.fn(); const onRun = jest.fn();
const catalogEntityItem = new CatalogEntityItem(createMockCatalogEntity(onRun), catalogEntityRegistry); const catalogEntityItem = createMockCatalogEntity(onRun);
// mock as if there is a selected item > the detail panel opens // mock as if there is a selected item > the detail panel opens
jest jest

View File

@ -26,7 +26,6 @@ import { disposeOnUnmount, observer } from "mobx-react";
import { ItemListLayout } from "../item-object-list"; import { ItemListLayout } from "../item-object-list";
import { action, makeObservable, observable, reaction, runInAction, when } from "mobx"; import { action, makeObservable, observable, reaction, runInAction, when } from "mobx";
import { CatalogEntityStore } from "./catalog-entity.store"; import { CatalogEntityStore } from "./catalog-entity.store";
import type { CatalogEntityItem } from "./catalog-entity-item";
import { navigate } from "../../navigation"; import { navigate } from "../../navigation";
import { MenuItem, MenuActions } from "../menu"; import { MenuItem, MenuActions } from "../menu";
import type { CatalogEntityContextMenu, CatalogEntityContextMenuContext } from "../../api/catalog-entity"; import type { CatalogEntityContextMenu, CatalogEntityContextMenuContext } from "../../api/catalog-entity";
@ -45,6 +44,8 @@ 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";
export const previousActiveTab = createStorage("catalog-previous-active-tab", browseCatalogTab); export const previousActiveTab = createStorage("catalog-previous-active-tab", browseCatalogTab);
@ -125,19 +126,19 @@ export class Catalog extends React.Component<Props> {
})); }));
} }
addToHotbar(item: CatalogEntityItem<CatalogEntity>): void { addToHotbar(entity: CatalogEntity): void {
HotbarStore.getInstance().addToHotbar(item.entity); HotbarStore.getInstance().addToHotbar(entity);
} }
removeFromHotbar(item: CatalogEntityItem<CatalogEntity>): void { removeFromHotbar(entity: CatalogEntity): void {
HotbarStore.getInstance().removeFromHotbar(item.getId()); HotbarStore.getInstance().removeFromHotbar(entity.getId());
} }
onDetails = (item: CatalogEntityItem<CatalogEntity>) => { onDetails = (entity: CatalogEntity) => {
if (this.catalogEntityStore.selectedItemId) { if (this.catalogEntityStore.selectedItemId) {
this.catalogEntityStore.selectedItemId = null; this.catalogEntityStore.selectedItemId = null;
} else { } else {
item.onRun(); this.catalogEntityStore.onRun(entity);
} }
}; };
@ -179,16 +180,16 @@ export class Catalog extends React.Component<Props> {
); );
} }
renderItemMenu = (item: CatalogEntityItem<CatalogEntity>) => { renderItemMenu = (entity: CatalogEntity) => {
const onOpen = () => { const onOpen = () => {
this.contextMenu.menuItems = []; this.contextMenu.menuItems = [];
item.onContextMenuOpen(this.contextMenu); entity.onContextMenuOpen(this.contextMenu);
}; };
return ( return (
<MenuActions onOpen={onOpen}> <MenuActions onOpen={onOpen}>
<MenuItem key="open-details" onClick={() => this.catalogEntityStore.selectedItemId = item.getId()}> <MenuItem key="open-details" onClick={() => this.catalogEntityStore.selectedItemId = entity.getId()}>
View Details View Details
</MenuItem> </MenuItem>
{ {
@ -200,7 +201,7 @@ export class Catalog extends React.Component<Props> {
} }
<HotbarToggleMenuItem <HotbarToggleMenuItem
key="hotbar-toggle" key="hotbar-toggle"
entity={item.entity} entity={entity}
addContent="Add to Hotbar" addContent="Add to Hotbar"
removeContent="Remove from Hotbar" removeContent="Remove from Hotbar"
/> />
@ -208,29 +209,29 @@ export class Catalog extends React.Component<Props> {
); );
}; };
renderName(item: CatalogEntityItem<CatalogEntity>) { renderName(entity: CatalogEntity) {
const isItemInHotbar = HotbarStore.getInstance().isAddedToActive(item.entity); const isItemInHotbar = HotbarStore.getInstance().isAddedToActive(entity);
return ( return (
<> <>
<Avatar <Avatar
title={item.getName()} title={entity.getName()}
colorHash={`${item.getName()}-${item.source}`} colorHash={`${entity.getName()}-${entity.getSource()}`}
src={item.entity.spec.icon?.src} src={entity.spec.icon?.src}
background={item.entity.spec.icon?.background} background={entity.spec.icon?.background}
className={styles.catalogAvatar} className={styles.catalogAvatar}
size={24} size={24}
> >
{item.entity.spec.icon?.material && <Icon material={item.entity.spec.icon?.material} small/>} {entity.spec.icon?.material && <Icon material={entity.spec.icon?.material} small/>}
</Avatar> </Avatar>
<span>{item.name}</span> <span>{entity.getName()}</span>
<Icon <Icon
small small
className={styles.pinIcon} className={styles.pinIcon}
material={!isItemInHotbar && "push_pin"} material={!isItemInHotbar && "push_pin"}
svg={isItemInHotbar ? "push_off" : "push_pin"} svg={isItemInHotbar ? "push_off" : "push_pin"}
tooltip={isItemInHotbar ? "Remove from Hotbar" : "Add to Hotbar"} tooltip={isItemInHotbar ? "Remove from Hotbar" : "Add to Hotbar"}
onClick={prevDefault(() => isItemInHotbar ? this.removeFromHotbar(item) : this.addToHotbar(item))} onClick={prevDefault(() => isItemInHotbar ? this.removeFromHotbar(entity) : this.addToHotbar(entity))}
/> />
</> </>
); );
@ -253,13 +254,19 @@ export class Catalog extends React.Component<Props> {
isConfigurable={true} isConfigurable={true}
store={this.catalogEntityStore} store={this.catalogEntityStore}
sortingCallbacks={{ sortingCallbacks={{
[sortBy.name]: item => item.name, [sortBy.name]: entity => entity.getName(),
[sortBy.source]: item => item.source, [sortBy.source]: entity => entity.getSource(),
[sortBy.status]: item => item.phase, [sortBy.status]: entity => entity.status.phase,
[sortBy.kind]: item => item.kind, [sortBy.kind]: entity => entity.kind,
}} }}
searchFilters={[ searchFilters={[
entity => entity.searchFields, entity => [
entity.getName(),
entity.getId(),
entity.status.phase,
`source=${entity.getSource()}`,
...KubeObject.stringifyLabels(entity.metadata.labels),
],
]} ]}
renderTableHeader={[ renderTableHeader={[
{ title: "Name", className: styles.entityName, sortBy: sortBy.name, id: "name" }, { title: "Name", className: styles.entityName, sortBy: sortBy.name, id: "name" },
@ -268,15 +275,15 @@ export class Catalog extends React.Component<Props> {
{ title: "Labels", className: `${styles.labelsCell} scrollable`, id: "labels" }, { title: "Labels", className: `${styles.labelsCell} scrollable`, id: "labels" },
{ title: "Status", className: styles.statusCell, sortBy: sortBy.status, id: "status" }, { title: "Status", className: styles.statusCell, sortBy: sortBy.status, id: "status" },
].filter(Boolean)} ].filter(Boolean)}
customizeTableRowProps={item => ({ customizeTableRowProps={entity => ({
disabled: !item.enabled, disabled: !entity.isEnabled(),
})} })}
renderTableContents={item => [ renderTableContents={entity => [
this.renderName(item), this.renderName(entity),
!activeCategory && item.kind, !activeCategory && entity.kind,
item.source, entity.getSource(),
item.getLabelBadges(), getLabelBadges(entity),
<span key="phase" className={item.phase}>{item.phase}</span>, <span key="phase" className={entity.status.phase}>{entity.status.phase}</span>,
].filter(Boolean)} ].filter(Boolean)}
onDetails={this.onDetails} onDetails={this.onDetails}
renderItemMenu={this.renderItemMenu} renderItemMenu={this.renderItemMenu}
@ -289,16 +296,19 @@ export class Catalog extends React.Component<Props> {
return null; return null;
} }
const selectedEntity = this.catalogEntityStore.selectedItem;
return ( return (
<MainLayout sidebar={this.renderNavigation()}> <MainLayout sidebar={this.renderNavigation()}>
<div className="p-6 h-full"> <div className="p-6 h-full">
{this.renderList()} {this.renderList()}
</div> </div>
{ {
this.catalogEntityStore.selectedItem selectedEntity
? <CatalogEntityDetails ? <CatalogEntityDetails
item={this.catalogEntityStore.selectedItem} entity={selectedEntity}
hideDetails={() => this.catalogEntityStore.selectedItemId = null} hideDetails={() => this.catalogEntityStore.selectedItemId = null}
onRun={() => this.catalogEntityStore.onRun(selectedEntity)}
/> />
: ( : (
<RenderDelay> <RenderDelay>

View File

@ -0,0 +1,49 @@
/**
* 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 { KubeObject } from "../../../common/k8s-api/kube-object";
import type { CatalogEntity } from "../../api/catalog-entity";
import { Badge } from "../badge";
import { searchUrlParam } from "../input";
/**
* @param entity The entity to render badge labels for
*/
export function getLabelBadges(entity: CatalogEntity, onClick?: (evt: React.MouseEvent<any, MouseEvent>) => void) {
return KubeObject.stringifyLabels(entity.metadata.labels)
.map(label => (
<Badge
scrollable
className={styles.badge}
key={label}
label={label}
title={label}
onClick={(event) => {
searchUrlParam.set(label);
onClick?.(event);
event.stopPropagation();
}}
expandable={false}
/>
));
}