mirror of
https://github.com/lensapp/lens.git
synced 2025-05-20 05:10:56 +00:00
Starting attempt to move kubeconfig sync tests to application builder
- Blocked on catalog sync not being injectable yet Signed-off-by: Sebastian Malton <sebastian@malton.name>
This commit is contained in:
parent
d2ff35551c
commit
6f8de6ab80
@ -2,7 +2,7 @@
|
||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
import { getGlobalOverride } from "../../test-utils/get-global-override";
|
||||
import { getGlobalOverride } from "../test-utils/get-global-override";
|
||||
import watchInjectable from "./watch.injectable";
|
||||
|
||||
export default getGlobalOverride(watchInjectable, () => () => {
|
||||
@ -6,7 +6,7 @@ import { getInjectable } from "@ogre-tools/injectable";
|
||||
import { watch } from "chokidar";
|
||||
import type { Stats } from "fs";
|
||||
import type TypedEventEmitter from "typed-emitter";
|
||||
import type { SingleOrMany } from "../../utils";
|
||||
import type { SingleOrMany } from "../utils";
|
||||
|
||||
export interface AlwaysStatWatcherEvents {
|
||||
add: (path: string, stats: Stats) => void;
|
||||
@ -13,7 +13,7 @@ import extensionPackageRootDirectoryInjectable from "../extension-installer/exte
|
||||
import readJsonFileInjectable from "../../common/fs/read-json-file.injectable";
|
||||
import loggerInjectable from "../../common/logger.injectable";
|
||||
import pathExistsInjectable from "../../common/fs/path-exists.injectable";
|
||||
import watchInjectable from "../../common/fs/watch/watch.injectable";
|
||||
import watchInjectable from "../../common/fs/watch.injectable";
|
||||
import accessPathInjectable from "../../common/fs/access-path.injectable";
|
||||
import copyInjectable from "../../common/fs/copy.injectable";
|
||||
import ensureDirInjectable from "../../common/fs/ensure-dir.injectable";
|
||||
|
||||
@ -13,7 +13,7 @@ import { delay } from "../../renderer/utils";
|
||||
import { observable, runInAction, when } from "mobx";
|
||||
import readJsonFileInjectable from "../../common/fs/read-json-file.injectable";
|
||||
import pathExistsInjectable from "../../common/fs/path-exists.injectable";
|
||||
import watchInjectable from "../../common/fs/watch/watch.injectable";
|
||||
import watchInjectable from "../../common/fs/watch.injectable";
|
||||
import extensionApiVersionInjectable from "../../common/vars/extension-api-version.injectable";
|
||||
import removePathInjectable from "../../common/fs/remove.injectable";
|
||||
import type { JoinPaths } from "../../common/path/join-paths.injectable";
|
||||
|
||||
@ -17,7 +17,7 @@ import { requestInitialExtensionDiscovery } from "../../renderer/ipc";
|
||||
import type { ReadJson } from "../../common/fs/read-json-file.injectable";
|
||||
import type { Logger } from "../../common/logger";
|
||||
import type { PathExists } from "../../common/fs/path-exists.injectable";
|
||||
import type { Watch } from "../../common/fs/watch/watch.injectable";
|
||||
import type { Watch } from "../../common/fs/watch.injectable";
|
||||
import type { Stats } from "fs";
|
||||
import type { LStat } from "../../common/fs/lstat.injectable";
|
||||
import type { ReadDirectory } from "../../common/fs/read-directory.injectable";
|
||||
|
||||
@ -0,0 +1,882 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`kubeconfig sync showing reactive catalog 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://k8slens.dev/slack.html"
|
||||
rel="noreferrer"
|
||||
target="_blank"
|
||||
>
|
||||
Lens Community slack channel
|
||||
</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>
|
||||
</body>
|
||||
`;
|
||||
|
||||
exports[`kubeconfig sync showing reactive catalog when navigating to the catalog renders 1`] = `
|
||||
<body>
|
||||
<div>
|
||||
<div
|
||||
class="ClusterManager"
|
||||
>
|
||||
<div
|
||||
class="topBar"
|
||||
>
|
||||
<div
|
||||
class="items"
|
||||
>
|
||||
<div
|
||||
class="preventedDragging"
|
||||
>
|
||||
<i
|
||||
class="Icon material interactive focusable"
|
||||
data-testid="home-button"
|
||||
tabindex="0"
|
||||
>
|
||||
<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="mainLayout"
|
||||
style="--sidebar-width: 200px;"
|
||||
>
|
||||
<div
|
||||
class="sidebar"
|
||||
>
|
||||
<div
|
||||
class="flex flex-col w-full"
|
||||
>
|
||||
<div
|
||||
class="catalog"
|
||||
>
|
||||
Catalog
|
||||
</div>
|
||||
<ul
|
||||
aria-multiselectable="false"
|
||||
class="MuiTreeView-root"
|
||||
role="tree"
|
||||
>
|
||||
<li
|
||||
aria-selected="true"
|
||||
class="MuiTreeItem-root Mui-selected"
|
||||
data-testid="*-tab"
|
||||
role="treeitem"
|
||||
tabindex="0"
|
||||
>
|
||||
<div
|
||||
class="MuiTreeItem-content"
|
||||
>
|
||||
<div
|
||||
class="MuiTreeItem-iconContainer"
|
||||
/>
|
||||
<div
|
||||
class="MuiTypography-root MuiTreeItem-label MuiTypography-body1"
|
||||
>
|
||||
Browse
|
||||
</div>
|
||||
</div>
|
||||
</li>
|
||||
<li
|
||||
aria-expanded="true"
|
||||
class="MuiTreeItem-root bordered Mui-expanded"
|
||||
role="treeitem"
|
||||
tabindex="-1"
|
||||
>
|
||||
<div
|
||||
class="MuiTreeItem-content"
|
||||
>
|
||||
<div
|
||||
class="MuiTreeItem-iconContainer"
|
||||
>
|
||||
<i
|
||||
class="Icon material focusable"
|
||||
>
|
||||
<span
|
||||
class="icon"
|
||||
data-icon-name="expand_more"
|
||||
>
|
||||
expand_more
|
||||
</span>
|
||||
</i>
|
||||
</div>
|
||||
<div
|
||||
class="MuiTypography-root MuiTreeItem-label MuiTypography-body1"
|
||||
>
|
||||
<div
|
||||
class="parent"
|
||||
>
|
||||
Categories
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<ul
|
||||
class="MuiCollapse-root MuiTreeItem-group MuiCollapse-entered"
|
||||
role="group"
|
||||
style="min-height: 0px;"
|
||||
>
|
||||
<div
|
||||
class="MuiCollapse-wrapper"
|
||||
>
|
||||
<div
|
||||
class="MuiCollapse-wrapperInner"
|
||||
>
|
||||
<li
|
||||
class="MuiTreeItem-root"
|
||||
data-testid="entity.k8slens.dev/General-tab"
|
||||
role="treeitem"
|
||||
tabindex="-1"
|
||||
>
|
||||
<div
|
||||
class="MuiTreeItem-content"
|
||||
>
|
||||
<div
|
||||
class="MuiTreeItem-iconContainer"
|
||||
>
|
||||
<i
|
||||
class="Icon material focusable small"
|
||||
>
|
||||
<span
|
||||
class="icon"
|
||||
data-icon-name="settings"
|
||||
>
|
||||
settings
|
||||
</span>
|
||||
</i>
|
||||
</div>
|
||||
<div
|
||||
class="MuiTypography-root MuiTreeItem-label MuiTypography-body1"
|
||||
>
|
||||
<div
|
||||
class="flex"
|
||||
>
|
||||
<div>
|
||||
General
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</li>
|
||||
<li
|
||||
class="MuiTreeItem-root"
|
||||
data-testid="entity.k8slens.dev/KubernetesCluster-tab"
|
||||
role="treeitem"
|
||||
tabindex="-1"
|
||||
>
|
||||
<div
|
||||
class="MuiTreeItem-content"
|
||||
>
|
||||
<div
|
||||
class="MuiTreeItem-iconContainer"
|
||||
>
|
||||
<i
|
||||
class="Icon focusable small"
|
||||
>
|
||||
<span
|
||||
class="icon"
|
||||
data-icon-name=""
|
||||
/>
|
||||
</i>
|
||||
</div>
|
||||
<div
|
||||
class="MuiTypography-root MuiTreeItem-label MuiTypography-body1"
|
||||
>
|
||||
<div
|
||||
class="flex"
|
||||
>
|
||||
<div>
|
||||
Clusters
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</li>
|
||||
<li
|
||||
class="MuiTreeItem-root"
|
||||
data-testid="entity.k8slens.dev/WebLink-tab"
|
||||
role="treeitem"
|
||||
tabindex="-1"
|
||||
>
|
||||
<div
|
||||
class="MuiTreeItem-content"
|
||||
>
|
||||
<div
|
||||
class="MuiTreeItem-iconContainer"
|
||||
>
|
||||
<i
|
||||
class="Icon material focusable small"
|
||||
>
|
||||
<span
|
||||
class="icon"
|
||||
data-icon-name="public"
|
||||
>
|
||||
public
|
||||
</span>
|
||||
</i>
|
||||
</div>
|
||||
<div
|
||||
class="MuiTypography-root MuiTreeItem-label MuiTypography-body1"
|
||||
>
|
||||
<div
|
||||
class="flex"
|
||||
>
|
||||
<div>
|
||||
Web Links
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</li>
|
||||
</div>
|
||||
</div>
|
||||
</ul>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div
|
||||
class="ResizingAnchor horizontal trailing"
|
||||
/>
|
||||
</div>
|
||||
<div
|
||||
class="contents"
|
||||
>
|
||||
<div
|
||||
class="views"
|
||||
>
|
||||
<div
|
||||
class="ItemListLayout flex column Catalog"
|
||||
data-testid="catalog-list-for-browse-all"
|
||||
>
|
||||
<div
|
||||
class="header flex gaps align-center"
|
||||
>
|
||||
<h5
|
||||
class="title"
|
||||
>
|
||||
Browse All
|
||||
</h5>
|
||||
<div
|
||||
class="info-panel box grow"
|
||||
>
|
||||
0 items
|
||||
</div>
|
||||
<div
|
||||
class="Input SearchInput focused"
|
||||
>
|
||||
<label
|
||||
class="input-area flex gaps align-center"
|
||||
id=""
|
||||
>
|
||||
<input
|
||||
class="input box grow"
|
||||
placeholder="Search..."
|
||||
spellcheck="false"
|
||||
value=""
|
||||
/>
|
||||
<i
|
||||
class="Icon material focusable small"
|
||||
>
|
||||
<span
|
||||
class="icon"
|
||||
data-icon-name="search"
|
||||
>
|
||||
search
|
||||
</span>
|
||||
</i>
|
||||
</label>
|
||||
<div
|
||||
class="input-info flex gaps"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="items box grow flex column"
|
||||
>
|
||||
<div
|
||||
class="Table flex column Catalog box grow dark selectable scrollable sortable autoSize virtual"
|
||||
>
|
||||
<div
|
||||
class="TableHead sticky nowrap topLine"
|
||||
>
|
||||
<div
|
||||
class="TableCell entityName nowrap sorting"
|
||||
id="name"
|
||||
>
|
||||
<div
|
||||
class="content"
|
||||
>
|
||||
Name
|
||||
</div>
|
||||
<i
|
||||
class="Icon sortIcon material focusable"
|
||||
>
|
||||
<span
|
||||
class="icon"
|
||||
data-icon-name="arrow_drop_down"
|
||||
>
|
||||
arrow_drop_down
|
||||
</span>
|
||||
</i>
|
||||
</div>
|
||||
<div
|
||||
class="TableCell nowrap sorting"
|
||||
id="kind"
|
||||
>
|
||||
<div
|
||||
class="content"
|
||||
>
|
||||
Kind
|
||||
</div>
|
||||
<i
|
||||
class="Icon sortIcon material focusable"
|
||||
>
|
||||
<span
|
||||
class="icon"
|
||||
data-icon-name="arrow_drop_down"
|
||||
>
|
||||
arrow_drop_down
|
||||
</span>
|
||||
</i>
|
||||
</div>
|
||||
<div
|
||||
class="TableCell sourceCell nowrap sorting"
|
||||
id="source"
|
||||
>
|
||||
<div
|
||||
class="content"
|
||||
>
|
||||
Source
|
||||
</div>
|
||||
<i
|
||||
class="Icon sortIcon material focusable"
|
||||
>
|
||||
<span
|
||||
class="icon"
|
||||
data-icon-name="arrow_drop_down"
|
||||
>
|
||||
arrow_drop_down
|
||||
</span>
|
||||
</i>
|
||||
</div>
|
||||
<div
|
||||
class="TableCell labelsCell scrollable nowrap"
|
||||
id="labels"
|
||||
>
|
||||
<div
|
||||
class="content"
|
||||
>
|
||||
Labels
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="TableCell statusCell nowrap sorting"
|
||||
id="status"
|
||||
>
|
||||
<div
|
||||
class="content"
|
||||
>
|
||||
Status
|
||||
</div>
|
||||
<i
|
||||
class="Icon sortIcon material focusable"
|
||||
>
|
||||
<span
|
||||
class="icon"
|
||||
data-icon-name="arrow_drop_down"
|
||||
>
|
||||
arrow_drop_down
|
||||
</span>
|
||||
</i>
|
||||
</div>
|
||||
<div
|
||||
class="TableCell menu nowrap"
|
||||
>
|
||||
<div
|
||||
class="content"
|
||||
>
|
||||
<i
|
||||
class="Icon material interactive focusable"
|
||||
id="menu-actions-for-item-object-list-content"
|
||||
tabindex="0"
|
||||
>
|
||||
<span
|
||||
class="icon"
|
||||
data-icon-name="more_vert"
|
||||
>
|
||||
more_vert
|
||||
</span>
|
||||
</i>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="NoItems flex box grow"
|
||||
>
|
||||
<div
|
||||
class="box center"
|
||||
>
|
||||
Item list is empty
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="AddRemoveButtons flex gaps"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="footer"
|
||||
/>
|
||||
</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>
|
||||
</body>
|
||||
`;
|
||||
80
src/features/kubeconfig-sync/reactive-catalog.test.tsx
Normal file
80
src/features/kubeconfig-sync/reactive-catalog.test.tsx
Normal file
@ -0,0 +1,80 @@
|
||||
/**
|
||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
|
||||
import type { DiContainer } from "@ogre-tools/injectable";
|
||||
import type { RenderResult } from "@testing-library/react";
|
||||
import navigateToCatalogInjectable from "../../common/front-end-routing/routes/catalog/navigate-to-catalog.injectable";
|
||||
import writeFileInjectable from "../../common/fs/write-file.injectable";
|
||||
import { dumpConfigYaml } from "../../common/kube-helpers";
|
||||
import homeDirectoryPathInjectable from "../../common/os/home-directory-path.injectable";
|
||||
import joinPathsInjectable from "../../common/path/join-paths.injectable";
|
||||
import { flushPromises } from "../../common/test-utils/flush-promises";
|
||||
import type { ApplicationBuilder } from "../../renderer/components/test-utils/get-application-builder";
|
||||
import { getApplicationBuilder } from "../../renderer/components/test-utils/get-application-builder";
|
||||
|
||||
describe("kubeconfig sync showing reactive catalog", () => {
|
||||
let builder: ApplicationBuilder;
|
||||
let rendered: RenderResult;
|
||||
let windowDi: DiContainer;
|
||||
let mainDi: DiContainer;
|
||||
|
||||
beforeEach(async () => {
|
||||
builder = getApplicationBuilder();
|
||||
|
||||
// builder.mainDi.override(loggerInjectable, () => console as any);
|
||||
rendered = await builder.render();
|
||||
windowDi = builder.applicationWindow.only.di;
|
||||
mainDi = builder.mainDi;
|
||||
});
|
||||
|
||||
it("renders", () => {
|
||||
expect(rendered.baseElement).toMatchSnapshot();
|
||||
});
|
||||
|
||||
describe("when navigating to the catalog", () => {
|
||||
beforeEach(() => {
|
||||
const navigateToCatalog = windowDi.inject(navigateToCatalogInjectable);
|
||||
|
||||
navigateToCatalog();
|
||||
});
|
||||
|
||||
it("renders", () => {
|
||||
expect(rendered.baseElement).toMatchSnapshot();
|
||||
});
|
||||
|
||||
describe("when a config file is written under ~/.kube", () => {
|
||||
beforeEach(async () => {
|
||||
const writeFile = mainDi.inject(writeFileInjectable);
|
||||
const joinPaths = mainDi.inject(joinPathsInjectable);
|
||||
const homeDirectoryPath = mainDi.inject(homeDirectoryPathInjectable);
|
||||
|
||||
const configContents = dumpConfigYaml({
|
||||
clusters: [{
|
||||
name: "some-cluster-name",
|
||||
server: "https://1.2.3.4",
|
||||
skipTLSVerify: false,
|
||||
}],
|
||||
users: [{
|
||||
name: "some-user-name",
|
||||
}],
|
||||
contexts: [{
|
||||
cluster: "some-cluster-name",
|
||||
name: "some-context-name",
|
||||
user: "some-user-name",
|
||||
}],
|
||||
});
|
||||
|
||||
await writeFile(joinPaths(homeDirectoryPath, ".kube", "config"), configContents);
|
||||
await flushPromises();
|
||||
});
|
||||
|
||||
it.only("eventually shows the cluster as a new entity", async () => {
|
||||
await rendered.findByTestId("catalog-entity-row-for-some-cluster-name", undefined, {
|
||||
timeout: 10_000,
|
||||
});
|
||||
}, 100_000);
|
||||
});
|
||||
});
|
||||
});
|
||||
@ -25,8 +25,8 @@ import type { AsyncFnMock } from "@async-fn/jest";
|
||||
import type { Stat } from "../../../common/fs/stat.injectable";
|
||||
import asyncFn from "@async-fn/jest";
|
||||
import statInjectable from "../../../common/fs/stat.injectable";
|
||||
import type { Watcher } from "../../../common/fs/watch/watch.injectable";
|
||||
import watchInjectable from "../../../common/fs/watch/watch.injectable";
|
||||
import type { Watcher } from "../../../common/fs/watch.injectable";
|
||||
import watchInjectable from "../../../common/fs/watch.injectable";
|
||||
import EventEmitter from "events";
|
||||
import type { ReadStream, Stats } from "fs";
|
||||
import createReadFileStreamInjectable from "../../../common/fs/create-read-file-stream.injectable";
|
||||
|
||||
@ -86,7 +86,7 @@ const computeKubeconfigDiffInjectable = getInjectable({
|
||||
|
||||
logger.debug(`Added new cluster from sync`, { filePath, contextName });
|
||||
} catch (error) {
|
||||
logger.warn(`Failed to create cluster from model: ${error}`, { filePath, contextName });
|
||||
logger.warn(`Failed to create cluster with context="${contextName}" from path="${filePath}"`, error);
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
|
||||
@ -0,0 +1,66 @@
|
||||
/**
|
||||
* 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 { Stats } from "fs";
|
||||
import watchInjectable from "../../../common/fs/watch.injectable";
|
||||
import loggerInjectable from "../../../common/logger.injectable";
|
||||
|
||||
export interface CreateKubeSyncWatcherOptions {
|
||||
isDirectorySync: boolean;
|
||||
onChange: (filePath: string, stats: Stats) => void;
|
||||
onAdd: (filePath: string, stats: Stats) => void;
|
||||
onRemove: (filePath: string) => void;
|
||||
onError: (error: Error) => void;
|
||||
}
|
||||
|
||||
export interface KubeSyncWatcher {
|
||||
stop: () => void;
|
||||
}
|
||||
|
||||
export type CreateKubeSyncWatcher = (filePath: string, opts: CreateKubeSyncWatcherOptions) => KubeSyncWatcher;
|
||||
|
||||
const createKubeSyncWatcherInjectable = getInjectable({
|
||||
id: "create-kube-sync-watcher",
|
||||
instantiate: (di): CreateKubeSyncWatcher => {
|
||||
const watch = di.inject(watchInjectable);
|
||||
const logger = di.inject(loggerInjectable);
|
||||
|
||||
return (filePath, { isDirectorySync, ...handlers }) => {
|
||||
const watcher = watch<true>(filePath, {
|
||||
followSymlinks: true,
|
||||
depth: isDirectorySync ? 0 : 1, // DIRs works with 0 but files need 1 (bug: https://github.com/paulmillr/chokidar/issues/1095)
|
||||
disableGlobbing: true,
|
||||
ignorePermissionErrors: true,
|
||||
usePolling: false,
|
||||
awaitWriteFinish: {
|
||||
pollInterval: 100,
|
||||
stabilityThreshold: 1000,
|
||||
},
|
||||
atomic: 150, // for "atomic writes"
|
||||
alwaysStat: true,
|
||||
});
|
||||
|
||||
watcher
|
||||
.on("change", handlers.onChange)
|
||||
.on("add", handlers.onAdd)
|
||||
.on("unlink", handlers.onRemove)
|
||||
.on("error", handlers.onError);
|
||||
|
||||
return {
|
||||
stop: () => {
|
||||
void (async () => {
|
||||
try {
|
||||
await watcher.close();
|
||||
} catch (error) {
|
||||
logger.warn(`[KUBE-SYNC-WATCHER]: failed to stop watching "${filePath}": ${error}`);
|
||||
}
|
||||
})();
|
||||
},
|
||||
};
|
||||
};
|
||||
},
|
||||
});
|
||||
|
||||
export default createKubeSyncWatcherInjectable;
|
||||
@ -11,8 +11,8 @@ import { inspect } from "util";
|
||||
import type { CatalogEntity } from "../../../common/catalog";
|
||||
import type { Cluster } from "../../../common/cluster/cluster";
|
||||
import statInjectable from "../../../common/fs/stat.injectable";
|
||||
import type { Watcher } from "../../../common/fs/watch/watch.injectable";
|
||||
import watchInjectable from "../../../common/fs/watch/watch.injectable";
|
||||
import type { KubeSyncWatcher } from "./create-watcher.injectable";
|
||||
import createKubeSyncWatcherInjectable from "./create-watcher.injectable";
|
||||
import type { Disposer } from "../../../common/utils";
|
||||
import { getOrInsertWith, iter } from "../../../common/utils";
|
||||
import diffChangedKubeconfigInjectable from "./diff-changed-kubeconfig.injectable";
|
||||
@ -38,8 +38,8 @@ const ignoreGlobs = [
|
||||
* Even if you have a cert-file, key-file, and client-cert files that is only
|
||||
* 12kb of extra data (at 4096 bytes each) which allows for around 150 entries.
|
||||
*/
|
||||
const folderSyncMaxAllowedFileReadSize = 2 * 1024 * 1024; // 2 MiB
|
||||
const fileSyncMaxAllowedFileReadSize = 16 * folderSyncMaxAllowedFileReadSize; // 32 MiB
|
||||
const dirSyncMaxAllowedFileReadSize = 2 * 1024 * 1024; // 2 MiB
|
||||
const fileSyncMaxAllowedFileReadSize = 16 * dirSyncMaxAllowedFileReadSize; // 32 MiB
|
||||
|
||||
const watchKubeconfigFileChangesInjectable = getInjectable({
|
||||
id: "watch-kubeconfig-file-changes",
|
||||
@ -47,39 +47,27 @@ const watchKubeconfigFileChangesInjectable = getInjectable({
|
||||
const diffChangedKubeconfig = di.inject(diffChangedKubeconfigInjectable);
|
||||
const logger = di.inject(kubeconfigSyncLoggerInjectable);
|
||||
const stat = di.inject(statInjectable);
|
||||
const watch = di.inject(watchInjectable);
|
||||
const createKubeSyncWatcher = di.inject(createKubeSyncWatcherInjectable);
|
||||
|
||||
return (filePath) => {
|
||||
const rootSource = observable.map<string, ObservableMap<string, [Cluster, CatalogEntity]>>();
|
||||
const derivedSource = computed(() => Array.from(iter.flatMap(rootSource.values(), from => iter.map(from.values(), child => child[1]))));
|
||||
|
||||
let watcher: Watcher<true>;
|
||||
let watcher: KubeSyncWatcher;
|
||||
|
||||
(async () => {
|
||||
try {
|
||||
const stats = await stat(filePath);
|
||||
const isFolderSync = stats.isDirectory();
|
||||
const isDirectorySync = stats.isDirectory();
|
||||
const cleanupFns = new Map<string, Disposer>();
|
||||
const maxAllowedFileReadSize = isFolderSync
|
||||
? folderSyncMaxAllowedFileReadSize
|
||||
const maxAllowedFileReadSize = isDirectorySync
|
||||
? dirSyncMaxAllowedFileReadSize
|
||||
: fileSyncMaxAllowedFileReadSize;
|
||||
|
||||
watcher = watch<true>(filePath, {
|
||||
followSymlinks: true,
|
||||
depth: isFolderSync ? 0 : 1, // DIRs works with 0 but files need 1 (bug: https://github.com/paulmillr/chokidar/issues/1095)
|
||||
disableGlobbing: true,
|
||||
ignorePermissionErrors: true,
|
||||
usePolling: false,
|
||||
awaitWriteFinish: {
|
||||
pollInterval: 100,
|
||||
stabilityThreshold: 1000,
|
||||
},
|
||||
atomic: 150, // for "atomic writes"
|
||||
alwaysStat: true,
|
||||
});
|
||||
|
||||
watcher
|
||||
.on("change", (childFilePath, stats): void => {
|
||||
watcher = createKubeSyncWatcher(filePath, {
|
||||
isDirectorySync,
|
||||
onChange: (childFilePath, stats): void => {
|
||||
console.log("change", childFilePath);
|
||||
const cleanup = cleanupFns.get(childFilePath);
|
||||
|
||||
if (!cleanup) {
|
||||
@ -94,9 +82,11 @@ const watchKubeconfigFileChangesInjectable = getInjectable({
|
||||
stats,
|
||||
maxAllowedFileReadSize,
|
||||
}));
|
||||
})
|
||||
.on("add", (childFilePath, stats): void => {
|
||||
if (isFolderSync) {
|
||||
},
|
||||
onAdd: (childFilePath, stats): void => {
|
||||
console.log("add", childFilePath);
|
||||
|
||||
if (isDirectorySync) {
|
||||
const fileName = path.basename(childFilePath);
|
||||
|
||||
for (const ignoreGlob of ignoreGlobs) {
|
||||
@ -112,20 +102,24 @@ const watchKubeconfigFileChangesInjectable = getInjectable({
|
||||
stats,
|
||||
maxAllowedFileReadSize,
|
||||
}));
|
||||
})
|
||||
.on("unlink", (childFilePath) => {
|
||||
},
|
||||
onRemove: (childFilePath) => {
|
||||
cleanupFns.get(childFilePath)?.();
|
||||
cleanupFns.delete(childFilePath);
|
||||
rootSource.delete(childFilePath);
|
||||
})
|
||||
.on("error", error => logger.error(`watching file/folder failed: ${error}`, { filePath }));
|
||||
},
|
||||
onError: (error) => {
|
||||
console.log("error", error);
|
||||
logger.error(`watching file/folder failed: ${error}`, { filePath });
|
||||
},
|
||||
});
|
||||
} catch (error) {
|
||||
logger.warn(`failed to start watching changes: ${error}`);
|
||||
}
|
||||
})();
|
||||
|
||||
return [derivedSource, () => {
|
||||
watcher?.close();
|
||||
watcher?.stop();
|
||||
}];
|
||||
};
|
||||
},
|
||||
|
||||
@ -307,6 +307,7 @@ class NonInjectedCatalog extends React.Component<Dependencies> {
|
||||
getItems={() => catalogEntityStore.entities.get()}
|
||||
customizeTableRowProps={entity => ({
|
||||
disabled: !entity.isEnabled(),
|
||||
testId: `catalog-entity-row-for-${entity.getId()}`,
|
||||
})}
|
||||
{...getCategoryColumns({ activeCategory })}
|
||||
onDetails={this.onDetails}
|
||||
|
||||
@ -9,7 +9,7 @@ import { readFile } from "fs/promises";
|
||||
import { hasCorrectExtension } from "./has-correct-extension";
|
||||
import type { RawTemplates } from "./create-resource-templates.injectable";
|
||||
import joinPathsInjectable from "../../../../common/path/join-paths.injectable";
|
||||
import watchInjectable from "../../../../common/fs/watch/watch.injectable";
|
||||
import watchInjectable from "../../../../common/fs/watch.injectable";
|
||||
import getRelativePathInjectable from "../../../../common/path/get-relative-path.injectable";
|
||||
import homeDirectoryPathInjectable from "../../../../common/os/home-directory-path.injectable";
|
||||
import getDirnameOfPathInjectable from "../../../../common/path/get-dirname.injectable";
|
||||
|
||||
@ -192,7 +192,7 @@ export const getApplicationBuilder = ({ useFakeTime = true }: ApplicationBuilder
|
||||
|
||||
const overrideFsWithFakes = getOverrideFsWithFakes();
|
||||
|
||||
overrideFsWithFakes(mainDi);
|
||||
overrideFsWithFakes(mainDi, true);
|
||||
|
||||
// Set up ~/.kube as existing as a folder
|
||||
{
|
||||
|
||||
@ -10,6 +10,9 @@ import type {
|
||||
readJsonSync as readJsonSyncImpl,
|
||||
writeJsonSync as writeJsonSyncImpl,
|
||||
} from "fs-extra";
|
||||
import createKubeSyncWatcherInjectable from "../main/catalog-sources/kubeconfig-sync/create-watcher.injectable";
|
||||
import { isErrnoException } from "../common/utils";
|
||||
import joinPathsInjectable from "../common/path/join-paths.injectable";
|
||||
|
||||
export const getOverrideFsWithFakes = () => {
|
||||
const root = createFsFromVolume(Volume.fromJSON({}));
|
||||
@ -41,7 +44,7 @@ export const getOverrideFsWithFakes = () => {
|
||||
root.mkdirpSync(path, mode);
|
||||
}) as typeof ensureDirSyncImpl;
|
||||
|
||||
return (di: DiContainer) => {
|
||||
return (di: DiContainer, overrideWatches = false) => {
|
||||
di.override(fsInjectable, () => ({
|
||||
pathExists: async (path) => root.existsSync(path),
|
||||
pathExistsSync: root.existsSync,
|
||||
@ -63,5 +66,76 @@ export const getOverrideFsWithFakes = () => {
|
||||
createReadStream: root.createReadStream as any,
|
||||
stat: root.promises.stat as any,
|
||||
}));
|
||||
|
||||
if (overrideWatches) {
|
||||
di.override(createKubeSyncWatcherInjectable, (di) => {
|
||||
const joinPaths = di.inject(joinPathsInjectable);
|
||||
|
||||
return ((path, options) => {
|
||||
const watcher = root.watch( path, {
|
||||
recursive: options.isDirectorySync,
|
||||
});
|
||||
const seenPaths = new Set<string>();
|
||||
|
||||
console.log("watching", path);
|
||||
|
||||
watcher.addListener("rename", (eventType, filename: string) => {
|
||||
try {
|
||||
const stats = root.statSync(filename);
|
||||
|
||||
options.onAdd(filename, stats);
|
||||
} catch (error) {
|
||||
if (isErrnoException(error) && error.code === "ENOENT") {
|
||||
options.onRemove(filename);
|
||||
} else {
|
||||
options.onError(error as Error);
|
||||
}
|
||||
}
|
||||
});
|
||||
watcher.addListener("change", (...args) => {
|
||||
const [,filename] = args;
|
||||
|
||||
if (options.isDirectorySync) {
|
||||
// For testing purposes just emit change events for all files
|
||||
for (const entry of root.readdirSync(filename) as string[]) {
|
||||
const path = joinPaths(filename, entry);
|
||||
|
||||
try {
|
||||
const stats = root.statSync(path);
|
||||
|
||||
if (seenPaths.has(path)) {
|
||||
options.onChange(path, stats);
|
||||
} else {
|
||||
seenPaths.add(path);
|
||||
options.onAdd(path, stats);
|
||||
}
|
||||
} catch (error) {
|
||||
options.onError(error as Error);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
try {
|
||||
const stats = root.statSync(filename);
|
||||
|
||||
if (seenPaths.has(filename)) {
|
||||
options.onChange(filename, stats);
|
||||
} else {
|
||||
seenPaths.add(filename);
|
||||
options.onAdd(filename, stats);
|
||||
}
|
||||
} catch (error) {
|
||||
options.onError(error as Error);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
return {
|
||||
stop: () => {
|
||||
watcher.close();
|
||||
},
|
||||
};
|
||||
});
|
||||
});
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
Loading…
Reference in New Issue
Block a user