mirror of
https://github.com/lensapp/lens.git
synced 2025-05-20 05:10:56 +00:00
Allow CatalogEntityDetails to be opened anywhere (#6939)
* Extract CatalogEntityDetails to seperate root component Signed-off-by: Sebastian Malton <sebastian@malton.name> * Expose entity details to extension API Signed-off-by: Sebastian Malton <sebastian@malton.name> * Add behavioural tests Signed-off-by: Sebastian Malton <sebastian@malton.name> * Fixup catalog technical tests to use ApplicationBuilder Signed-off-by: Sebastian Malton <sebastian@malton.name> * Update snapshot Signed-off-by: Sebastian Malton <sebastian@malton.name> * Update test and then fix it Signed-off-by: Sebastian Malton <sebastian@malton.name> --------- Signed-off-by: Sebastian Malton <sebastian@malton.name>
This commit is contained in:
parent
f88555a1d7
commit
0084af56d6
@ -34,6 +34,10 @@ export class CatalogCategoryRegistry {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
getById(id: string) {
|
||||||
|
return iter.find(this.categories.values(), (category) => category.getId() === id);
|
||||||
|
}
|
||||||
|
|
||||||
@computed get items() {
|
@computed get items() {
|
||||||
return Array.from(this.categories);
|
return Array.from(this.categories);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -3,6 +3,8 @@
|
|||||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
import hideEntityDetailsInjectable from "../../renderer/components/+catalog/entity-details/hide.injectable";
|
||||||
|
import showEntityDetailsInjectable from "../../renderer/components/+catalog/entity-details/show.injectable";
|
||||||
import getDetailsUrlInjectable from "../../renderer/components/kube-detail-params/get-details-url.injectable";
|
import getDetailsUrlInjectable from "../../renderer/components/kube-detail-params/get-details-url.injectable";
|
||||||
import hideDetailsInjectable from "../../renderer/components/kube-detail-params/hide-details.injectable";
|
import hideDetailsInjectable from "../../renderer/components/kube-detail-params/hide-details.injectable";
|
||||||
import showDetailsInjectable from "../../renderer/components/kube-detail-params/show-details.injectable";
|
import showDetailsInjectable from "../../renderer/components/kube-detail-params/show-details.injectable";
|
||||||
@ -20,3 +22,6 @@ export const hideDetails = asLegacyGlobalFunctionForExtensionApi(hideDetailsInje
|
|||||||
export const createPageParam = asLegacyGlobalFunctionForExtensionApi(createPageParamInjectable);
|
export const createPageParam = asLegacyGlobalFunctionForExtensionApi(createPageParamInjectable);
|
||||||
export const isActiveRoute = asLegacyGlobalFunctionForExtensionApi(isActiveRouteInjectable);
|
export const isActiveRoute = asLegacyGlobalFunctionForExtensionApi(isActiveRouteInjectable);
|
||||||
export const navigate = asLegacyGlobalFunctionForExtensionApi(navigateInjectable);
|
export const navigate = asLegacyGlobalFunctionForExtensionApi(navigateInjectable);
|
||||||
|
|
||||||
|
export const showEntityDetails = asLegacyGlobalFunctionForExtensionApi(showEntityDetailsInjectable);
|
||||||
|
export const hideEntityDetails = asLegacyGlobalFunctionForExtensionApi(hideEntityDetailsInjectable);
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@ -1925,10 +1925,10 @@ exports[`opening catalog entity details panel when navigated to the catalog when
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<ul
|
<ul
|
||||||
class="Animate opacity Menu MenuActions flex right bottom portal enter"
|
class="Animate opacity Menu MenuActions flex bottom right portal enter"
|
||||||
data-testid="menu-actions-for-catalog-for-some-entity-id"
|
data-testid="menu-actions-for-catalog-for-some-entity-id"
|
||||||
id="menu-actions-for-catalog-for-some-entity-id"
|
id="menu-actions-for-catalog-for-some-entity-id"
|
||||||
style="--enter-duration: 100ms; --leave-duration: 100ms;"
|
style="--enter-duration: 100ms; --leave-duration: 100ms; left: 0px; top: 8px;"
|
||||||
>
|
>
|
||||||
<li
|
<li
|
||||||
class="MenuItem"
|
class="MenuItem"
|
||||||
@ -2778,10 +2778,10 @@ exports[`opening catalog entity details panel when navigated to the catalog when
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<ul
|
<ul
|
||||||
class="Animate opacity Menu MenuActions flex right bottom portal enter"
|
class="Animate opacity Menu MenuActions flex bottom right portal enter"
|
||||||
data-testid="menu-actions-for-catalog-for-some-entity-id"
|
data-testid="menu-actions-for-catalog-for-some-entity-id"
|
||||||
id="menu-actions-for-catalog-for-some-entity-id"
|
id="menu-actions-for-catalog-for-some-entity-id"
|
||||||
style="--enter-duration: 100ms; --leave-duration: 100ms;"
|
style="--enter-duration: 100ms; --leave-duration: 100ms; left: 0px; top: 8px;"
|
||||||
>
|
>
|
||||||
<li
|
<li
|
||||||
class="MenuItem"
|
class="MenuItem"
|
||||||
@ -6609,3 +6609,459 @@ exports[`opening catalog entity details panel when navigated to the catalog when
|
|||||||
</div>
|
</div>
|
||||||
</body>
|
</body>
|
||||||
`;
|
`;
|
||||||
|
|
||||||
|
exports[`opening catalog entity details panel when not navigated to the catalog and showEntityDetails is called from someplace renders 1`] = `
|
||||||
|
<body>
|
||||||
|
<div>
|
||||||
|
<div
|
||||||
|
class="ClusterManager"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="topBar"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="items"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="preventedDragging"
|
||||||
|
>
|
||||||
|
<i
|
||||||
|
class="Icon material interactive disabled focusable"
|
||||||
|
data-testid="home-button"
|
||||||
|
>
|
||||||
|
<span
|
||||||
|
class="icon"
|
||||||
|
data-icon-name="home"
|
||||||
|
>
|
||||||
|
home
|
||||||
|
</span>
|
||||||
|
</i>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
class="size-sm"
|
||||||
|
/>
|
||||||
|
<div
|
||||||
|
class="preventedDragging"
|
||||||
|
>
|
||||||
|
<i
|
||||||
|
class="Icon material interactive disabled focusable"
|
||||||
|
data-testid="history-back"
|
||||||
|
>
|
||||||
|
<span
|
||||||
|
class="icon"
|
||||||
|
data-icon-name="arrow_back"
|
||||||
|
>
|
||||||
|
arrow_back
|
||||||
|
</span>
|
||||||
|
</i>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
class="size-sm"
|
||||||
|
/>
|
||||||
|
<div
|
||||||
|
class="preventedDragging"
|
||||||
|
>
|
||||||
|
<i
|
||||||
|
class="Icon material interactive disabled focusable"
|
||||||
|
data-testid="history-forward"
|
||||||
|
>
|
||||||
|
<span
|
||||||
|
class="icon"
|
||||||
|
data-icon-name="arrow_forward"
|
||||||
|
>
|
||||||
|
arrow_forward
|
||||||
|
</span>
|
||||||
|
</i>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
class="separator"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<main>
|
||||||
|
<div
|
||||||
|
id="lens-views"
|
||||||
|
/>
|
||||||
|
<div
|
||||||
|
class="flex justify-center Welcome align-center"
|
||||||
|
data-testid="welcome-page"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
data-testid="welcome-banner-container"
|
||||||
|
style="width: 320px;"
|
||||||
|
>
|
||||||
|
<i
|
||||||
|
class="Icon logo svg focusable"
|
||||||
|
>
|
||||||
|
<span
|
||||||
|
class="icon"
|
||||||
|
/>
|
||||||
|
</i>
|
||||||
|
<div
|
||||||
|
class="flex justify-center"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
data-testid="welcome-text-container"
|
||||||
|
style="width: 320px;"
|
||||||
|
>
|
||||||
|
<h2>
|
||||||
|
Welcome to some-product-name!
|
||||||
|
</h2>
|
||||||
|
<p>
|
||||||
|
To get you started we have auto-detected your clusters in your
|
||||||
|
|
||||||
|
kubeconfig file and added them to the catalog, your centralized
|
||||||
|
|
||||||
|
view for managing all your cloud-native resources.
|
||||||
|
<br />
|
||||||
|
<br />
|
||||||
|
If you have any questions or feedback, please join our
|
||||||
|
<a
|
||||||
|
class="link"
|
||||||
|
href="https://forums.k8slens.dev"
|
||||||
|
rel="noreferrer"
|
||||||
|
target="_blank"
|
||||||
|
>
|
||||||
|
Lens Forums
|
||||||
|
</a>
|
||||||
|
.
|
||||||
|
</p>
|
||||||
|
<ul
|
||||||
|
class="block"
|
||||||
|
data-testid="welcome-menu-container"
|
||||||
|
style="width: 320px;"
|
||||||
|
>
|
||||||
|
<li
|
||||||
|
class="flex grid-12"
|
||||||
|
>
|
||||||
|
<i
|
||||||
|
class="Icon box col-1 material focusable"
|
||||||
|
>
|
||||||
|
<span
|
||||||
|
class="icon"
|
||||||
|
data-icon-name="view_list"
|
||||||
|
>
|
||||||
|
view_list
|
||||||
|
</span>
|
||||||
|
</i>
|
||||||
|
<a
|
||||||
|
class="box col-10"
|
||||||
|
>
|
||||||
|
Browse Clusters in Catalog
|
||||||
|
</a>
|
||||||
|
<i
|
||||||
|
class="Icon box col-1 material focusable"
|
||||||
|
>
|
||||||
|
<span
|
||||||
|
class="icon"
|
||||||
|
data-icon-name="navigate_next"
|
||||||
|
>
|
||||||
|
navigate_next
|
||||||
|
</span>
|
||||||
|
</i>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</main>
|
||||||
|
<div
|
||||||
|
class="HotbarMenu flex column"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="HotbarItems flex column gaps"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="HotbarCell isDraggingOwner animateDown"
|
||||||
|
index="0"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
style="z-index: 12; position: absolute;"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="HotbarIcon contextMenuAvailable"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="Avatar rounded disabled avatar"
|
||||||
|
id="hotbarIcon-hotbar-icon-catalog-entity"
|
||||||
|
style="width: 40px; height: 40px; background: rgb(5, 1, 130);"
|
||||||
|
>
|
||||||
|
Ca
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
class="HotbarCell isDraggingOwner animateDown"
|
||||||
|
index="1"
|
||||||
|
/>
|
||||||
|
<div
|
||||||
|
class="HotbarCell isDraggingOwner animateDown"
|
||||||
|
index="2"
|
||||||
|
/>
|
||||||
|
<div
|
||||||
|
class="HotbarCell isDraggingOwner animateDown"
|
||||||
|
index="3"
|
||||||
|
/>
|
||||||
|
<div
|
||||||
|
class="HotbarCell isDraggingOwner animateDown"
|
||||||
|
index="4"
|
||||||
|
/>
|
||||||
|
<div
|
||||||
|
class="HotbarCell isDraggingOwner animateDown"
|
||||||
|
index="5"
|
||||||
|
/>
|
||||||
|
<div
|
||||||
|
class="HotbarCell isDraggingOwner animateDown"
|
||||||
|
index="6"
|
||||||
|
/>
|
||||||
|
<div
|
||||||
|
class="HotbarCell isDraggingOwner animateDown"
|
||||||
|
index="7"
|
||||||
|
/>
|
||||||
|
<div
|
||||||
|
class="HotbarCell isDraggingOwner animateDown"
|
||||||
|
index="8"
|
||||||
|
/>
|
||||||
|
<div
|
||||||
|
class="HotbarCell isDraggingOwner animateDown"
|
||||||
|
index="9"
|
||||||
|
/>
|
||||||
|
<div
|
||||||
|
class="HotbarCell isDraggingOwner animateDown"
|
||||||
|
index="10"
|
||||||
|
/>
|
||||||
|
<div
|
||||||
|
class="HotbarCell isDraggingOwner animateDown"
|
||||||
|
index="11"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
class="HotbarSelector"
|
||||||
|
>
|
||||||
|
<i
|
||||||
|
class="Icon Icon previous material interactive focusable"
|
||||||
|
tabindex="0"
|
||||||
|
>
|
||||||
|
<span
|
||||||
|
class="icon"
|
||||||
|
data-icon-name="arrow_left"
|
||||||
|
>
|
||||||
|
arrow_left
|
||||||
|
</span>
|
||||||
|
</i>
|
||||||
|
<div
|
||||||
|
class="HotbarIndex"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="badge Badge small clickable"
|
||||||
|
id="hotbarIndex"
|
||||||
|
>
|
||||||
|
1
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<i
|
||||||
|
class="Icon material interactive focusable"
|
||||||
|
tabindex="0"
|
||||||
|
>
|
||||||
|
<span
|
||||||
|
class="icon"
|
||||||
|
data-icon-name="arrow_right"
|
||||||
|
>
|
||||||
|
arrow_right
|
||||||
|
</span>
|
||||||
|
</i>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
class="StatusBar"
|
||||||
|
data-testid="status-bar"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="leftSide"
|
||||||
|
data-testid="status-bar-left"
|
||||||
|
/>
|
||||||
|
<div
|
||||||
|
class="rightSide"
|
||||||
|
data-testid="status-bar-right"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
class="Notifications flex column align-flex-end"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
class="Animate slide-right Drawer entityDetails right enter"
|
||||||
|
data-testid="catalog-entity-details-drawer"
|
||||||
|
style="--size: 725px; --enter-duration: 100ms; --leave-duration: 100ms;"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="drawer-wrapper flex column"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="drawer-title flex align-center"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="drawer-title-text flex gaps align-center"
|
||||||
|
>
|
||||||
|
WebLink: some-weblink
|
||||||
|
<i
|
||||||
|
class="Icon material interactive focusable"
|
||||||
|
tabindex="0"
|
||||||
|
>
|
||||||
|
<span
|
||||||
|
class="icon"
|
||||||
|
data-icon-name="content_copy"
|
||||||
|
>
|
||||||
|
content_copy
|
||||||
|
</span>
|
||||||
|
</i>
|
||||||
|
<div>
|
||||||
|
Copy
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<i
|
||||||
|
class="Icon material interactive focusable"
|
||||||
|
tabindex="0"
|
||||||
|
>
|
||||||
|
<span
|
||||||
|
class="icon"
|
||||||
|
data-icon-name="close"
|
||||||
|
>
|
||||||
|
close
|
||||||
|
</span>
|
||||||
|
</i>
|
||||||
|
<div>
|
||||||
|
Close
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
class="drawer-content flex column box grow"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="flex"
|
||||||
|
data-testid="catalog-entity-details-content-for-some-weblink-id"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="entityIcon"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="Avatar rounded avatar"
|
||||||
|
data-testid="detail-panel-hot-bar-icon"
|
||||||
|
style="width: 128px; height: 128px; background: rgb(77, 163, 16);"
|
||||||
|
>
|
||||||
|
sw
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
class="hint"
|
||||||
|
>
|
||||||
|
Click to open
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
class="box grow metadata"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="DrawerItem"
|
||||||
|
>
|
||||||
|
<span
|
||||||
|
class="name"
|
||||||
|
>
|
||||||
|
Name
|
||||||
|
</span>
|
||||||
|
<span
|
||||||
|
class="value"
|
||||||
|
>
|
||||||
|
some-weblink
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
class="DrawerItem"
|
||||||
|
>
|
||||||
|
<span
|
||||||
|
class="name"
|
||||||
|
>
|
||||||
|
Kind
|
||||||
|
</span>
|
||||||
|
<span
|
||||||
|
class="value"
|
||||||
|
>
|
||||||
|
WebLink
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
class="DrawerItem"
|
||||||
|
>
|
||||||
|
<span
|
||||||
|
class="name"
|
||||||
|
>
|
||||||
|
Source
|
||||||
|
</span>
|
||||||
|
<span
|
||||||
|
class="value"
|
||||||
|
>
|
||||||
|
unknown
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
class="DrawerItem"
|
||||||
|
>
|
||||||
|
<span
|
||||||
|
class="name"
|
||||||
|
>
|
||||||
|
Status
|
||||||
|
</span>
|
||||||
|
<span
|
||||||
|
class="value"
|
||||||
|
>
|
||||||
|
available
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
class="DrawerItem"
|
||||||
|
>
|
||||||
|
<span
|
||||||
|
class="name"
|
||||||
|
>
|
||||||
|
Labels
|
||||||
|
</span>
|
||||||
|
<span
|
||||||
|
class="value"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
class="box grow"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="DrawerTitle title"
|
||||||
|
>
|
||||||
|
More Information
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
class="DrawerItem"
|
||||||
|
data-testid="weblink-url-for-some-weblink-id"
|
||||||
|
>
|
||||||
|
<span
|
||||||
|
class="name"
|
||||||
|
>
|
||||||
|
URL
|
||||||
|
</span>
|
||||||
|
<span
|
||||||
|
class="value"
|
||||||
|
>
|
||||||
|
https://my-websome.com
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
class="ResizingAnchor horizontal leading"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</body>
|
||||||
|
`;
|
||||||
|
|||||||
234
packages/core/src/features/catalog/entity-running.test.tsx
Normal file
234
packages/core/src/features/catalog/entity-running.test.tsx
Normal file
@ -0,0 +1,234 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||||
|
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import asyncFn, { type AsyncFnMock } from "@async-fn/jest";
|
||||||
|
import type { DiContainer } from "@ogre-tools/injectable";
|
||||||
|
import type { RenderResult } from "@testing-library/react";
|
||||||
|
import appEventBusInjectable from "../../common/app-event-bus/app-event-bus.injectable";
|
||||||
|
import type { AppEvent } from "../../common/app-event-bus/event-bus";
|
||||||
|
import type { CatalogEntityActionContext } from "../../common/catalog";
|
||||||
|
import { CatalogCategory, categoryVersion, CatalogEntity } from "../../common/catalog";
|
||||||
|
import catalogCategoryRegistryInjectable from "../../common/catalog/category-registry.injectable";
|
||||||
|
import navigateToCatalogInjectable from "../../common/front-end-routing/routes/catalog/navigate-to-catalog.injectable";
|
||||||
|
import { flushPromises } from "../../common/test-utils/flush-promises";
|
||||||
|
import { advanceFakeTime, testUsingFakeTime } from "../../common/test-utils/use-fake-time";
|
||||||
|
import type { CatalogEntityOnBeforeRun, CatalogEntityRegistry } from "../../renderer/api/catalog/entity/registry";
|
||||||
|
import catalogEntityRegistryInjectable from "../../renderer/api/catalog/entity/registry.injectable";
|
||||||
|
import type { ApplicationBuilder } from "../../renderer/components/test-utils/get-application-builder";
|
||||||
|
import { getApplicationBuilder } from "../../renderer/components/test-utils/get-application-builder";
|
||||||
|
|
||||||
|
class MockCatalogCategory extends CatalogCategory {
|
||||||
|
apiVersion = "catalog.k8slens.dev/v1alpha1";
|
||||||
|
kind = "CatalogCategory";
|
||||||
|
metadata = {
|
||||||
|
name: "mock",
|
||||||
|
icon: "gear",
|
||||||
|
};
|
||||||
|
spec = {
|
||||||
|
group: "entity.k8slens.dev",
|
||||||
|
versions: [
|
||||||
|
categoryVersion("v1alpha1", (() => {
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-this-alias
|
||||||
|
const self = this;
|
||||||
|
|
||||||
|
return function (data: any) {
|
||||||
|
const entity = new MockCatalogEntity(data);
|
||||||
|
|
||||||
|
entity.onRun = self.onRun;
|
||||||
|
|
||||||
|
return entity;
|
||||||
|
} as any;
|
||||||
|
})()),
|
||||||
|
],
|
||||||
|
names: {
|
||||||
|
kind: "Mock",
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
constructor(private onRun: (context: CatalogEntityActionContext) => void | Promise<void>) {
|
||||||
|
super();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class MockCatalogEntity extends CatalogEntity {
|
||||||
|
public apiVersion = "entity.k8slens.dev/v1alpha1";
|
||||||
|
public kind = "Mock";
|
||||||
|
}
|
||||||
|
|
||||||
|
function createMockCatalogEntity() {
|
||||||
|
return new MockCatalogEntity({
|
||||||
|
metadata: {
|
||||||
|
uid: "a_catalogEntity_uid",
|
||||||
|
name: "a catalog entity",
|
||||||
|
labels: {
|
||||||
|
test: "label",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
status: {
|
||||||
|
phase: "",
|
||||||
|
},
|
||||||
|
spec: {},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
describe("entity running technical tests", () => {
|
||||||
|
let builder: ApplicationBuilder;
|
||||||
|
let windowDi: DiContainer;
|
||||||
|
let rendered: RenderResult;
|
||||||
|
let appEventListener: jest.MockedFunction<(event: AppEvent) => void>;
|
||||||
|
let onRun: jest.MockedFunction<(context: CatalogEntityActionContext) => void | Promise<void>>;
|
||||||
|
let catalogEntityRegistry: CatalogEntityRegistry;
|
||||||
|
|
||||||
|
beforeEach(async () => {
|
||||||
|
builder = getApplicationBuilder();
|
||||||
|
|
||||||
|
builder.afterWindowStart((windowDi) => {
|
||||||
|
onRun = jest.fn();
|
||||||
|
|
||||||
|
const catalogCategoryRegistery = windowDi.inject(catalogCategoryRegistryInjectable);
|
||||||
|
|
||||||
|
catalogCategoryRegistery.add(new MockCatalogCategory(onRun));
|
||||||
|
|
||||||
|
catalogEntityRegistry = windowDi.inject(catalogEntityRegistryInjectable);
|
||||||
|
|
||||||
|
const catalogEntityItem = createMockCatalogEntity();
|
||||||
|
|
||||||
|
catalogEntityRegistry.updateItems([catalogEntityItem]);
|
||||||
|
|
||||||
|
appEventListener = jest.fn();
|
||||||
|
windowDi.inject(appEventBusInjectable).addListener(appEventListener);
|
||||||
|
});
|
||||||
|
|
||||||
|
testUsingFakeTime();
|
||||||
|
rendered = await builder.render();
|
||||||
|
windowDi = builder.applicationWindow.only.di;
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("when navigated to catalog", () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
const navigateToCatalog = windowDi.inject(navigateToCatalogInjectable);
|
||||||
|
|
||||||
|
navigateToCatalog();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("renders", () => {
|
||||||
|
expect(rendered.baseElement).toMatchSnapshot();
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("when details panel is opened", () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
rendered.getByTestId("icon-for-menu-actions-for-catalog-for-a_catalogEntity_uid").click();
|
||||||
|
advanceFakeTime(500);
|
||||||
|
rendered.getByTestId("open-details-menu-item-for-a_catalogEntity_uid").click();
|
||||||
|
advanceFakeTime(500);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("renders", () => {
|
||||||
|
expect(rendered.baseElement).toMatchSnapshot();
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("can use catalogEntityRegistry.addOnBeforeRun to add hooks for catalog entities", () => {
|
||||||
|
let onBeforeRunMock: AsyncFnMock<CatalogEntityOnBeforeRun>;
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
onBeforeRunMock = asyncFn();
|
||||||
|
catalogEntityRegistry.addOnBeforeRun(onBeforeRunMock);
|
||||||
|
rendered.getByTestId("detail-panel-hot-bar-icon").click();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("calls on before run event", () => {
|
||||||
|
const target = onBeforeRunMock.mock.calls[0][0].target;
|
||||||
|
const actual = { id: target.getId(), name: target.getName() };
|
||||||
|
|
||||||
|
expect(actual).toEqual({
|
||||||
|
id: "a_catalogEntity_uid",
|
||||||
|
name: "a catalog entity",
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it("does not call onRun yet", () => {
|
||||||
|
expect(onRun).not.toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("when before run event resolves, calls onRun", async () => {
|
||||||
|
await onBeforeRunMock.resolve();
|
||||||
|
|
||||||
|
expect(onRun).toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it("onBeforeRun prevents event => onRun wont be triggered", async () => {
|
||||||
|
const onBeforeRunMock = jest.fn((event) => event.preventDefault());
|
||||||
|
|
||||||
|
catalogEntityRegistry.addOnBeforeRun(onBeforeRunMock);
|
||||||
|
|
||||||
|
rendered.getByTestId("detail-panel-hot-bar-icon").click();
|
||||||
|
|
||||||
|
await flushPromises();
|
||||||
|
|
||||||
|
expect(onRun).not.toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("addOnBeforeRun throw an exception => onRun will be triggered", async () => {
|
||||||
|
catalogEntityRegistry.addOnBeforeRun(() => {
|
||||||
|
throw new Error("some error");
|
||||||
|
});
|
||||||
|
|
||||||
|
rendered.getByTestId("detail-panel-hot-bar-icon").click();
|
||||||
|
|
||||||
|
await flushPromises();
|
||||||
|
|
||||||
|
expect(onRun).toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("addOnRunHook return a promise and does not prevent run event => onRun()", (done) => {
|
||||||
|
onRun.mockImplementation(() => done());
|
||||||
|
|
||||||
|
catalogEntityRegistry.addOnBeforeRun(async () => {});
|
||||||
|
|
||||||
|
rendered.getByTestId("detail-panel-hot-bar-icon").click();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("addOnRunHook return a promise and prevents event wont be triggered", async () => {
|
||||||
|
catalogEntityRegistry.addOnBeforeRun(async (event) => event.preventDefault());
|
||||||
|
|
||||||
|
rendered.getByTestId("detail-panel-hot-bar-icon").click();
|
||||||
|
|
||||||
|
expect(onRun).not.toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("addOnRunHook return a promise and reject => onRun will be triggered", async () => {
|
||||||
|
const onBeforeRunMock = asyncFn();
|
||||||
|
|
||||||
|
catalogEntityRegistry.addOnBeforeRun(onBeforeRunMock);
|
||||||
|
|
||||||
|
rendered.getByTestId("detail-panel-hot-bar-icon").click();
|
||||||
|
|
||||||
|
await onBeforeRunMock.reject();
|
||||||
|
|
||||||
|
expect(onRun).toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("emits catalog open AppEvent", () => {
|
||||||
|
expect(appEventListener).toHaveBeenCalledWith( {
|
||||||
|
action: "open",
|
||||||
|
name: "catalog",
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it("emits catalog change AppEvent when changing the category", () => {
|
||||||
|
rendered.getByText("Web Links").click();
|
||||||
|
|
||||||
|
expect(appEventListener).toHaveBeenCalledWith({
|
||||||
|
action: "change-category",
|
||||||
|
name: "catalog",
|
||||||
|
params: {
|
||||||
|
category: "Web Links",
|
||||||
|
},
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
@ -9,18 +9,20 @@ import { KubernetesCluster, WebLink } from "../../common/catalog-entities";
|
|||||||
import getClusterByIdInjectable from "../../common/cluster-store/get-by-id.injectable";
|
import getClusterByIdInjectable from "../../common/cluster-store/get-by-id.injectable";
|
||||||
import type { Cluster } from "../../common/cluster/cluster";
|
import type { Cluster } from "../../common/cluster/cluster";
|
||||||
import navigateToCatalogInjectable from "../../common/front-end-routing/routes/catalog/navigate-to-catalog.injectable";
|
import navigateToCatalogInjectable from "../../common/front-end-routing/routes/catalog/navigate-to-catalog.injectable";
|
||||||
|
import { advanceFakeTime, testUsingFakeTime } from "../../common/test-utils/use-fake-time";
|
||||||
import catalogEntityRegistryInjectable from "../../renderer/api/catalog/entity/registry.injectable";
|
import catalogEntityRegistryInjectable from "../../renderer/api/catalog/entity/registry.injectable";
|
||||||
import createClusterInjectable from "../../renderer/cluster/create-cluster.injectable";
|
import createClusterInjectable from "../../renderer/cluster/create-cluster.injectable";
|
||||||
|
import showEntityDetailsInjectable from "../../renderer/components/+catalog/entity-details/show.injectable";
|
||||||
import { type ApplicationBuilder, getApplicationBuilder } from "../../renderer/components/test-utils/get-application-builder";
|
import { type ApplicationBuilder, getApplicationBuilder } from "../../renderer/components/test-utils/get-application-builder";
|
||||||
|
|
||||||
describe("opening catalog entity details panel", () => {
|
describe("opening catalog entity details panel", () => {
|
||||||
let builder: ApplicationBuilder;
|
let builder: ApplicationBuilder;
|
||||||
let rendered: RenderResult;
|
let rendered: RenderResult;
|
||||||
let windowDi: DiContainer;
|
let windowDi: DiContainer;
|
||||||
|
let cluster: Cluster;
|
||||||
let clusterEntity: KubernetesCluster;
|
let clusterEntity: KubernetesCluster;
|
||||||
let localClusterEntity: KubernetesCluster;
|
let localClusterEntity: KubernetesCluster;
|
||||||
let otherEntity: WebLink;
|
let otherEntity: WebLink;
|
||||||
let cluster: Cluster;
|
|
||||||
|
|
||||||
beforeEach(async () => {
|
beforeEach(async () => {
|
||||||
builder = getApplicationBuilder();
|
builder = getApplicationBuilder();
|
||||||
@ -28,7 +30,7 @@ describe("opening catalog entity details panel", () => {
|
|||||||
builder.beforeWindowStart((windowDi) => {
|
builder.beforeWindowStart((windowDi) => {
|
||||||
// TODO: remove once ClusterStore can be used without overriding it
|
// TODO: remove once ClusterStore can be used without overriding it
|
||||||
windowDi.override(getClusterByIdInjectable, () => (clusterId) => {
|
windowDi.override(getClusterByIdInjectable, () => (clusterId) => {
|
||||||
if (clusterId === cluster.id) {
|
if (clusterId === cluster?.id) {
|
||||||
return cluster;
|
return cluster;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -36,6 +38,8 @@ describe("opening catalog entity details panel", () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
testUsingFakeTime();
|
||||||
|
|
||||||
builder.afterWindowStart((windowDi) => {
|
builder.afterWindowStart((windowDi) => {
|
||||||
const createCluster = windowDi.inject(createClusterInjectable);
|
const createCluster = windowDi.inject(createClusterInjectable);
|
||||||
|
|
||||||
@ -129,6 +133,7 @@ describe("opening catalog entity details panel", () => {
|
|||||||
describe("when opening the menu 'some-kubernetes-cluster'", () => {
|
describe("when opening the menu 'some-kubernetes-cluster'", () => {
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
rendered.getByTestId("icon-for-menu-actions-for-catalog-for-some-entity-id").click();
|
rendered.getByTestId("icon-for-menu-actions-for-catalog-for-some-entity-id").click();
|
||||||
|
advanceFakeTime(1000);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("renders", () => {
|
it("renders", () => {
|
||||||
@ -154,6 +159,7 @@ describe("opening catalog entity details panel", () => {
|
|||||||
|
|
||||||
describe("when the panel opens", () => {
|
describe("when the panel opens", () => {
|
||||||
beforeEach(async () => {
|
beforeEach(async () => {
|
||||||
|
advanceFakeTime(1000);
|
||||||
await rendered.findAllByTestId("catalog-entity-details-drawer");
|
await rendered.findAllByTestId("catalog-entity-details-drawer");
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -222,4 +228,21 @@ describe("opening catalog entity details panel", () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe("when not navigated to the catalog and showEntityDetails is called from someplace", () => {
|
||||||
|
beforeEach(async () => {
|
||||||
|
const showEntityDetails = windowDi.inject(showEntityDetailsInjectable);
|
||||||
|
|
||||||
|
showEntityDetails("some-weblink-id");
|
||||||
|
advanceFakeTime(1000);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("renders", async () => {
|
||||||
|
expect(rendered.baseElement).toMatchSnapshot();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("opens the detail panel for the correct item", () => {
|
||||||
|
expect(rendered.queryByTestId("catalog-entity-details-content-for-some-weblink-id")).toBeInTheDocument();
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@ -0,0 +1,20 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||||
|
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||||
|
*/
|
||||||
|
import { getInjectable } from "@ogre-tools/injectable";
|
||||||
|
import type { CatalogEntity } from "../../catalog-entity";
|
||||||
|
import catalogEntityRegistryInjectable from "./registry.injectable";
|
||||||
|
|
||||||
|
export type GetEntityById = (id: string) => CatalogEntity | undefined;
|
||||||
|
|
||||||
|
const getEntityByIdInjectable = getInjectable({
|
||||||
|
id: "get-entity-by-id",
|
||||||
|
instantiate: (di): GetEntityById => {
|
||||||
|
const catalogEntityRegistry = di.inject(catalogEntityRegistryInjectable);
|
||||||
|
|
||||||
|
return (id) => catalogEntityRegistry.getById(id);
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
export default getEntityByIdInjectable;
|
||||||
@ -23,7 +23,7 @@ export type CatalogEntityOnBeforeRun = (event: CatalogRunEvent) => void | Promis
|
|||||||
interface Dependencies {
|
interface Dependencies {
|
||||||
navigate: Navigate;
|
navigate: Navigate;
|
||||||
readonly categoryRegistry: CatalogCategoryRegistry;
|
readonly categoryRegistry: CatalogCategoryRegistry;
|
||||||
logger: Logger;
|
readonly logger: Logger;
|
||||||
}
|
}
|
||||||
|
|
||||||
export class CatalogEntityRegistry {
|
export class CatalogEntityRegistry {
|
||||||
@ -243,20 +243,24 @@ export class CatalogEntityRegistry {
|
|||||||
* Perform the onBeforeRun check and, if successful, then proceed to call `entity`'s onRun method
|
* Perform the onBeforeRun check and, if successful, then proceed to call `entity`'s onRun method
|
||||||
* @param entity The instance to invoke the hooks and then execute the onRun
|
* @param entity The instance to invoke the hooks and then execute the onRun
|
||||||
*/
|
*/
|
||||||
onRun(entity: CatalogEntity): void {
|
async onRun(entity: CatalogEntity) {
|
||||||
this.onBeforeRun(entity)
|
try {
|
||||||
.then(doOnRun => {
|
const doOnRun = await this.onBeforeRun(entity);
|
||||||
if (doOnRun) {
|
|
||||||
return entity.onRun?.({
|
if (!doOnRun) {
|
||||||
navigate: this.dependencies.navigate,
|
this.dependencies.logger.debug(`onBeforeRun for ${entity.getId()} returned false`);
|
||||||
setCommandPaletteContext: (entity) => {
|
|
||||||
this.activeEntity = entity;
|
return;
|
||||||
},
|
}
|
||||||
});
|
|
||||||
} else {
|
await entity.onRun?.({
|
||||||
this.dependencies.logger.debug(`onBeforeRun for ${entity.getId()} returned false`);
|
navigate: this.dependencies.navigate,
|
||||||
}
|
setCommandPaletteContext: (entity) => {
|
||||||
})
|
this.activeEntity = entity;
|
||||||
.catch(error => this.dependencies.logger.error(`[CATALOG-ENTITY-REGISTRY]: entity ${entity.getId()} onRun threw an error`, error));
|
},
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
this.dependencies.logger.error(`[CATALOG-ENTITY-REGISTRY]: entity ${entity.getId()} onRun threw an error`, error);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -3,12 +3,16 @@
|
|||||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
import type { DiContainer } from "@ogre-tools/injectable";
|
||||||
import type { CatalogCategoryMetadata, CatalogCategorySpec } from "../../../../common/catalog";
|
import type { CatalogCategoryMetadata, CatalogCategorySpec } from "../../../../common/catalog";
|
||||||
import { CatalogEntity, categoryVersion } from "../../../../common/catalog";
|
import { CatalogEntity, categoryVersion } from "../../../../common/catalog";
|
||||||
|
import catalogCategoryRegistryInjectable from "../../../../common/catalog/category-registry.injectable";
|
||||||
import { CatalogCategory } from "../../../api/catalog-entity";
|
import { CatalogCategory } from "../../../api/catalog-entity";
|
||||||
|
import catalogEntityRegistryInjectable from "../../../api/catalog/entity/registry.injectable";
|
||||||
|
import { getDiForUnitTesting } from "../../../getDiForUnitTesting";
|
||||||
import { noop } from "../../../utils";
|
import { noop } from "../../../utils";
|
||||||
import type { CatalogEntityStore } from "../catalog-entity-store/catalog-entity.store";
|
import type { CatalogEntityStore } from "../catalog-entity-store.injectable";
|
||||||
import { catalogEntityStore } from "../catalog-entity-store/catalog-entity.store";
|
import catalogEntityStoreInjectable from "../catalog-entity-store.injectable";
|
||||||
|
|
||||||
class TestEntityOne extends CatalogEntity {
|
class TestEntityOne extends CatalogEntity {
|
||||||
public static readonly apiVersion: string = "entity.k8slens.dev/v1alpha1";
|
public static readonly apiVersion: string = "entity.k8slens.dev/v1alpha1";
|
||||||
@ -63,6 +67,12 @@ class TestCategoryTwo extends CatalogCategory {
|
|||||||
}
|
}
|
||||||
|
|
||||||
describe("CatalogEntityStore", () => {
|
describe("CatalogEntityStore", () => {
|
||||||
|
let di: DiContainer;
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
di = getDiForUnitTesting({ doGeneralOverrides: true });
|
||||||
|
});
|
||||||
|
|
||||||
describe("getTotalCount", () => {
|
describe("getTotalCount", () => {
|
||||||
let store: CatalogEntityStore;
|
let store: CatalogEntityStore;
|
||||||
let testCategoryOne: TestCategoryOne;
|
let testCategoryOne: TestCategoryOne;
|
||||||
@ -129,21 +139,22 @@ describe("CatalogEntityStore", () => {
|
|||||||
|
|
||||||
testCategoryOne = new TestCategoryOne();
|
testCategoryOne = new TestCategoryOne();
|
||||||
testCategoryTwo = new TestCategoryTwo();
|
testCategoryTwo = new TestCategoryTwo();
|
||||||
store = catalogEntityStore({
|
|
||||||
catalogRegistry: {
|
di.override(catalogCategoryRegistryInjectable, () => ({
|
||||||
items: [
|
items: [
|
||||||
testCategoryOne,
|
testCategoryOne,
|
||||||
testCategoryTwo,
|
testCategoryTwo,
|
||||||
],
|
],
|
||||||
|
}));
|
||||||
|
di.override(catalogEntityRegistryInjectable, () => ({
|
||||||
|
onRun: noop,
|
||||||
|
filteredItems: entityItems,
|
||||||
|
getItemsForCategory: <T extends CatalogEntity>(category: CatalogCategory): T[] => {
|
||||||
|
return entityItems.filter(item => category.spec.versions.some(version => item instanceof version.entityClass)) as T[];
|
||||||
},
|
},
|
||||||
entityRegistry: {
|
} as any));
|
||||||
onRun: noop,
|
|
||||||
filteredItems: entityItems,
|
store = di.inject(catalogEntityStoreInjectable);
|
||||||
getItemsForCategory: <T extends CatalogEntity>(category: CatalogCategory): T[] => {
|
|
||||||
return entityItems.filter(item => category.spec.versions.some(version => item instanceof version.entityClass)) as T[];
|
|
||||||
},
|
|
||||||
},
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it("given no active category, returns count of all kinds", () => {
|
it("given no active category, returns count of all kinds", () => {
|
||||||
|
|||||||
@ -0,0 +1,74 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||||
|
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||||
|
*/
|
||||||
|
import { getInjectable } from "@ogre-tools/injectable";
|
||||||
|
import catalogEntityRegistryInjectable from "../../api/catalog/entity/registry.injectable";
|
||||||
|
|
||||||
|
import type { IComputedValue, IObservableValue } from "mobx";
|
||||||
|
import { computed, observable, reaction } from "mobx";
|
||||||
|
import type { CatalogEntity } from "../../api/catalog-entity";
|
||||||
|
import type { CatalogCategory } from "../../../common/catalog";
|
||||||
|
import type { Disposer } from "../../../common/utils";
|
||||||
|
import { disposer } from "../../../common/utils";
|
||||||
|
import type { ItemListStore } from "../item-object-list";
|
||||||
|
import catalogCategoryRegistryInjectable from "../../../common/catalog/category-registry.injectable";
|
||||||
|
import selectedCatalogEntityParamInjectable from "./entity-details/selected-uid.injectable";
|
||||||
|
|
||||||
|
export type CatalogEntityStore = ItemListStore<CatalogEntity, false> & {
|
||||||
|
readonly entities: IComputedValue<CatalogEntity[]>;
|
||||||
|
readonly activeCategory: IObservableValue<CatalogCategory | undefined>;
|
||||||
|
watch(): Disposer;
|
||||||
|
onRun(entity: CatalogEntity): void;
|
||||||
|
};
|
||||||
|
|
||||||
|
const catalogEntityStoreInjectable = getInjectable({
|
||||||
|
id: "catalog-entity-store",
|
||||||
|
|
||||||
|
instantiate: (di): CatalogEntityStore => {
|
||||||
|
const catalogEntityRegistry = di.inject(catalogEntityRegistryInjectable);
|
||||||
|
const catalogCategoryRegistry = di.inject(catalogCategoryRegistryInjectable);
|
||||||
|
const selectedCatalogEntityParam = di.inject(selectedCatalogEntityParamInjectable);
|
||||||
|
|
||||||
|
const activeCategory = observable.box<CatalogCategory | undefined>(undefined);
|
||||||
|
const entities = computed(() => {
|
||||||
|
const category = activeCategory.get();
|
||||||
|
|
||||||
|
return category
|
||||||
|
? catalogEntityRegistry.getItemsForCategory(category, { filtered: true })
|
||||||
|
: catalogEntityRegistry.filteredItems;
|
||||||
|
});
|
||||||
|
const loadAll = () => {
|
||||||
|
const category = activeCategory.get();
|
||||||
|
|
||||||
|
if (category) {
|
||||||
|
category.emit("load");
|
||||||
|
} else {
|
||||||
|
for (const category of catalogCategoryRegistry.items) {
|
||||||
|
category.emit("load");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return {
|
||||||
|
entities,
|
||||||
|
activeCategory,
|
||||||
|
watch: () => disposer(
|
||||||
|
reaction(() => entities.get(), loadAll),
|
||||||
|
reaction(() => activeCategory.get(), loadAll, { delay: 100 }),
|
||||||
|
),
|
||||||
|
onRun: entity => catalogEntityRegistry.onRun(entity),
|
||||||
|
failedLoading: false,
|
||||||
|
getTotalCount: () => entities.get().length,
|
||||||
|
isLoaded: true,
|
||||||
|
isSelected: (item) => item.getId() === selectedCatalogEntityParam.get(),
|
||||||
|
isSelectedAll: () => false,
|
||||||
|
pickOnlySelected: () => [],
|
||||||
|
toggleSelection: () => {},
|
||||||
|
toggleSelectionAll: () => {},
|
||||||
|
removeSelectedItems: async () => {},
|
||||||
|
};
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
export default catalogEntityStoreInjectable;
|
||||||
@ -1,19 +0,0 @@
|
|||||||
/**
|
|
||||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
|
||||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
|
||||||
*/
|
|
||||||
import { getInjectable } from "@ogre-tools/injectable";
|
|
||||||
import { catalogEntityStore } from "./catalog-entity.store";
|
|
||||||
import catalogEntityRegistryInjectable from "../../../api/catalog/entity/registry.injectable";
|
|
||||||
import catalogCategoryRegistryInjectable from "../../../../common/catalog/category-registry.injectable";
|
|
||||||
|
|
||||||
const catalogEntityStoreInjectable = getInjectable({
|
|
||||||
id: "catalog-entity-store",
|
|
||||||
|
|
||||||
instantiate: (di) => catalogEntityStore({
|
|
||||||
entityRegistry: di.inject(catalogEntityRegistryInjectable),
|
|
||||||
catalogRegistry: di.inject(catalogCategoryRegistryInjectable),
|
|
||||||
}),
|
|
||||||
});
|
|
||||||
|
|
||||||
export default catalogEntityStoreInjectable;
|
|
||||||
@ -1,86 +0,0 @@
|
|||||||
/**
|
|
||||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
|
||||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
|
||||||
*/
|
|
||||||
|
|
||||||
import type { IComputedValue, IObservableValue } from "mobx";
|
|
||||||
import { computed, observable, reaction } from "mobx";
|
|
||||||
import type { CatalogEntityRegistry } from "../../../api/catalog/entity/registry";
|
|
||||||
import type { CatalogEntity } from "../../../api/catalog-entity";
|
|
||||||
import type { CatalogCategory, CatalogCategoryRegistry } from "../../../../common/catalog";
|
|
||||||
import type { Disposer } from "../../../../common/utils";
|
|
||||||
import { disposer } from "../../../../common/utils";
|
|
||||||
import type { ItemListStore } from "../../item-object-list";
|
|
||||||
|
|
||||||
type EntityRegistry = Pick<CatalogEntityRegistry, "getItemsForCategory" | "filteredItems" | "onRun">;
|
|
||||||
type CatalogRegistry = Pick<CatalogCategoryRegistry, "items">;
|
|
||||||
|
|
||||||
interface Dependencies {
|
|
||||||
entityRegistry: EntityRegistry;
|
|
||||||
catalogRegistry: CatalogRegistry;
|
|
||||||
}
|
|
||||||
|
|
||||||
export type CatalogEntityStore = ItemListStore<CatalogEntity, false> & {
|
|
||||||
readonly entities: IComputedValue<CatalogEntity[]>;
|
|
||||||
readonly activeCategory: IObservableValue<CatalogCategory | undefined>;
|
|
||||||
readonly selectedItemId: IObservableValue<string | undefined>;
|
|
||||||
readonly selectedItem: IComputedValue<CatalogEntity | undefined>;
|
|
||||||
watch(): Disposer;
|
|
||||||
onRun(entity: CatalogEntity): void;
|
|
||||||
};
|
|
||||||
|
|
||||||
export function catalogEntityStore({
|
|
||||||
entityRegistry,
|
|
||||||
catalogRegistry,
|
|
||||||
}: Dependencies): CatalogEntityStore {
|
|
||||||
const activeCategory = observable.box<CatalogCategory | undefined>(undefined);
|
|
||||||
const selectedItemId = observable.box<string | undefined>(undefined);
|
|
||||||
const entities = computed(() => {
|
|
||||||
const category = activeCategory.get();
|
|
||||||
|
|
||||||
return category
|
|
||||||
? entityRegistry.getItemsForCategory(category, { filtered: true })
|
|
||||||
: entityRegistry.filteredItems;
|
|
||||||
});
|
|
||||||
const selectedItem = computed(() => {
|
|
||||||
const id = selectedItemId.get();
|
|
||||||
|
|
||||||
if (!id) {
|
|
||||||
return undefined;
|
|
||||||
}
|
|
||||||
|
|
||||||
return entities.get().find(entity => entity.getId() === id);
|
|
||||||
});
|
|
||||||
const loadAll = () => {
|
|
||||||
const category = activeCategory.get();
|
|
||||||
|
|
||||||
if (category) {
|
|
||||||
category.emit("load");
|
|
||||||
} else {
|
|
||||||
for (const category of catalogRegistry.items) {
|
|
||||||
category.emit("load");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
return {
|
|
||||||
entities,
|
|
||||||
selectedItem,
|
|
||||||
activeCategory,
|
|
||||||
selectedItemId,
|
|
||||||
watch: () => disposer(
|
|
||||||
reaction(() => entities.get(), loadAll),
|
|
||||||
reaction(() => activeCategory.get(), loadAll, { delay: 100 }),
|
|
||||||
),
|
|
||||||
onRun: entity => entityRegistry.onRun(entity),
|
|
||||||
failedLoading: false,
|
|
||||||
getTotalCount: () => entities.get().length,
|
|
||||||
isLoaded: true,
|
|
||||||
isSelected: (item) => item.getId() === selectedItemId.get(),
|
|
||||||
isSelectedAll: () => false,
|
|
||||||
pickOnlySelected: () => [],
|
|
||||||
toggleSelection: () => {},
|
|
||||||
toggleSelectionAll: () => {},
|
|
||||||
removeSelectedItems: async () => {},
|
|
||||||
};
|
|
||||||
}
|
|
||||||
@ -1,229 +0,0 @@
|
|||||||
/**
|
|
||||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
|
||||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
|
||||||
*/
|
|
||||||
|
|
||||||
import React from "react";
|
|
||||||
import { screen } from "@testing-library/react";
|
|
||||||
import userEvent from "@testing-library/user-event";
|
|
||||||
import { Catalog } from "./catalog";
|
|
||||||
import type { CatalogEntityActionContext, CatalogEntityData } from "../../../common/catalog";
|
|
||||||
import { CatalogEntity } from "../../../common/catalog";
|
|
||||||
import type { CatalogEntityOnBeforeRun, CatalogEntityRegistry } from "../../api/catalog/entity/registry";
|
|
||||||
import type { CatalogEntityStore } from "./catalog-entity-store/catalog-entity.store";
|
|
||||||
import { getDiForUnitTesting } from "../../getDiForUnitTesting";
|
|
||||||
import type { DiContainer } from "@ogre-tools/injectable";
|
|
||||||
import catalogEntityStoreInjectable from "./catalog-entity-store/catalog-entity-store.injectable";
|
|
||||||
import catalogEntityRegistryInjectable from "../../api/catalog/entity/registry.injectable";
|
|
||||||
import type { DiRender } from "../test-utils/renderFor";
|
|
||||||
import { renderFor } from "../test-utils/renderFor";
|
|
||||||
import directoryForUserDataInjectable from "../../../common/app-paths/directory-for-user-data/directory-for-user-data.injectable";
|
|
||||||
import getConfigurationFileModelInjectable from "../../../common/get-configuration-file-model/get-configuration-file-model.injectable";
|
|
||||||
import type { AppEvent } from "../../../common/app-event-bus/event-bus";
|
|
||||||
import appEventBusInjectable from "../../../common/app-event-bus/app-event-bus.injectable";
|
|
||||||
import { computed } from "mobx";
|
|
||||||
import broadcastMessageInjectable from "../../../common/ipc/broadcast-message.injectable";
|
|
||||||
import type { AsyncFnMock } from "@async-fn/jest";
|
|
||||||
import asyncFn from "@async-fn/jest";
|
|
||||||
import { flushPromises } from "../../../common/test-utils/flush-promises";
|
|
||||||
import userStoreInjectable from "../../../common/user-store/user-store.injectable";
|
|
||||||
import releaseChannelInjectable from "../../../common/vars/release-channel.injectable";
|
|
||||||
import defaultUpdateChannelInjectable from "../../../features/application-update/common/selected-update-channel/default-update-channel.injectable";
|
|
||||||
import currentlyInClusterFrameInjectable from "../../routes/currently-in-cluster-frame.injectable";
|
|
||||||
|
|
||||||
class MockCatalogEntity extends CatalogEntity {
|
|
||||||
public apiVersion = "api";
|
|
||||||
public kind = "kind";
|
|
||||||
|
|
||||||
constructor(data: CatalogEntityData, public onRun: (context: CatalogEntityActionContext) => void | Promise<void>) {
|
|
||||||
super(data);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function createMockCatalogEntity(onRun: (context: CatalogEntityActionContext) => void | Promise<void>) {
|
|
||||||
return new MockCatalogEntity({
|
|
||||||
metadata: {
|
|
||||||
uid: "a_catalogEntity_uid",
|
|
||||||
name: "a catalog entity",
|
|
||||||
labels: {
|
|
||||||
test: "label",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
status: {
|
|
||||||
phase: "",
|
|
||||||
},
|
|
||||||
spec: {},
|
|
||||||
}, onRun);
|
|
||||||
}
|
|
||||||
|
|
||||||
describe("<Catalog />", () => {
|
|
||||||
let di: DiContainer;
|
|
||||||
let catalogEntityStore: CatalogEntityStore;
|
|
||||||
let catalogEntityRegistry: CatalogEntityRegistry;
|
|
||||||
let appEventListener: jest.MockedFunction<(event: AppEvent) => void>;
|
|
||||||
let onRun: jest.MockedFunction<(context: CatalogEntityActionContext) => void | Promise<void>>;
|
|
||||||
let catalogEntityItem: MockCatalogEntity;
|
|
||||||
let render: DiRender;
|
|
||||||
|
|
||||||
beforeEach(async () => {
|
|
||||||
di = getDiForUnitTesting({ doGeneralOverrides: true });
|
|
||||||
|
|
||||||
di.override(directoryForUserDataInjectable, () => "some-directory-for-user-data");
|
|
||||||
di.override(currentlyInClusterFrameInjectable, () => false);
|
|
||||||
di.override(broadcastMessageInjectable, () => async () => {});
|
|
||||||
|
|
||||||
di.permitSideEffects(getConfigurationFileModelInjectable);
|
|
||||||
|
|
||||||
render = renderFor(di);
|
|
||||||
onRun = jest.fn();
|
|
||||||
catalogEntityItem = createMockCatalogEntity(onRun);
|
|
||||||
catalogEntityRegistry = di.inject(catalogEntityRegistryInjectable);
|
|
||||||
|
|
||||||
appEventListener = jest.fn();
|
|
||||||
di.inject(appEventBusInjectable).addListener(appEventListener);
|
|
||||||
|
|
||||||
catalogEntityStore = di.inject(catalogEntityStoreInjectable);
|
|
||||||
Object.assign(catalogEntityStore, {
|
|
||||||
selectedItem: computed(() => catalogEntityItem),
|
|
||||||
});
|
|
||||||
|
|
||||||
di.override(releaseChannelInjectable, () => ({
|
|
||||||
get: () => "latest" as const,
|
|
||||||
init: async () => {},
|
|
||||||
}));
|
|
||||||
await di.inject(defaultUpdateChannelInjectable).init();
|
|
||||||
di.inject(userStoreInjectable).load();
|
|
||||||
});
|
|
||||||
|
|
||||||
describe("can use catalogEntityRegistry.addOnBeforeRun to add hooks for catalog entities", () => {
|
|
||||||
let onBeforeRunMock: AsyncFnMock<CatalogEntityOnBeforeRun>;
|
|
||||||
|
|
||||||
beforeEach(() => {
|
|
||||||
onBeforeRunMock = asyncFn();
|
|
||||||
|
|
||||||
catalogEntityRegistry.addOnBeforeRun(onBeforeRunMock);
|
|
||||||
|
|
||||||
render(<Catalog />);
|
|
||||||
|
|
||||||
userEvent.click(screen.getByTestId("detail-panel-hot-bar-icon"));
|
|
||||||
});
|
|
||||||
|
|
||||||
it("calls on before run event", () => {
|
|
||||||
const target = onBeforeRunMock.mock.calls[0][0].target;
|
|
||||||
|
|
||||||
const actual = { id: target.getId(), name: target.getName() };
|
|
||||||
|
|
||||||
expect(actual).toEqual({
|
|
||||||
id: "a_catalogEntity_uid",
|
|
||||||
name: "a catalog entity",
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it("does not call onRun yet", () => {
|
|
||||||
expect(onRun).not.toHaveBeenCalled();
|
|
||||||
});
|
|
||||||
|
|
||||||
it("when before run event resolves, calls onRun", async () => {
|
|
||||||
await onBeforeRunMock.resolve();
|
|
||||||
|
|
||||||
expect(onRun).toHaveBeenCalled();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it("onBeforeRun prevents event => onRun wont be triggered", async () => {
|
|
||||||
const onBeforeRunMock = jest.fn((event) => event.preventDefault());
|
|
||||||
|
|
||||||
catalogEntityRegistry.addOnBeforeRun(onBeforeRunMock);
|
|
||||||
|
|
||||||
render(<Catalog />);
|
|
||||||
|
|
||||||
userEvent.click(screen.getByTestId("detail-panel-hot-bar-icon"));
|
|
||||||
|
|
||||||
await flushPromises();
|
|
||||||
|
|
||||||
expect(onRun).not.toHaveBeenCalled();
|
|
||||||
});
|
|
||||||
|
|
||||||
it("addOnBeforeRun throw an exception => onRun will be triggered", async () => {
|
|
||||||
const onBeforeRunMock = jest.fn(() => {
|
|
||||||
throw new Error("some error");
|
|
||||||
});
|
|
||||||
|
|
||||||
catalogEntityRegistry.addOnBeforeRun(onBeforeRunMock);
|
|
||||||
|
|
||||||
render(<Catalog />);
|
|
||||||
|
|
||||||
userEvent.click(screen.getByTestId("detail-panel-hot-bar-icon"));
|
|
||||||
|
|
||||||
await flushPromises();
|
|
||||||
|
|
||||||
expect(onRun).toHaveBeenCalled();
|
|
||||||
});
|
|
||||||
|
|
||||||
it("addOnRunHook return a promise and does not prevent run event => onRun()", (done) => {
|
|
||||||
onRun.mockImplementation(() => done());
|
|
||||||
|
|
||||||
catalogEntityRegistry.addOnBeforeRun(
|
|
||||||
async () => {
|
|
||||||
// no op
|
|
||||||
},
|
|
||||||
);
|
|
||||||
|
|
||||||
render(<Catalog />);
|
|
||||||
|
|
||||||
userEvent.click(screen.getByTestId("detail-panel-hot-bar-icon"));
|
|
||||||
});
|
|
||||||
|
|
||||||
it("addOnRunHook return a promise and prevents event wont be triggered", async () => {
|
|
||||||
const onBeforeRunMock = asyncFn();
|
|
||||||
|
|
||||||
catalogEntityRegistry.addOnBeforeRun(onBeforeRunMock);
|
|
||||||
|
|
||||||
render(<Catalog />);
|
|
||||||
|
|
||||||
userEvent.click(screen.getByTestId("detail-panel-hot-bar-icon"));
|
|
||||||
|
|
||||||
onBeforeRunMock.mock.calls[0][0].preventDefault();
|
|
||||||
|
|
||||||
await onBeforeRunMock.resolve();
|
|
||||||
|
|
||||||
expect(onRun).not.toHaveBeenCalled();
|
|
||||||
});
|
|
||||||
|
|
||||||
it("addOnRunHook return a promise and reject => onRun will be triggered", async () => {
|
|
||||||
const onBeforeRunMock = asyncFn();
|
|
||||||
|
|
||||||
catalogEntityRegistry.addOnBeforeRun(onBeforeRunMock);
|
|
||||||
|
|
||||||
render(<Catalog />);
|
|
||||||
|
|
||||||
userEvent.click(screen.getByTestId("detail-panel-hot-bar-icon"));
|
|
||||||
|
|
||||||
await onBeforeRunMock.reject();
|
|
||||||
|
|
||||||
expect(onRun).toHaveBeenCalled();
|
|
||||||
});
|
|
||||||
|
|
||||||
it("emits catalog open AppEvent", () => {
|
|
||||||
render(<Catalog />);
|
|
||||||
|
|
||||||
expect(appEventListener).toHaveBeenCalledWith( {
|
|
||||||
action: "open",
|
|
||||||
name: "catalog",
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it("emits catalog change AppEvent when changing the category", () => {
|
|
||||||
render(<Catalog />);
|
|
||||||
|
|
||||||
userEvent.click(screen.getByText("Web Links"));
|
|
||||||
|
|
||||||
expect(appEventListener).toHaveBeenCalledWith({
|
|
||||||
action: "change-category",
|
|
||||||
name: "catalog",
|
|
||||||
params: {
|
|
||||||
category: "Web Links",
|
|
||||||
},
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
@ -10,7 +10,7 @@ import { disposeOnUnmount, observer } from "mobx-react";
|
|||||||
import { ItemListLayout } from "../item-object-list";
|
import { ItemListLayout } from "../item-object-list";
|
||||||
import type { IComputedValue } from "mobx";
|
import type { IComputedValue } from "mobx";
|
||||||
import { action, computed, makeObservable, observable, reaction, runInAction, when } from "mobx";
|
import { action, computed, makeObservable, observable, reaction, runInAction, when } from "mobx";
|
||||||
import type { CatalogEntityStore } from "./catalog-entity-store/catalog-entity.store";
|
import type { CatalogEntityStore } from "./catalog-entity-store.injectable";
|
||||||
import { MenuItem, MenuActions } from "../menu";
|
import { MenuItem, MenuActions } from "../menu";
|
||||||
import type { CatalogEntityContextMenu } from "../../api/catalog-entity";
|
import type { CatalogEntityContextMenu } from "../../api/catalog-entity";
|
||||||
import type { CatalogCategory, CatalogCategoryRegistry, CatalogEntity } from "../../../common/catalog";
|
import type { CatalogCategory, CatalogCategoryRegistry, CatalogEntity } from "../../../common/catalog";
|
||||||
@ -19,7 +19,6 @@ import type { ShowNotification } from "../notifications";
|
|||||||
import { MainLayout } from "../layout/main-layout";
|
import { MainLayout } from "../layout/main-layout";
|
||||||
import type { StorageLayer } from "../../utils";
|
import type { StorageLayer } from "../../utils";
|
||||||
import { prevDefault } from "../../utils";
|
import { prevDefault } from "../../utils";
|
||||||
import { CatalogEntityDetails } from "./entity-details/view";
|
|
||||||
import { CatalogMenu } from "./catalog-menu";
|
import { CatalogMenu } from "./catalog-menu";
|
||||||
import { RenderDelay } from "../render-delay/render-delay";
|
import { RenderDelay } from "../render-delay/render-delay";
|
||||||
import { Icon } from "../icon";
|
import { Icon } from "../icon";
|
||||||
@ -27,7 +26,7 @@ import { HotbarToggleMenuItem } from "./hotbar-toggle-menu-item";
|
|||||||
import { Avatar } from "../avatar";
|
import { Avatar } from "../avatar";
|
||||||
import { withInjectables } from "@ogre-tools/injectable-react";
|
import { withInjectables } from "@ogre-tools/injectable-react";
|
||||||
import catalogPreviousActiveTabStorageInjectable from "./catalog-previous-active-tab-storage/catalog-previous-active-tab-storage.injectable";
|
import catalogPreviousActiveTabStorageInjectable 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.injectable";
|
||||||
import type { GetCategoryColumnsParams, CategoryColumns } from "./columns/get.injectable";
|
import type { GetCategoryColumnsParams, CategoryColumns } from "./columns/get.injectable";
|
||||||
import getCategoryColumnsInjectable from "./columns/get.injectable";
|
import getCategoryColumnsInjectable from "./columns/get.injectable";
|
||||||
import type { RegisteredCustomCategoryViewDecl } from "./custom-views.injectable";
|
import type { RegisteredCustomCategoryViewDecl } from "./custom-views.injectable";
|
||||||
@ -51,6 +50,10 @@ import emitAppEventInjectable from "../../../common/app-event-bus/emit-event.inj
|
|||||||
import type { Logger } from "../../../common/logger";
|
import type { Logger } from "../../../common/logger";
|
||||||
import loggerInjectable from "../../../common/logger.injectable";
|
import loggerInjectable from "../../../common/logger.injectable";
|
||||||
import showErrorNotificationInjectable from "../notifications/show-error-notification.injectable";
|
import showErrorNotificationInjectable from "../notifications/show-error-notification.injectable";
|
||||||
|
import type { ShowEntityDetails } from "./entity-details/show.injectable";
|
||||||
|
import showEntityDetailsInjectable from "./entity-details/show.injectable";
|
||||||
|
import type { OnCatalogEntityListClick } from "./entity-details/on-catalog-click.injectable";
|
||||||
|
import onCatalogEntityListClickInjectable from "./entity-details/on-catalog-click.injectable";
|
||||||
|
|
||||||
interface Dependencies {
|
interface Dependencies {
|
||||||
catalogPreviousActiveTabStorage: StorageLayer<string | null>;
|
catalogPreviousActiveTabStorage: StorageLayer<string | null>;
|
||||||
@ -58,6 +61,8 @@ interface Dependencies {
|
|||||||
getCategoryColumns: (params: GetCategoryColumnsParams) => CategoryColumns;
|
getCategoryColumns: (params: GetCategoryColumnsParams) => CategoryColumns;
|
||||||
customCategoryViews: IComputedValue<Map<string, Map<string, RegisteredCustomCategoryViewDecl>>>;
|
customCategoryViews: IComputedValue<Map<string, Map<string, RegisteredCustomCategoryViewDecl>>>;
|
||||||
emitEvent: EmitAppEvent;
|
emitEvent: EmitAppEvent;
|
||||||
|
showEntityDetails: ShowEntityDetails;
|
||||||
|
onCatalogEntityListClick: OnCatalogEntityListClick;
|
||||||
routeParameters: {
|
routeParameters: {
|
||||||
group: IComputedValue<string>;
|
group: IComputedValue<string>;
|
||||||
kind: IComputedValue<string>;
|
kind: IComputedValue<string>;
|
||||||
@ -161,26 +166,16 @@ class NonInjectedCatalog extends React.Component<Dependencies> {
|
|||||||
this.props.hotbarStore.removeFromHotbar(entity.getId());
|
this.props.hotbarStore.removeFromHotbar(entity.getId());
|
||||||
}
|
}
|
||||||
|
|
||||||
onDetails = (entity: CatalogEntity) => {
|
|
||||||
if (this.props.catalogEntityStore.selectedItemId.get()) {
|
|
||||||
this.props.catalogEntityStore.selectedItemId.set(undefined);
|
|
||||||
} else {
|
|
||||||
this.props.catalogEntityStore.onRun(entity);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
get categories() {
|
|
||||||
return this.props.catalogCategoryRegistry.items;
|
|
||||||
}
|
|
||||||
|
|
||||||
onTabChange = action((tabId: string | null) => {
|
onTabChange = action((tabId: string | null) => {
|
||||||
const activeCategory = this.categories.find(category => category.getId() === tabId);
|
const activeCategory = tabId
|
||||||
|
? this.props.catalogCategoryRegistry.getById(tabId)
|
||||||
|
: undefined;
|
||||||
|
|
||||||
this.props.emitEvent({
|
this.props.emitEvent({
|
||||||
name: "catalog",
|
name: "catalog",
|
||||||
action: "change-category",
|
action: "change-category",
|
||||||
params: {
|
params: {
|
||||||
category: activeCategory ? activeCategory.getName() : "Browse",
|
category: activeCategory?.getName() ?? "Browse",
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -211,7 +206,7 @@ class NonInjectedCatalog extends React.Component<Dependencies> {
|
|||||||
<MenuItem
|
<MenuItem
|
||||||
key="open-details"
|
key="open-details"
|
||||||
data-testid={`open-details-menu-item-for-${entity.getId()}`}
|
data-testid={`open-details-menu-item-for-${entity.getId()}`}
|
||||||
onClick={() => this.props.catalogEntityStore.selectedItemId.set(entity.getId())}
|
onClick={() => this.props.showEntityDetails(entity.getId())}
|
||||||
>
|
>
|
||||||
View Details
|
View Details
|
||||||
</MenuItem>
|
</MenuItem>
|
||||||
@ -309,7 +304,7 @@ class NonInjectedCatalog extends React.Component<Dependencies> {
|
|||||||
disabled: !entity.isEnabled(),
|
disabled: !entity.isEnabled(),
|
||||||
})}
|
})}
|
||||||
{...getCategoryColumns({ activeCategory })}
|
{...getCategoryColumns({ activeCategory })}
|
||||||
onDetails={this.onDetails}
|
onDetails={this.props.onCatalogEntityListClick}
|
||||||
renderItemMenu={this.renderItemMenu}
|
renderItemMenu={this.renderItemMenu}
|
||||||
data-testid={`catalog-list-for-${activeCategory?.metadata.name ?? "browse-all"}`}
|
data-testid={`catalog-list-for-${activeCategory?.metadata.name ?? "browse-all"}`}
|
||||||
/>
|
/>
|
||||||
@ -318,7 +313,6 @@ class NonInjectedCatalog extends React.Component<Dependencies> {
|
|||||||
|
|
||||||
render() {
|
render() {
|
||||||
const activeCategory = this.props.catalogEntityStore.activeCategory.get();
|
const activeCategory = this.props.catalogEntityStore.activeCategory.get();
|
||||||
const selectedItem = this.props.catalogEntityStore.selectedItem.get();
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<MainLayout
|
<MainLayout
|
||||||
@ -333,21 +327,13 @@ class NonInjectedCatalog extends React.Component<Dependencies> {
|
|||||||
{this.renderViews(activeCategory)}
|
{this.renderViews(activeCategory)}
|
||||||
</div>
|
</div>
|
||||||
{
|
{
|
||||||
selectedItem
|
activeCategory
|
||||||
? (
|
? (
|
||||||
<CatalogEntityDetails
|
<RenderDelay>
|
||||||
entity={selectedItem}
|
<CatalogAddButton category={activeCategory} />
|
||||||
hideDetails={() => this.props.catalogEntityStore.selectedItemId.set(undefined)}
|
</RenderDelay>
|
||||||
onRun={() => this.props.catalogEntityStore.onRun(selectedItem)}
|
|
||||||
/>
|
|
||||||
)
|
)
|
||||||
: activeCategory
|
: null
|
||||||
? (
|
|
||||||
<RenderDelay>
|
|
||||||
<CatalogAddButton category={activeCategory} />
|
|
||||||
</RenderDelay>
|
|
||||||
)
|
|
||||||
: null
|
|
||||||
}
|
}
|
||||||
</MainLayout>
|
</MainLayout>
|
||||||
);
|
);
|
||||||
@ -371,5 +357,7 @@ export const Catalog = withInjectables<Dependencies>(NonInjectedCatalog, {
|
|||||||
normalizeMenuItem: di.inject(normalizeCatalogEntityContextMenuInjectable),
|
normalizeMenuItem: di.inject(normalizeCatalogEntityContextMenuInjectable),
|
||||||
logger: di.inject(loggerInjectable),
|
logger: di.inject(loggerInjectable),
|
||||||
showErrorNotification: di.inject(showErrorNotificationInjectable),
|
showErrorNotification: di.inject(showErrorNotificationInjectable),
|
||||||
|
showEntityDetails: di.inject(showEntityDetailsInjectable),
|
||||||
|
onCatalogEntityListClick: di.inject(onCatalogEntityListClickInjectable),
|
||||||
}),
|
}),
|
||||||
});
|
});
|
||||||
|
|||||||
@ -0,0 +1,65 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||||
|
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||||
|
*/
|
||||||
|
import { getInjectable } from "@ogre-tools/injectable";
|
||||||
|
import { withInjectables } from "@ogre-tools/injectable-react";
|
||||||
|
import type { IComputedValue } from "mobx";
|
||||||
|
import { computed } from "mobx";
|
||||||
|
import { observer } from "mobx-react";
|
||||||
|
import React from "react";
|
||||||
|
import type { CatalogEntity } from "../../../api/catalog-entity";
|
||||||
|
import type { CatalogEntityRegistry } from "../../../api/catalog/entity/registry";
|
||||||
|
import catalogEntityRegistryInjectable from "../../../api/catalog/entity/registry.injectable";
|
||||||
|
import { rootFrameChildComponentInjectionToken } from "../../../frames/root-frame/root-frame-child-component-injection-token";
|
||||||
|
import type { HideEntityDetails } from "./hide.injectable";
|
||||||
|
import hideEntityDetailsInjectable from "./hide.injectable";
|
||||||
|
import selectedCatalogEntityInjectable from "./selected-entity.injectable";
|
||||||
|
import { CatalogEntityDetails } from "./view";
|
||||||
|
|
||||||
|
interface Dependencies {
|
||||||
|
selectedCatalogEntity: IComputedValue<CatalogEntity | undefined>;
|
||||||
|
hideEntityDetails: HideEntityDetails;
|
||||||
|
catalogEntityRegistry: CatalogEntityRegistry;
|
||||||
|
}
|
||||||
|
|
||||||
|
const NonInjectedCatalogEntityDetailsComponent = observer(({
|
||||||
|
selectedCatalogEntity,
|
||||||
|
hideEntityDetails,
|
||||||
|
catalogEntityRegistry,
|
||||||
|
}: Dependencies) => {
|
||||||
|
const entity = selectedCatalogEntity.get();
|
||||||
|
|
||||||
|
if (!entity) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<CatalogEntityDetails
|
||||||
|
entity={entity}
|
||||||
|
hideDetails={hideEntityDetails}
|
||||||
|
onRun={() => catalogEntityRegistry.onRun(entity)}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
const CatalogEntityDetailsComponent = withInjectables<Dependencies>(NonInjectedCatalogEntityDetailsComponent, {
|
||||||
|
getProps: (di, props) => ({
|
||||||
|
...props,
|
||||||
|
selectedCatalogEntity: di.inject(selectedCatalogEntityInjectable),
|
||||||
|
catalogEntityRegistry: di.inject(catalogEntityRegistryInjectable),
|
||||||
|
hideEntityDetails: di.inject(hideEntityDetailsInjectable),
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
|
||||||
|
const catalogEntityDetailsComponentInjectable = getInjectable({
|
||||||
|
id: "catalog-entity-details-component",
|
||||||
|
instantiate: () => ({
|
||||||
|
id: "catalog-entity-details-component",
|
||||||
|
Component: CatalogEntityDetailsComponent,
|
||||||
|
shouldRender: computed(() => true),
|
||||||
|
}),
|
||||||
|
injectionToken: rootFrameChildComponentInjectionToken,
|
||||||
|
});
|
||||||
|
|
||||||
|
export default catalogEntityDetailsComponentInjectable;
|
||||||
@ -0,0 +1,20 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||||
|
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { getInjectable } from "@ogre-tools/injectable";
|
||||||
|
import selectedCatalogEntityParamInjectable from "./selected-uid.injectable";
|
||||||
|
|
||||||
|
export type HideEntityDetails = () => void;
|
||||||
|
|
||||||
|
const hideEntityDetailsInjectable = getInjectable({
|
||||||
|
id: "hide-entity-details",
|
||||||
|
instantiate: (di) => {
|
||||||
|
const selectedCatalogEntityParam = di.inject(selectedCatalogEntityParamInjectable);
|
||||||
|
|
||||||
|
return () => selectedCatalogEntityParam.clear();
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
export default hideEntityDetailsInjectable;
|
||||||
@ -0,0 +1,31 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||||
|
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||||
|
*/
|
||||||
|
import { getInjectable } from "@ogre-tools/injectable";
|
||||||
|
import { action } from "mobx";
|
||||||
|
import type { CatalogEntity } from "../../../api/catalog-entity";
|
||||||
|
import catalogEntityRegistryInjectable from "../../../api/catalog/entity/registry.injectable";
|
||||||
|
import selectedCatalogEntityParamInjectable from "./selected-uid.injectable";
|
||||||
|
|
||||||
|
export type OnCatalogEntityListClick = (entity: CatalogEntity) => void;
|
||||||
|
|
||||||
|
const onCatalogEntityListClickInjectable = getInjectable({
|
||||||
|
id: "on-catalog-entity-list-click",
|
||||||
|
instantiate: (di): OnCatalogEntityListClick => {
|
||||||
|
const selectedCatalogEntityParam = di.inject(selectedCatalogEntityParamInjectable);
|
||||||
|
const catalogEntityRegistry = di.inject(catalogEntityRegistryInjectable);
|
||||||
|
|
||||||
|
return action(entity => {
|
||||||
|
if (selectedCatalogEntityParam.get() === entity.getId()) {
|
||||||
|
selectedCatalogEntityParam.clear();
|
||||||
|
} else if (selectedCatalogEntityParam.get() === undefined) {
|
||||||
|
catalogEntityRegistry.onRun(entity);
|
||||||
|
} else {
|
||||||
|
selectedCatalogEntityParam.set(entity.getId());
|
||||||
|
}
|
||||||
|
});
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
export default onCatalogEntityListClickInjectable;
|
||||||
@ -0,0 +1,28 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||||
|
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||||
|
*/
|
||||||
|
import { getInjectable } from "@ogre-tools/injectable";
|
||||||
|
import { computed } from "mobx";
|
||||||
|
import getEntityByIdInjectable from "../../../api/catalog/entity/get-by-id.injectable";
|
||||||
|
import selectedCatalogEntityParamInjectable from "./selected-uid.injectable";
|
||||||
|
|
||||||
|
const selectedCatalogEntityInjectable = getInjectable({
|
||||||
|
id: "selected-catalog-entity",
|
||||||
|
instantiate: (di) => {
|
||||||
|
const getEntityById = di.inject(getEntityByIdInjectable);
|
||||||
|
const selectedCatalogEntityParam = di.inject(selectedCatalogEntityParamInjectable);
|
||||||
|
|
||||||
|
return computed(() => {
|
||||||
|
const id = selectedCatalogEntityParam.get();
|
||||||
|
|
||||||
|
if (!id) {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
return getEntityById(id);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
export default selectedCatalogEntityInjectable;
|
||||||
@ -0,0 +1,19 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||||
|
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||||
|
*/
|
||||||
|
import { getInjectable } from "@ogre-tools/injectable";
|
||||||
|
import createPageParamInjectable from "../../../navigation/create-page-param.injectable";
|
||||||
|
|
||||||
|
const selectedCatalogEntityParamInjectable = getInjectable({
|
||||||
|
id: "selected-catalog-entity-param",
|
||||||
|
instantiate: (di) => {
|
||||||
|
const createPageParam = di.inject(createPageParamInjectable);
|
||||||
|
|
||||||
|
return createPageParam({
|
||||||
|
name: "catalog-entity-details",
|
||||||
|
});
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
export default selectedCatalogEntityParamInjectable;
|
||||||
@ -0,0 +1,19 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||||
|
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||||
|
*/
|
||||||
|
import { getInjectable } from "@ogre-tools/injectable";
|
||||||
|
import selectedCatalogEntityParamInjectable from "./selected-uid.injectable";
|
||||||
|
|
||||||
|
export type ShowEntityDetails = (id: string) => void;
|
||||||
|
|
||||||
|
const showEntityDetailsInjectable = getInjectable({
|
||||||
|
id: "show-entity-details",
|
||||||
|
instantiate: (di): ShowEntityDetails => {
|
||||||
|
const selectedCatalogEntityParam = di.inject(selectedCatalogEntityParamInjectable);
|
||||||
|
|
||||||
|
return (id) => selectedCatalogEntityParam.set(id);
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
export default showEntityDetailsInjectable;
|
||||||
Loading…
Reference in New Issue
Block a user