Turn on strict mode in tsconfig.json, some helpful lints, and required cleanup where strictness necessitates it (#5195)

* Turn on strict mode in tsconfig.json

- Add route, clusterRoute, and payloadValidatedClusterRoute helper
  functions to improve types with backend routes

- Turn on the following new lints:
  - react/jsx-first-prop-new-line
  - react/jsx-wrap-multilines
  - react/jsx-one-expression-per-line
  - react/jsx-max-props-per-line
  - react/jsx-indent
  - react/jsx-indent-props

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

* fix build

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

* Replace KubeObject scope strings with enum

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

* Revert package.json version changes

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

* revert move hostedCluster(Id)

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

* change some type param names to be not single letters

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

* remove copy-extension-themes

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

* add new make clean action

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

* fix build to not use webpack for generating types

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

* fix kube-object-menu.test.tsx

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

* fix select.test.tsx

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

* fix catalog.test.tsx

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

* revert move fileNameMigration to index

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

* fix ref logic error

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

* fix log-resource-selector.test.tsx tests

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

* fix dock-store.test.ts test by overriding createStorage to not touch file system

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

* fix cluster.test.ts tests

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

* fix kube=api.test.ts

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

* fixed hotbar-store.test.ts

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

* fix kubeconfig-manager.test.ts

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

* fix cluster-role-bindings/__tests__/dialog.test.tsx

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

* fix role-bindings/__tests__/dialog.test.tsx

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

* fix pods.test.ts

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

* fix delete-cluster-dialog.test.tsx

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

* fix daemonset.store.test.ts

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

* fix replicaset.store.test.ts

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

* fix statefulsets/dialog/dialog.test.tsx

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

* fix replicasets/scale-dialog/dialog.test.tsx

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

* fix deployments.store.test.ts

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

* fix deployments/scale/dialog.test.tsx

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

* fix cronjob.store.test.ts

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

* fix stateful-set.api.test.ts

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

* fix deployment.api.test.ts

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

* fix api-manager.test.ts

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

* fix statefulset.store.test.ts

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

* fix job.store.test.ts

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

* fix pods.store.test.ts

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

* fix scroll-spy.test.tsx

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

* fix hotbar-remove-command.test.tsx

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

* fix catalog-entity-registry.test.ts

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

* fix welcome.test.tsx

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

* fix verify-that-all-routes-have-route-component.test.ts

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

* fix pod-tolerations.test.tsx

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

* better fix for previous 3 fixes, plus also select.test.tsx

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

* fix kube-object-menu.test.tsx

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

* fix app-paths.test.ts

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

* fix dock-tabs.test.tsx

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

* fix isReactNode typing

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

* fix sub-title.tsx

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

* fix drawer.tsx

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

* fix list-layout.tsx and header.tsx

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

* fix error-boundary.tsx

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

* fix upgrade-chart/store.ts and dock-tab.store.ts

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

* fix install-chart/store.ts

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

* fix edit-resource/store.ts

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

* fix create-resource/store.ts

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

* fix namespace-select.tsx

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

* fix namespace-select-filter.tsx

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

* fix crd-list.tsx

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

* fix wrong types for extensions

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

* fix circular dependency

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

* fix circular dependency on catalogCategoryRegistry

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

* fix api-kube

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

* fix type errors, most <Select /> errors

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

* fixing more type errors

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

* some more fixing type errors

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

* convert all KubeApis to injectable with legacy global backups

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

* factor out into a common file all the exports

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

* convert all KubeObjectStores to injectable with legacy global backups

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

* fix lint

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

* remove unused legacy KubeApi globals

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

* fix bad previous commit

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

* more crash fixing

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

* try and fix behavioural tests

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

* fix sidebar-and-tab-navigation-for-core.test.tsx

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

* fix sidebar-and-tab-navigation-for-extensions.test.tsx

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

* fix navigation-using-application-menu.test.ts

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

* fix catalog.test.tsx

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

* Make ThemeStore non-singleton and fix navigation-to-terminal-preferences.test.ts

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

* extensions.test.tsx

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

* fix catalog-entity-registry.test.ts

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

* fix navigation-using-application-menu.test.ts

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

* fix log-resource-selector.test.tsx

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

* fix dock-tabs.test.tsx

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

* fix delete-cluster-dialog.test.tsx

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

* fix navigation-to-kubernetes-preferences.test.ts

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

* fix navigation-to-editor-preferences.test.ts

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

* fix navigation-to-proxy-preferences.test.ts

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

* fix navigation-using-application-menu.test.ts

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

* fix navigation-to-application-preferences.test.ts

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

* fix dock-store.test.ts

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

* fix select.test.tsx

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

* fix role-bindings/__tests__/dialog.test.tsx

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

* fix hotbar-remove-command.test.tsx

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

* fix cluster-role-bindings/__tests__/dialog.test.tsx

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

* fix navigation-to-extension-specific-preferences.test.tsx

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

* fix navigation-to-telemetry-preferences.test.tsx

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

* fix closing-preferences.test.tsx

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

* fix navigation-to-editor-preferences.test.ts

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

* fix navigation-to-proxy-preferences.test.ts

- Fix other type errors too

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

* final tweaks

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

* Add more tsconfig files, fix bug in <Catalog>

- Make all of history, navigation injectable

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

* fix type errors

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

* Convert all of kube-details-params/ and navigate/ to injectable

- This fixes a runtime error that was encountered during testing

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

* Fix runtime errors on renderer

- remove all static uses of `createPageParam` (and then removed the
  legacy global)
- Made LensRendererExtension and LensMainExtension just used
  dependencies and not the getLegacyDi
- Fixed circular dep in extension-loader

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

* Move registerStore calls to after injectMany

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

* replace all the rest of the legacy uses of apiManager

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

* fix stack overflow and cycles in DI

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

* fix NamespaceSelectFilter not opening

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

* fix WizardStep and AddNamespaceDialog

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

* fix KubeApi's not being registered

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

* cleanup WindowManager

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

* Proper fix for Wizard, fix NamespaceStore.subscribe

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

* Rewrite withTooltip to be more type correct

- Fixes mobx related "too many recursive actions" error

- Change all the uses of withTooltips to be functional components

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

* Add e2e test to cover kube api registration

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

* cleanup internal-commands

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

* remove cast in <Animate>

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

* Fix command-palette e2e test

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

* Fix type error after rebase

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

* Fix test name

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

* fix lint

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

* fix code to help CodeQL scanner

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

* update intree extension lock files

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

* Fix build-extensions picking wrong @types/react

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

* fix tests from rebase

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

* fix type error

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

* Make KubeconfigSyncManager more injectable

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

* fix crash in test mode for Dialog

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

* make Select snapshots deterministic

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

* fix new type error

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

* fix kube-object.store.test.ts typing

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

* fix merge build issues

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

* fix snapshots after merge

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

* fix lint after merge

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

* reexport BaseKubeJsonApiObjectMetadata

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

* fix typo in terminalSpawningPool

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

* remove duplicate license header

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

* fix typo to waitUntilDefined

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

* remove iter use from getLegacyGlobalDiForExtensionApi

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

* remove complex createStorage override

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

* override logger with mocks only when needed for tests

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

* remove specialized overrideStore flags for getDiForUnitTesting

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

* remove unnecessary | undefined types from the exactOptionalFieldTypes experiment

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

* use more descriptive name for local test mocks

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

* remove unnecessary addition to 'make clean' target

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

* remove oddity of KubeObjectStore.getById(undefined) being allowed

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

* rename KubeObject.getDescriptor in favour of name without fundemental JS meaning

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

* Simplify legacyRegisterApi when working in behaviour unit tests

- Don't emit within main environment as there should be no auto
  registering there

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

* change confusing variable name in ReactiveDuration

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

* make visitor pattern more explicit for Entity contextMenuOpen

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

* toggleDetails -> toggleKubeDetailsPane is more specific

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

* remove outdated comment

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

* fix bug where LensExtension dependencies are not set

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

* Fix tests from the revert of react 18

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

* fix more tests from merge

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

* fix typings with new is-compatible-extension tests

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

* more type fixing

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

* Revert in-tree extension versions

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

* Improve name of guarding injectable for stores and apis

- New name better implies that it is just a guard state and does not do
  anything

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

* Add helper for <Select>.isMulti for storing in a Set<Value>

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

* fix is-compatible-extension.test.ts types

Signed-off-by: Sebastian Malton <sebastian@malton.name>
This commit is contained in:
Sebastian Malton authored and GitHub committed 2022-05-16 07:17:57 -04:00
1 parent 381d77c633
commit dfcb7c3330
1062 files changed
+26512 -28689

No files matched your search

@@ -19,7 +19,7 @@ exports[`add-cluster - navigation using application menu when navigating to add
Add Clusters from Kubeconfig
</h2>
<p>
Clusters added here are
Clusters added here are
<b>
not
</b>
@@ -27,16 +27,14 @@ exports[`add-cluster - navigation using application menu when navigating to add
<code>
~/.kube/config
</code>
file.
file.
<a
href="https://docs.k8slens.dev/main//catalog/add-clusters/"
rel="noreferrer"
target="_blank"
>
Read more about adding clusters
Read more about adding clusters.
</a>
.
</p>
<div
class="flex column"
@@ -7,10 +7,18 @@ import type { RenderResult } from "@testing-library/react";
import type { ApplicationBuilder } from "../../renderer/components/test-utils/get-application-builder";
import { getApplicationBuilder } from "../../renderer/components/test-utils/get-application-builder";
import isAutoUpdateEnabledInjectable from "../../main/is-auto-update-enabled.injectable";
import React from "react";
// TODO: Make components free of side effects by making them deterministic
jest.mock("../../renderer/components/tooltip");
jest.mock("../../renderer/components/monaco-editor/monaco-editor");
jest.mock("../../renderer/components/tooltip/tooltip", () => ({
Tooltip: () => null,
}));
jest.mock("../../renderer/components/tooltip/withTooltip", () => ({
withTooltip: (Target: any) => ({ tooltip, tooltipOverrideDisabled, ...props }: any) => <Target {...props} />,
}));
jest.mock("../../renderer/components/monaco-editor/monaco-editor", () => ({
MonacoEditor: () => null,
}));
describe("add-cluster - navigation using application menu", () => {
let applicationBuilder: ApplicationBuilder;
@@ -261,14 +261,14 @@ exports[`cluster - sidebar and tab navigation for extensions given extension wit
</div>
<div
class="SidebarItem"
data-id-test="some-extension-id-some-parent-id"
data-id-test="some-extension-name-some-parent-id"
data-is-active-test="false"
data-test-id="some-extension-id-some-parent-id"
data-test-id="some-extension-name-some-parent-id"
data-testid="sidebar-item"
>
<a
class="nav-item flex gaps align-center expandable"
data-testid="sidebar-item-link-for-some-extension-id-some-parent-id"
data-testid="sidebar-item-link-for-some-extension-name-some-parent-id"
href="/"
>
<div>
@@ -557,14 +557,14 @@ exports[`cluster - sidebar and tab navigation for extensions given extension wit
</div>
<div
class="SidebarItem"
data-id-test="some-extension-id-some-parent-id"
data-id-test="some-extension-name-some-parent-id"
data-is-active-test="false"
data-test-id="some-extension-id-some-parent-id"
data-test-id="some-extension-name-some-parent-id"
data-testid="sidebar-item"
>
<a
class="nav-item flex gaps align-center expandable"
data-testid="sidebar-item-link-for-some-extension-id-some-parent-id"
data-testid="sidebar-item-link-for-some-extension-name-some-parent-id"
href="/"
>
<div>
@@ -853,14 +853,14 @@ exports[`cluster - sidebar and tab navigation for extensions given extension wit
</div>
<div
class="SidebarItem"
data-id-test="some-extension-id-some-parent-id"
data-id-test="some-extension-name-some-parent-id"
data-is-active-test="false"
data-test-id="some-extension-id-some-parent-id"
data-test-id="some-extension-name-some-parent-id"
data-testid="sidebar-item"
>
<a
class="nav-item flex gaps align-center expandable"
data-testid="sidebar-item-link-for-some-extension-id-some-parent-id"
data-testid="sidebar-item-link-for-some-extension-name-some-parent-id"
href="/"
>
<div>
@@ -887,15 +887,15 @@ exports[`cluster - sidebar and tab navigation for extensions given extension wit
>
<div
class="SidebarItem"
data-id-test="some-extension-id-some-child-id"
data-id-test="some-extension-name-some-child-id"
data-is-active-test="false"
data-parent-id-test="some-extension-id-some-parent-id"
data-test-id="some-extension-id-some-child-id"
data-parent-id-test="some-extension-name-some-parent-id"
data-test-id="some-extension-name-some-child-id"
data-testid="sidebar-item"
>
<a
class="nav-item flex gaps align-center"
data-testid="sidebar-item-link-for-some-extension-id-some-child-id"
data-testid="sidebar-item-link-for-some-extension-name-some-child-id"
href="/"
>
<span
@@ -907,15 +907,15 @@ exports[`cluster - sidebar and tab navigation for extensions given extension wit
</div>
<div
class="SidebarItem"
data-id-test="some-extension-id-some-other-child-id"
data-id-test="some-extension-name-some-other-child-id"
data-is-active-test="false"
data-parent-id-test="some-extension-id-some-parent-id"
data-test-id="some-extension-id-some-other-child-id"
data-parent-id-test="some-extension-name-some-parent-id"
data-test-id="some-extension-name-some-other-child-id"
data-testid="sidebar-item"
>
<a
class="nav-item flex gaps align-center"
data-testid="sidebar-item-link-for-some-extension-id-some-other-child-id"
data-testid="sidebar-item-link-for-some-extension-name-some-other-child-id"
href="/"
>
<span
@@ -1193,15 +1193,15 @@ exports[`cluster - sidebar and tab navigation for extensions given extension wit
</div>
<div
class="SidebarItem"
data-id-test="some-extension-id-some-parent-id"
data-id-test="some-extension-name-some-parent-id"
data-is-active-test="true"
data-test-id="some-extension-id-some-parent-id"
data-test-id="some-extension-name-some-parent-id"
data-testid="sidebar-item"
>
<a
aria-current="page"
class="nav-item flex gaps align-center expandable active"
data-testid="sidebar-item-link-for-some-extension-id-some-parent-id"
data-testid="sidebar-item-link-for-some-extension-name-some-parent-id"
href="/"
>
<div>
@@ -1228,16 +1228,16 @@ exports[`cluster - sidebar and tab navigation for extensions given extension wit
>
<div
class="SidebarItem"
data-id-test="some-extension-id-some-child-id"
data-id-test="some-extension-name-some-child-id"
data-is-active-test="true"
data-parent-id-test="some-extension-id-some-parent-id"
data-test-id="some-extension-id-some-child-id"
data-parent-id-test="some-extension-name-some-parent-id"
data-test-id="some-extension-name-some-child-id"
data-testid="sidebar-item"
>
<a
aria-current="page"
class="nav-item flex gaps align-center active"
data-testid="sidebar-item-link-for-some-extension-id-some-child-id"
data-testid="sidebar-item-link-for-some-extension-name-some-child-id"
href="/"
>
<span
@@ -1249,15 +1249,15 @@ exports[`cluster - sidebar and tab navigation for extensions given extension wit
</div>
<div
class="SidebarItem"
data-id-test="some-extension-id-some-other-child-id"
data-id-test="some-extension-name-some-other-child-id"
data-is-active-test="false"
data-parent-id-test="some-extension-id-some-parent-id"
data-test-id="some-extension-id-some-other-child-id"
data-parent-id-test="some-extension-name-some-parent-id"
data-test-id="some-extension-name-some-other-child-id"
data-testid="sidebar-item"
>
<a
class="nav-item flex gaps align-center"
data-testid="sidebar-item-link-for-some-extension-id-some-other-child-id"
data-testid="sidebar-item-link-for-some-extension-name-some-other-child-id"
href="/"
>
<span
@@ -1281,7 +1281,7 @@ exports[`cluster - sidebar and tab navigation for extensions given extension wit
<div
class="Tab flex gaps align-center active"
data-is-active-test="true"
data-testid="tab-link-for-some-extension-id-some-child-id"
data-testid="tab-link-for-some-extension-name-some-child-id"
role="tab"
tabindex="0"
>
@@ -1294,7 +1294,7 @@ exports[`cluster - sidebar and tab navigation for extensions given extension wit
<div
class="Tab flex gaps align-center"
data-is-active-test="false"
data-testid="tab-link-for-some-extension-id-some-other-child-id"
data-testid="tab-link-for-some-extension-name-some-other-child-id"
role="tab"
tabindex="0"
>
@@ -1577,15 +1577,15 @@ exports[`cluster - sidebar and tab navigation for extensions given extension wit
</div>
<div
class="SidebarItem"
data-id-test="some-extension-id-some-parent-id"
data-id-test="some-extension-name-some-parent-id"
data-is-active-test="true"
data-test-id="some-extension-id-some-parent-id"
data-test-id="some-extension-name-some-parent-id"
data-testid="sidebar-item"
>
<a
aria-current="page"
class="nav-item flex gaps align-center expandable active"
data-testid="sidebar-item-link-for-some-extension-id-some-parent-id"
data-testid="sidebar-item-link-for-some-extension-name-some-parent-id"
href="/"
>
<div>
@@ -1612,15 +1612,15 @@ exports[`cluster - sidebar and tab navigation for extensions given extension wit
>
<div
class="SidebarItem"
data-id-test="some-extension-id-some-child-id"
data-id-test="some-extension-name-some-child-id"
data-is-active-test="false"
data-parent-id-test="some-extension-id-some-parent-id"
data-test-id="some-extension-id-some-child-id"
data-parent-id-test="some-extension-name-some-parent-id"
data-test-id="some-extension-name-some-child-id"
data-testid="sidebar-item"
>
<a
class="nav-item flex gaps align-center"
data-testid="sidebar-item-link-for-some-extension-id-some-child-id"
data-testid="sidebar-item-link-for-some-extension-name-some-child-id"
href="/"
>
<span
@@ -1632,16 +1632,16 @@ exports[`cluster - sidebar and tab navigation for extensions given extension wit
</div>
<div
class="SidebarItem"
data-id-test="some-extension-id-some-other-child-id"
data-id-test="some-extension-name-some-other-child-id"
data-is-active-test="true"
data-parent-id-test="some-extension-id-some-parent-id"
data-test-id="some-extension-id-some-other-child-id"
data-parent-id-test="some-extension-name-some-parent-id"
data-test-id="some-extension-name-some-other-child-id"
data-testid="sidebar-item"
>
<a
aria-current="page"
class="nav-item flex gaps align-center active"
data-testid="sidebar-item-link-for-some-extension-id-some-other-child-id"
data-testid="sidebar-item-link-for-some-extension-name-some-other-child-id"
href="/"
>
<span
@@ -1665,7 +1665,7 @@ exports[`cluster - sidebar and tab navigation for extensions given extension wit
<div
class="Tab flex gaps align-center"
data-is-active-test="false"
data-testid="tab-link-for-some-extension-id-some-child-id"
data-testid="tab-link-for-some-extension-name-some-child-id"
role="tab"
tabindex="0"
>
@@ -1678,7 +1678,7 @@ exports[`cluster - sidebar and tab navigation for extensions given extension wit
<div
class="Tab flex gaps align-center active"
data-is-active-test="true"
data-testid="tab-link-for-some-extension-id-some-other-child-id"
data-testid="tab-link-for-some-extension-name-some-other-child-id"
role="tab"
tabindex="0"
>
@@ -1961,15 +1961,15 @@ exports[`cluster - sidebar and tab navigation for extensions given extension wit
</div>
<div
class="SidebarItem"
data-id-test="some-extension-id-some-parent-id"
data-id-test="some-extension-name-some-parent-id"
data-is-active-test="true"
data-test-id="some-extension-id-some-parent-id"
data-test-id="some-extension-name-some-parent-id"
data-testid="sidebar-item"
>
<a
aria-current="page"
class="nav-item flex gaps align-center expandable active"
data-testid="sidebar-item-link-for-some-extension-id-some-parent-id"
data-testid="sidebar-item-link-for-some-extension-name-some-parent-id"
href="/"
>
<div>
@@ -2004,7 +2004,7 @@ exports[`cluster - sidebar and tab navigation for extensions given extension wit
<div
class="Tab flex gaps align-center active"
data-is-active-test="true"
data-testid="tab-link-for-some-extension-id-some-child-id"
data-testid="tab-link-for-some-extension-name-some-child-id"
role="tab"
tabindex="0"
>
@@ -2017,7 +2017,7 @@ exports[`cluster - sidebar and tab navigation for extensions given extension wit
<div
class="Tab flex gaps align-center"
data-is-active-test="false"
data-testid="tab-link-for-some-extension-id-some-other-child-id"
data-testid="tab-link-for-some-extension-name-some-other-child-id"
role="tab"
tabindex="0"
>
@@ -2300,14 +2300,14 @@ exports[`cluster - sidebar and tab navigation for extensions given extension wit
</div>
<div
class="SidebarItem"
data-id-test="some-extension-id-some-parent-id"
data-id-test="some-extension-name-some-parent-id"
data-is-active-test="false"
data-test-id="some-extension-id-some-parent-id"
data-test-id="some-extension-name-some-parent-id"
data-testid="sidebar-item"
>
<a
class="nav-item flex gaps align-center expandable"
data-testid="sidebar-item-link-for-some-extension-id-some-parent-id"
data-testid="sidebar-item-link-for-some-extension-name-some-parent-id"
href="/"
>
<div>
@@ -2334,15 +2334,15 @@ exports[`cluster - sidebar and tab navigation for extensions given extension wit
>
<div
class="SidebarItem"
data-id-test="some-extension-id-some-child-id"
data-id-test="some-extension-name-some-child-id"
data-is-active-test="false"
data-parent-id-test="some-extension-id-some-parent-id"
data-test-id="some-extension-id-some-child-id"
data-parent-id-test="some-extension-name-some-parent-id"
data-test-id="some-extension-name-some-child-id"
data-testid="sidebar-item"
>
<a
class="nav-item flex gaps align-center"
data-testid="sidebar-item-link-for-some-extension-id-some-child-id"
data-testid="sidebar-item-link-for-some-extension-name-some-child-id"
href="/"
>
<span
@@ -2354,15 +2354,15 @@ exports[`cluster - sidebar and tab navigation for extensions given extension wit
</div>
<div
class="SidebarItem"
data-id-test="some-extension-id-some-other-child-id"
data-id-test="some-extension-name-some-other-child-id"
data-is-active-test="false"
data-parent-id-test="some-extension-id-some-parent-id"
data-test-id="some-extension-id-some-other-child-id"
data-parent-id-test="some-extension-name-some-parent-id"
data-test-id="some-extension-name-some-other-child-id"
data-testid="sidebar-item"
>
<a
class="nav-item flex gaps align-center"
data-testid="sidebar-item-link-for-some-extension-id-some-other-child-id"
data-testid="sidebar-item-link-for-some-extension-name-some-other-child-id"
href="/"
>
<span
@@ -2640,14 +2640,14 @@ exports[`cluster - sidebar and tab navigation for extensions given extension wit
</div>
<div
class="SidebarItem"
data-id-test="some-extension-id-some-parent-id"
data-id-test="some-extension-name-some-parent-id"
data-is-active-test="false"
data-test-id="some-extension-id-some-parent-id"
data-test-id="some-extension-name-some-parent-id"
data-testid="sidebar-item"
>
<a
class="nav-item flex gaps align-center expandable"
data-testid="sidebar-item-link-for-some-extension-id-some-parent-id"
data-testid="sidebar-item-link-for-some-extension-name-some-parent-id"
href="/"
>
<div>
@@ -21,6 +21,8 @@ import writeJsonFileInjectable from "../../common/fs/write-json-file.injectable"
import pathExistsInjectable from "../../common/fs/path-exists.injectable";
import readJsonFileInjectable from "../../common/fs/read-json-file.injectable";
import { navigateToRouteInjectionToken } from "../../common/front-end-routing/navigate-to-route-injection-token";
import { getSidebarItem } from "../utils";
import sidebarStorageInjectable from "../../renderer/components/layout/sidebar-storage/sidebar-storage.injectable";
describe("cluster - sidebar and tab navigation for core", () => {
let applicationBuilder: ApplicationBuilder;
@@ -34,7 +36,6 @@ describe("cluster - sidebar and tab navigation for core", () => {
rendererDi = applicationBuilder.dis.rendererDi;
applicationBuilder.setEnvironmentToClusterFrame();
applicationBuilder.beforeSetups(({ rendererDi }) => {
rendererDi.override(
directoryForLensLocalStorageInjectable,
@@ -72,13 +73,13 @@ describe("cluster - sidebar and tab navigation for core", () => {
it("parent is highlighted", () => {
const parent = getSidebarItem(rendered, "some-parent-id");
expect(parent.dataset.isActiveTest).toBe("true");
expect(parent?.dataset.isActiveTest).toBe("true");
});
it("parent sidebar item is not expanded", () => {
const child = getSidebarItem(rendered, "some-child-id");
expect(child).toBe(null);
expect(child).toBeUndefined();
});
it("child page is shown", () => {
@@ -101,6 +102,11 @@ describe("cluster - sidebar and tab navigation for core", () => {
},
);
});
applicationBuilder.beforeRender(async ({ rendererDi }) => {
const sidebarStorage = rendererDi.inject(sidebarStorageInjectable);
await sidebarStorage.whenReady;
});
rendered = await applicationBuilder.render();
});
@@ -112,13 +118,13 @@ describe("cluster - sidebar and tab navigation for core", () => {
it("parent sidebar item is not highlighted", () => {
const parent = getSidebarItem(rendered, "some-parent-id");
expect(parent.dataset.isActiveTest).toBe("false");
expect(parent?.dataset.isActiveTest).toBe("false");
});
it("parent sidebar item is expanded", () => {
const child = getSidebarItem(rendered, "some-child-id");
expect(child).not.toBe(null);
expect(child).not.toBeUndefined();
});
});
@@ -148,7 +154,7 @@ describe("cluster - sidebar and tab navigation for core", () => {
it("parent sidebar item is not expanded", () => {
const child = getSidebarItem(rendered, "some-child-id");
expect(child).toBe(null);
expect(child).toBeUndefined();
});
});
@@ -175,7 +181,7 @@ describe("cluster - sidebar and tab navigation for core", () => {
it("parent sidebar item is not expanded", () => {
const child = getSidebarItem(rendered, "some-child-id");
expect(child).toBe(null);
expect(child).toBeUndefined();
});
});
@@ -191,13 +197,13 @@ describe("cluster - sidebar and tab navigation for core", () => {
it("parent sidebar item is not highlighted", () => {
const parent = getSidebarItem(rendered, "some-parent-id");
expect(parent.dataset.isActiveTest).toBe("false");
expect(parent?.dataset.isActiveTest).toBe("false");
});
it("parent sidebar item is not expanded", () => {
const child = getSidebarItem(rendered, "some-child-id");
expect(child).toBe(null);
expect(child).toBeUndefined();
});
describe("when a parent sidebar item is expanded", () => {
@@ -216,13 +222,13 @@ describe("cluster - sidebar and tab navigation for core", () => {
it("parent sidebar item is not highlighted", () => {
const parent = getSidebarItem(rendered, "some-parent-id");
expect(parent.dataset.isActiveTest).toBe("false");
expect(parent?.dataset.isActiveTest).toBe("false");
});
it("parent sidebar item is expanded", () => {
const child = getSidebarItem(rendered, "some-child-id");
expect(child).not.toBe(null);
expect(child).not.toBeUndefined();
});
describe("when a child of the parent is selected", () => {
@@ -241,13 +247,13 @@ describe("cluster - sidebar and tab navigation for core", () => {
it("parent is highlighted", () => {
const parent = getSidebarItem(rendered, "some-parent-id");
expect(parent.dataset.isActiveTest).toBe("true");
expect(parent?.dataset.isActiveTest).toBe("true");
});
it("child is highlighted", () => {
const child = getSidebarItem(rendered, "some-child-id");
expect(child.dataset.isActiveTest).toBe("true");
expect(child?.dataset.isActiveTest).toBe("true");
});
it("child page is shown", () => {
@@ -288,11 +294,6 @@ describe("cluster - sidebar and tab navigation for core", () => {
});
});
const getSidebarItem = (rendered: RenderResult, itemId: string) =>
rendered
.queryAllByTestId("sidebar-item")
.find((x) => x.dataset.idTest === itemId) || null;
const testSidebarItemsInjectable = getInjectable({
id: "some-sidebar-items-injectable",
@@ -325,7 +326,7 @@ const testSidebarItemsInjectable = getInjectable({
injectionToken: sidebarItemsInjectionToken,
});
const testRouteInjectable = getInjectable({
const testRouteInjectable = getInjectable({
id: "some-route-injectable-id",
instantiate: () => ({
@@ -5,8 +5,6 @@
import React from "react";
import type { RenderResult } from "@testing-library/react";
import { fireEvent } from "@testing-library/react";
import { getRendererExtensionFake } from "../../renderer/components/test-utils/get-renderer-extension-fake";
import type { LensRendererExtension } from "../../extensions/lens-renderer-extension";
import directoryForLensLocalStorageInjectable from "../../common/directory-for-lens-local-storage/directory-for-lens-local-storage.injectable";
import routesInjectable from "../../renderer/routes/routes.injectable";
import { matches } from "lodash/fp";
@@ -17,6 +15,10 @@ import pathExistsInjectable from "../../common/fs/path-exists.injectable";
import readJsonFileInjectable from "../../common/fs/read-json-file.injectable";
import type { DiContainer } from "@ogre-tools/injectable";
import { navigateToRouteInjectionToken } from "../../common/front-end-routing/navigate-to-route-injection-token";
import assert from "assert";
import { getSidebarItem } from "../utils";
import type { FakeExtensionData } from "../../renderer/components/test-utils/get-renderer-extension-fake";
import { getRendererExtensionFakeFor } from "../../renderer/components/test-utils/get-renderer-extension-fake";
describe("cluster - sidebar and tab navigation for extensions", () => {
let applicationBuilder: ApplicationBuilder;
@@ -41,9 +43,8 @@ describe("cluster - sidebar and tab navigation for extensions", () => {
describe("given extension with cluster pages and cluster page menus", () => {
beforeEach(async () => {
const testExtension = getRendererExtensionFake(
extensionStubWithSidebarItems,
);
const getRendererExtensionFake = getRendererExtensionFakeFor(applicationBuilder);
const testExtension = getRendererExtensionFake(extensionStubWithSidebarItems);
await applicationBuilder.addExtensions(testExtension);
});
@@ -51,19 +52,17 @@ describe("cluster - sidebar and tab navigation for extensions", () => {
describe("given no state for expanded sidebar items exists, and navigated to child sidebar item, when rendered", () => {
beforeEach(async () => {
applicationBuilder.beforeRender(({ rendererDi }) => {
const navigateToRoute = rendererDi.inject(
navigateToRouteInjectionToken,
);
const navigateToRoute = rendererDi.inject(navigateToRouteInjectionToken);
const route = rendererDi
.inject(routesInjectable)
.get()
.find(
matches({
path: "/extension/some-extension-id/some-child-page-id",
path: "/extension/some-extension-name/some-child-page-id",
}),
);
assert(route);
navigateToRoute(route);
});
@@ -77,19 +76,19 @@ describe("cluster - sidebar and tab navigation for extensions", () => {
it("parent is highlighted", () => {
const parent = getSidebarItem(
rendered,
"some-extension-id-some-parent-id",
"some-extension-name-some-parent-id",
);
expect(parent.dataset.isActiveTest).toBe("true");
expect(parent?.dataset.isActiveTest).toBe("true");
});
it("parent sidebar item is not expanded", () => {
const child = getSidebarItem(
rendered,
"some-extension-id-some-child-id",
"some-extension-name-some-child-id",
);
expect(child).toBe(null);
expect(child).toBeUndefined();
});
it("child page is shown", () => {
@@ -106,7 +105,7 @@ describe("cluster - sidebar and tab navigation for extensions", () => {
"/some-directory-for-lens-local-storage/app.json",
{
sidebar: {
expanded: { "some-extension-id-some-parent-id": true },
expanded: { "some-extension-name-some-parent-id": true },
width: 200,
},
},
@@ -123,19 +122,19 @@ describe("cluster - sidebar and tab navigation for extensions", () => {
it("parent sidebar item is not highlighted", () => {
const parent = getSidebarItem(
rendered,
"some-extension-id-some-parent-id",
"some-extension-name-some-parent-id",
);
expect(parent.dataset.isActiveTest).toBe("false");
expect(parent?.dataset.isActiveTest).toBe("false");
});
it("parent sidebar item is expanded", () => {
const child = getSidebarItem(
rendered,
"some-extension-id-some-child-id",
"some-extension-name-some-child-id",
);
expect(child).not.toBe(null);
expect(child).not.toBeUndefined();
});
});
@@ -148,7 +147,7 @@ describe("cluster - sidebar and tab navigation for extensions", () => {
"/some-directory-for-lens-local-storage/app.json",
{
sidebar: {
expanded: { "some-extension-id-some-unknown-parent-id": true },
expanded: { "some-extension-name-some-unknown-parent-id": true },
width: 200,
},
},
@@ -165,10 +164,10 @@ describe("cluster - sidebar and tab navigation for extensions", () => {
it("parent sidebar item is not expanded", () => {
const child = getSidebarItem(
rendered,
"some-extension-id-some-child-id",
"some-extension-name-some-child-id",
);
expect(child).toBe(null);
expect(child).toBeUndefined();
});
});
@@ -195,10 +194,10 @@ describe("cluster - sidebar and tab navigation for extensions", () => {
it("parent sidebar item is not expanded", () => {
const child = getSidebarItem(
rendered,
"some-extension-id-some-child-id",
"some-extension-name-some-child-id",
);
expect(child).toBe(null);
expect(child).toBeUndefined();
});
});
@@ -214,25 +213,25 @@ describe("cluster - sidebar and tab navigation for extensions", () => {
it("parent sidebar item is not highlighted", () => {
const parent = getSidebarItem(
rendered,
"some-extension-id-some-parent-id",
"some-extension-name-some-parent-id",
);
expect(parent.dataset.isActiveTest).toBe("false");
expect(parent?.dataset.isActiveTest).toBe("false");
});
it("parent sidebar item is not expanded", () => {
const child = getSidebarItem(
rendered,
"some-extension-id-some-child-id",
"some-extension-name-some-child-id",
);
expect(child).toBe(null);
expect(child).toBeUndefined();
});
describe("when a parent sidebar item is expanded", () => {
beforeEach(() => {
const parentLink = rendered.getByTestId(
"sidebar-item-link-for-some-extension-id-some-parent-id",
"sidebar-item-link-for-some-extension-name-some-parent-id",
);
fireEvent.click(parentLink);
@@ -245,25 +244,25 @@ describe("cluster - sidebar and tab navigation for extensions", () => {
it("parent sidebar item is not highlighted", () => {
const parent = getSidebarItem(
rendered,
"some-extension-id-some-parent-id",
"some-extension-name-some-parent-id",
);
expect(parent.dataset.isActiveTest).toBe("false");
expect(parent?.dataset.isActiveTest).toBe("false");
});
it("parent sidebar item is expanded", () => {
const child = getSidebarItem(
rendered,
"some-extension-id-some-child-id",
"some-extension-name-some-child-id",
);
expect(child).not.toBe(null);
expect(child).not.toBeUndefined();
});
describe("when a child of the parent is selected", () => {
beforeEach(() => {
const childLink = rendered.getByTestId(
"sidebar-item-link-for-some-extension-id-some-child-id",
"sidebar-item-link-for-some-extension-name-some-child-id",
);
fireEvent.click(childLink);
@@ -276,19 +275,19 @@ describe("cluster - sidebar and tab navigation for extensions", () => {
it("parent is highlighted", () => {
const parent = getSidebarItem(
rendered,
"some-extension-id-some-parent-id",
"some-extension-name-some-parent-id",
);
expect(parent.dataset.isActiveTest).toBe("true");
expect(parent?.dataset.isActiveTest).toBe("true");
});
it("child is highlighted", () => {
const child = getSidebarItem(
rendered,
"some-extension-id-some-child-id",
"some-extension-name-some-child-id",
);
expect(child.dataset.isActiveTest).toBe("true");
expect(child?.dataset.isActiveTest).toBe("true");
});
it("child page is shown", () => {
@@ -301,7 +300,7 @@ describe("cluster - sidebar and tab navigation for extensions", () => {
it("tab for child page is active", () => {
const tabLink = rendered.getByTestId(
"tab-link-for-some-extension-id-some-child-id",
"tab-link-for-some-extension-name-some-child-id",
);
expect(tabLink.dataset.isActiveTest).toBe("true");
@@ -309,7 +308,7 @@ describe("cluster - sidebar and tab navigation for extensions", () => {
it("tab for sibling page is not active", () => {
const tabLink = rendered.getByTestId(
"tab-link-for-some-extension-id-some-other-child-id",
"tab-link-for-some-extension-name-some-other-child-id",
);
expect(tabLink.dataset.isActiveTest).toBe("false");
@@ -338,7 +337,7 @@ describe("cluster - sidebar and tab navigation for extensions", () => {
expect(actual).toEqual({
sidebar: {
expanded: { "some-extension-id-some-parent-id": true },
expanded: { "some-extension-name-some-parent-id": true },
width: 200,
},
});
@@ -347,7 +346,7 @@ describe("cluster - sidebar and tab navigation for extensions", () => {
describe("when selecting sibling tab", () => {
beforeEach(() => {
const childTabLink = rendered.getByTestId(
"tab-link-for-some-extension-id-some-other-child-id",
"tab-link-for-some-extension-name-some-other-child-id",
);
fireEvent.click(childTabLink);
@@ -365,7 +364,7 @@ describe("cluster - sidebar and tab navigation for extensions", () => {
it("tab for sibling page is active", () => {
const tabLink = rendered.getByTestId(
"tab-link-for-some-extension-id-some-other-child-id",
"tab-link-for-some-extension-name-some-other-child-id",
);
expect(tabLink.dataset.isActiveTest).toBe("true");
@@ -373,7 +372,7 @@ describe("cluster - sidebar and tab navigation for extensions", () => {
it("tab for previous page is not active", () => {
const tabLink = rendered.getByTestId(
"tab-link-for-some-extension-id-some-child-id",
"tab-link-for-some-extension-name-some-child-id",
);
expect(tabLink.dataset.isActiveTest).toBe("false");
@@ -385,9 +384,9 @@ describe("cluster - sidebar and tab navigation for extensions", () => {
});
});
const extensionStubWithSidebarItems: Partial<LensRendererExtension> = {
const extensionStubWithSidebarItems: FakeExtensionData = {
id: "some-extension-id",
name: "some-extension-name",
clusterPages: [
{
components: {
@@ -396,7 +395,6 @@ const extensionStubWithSidebarItems: Partial<LensRendererExtension> = {
},
},
},
{
id: "some-child-page-id",
@@ -404,7 +402,6 @@ const extensionStubWithSidebarItems: Partial<LensRendererExtension> = {
Page: () => <div data-testid="some-child-page">Some child page</div>,
},
},
{
id: "some-other-child-page-id",
@@ -415,7 +412,6 @@ const extensionStubWithSidebarItems: Partial<LensRendererExtension> = {
},
},
],
clusterPageMenus: [
{
id: "some-parent-id",
@@ -433,7 +429,7 @@ const extensionStubWithSidebarItems: Partial<LensRendererExtension> = {
title: "Child 1",
components: {
Icon: null,
Icon: null as never,
},
},
@@ -444,13 +440,8 @@ const extensionStubWithSidebarItems: Partial<LensRendererExtension> = {
title: "Child 2",
components: {
Icon: null,
Icon: null as never,
},
},
],
};
const getSidebarItem = (rendered: RenderResult, itemId: string) =>
rendered
.queryAllByTestId("sidebar-item")
.find((x) => x.dataset.idTest === itemId) || null;
@@ -14,6 +14,7 @@ import { routeInjectionToken } from "../../common/front-end-routing/route-inject
import type { ApplicationBuilder } from "../../renderer/components/test-utils/get-application-builder";
import { getApplicationBuilder } from "../../renderer/components/test-utils/get-application-builder";
import { navigateToRouteInjectionToken } from "../../common/front-end-routing/navigate-to-route-injection-token";
import { getSidebarItem } from "../utils";
describe("cluster - visibility of sidebar items", () => {
let applicationBuilder: ApplicationBuilder;
@@ -43,7 +44,7 @@ describe("cluster - visibility of sidebar items", () => {
it("related sidebar item does not exist", () => {
const item = getSidebarItem(rendered, "some-item-id");
expect(item).toBeNull();
expect(item).toBeUndefined();
});
describe("when kube resource becomes allowed", () => {
@@ -58,17 +59,12 @@ describe("cluster - visibility of sidebar items", () => {
it("related sidebar item exists", () => {
const item = getSidebarItem(rendered, "some-item-id");
expect(item).not.toBeNull();
expect(item).not.toBeUndefined();
});
});
});
});
const getSidebarItem = (rendered: RenderResult, itemId: string) =>
rendered
.queryAllByTestId("sidebar-item")
.find((x) => x.dataset.idTest === itemId) || null;
const testRouteInjectable = getInjectable({
id: "some-route-injectable-id",
@@ -2,12 +2,11 @@
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import type { TestExtension } from "../renderer/components/test-utils/get-renderer-extension-fake";
import { getRendererExtensionFake } from "../renderer/components/test-utils/get-renderer-extension-fake";
import type { FakeExtensionData, TestExtension } from "../renderer/components/test-utils/get-renderer-extension-fake";
import { getRendererExtensionFakeFor } from "../renderer/components/test-utils/get-renderer-extension-fake";
import React from "react";
import type { RenderResult } from "@testing-library/react";
import currentPathInjectable from "../renderer/routes/current-path.injectable";
import type { LensRendererExtension } from "../extensions/lens-renderer-extension";
import type { ApplicationBuilder } from "../renderer/components/test-utils/get-application-builder";
import { getApplicationBuilder } from "../renderer/components/test-utils/get-application-builder";
@@ -18,6 +17,7 @@ describe("extension special characters in page registrations", () => {
beforeEach(async () => {
applicationBuilder = getApplicationBuilder();
const getRendererExtensionFake = getRendererExtensionFakeFor(applicationBuilder);
testExtension = getRendererExtensionFake(
extensionWithPagesHavingSpecialCharacters,
@@ -44,14 +44,14 @@ describe("extension special characters in page registrations", () => {
it("knows URL", () => {
const currentPath = applicationBuilder.dis.rendererDi.inject(currentPathInjectable);
expect(currentPath.get()).toBe("/extension/some-extension-id--/some-page-id");
expect(currentPath.get()).toBe("/extension/some-extension-name--/some-page-id");
});
});
});
const extensionWithPagesHavingSpecialCharacters: Partial<LensRendererExtension> = {
id: "@some-extension-id/",
const extensionWithPagesHavingSpecialCharacters: FakeExtensionData = {
id: "some-extension-id",
name: "@some-extension-name/",
globalPages: [
{
id: "/some-page-id/",
@@ -23,9 +23,7 @@ exports[`extensions - navigation using application menu when navigating to exten
class="notice mb-14 mt-3"
>
<p>
Add new features via Lens Extensions.
Check out
Add new features via Lens Extensions. Check out the
<a
href="https://docs.k8slens.dev/main//extensions/"
rel="noreferrer"
@@ -33,8 +31,7 @@ exports[`extensions - navigation using application menu when navigating to exten
>
docs
</a>
and list of
and list of
<a
href="https://github.com/lensapp/lens-extensions/blob/main/README.md"
rel="noreferrer"
@@ -9,8 +9,8 @@ import { getApplicationBuilder } from "../../renderer/components/test-utils/get-
import isAutoUpdateEnabledInjectable from "../../main/is-auto-update-enabled.injectable";
import extensionsStoreInjectable from "../../extensions/extensions-store/extensions-store.injectable";
import type { ExtensionsStore } from "../../extensions/extensions-store/extensions-store";
import fileSystemProvisionerStoreInjectable from "../../extensions/extension-loader/create-extension-instance/file-system-provisioner-store/file-system-provisioner-store.injectable";
import type { FileSystemProvisionerStore } from "../../extensions/extension-loader/create-extension-instance/file-system-provisioner-store/file-system-provisioner-store";
import fileSystemProvisionerStoreInjectable from "../../extensions/extension-loader/file-system-provisioner-store/file-system-provisioner-store.injectable";
import type { FileSystemProvisionerStore } from "../../extensions/extension-loader/file-system-provisioner-store/file-system-provisioner-store";
import focusWindowInjectable from "../../renderer/ipc-channel-listeners/focus-window.injectable";
// TODO: Make components free of side effects by making them deterministic
@@ -2,8 +2,8 @@
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import type { TestExtension } from "../renderer/components/test-utils/get-renderer-extension-fake";
import { getRendererExtensionFake } from "../renderer/components/test-utils/get-renderer-extension-fake";
import type { FakeExtensionData, TestExtension } from "../renderer/components/test-utils/get-renderer-extension-fake";
import { getRendererExtensionFakeFor } from "../renderer/components/test-utils/get-renderer-extension-fake";
import React from "react";
import type { RenderResult } from "@testing-library/react";
import { fireEvent } from "@testing-library/react";
@@ -11,7 +11,6 @@ import isEmpty from "lodash/isEmpty";
import queryParametersInjectable from "../renderer/routes/query-parameters.injectable";
import currentPathInjectable from "../renderer/routes/current-path.injectable";
import type { IComputedValue } from "mobx";
import type { LensRendererExtension } from "../extensions/lens-renderer-extension";
import { getApplicationBuilder } from "../renderer/components/test-utils/get-application-builder";
describe("navigate to extension page", () => {
@@ -22,6 +21,7 @@ describe("navigate to extension page", () => {
beforeEach(async () => {
const applicationBuilder = getApplicationBuilder();
const getRendererExtensionFake = getRendererExtensionFakeFor(applicationBuilder);
testExtension = getRendererExtensionFake(
extensionWithPagesHavingParameters,
@@ -51,7 +51,7 @@ describe("navigate to extension page", () => {
});
it("URL is correct", () => {
expect(currentPath.get()).toBe("/extension/some-extension-id");
expect(currentPath.get()).toBe("/extension/some-extension-name");
});
it("query parameters is empty", () => {
@@ -70,7 +70,7 @@ describe("navigate to extension page", () => {
});
it("URL is correct", () => {
expect(currentPath.get()).toBe("/extension/some-extension-id");
expect(currentPath.get()).toBe("/extension/some-extension-name");
});
it("knows query parameters", () => {
@@ -98,7 +98,7 @@ describe("navigate to extension page", () => {
});
it("URL is correct", () => {
expect(currentPath.get()).toBe("/extension/some-extension-id");
expect(currentPath.get()).toBe("/extension/some-extension-name");
});
it("knows query parameters", () => {
@@ -120,14 +120,14 @@ describe("navigate to extension page", () => {
});
it("URL is correct", () => {
expect(currentPath.get()).toBe("/extension/some-extension-id/some-child-page-id");
expect(currentPath.get()).toBe("/extension/some-extension-name/some-child-page-id");
});
});
});
const extensionWithPagesHavingParameters: Partial<LensRendererExtension> = {
const extensionWithPagesHavingParameters: FakeExtensionData = {
id: "some-extension-id",
name: "some-extension-name",
globalPages: [
{
components: {
@@ -159,20 +159,14 @@ const extensionWithPagesHavingParameters: Partial<LensRendererExtension> = {
params: {
someStringParameter: "some-string-value",
someNumberParameter: {
defaultValue: 42,
stringify: (value) => value.toString(),
parse: (value) => (value ? Number(value) : undefined),
},
someArrayParameter: {
defaultValue: ["some-array-value", "some-other-array-value"],
stringify: (value) => value.join(","),
parse: (value: string[]) => (!isEmpty(value) ? value : undefined),
},
},
@@ -124,7 +124,7 @@ exports[`preferences - closing-preferences given accessing preferences directly
>
<span
class="css-1f43avz-a11yText-A11yText"
id="react-select-26-live-region"
id="react-select-theme-input-live-region"
/>
<span
aria-atomic="false"
@@ -140,7 +140,7 @@ exports[`preferences - closing-preferences given accessing preferences directly
>
<div
class="Select__placeholder css-14el2xx-placeholder"
id="react-select-26-placeholder"
id="react-select-theme-input-placeholder"
>
Select...
</div>
@@ -150,7 +150,7 @@ exports[`preferences - closing-preferences given accessing preferences directly
>
<input
aria-autocomplete="list"
aria-describedby="react-select-26-placeholder"
aria-describedby="react-select-theme-input-placeholder"
aria-expanded="false"
aria-haspopup="true"
autocapitalize="none"
@@ -209,7 +209,7 @@ exports[`preferences - closing-preferences given accessing preferences directly
>
<span
class="css-1f43avz-a11yText-A11yText"
id="react-select-27-live-region"
id="react-select-extension-install-registry-input-live-region"
/>
<span
aria-atomic="false"
@@ -225,7 +225,7 @@ exports[`preferences - closing-preferences given accessing preferences directly
>
<div
class="Select__placeholder css-14el2xx-placeholder"
id="react-select-27-placeholder"
id="react-select-extension-install-registry-input-placeholder"
>
Select...
</div>
@@ -235,7 +235,7 @@ exports[`preferences - closing-preferences given accessing preferences directly
>
<input
aria-autocomplete="list"
aria-describedby="react-select-27-placeholder"
aria-describedby="react-select-extension-install-registry-input-placeholder"
aria-expanded="false"
aria-haspopup="true"
autocapitalize="none"
@@ -281,17 +281,12 @@ exports[`preferences - closing-preferences given accessing preferences directly
<p
class="mt-4 mb-5 leading-relaxed"
>
This setting is to change the registry URL for installing extensions by name.
If you are unable to access the default registry (
https://registry.npmjs.org
)
you can change it in your
This setting is to change the registry URL for installing extensions by name.
If you are unable to access the default registry (https://registry.npmjs.org) you can change it in your
<b>
.npmrc
</b>
 file or in the input below.
file or in the input below.
</p>
<div
class="Input theme round black disabled invalid"
@@ -349,7 +344,7 @@ exports[`preferences - closing-preferences given accessing preferences directly
>
<span
class="css-1f43avz-a11yText-A11yText"
id="react-select-28-live-region"
id="react-select-update-channel-input-live-region"
/>
<span
aria-atomic="false"
@@ -365,7 +360,7 @@ exports[`preferences - closing-preferences given accessing preferences directly
>
<div
class="Select__placeholder css-14el2xx-placeholder"
id="react-select-28-placeholder"
id="react-select-update-channel-input-placeholder"
>
Select...
</div>
@@ -375,7 +370,7 @@ exports[`preferences - closing-preferences given accessing preferences directly
>
<input
aria-autocomplete="list"
aria-describedby="react-select-28-placeholder"
aria-describedby="react-select-update-channel-input-placeholder"
aria-expanded="false"
aria-haspopup="true"
autocapitalize="none"
@@ -434,7 +429,7 @@ exports[`preferences - closing-preferences given accessing preferences directly
>
<span
class="css-1f43avz-a11yText-A11yText"
id="react-select-29-live-region"
id="react-select-timezone-input-live-region"
/>
<span
aria-atomic="false"
@@ -450,7 +445,7 @@ exports[`preferences - closing-preferences given accessing preferences directly
>
<div
class="Select__placeholder css-14el2xx-placeholder"
id="react-select-29-placeholder"
id="react-select-timezone-input-placeholder"
>
Select...
</div>
@@ -460,7 +455,7 @@ exports[`preferences - closing-preferences given accessing preferences directly
>
<input
aria-autocomplete="list"
aria-describedby="react-select-29-placeholder"
aria-describedby="react-select-timezone-input-placeholder"
aria-expanded="false"
aria-haspopup="true"
autocapitalize="none"
@@ -827,7 +822,7 @@ exports[`preferences - closing-preferences given already in a page and then navi
>
<span
class="css-1f43avz-a11yText-A11yText"
id="react-select-2-live-region"
id="react-select-theme-input-live-region"
/>
<span
aria-atomic="false"
@@ -843,7 +838,7 @@ exports[`preferences - closing-preferences given already in a page and then navi
>
<div
class="Select__placeholder css-14el2xx-placeholder"
id="react-select-2-placeholder"
id="react-select-theme-input-placeholder"
>
Select...
</div>
@@ -853,7 +848,7 @@ exports[`preferences - closing-preferences given already in a page and then navi
>
<input
aria-autocomplete="list"
aria-describedby="react-select-2-placeholder"
aria-describedby="react-select-theme-input-placeholder"
aria-expanded="false"
aria-haspopup="true"
autocapitalize="none"
@@ -912,7 +907,7 @@ exports[`preferences - closing-preferences given already in a page and then navi
>
<span
class="css-1f43avz-a11yText-A11yText"
id="react-select-3-live-region"
id="react-select-extension-install-registry-input-live-region"
/>
<span
aria-atomic="false"
@@ -928,7 +923,7 @@ exports[`preferences - closing-preferences given already in a page and then navi
>
<div
class="Select__placeholder css-14el2xx-placeholder"
id="react-select-3-placeholder"
id="react-select-extension-install-registry-input-placeholder"
>
Select...
</div>
@@ -938,7 +933,7 @@ exports[`preferences - closing-preferences given already in a page and then navi
>
<input
aria-autocomplete="list"
aria-describedby="react-select-3-placeholder"
aria-describedby="react-select-extension-install-registry-input-placeholder"
aria-expanded="false"
aria-haspopup="true"
autocapitalize="none"
@@ -984,17 +979,12 @@ exports[`preferences - closing-preferences given already in a page and then navi
<p
class="mt-4 mb-5 leading-relaxed"
>
This setting is to change the registry URL for installing extensions by name.
If you are unable to access the default registry (
https://registry.npmjs.org
)
you can change it in your
This setting is to change the registry URL for installing extensions by name.
If you are unable to access the default registry (https://registry.npmjs.org) you can change it in your
<b>
.npmrc
</b>
 file or in the input below.
file or in the input below.
</p>
<div
class="Input theme round black disabled invalid"
@@ -1052,7 +1042,7 @@ exports[`preferences - closing-preferences given already in a page and then navi
>
<span
class="css-1f43avz-a11yText-A11yText"
id="react-select-4-live-region"
id="react-select-update-channel-input-live-region"
/>
<span
aria-atomic="false"
@@ -1068,7 +1058,7 @@ exports[`preferences - closing-preferences given already in a page and then navi
>
<div
class="Select__placeholder css-14el2xx-placeholder"
id="react-select-4-placeholder"
id="react-select-update-channel-input-placeholder"
>
Select...
</div>
@@ -1078,7 +1068,7 @@ exports[`preferences - closing-preferences given already in a page and then navi
>
<input
aria-autocomplete="list"
aria-describedby="react-select-4-placeholder"
aria-describedby="react-select-update-channel-input-placeholder"
aria-expanded="false"
aria-haspopup="true"
autocapitalize="none"
@@ -1137,7 +1127,7 @@ exports[`preferences - closing-preferences given already in a page and then navi
>
<span
class="css-1f43avz-a11yText-A11yText"
id="react-select-5-live-region"
id="react-select-timezone-input-live-region"
/>
<span
aria-atomic="false"
@@ -1153,7 +1143,7 @@ exports[`preferences - closing-preferences given already in a page and then navi
>
<div
class="Select__placeholder css-14el2xx-placeholder"
id="react-select-5-placeholder"
id="react-select-timezone-input-placeholder"
>
Select...
</div>
@@ -1163,7 +1153,7 @@ exports[`preferences - closing-preferences given already in a page and then navi
>
<input
aria-autocomplete="list"
aria-describedby="react-select-5-placeholder"
aria-describedby="react-select-timezone-input-placeholder"
aria-expanded="false"
aria-haspopup="true"
autocapitalize="none"
@@ -314,7 +314,7 @@ exports[`preferences - navigation to application preferences given in some child
>
<span
class="css-1f43avz-a11yText-A11yText"
id="react-select-2-live-region"
id="react-select-theme-input-live-region"
/>
<span
aria-atomic="false"
@@ -330,7 +330,7 @@ exports[`preferences - navigation to application preferences given in some child
>
<div
class="Select__placeholder css-14el2xx-placeholder"
id="react-select-2-placeholder"
id="react-select-theme-input-placeholder"
>
Select...
</div>
@@ -340,7 +340,7 @@ exports[`preferences - navigation to application preferences given in some child
>
<input
aria-autocomplete="list"
aria-describedby="react-select-2-placeholder"
aria-describedby="react-select-theme-input-placeholder"
aria-expanded="false"
aria-haspopup="true"
autocapitalize="none"
@@ -399,7 +399,7 @@ exports[`preferences - navigation to application preferences given in some child
>
<span
class="css-1f43avz-a11yText-A11yText"
id="react-select-3-live-region"
id="react-select-extension-install-registry-input-live-region"
/>
<span
aria-atomic="false"
@@ -415,7 +415,7 @@ exports[`preferences - navigation to application preferences given in some child
>
<div
class="Select__placeholder css-14el2xx-placeholder"
id="react-select-3-placeholder"
id="react-select-extension-install-registry-input-placeholder"
>
Select...
</div>
@@ -425,7 +425,7 @@ exports[`preferences - navigation to application preferences given in some child
>
<input
aria-autocomplete="list"
aria-describedby="react-select-3-placeholder"
aria-describedby="react-select-extension-install-registry-input-placeholder"
aria-expanded="false"
aria-haspopup="true"
autocapitalize="none"
@@ -471,17 +471,12 @@ exports[`preferences - navigation to application preferences given in some child
<p
class="mt-4 mb-5 leading-relaxed"
>
This setting is to change the registry URL for installing extensions by name.
If you are unable to access the default registry (
https://registry.npmjs.org
)
you can change it in your
This setting is to change the registry URL for installing extensions by name.
If you are unable to access the default registry (https://registry.npmjs.org) you can change it in your
<b>
.npmrc
</b>
 file or in the input below.
file or in the input below.
</p>
<div
class="Input theme round black disabled invalid"
@@ -539,7 +534,7 @@ exports[`preferences - navigation to application preferences given in some child
>
<span
class="css-1f43avz-a11yText-A11yText"
id="react-select-4-live-region"
id="react-select-update-channel-input-live-region"
/>
<span
aria-atomic="false"
@@ -555,7 +550,7 @@ exports[`preferences - navigation to application preferences given in some child
>
<div
class="Select__placeholder css-14el2xx-placeholder"
id="react-select-4-placeholder"
id="react-select-update-channel-input-placeholder"
>
Select...
</div>
@@ -565,7 +560,7 @@ exports[`preferences - navigation to application preferences given in some child
>
<input
aria-autocomplete="list"
aria-describedby="react-select-4-placeholder"
aria-describedby="react-select-update-channel-input-placeholder"
aria-expanded="false"
aria-haspopup="true"
autocapitalize="none"
@@ -624,7 +619,7 @@ exports[`preferences - navigation to application preferences given in some child
>
<span
class="css-1f43avz-a11yText-A11yText"
id="react-select-5-live-region"
id="react-select-timezone-input-live-region"
/>
<span
aria-atomic="false"
@@ -640,7 +635,7 @@ exports[`preferences - navigation to application preferences given in some child
>
<div
class="Select__placeholder css-14el2xx-placeholder"
id="react-select-5-placeholder"
id="react-select-timezone-input-placeholder"
>
Select...
</div>
@@ -650,7 +645,7 @@ exports[`preferences - navigation to application preferences given in some child
>
<input
aria-autocomplete="list"
aria-describedby="react-select-5-placeholder"
aria-describedby="react-select-timezone-input-placeholder"
aria-expanded="false"
aria-haspopup="true"
autocapitalize="none"
@@ -112,7 +112,7 @@ exports[`preferences - navigation to editor preferences given in preferences, wh
>
<span
class="css-1f43avz-a11yText-A11yText"
id="react-select-2-live-region"
id="react-select-theme-input-live-region"
/>
<span
aria-atomic="false"
@@ -128,7 +128,7 @@ exports[`preferences - navigation to editor preferences given in preferences, wh
>
<div
class="Select__placeholder css-14el2xx-placeholder"
id="react-select-2-placeholder"
id="react-select-theme-input-placeholder"
>
Select...
</div>
@@ -138,7 +138,7 @@ exports[`preferences - navigation to editor preferences given in preferences, wh
>
<input
aria-autocomplete="list"
aria-describedby="react-select-2-placeholder"
aria-describedby="react-select-theme-input-placeholder"
aria-expanded="false"
aria-haspopup="true"
autocapitalize="none"
@@ -197,7 +197,7 @@ exports[`preferences - navigation to editor preferences given in preferences, wh
>
<span
class="css-1f43avz-a11yText-A11yText"
id="react-select-3-live-region"
id="react-select-extension-install-registry-input-live-region"
/>
<span
aria-atomic="false"
@@ -213,7 +213,7 @@ exports[`preferences - navigation to editor preferences given in preferences, wh
>
<div
class="Select__placeholder css-14el2xx-placeholder"
id="react-select-3-placeholder"
id="react-select-extension-install-registry-input-placeholder"
>
Select...
</div>
@@ -223,7 +223,7 @@ exports[`preferences - navigation to editor preferences given in preferences, wh
>
<input
aria-autocomplete="list"
aria-describedby="react-select-3-placeholder"
aria-describedby="react-select-extension-install-registry-input-placeholder"
aria-expanded="false"
aria-haspopup="true"
autocapitalize="none"
@@ -269,17 +269,12 @@ exports[`preferences - navigation to editor preferences given in preferences, wh
<p
class="mt-4 mb-5 leading-relaxed"
>
This setting is to change the registry URL for installing extensions by name.
If you are unable to access the default registry (
https://registry.npmjs.org
)
you can change it in your
This setting is to change the registry URL for installing extensions by name.
If you are unable to access the default registry (https://registry.npmjs.org) you can change it in your
<b>
.npmrc
</b>
 file or in the input below.
file or in the input below.
</p>
<div
class="Input theme round black disabled invalid"
@@ -337,7 +332,7 @@ exports[`preferences - navigation to editor preferences given in preferences, wh
>
<span
class="css-1f43avz-a11yText-A11yText"
id="react-select-4-live-region"
id="react-select-update-channel-input-live-region"
/>
<span
aria-atomic="false"
@@ -353,7 +348,7 @@ exports[`preferences - navigation to editor preferences given in preferences, wh
>
<div
class="Select__placeholder css-14el2xx-placeholder"
id="react-select-4-placeholder"
id="react-select-update-channel-input-placeholder"
>
Select...
</div>
@@ -363,7 +358,7 @@ exports[`preferences - navigation to editor preferences given in preferences, wh
>
<input
aria-autocomplete="list"
aria-describedby="react-select-4-placeholder"
aria-describedby="react-select-update-channel-input-placeholder"
aria-expanded="false"
aria-haspopup="true"
autocapitalize="none"
@@ -422,7 +417,7 @@ exports[`preferences - navigation to editor preferences given in preferences, wh
>
<span
class="css-1f43avz-a11yText-A11yText"
id="react-select-5-live-region"
id="react-select-timezone-input-live-region"
/>
<span
aria-atomic="false"
@@ -438,7 +433,7 @@ exports[`preferences - navigation to editor preferences given in preferences, wh
>
<div
class="Select__placeholder css-14el2xx-placeholder"
id="react-select-5-placeholder"
id="react-select-timezone-input-placeholder"
>
Select...
</div>
@@ -448,7 +443,7 @@ exports[`preferences - navigation to editor preferences given in preferences, wh
>
<input
aria-autocomplete="list"
aria-describedby="react-select-5-placeholder"
aria-describedby="react-select-timezone-input-placeholder"
aria-expanded="false"
aria-haspopup="true"
autocapitalize="none"
@@ -668,7 +663,7 @@ exports[`preferences - navigation to editor preferences given in preferences, wh
>
<span
class="css-1f43avz-a11yText-A11yText"
id="react-select-14-live-region"
id="react-select-minimap-input-live-region"
/>
<span
aria-atomic="false"
@@ -684,7 +679,7 @@ exports[`preferences - navigation to editor preferences given in preferences, wh
>
<div
class="Select__placeholder css-14el2xx-placeholder"
id="react-select-14-placeholder"
id="react-select-minimap-input-placeholder"
>
Select...
</div>
@@ -694,7 +689,7 @@ exports[`preferences - navigation to editor preferences given in preferences, wh
>
<input
aria-autocomplete="list"
aria-describedby="react-select-14-placeholder"
aria-describedby="react-select-minimap-input-placeholder"
aria-expanded="false"
aria-haspopup="true"
autocapitalize="none"
@@ -752,7 +747,7 @@ exports[`preferences - navigation to editor preferences given in preferences, wh
>
<span
class="css-1f43avz-a11yText-A11yText"
id="react-select-15-live-region"
id="react-select-editor-line-numbers-input-live-region"
/>
<span
aria-atomic="false"
@@ -768,7 +763,7 @@ exports[`preferences - navigation to editor preferences given in preferences, wh
>
<div
class="Select__placeholder css-14el2xx-placeholder"
id="react-select-15-placeholder"
id="react-select-editor-line-numbers-input-placeholder"
>
Select...
</div>
@@ -778,7 +773,7 @@ exports[`preferences - navigation to editor preferences given in preferences, wh
>
<input
aria-autocomplete="list"
aria-describedby="react-select-15-placeholder"
aria-describedby="react-select-editor-line-numbers-input-placeholder"
aria-expanded="false"
aria-haspopup="true"
autocapitalize="none"
@@ -112,7 +112,7 @@ exports[`preferences - navigation to extension specific preferences given in pre
>
<span
class="css-1f43avz-a11yText-A11yText"
id="react-select-2-live-region"
id="react-select-theme-input-live-region"
/>
<span
aria-atomic="false"
@@ -128,7 +128,7 @@ exports[`preferences - navigation to extension specific preferences given in pre
>
<div
class="Select__placeholder css-14el2xx-placeholder"
id="react-select-2-placeholder"
id="react-select-theme-input-placeholder"
>
Select...
</div>
@@ -138,7 +138,7 @@ exports[`preferences - navigation to extension specific preferences given in pre
>
<input
aria-autocomplete="list"
aria-describedby="react-select-2-placeholder"
aria-describedby="react-select-theme-input-placeholder"
aria-expanded="false"
aria-haspopup="true"
autocapitalize="none"
@@ -197,7 +197,7 @@ exports[`preferences - navigation to extension specific preferences given in pre
>
<span
class="css-1f43avz-a11yText-A11yText"
id="react-select-3-live-region"
id="react-select-extension-install-registry-input-live-region"
/>
<span
aria-atomic="false"
@@ -213,7 +213,7 @@ exports[`preferences - navigation to extension specific preferences given in pre
>
<div
class="Select__placeholder css-14el2xx-placeholder"
id="react-select-3-placeholder"
id="react-select-extension-install-registry-input-placeholder"
>
Select...
</div>
@@ -223,7 +223,7 @@ exports[`preferences - navigation to extension specific preferences given in pre
>
<input
aria-autocomplete="list"
aria-describedby="react-select-3-placeholder"
aria-describedby="react-select-extension-install-registry-input-placeholder"
aria-expanded="false"
aria-haspopup="true"
autocapitalize="none"
@@ -269,17 +269,12 @@ exports[`preferences - navigation to extension specific preferences given in pre
<p
class="mt-4 mb-5 leading-relaxed"
>
This setting is to change the registry URL for installing extensions by name.
If you are unable to access the default registry (
https://registry.npmjs.org
)
you can change it in your
This setting is to change the registry URL for installing extensions by name.
If you are unable to access the default registry (https://registry.npmjs.org) you can change it in your
<b>
.npmrc
</b>
 file or in the input below.
file or in the input below.
</p>
<div
class="Input theme round black disabled invalid"
@@ -337,7 +332,7 @@ exports[`preferences - navigation to extension specific preferences given in pre
>
<span
class="css-1f43avz-a11yText-A11yText"
id="react-select-4-live-region"
id="react-select-update-channel-input-live-region"
/>
<span
aria-atomic="false"
@@ -353,7 +348,7 @@ exports[`preferences - navigation to extension specific preferences given in pre
>
<div
class="Select__placeholder css-14el2xx-placeholder"
id="react-select-4-placeholder"
id="react-select-update-channel-input-placeholder"
>
Select...
</div>
@@ -363,7 +358,7 @@ exports[`preferences - navigation to extension specific preferences given in pre
>
<input
aria-autocomplete="list"
aria-describedby="react-select-4-placeholder"
aria-describedby="react-select-update-channel-input-placeholder"
aria-expanded="false"
aria-haspopup="true"
autocapitalize="none"
@@ -422,7 +417,7 @@ exports[`preferences - navigation to extension specific preferences given in pre
>
<span
class="css-1f43avz-a11yText-A11yText"
id="react-select-5-live-region"
id="react-select-timezone-input-live-region"
/>
<span
aria-atomic="false"
@@ -438,7 +433,7 @@ exports[`preferences - navigation to extension specific preferences given in pre
>
<div
class="Select__placeholder css-14el2xx-placeholder"
id="react-select-5-placeholder"
id="react-select-timezone-input-placeholder"
>
Select...
</div>
@@ -448,7 +443,7 @@ exports[`preferences - navigation to extension specific preferences given in pre
>
<input
aria-autocomplete="list"
aria-describedby="react-select-5-placeholder"
aria-describedby="react-select-timezone-input-placeholder"
aria-expanded="false"
aria-haspopup="true"
autocapitalize="none"
@@ -657,7 +652,7 @@ exports[`preferences - navigation to extension specific preferences given in pre
>
<span
class="css-1f43avz-a11yText-A11yText"
id="react-select-14-live-region"
id="react-select-theme-input-live-region"
/>
<span
aria-atomic="false"
@@ -673,7 +668,7 @@ exports[`preferences - navigation to extension specific preferences given in pre
>
<div
class="Select__placeholder css-14el2xx-placeholder"
id="react-select-14-placeholder"
id="react-select-theme-input-placeholder"
>
Select...
</div>
@@ -683,7 +678,7 @@ exports[`preferences - navigation to extension specific preferences given in pre
>
<input
aria-autocomplete="list"
aria-describedby="react-select-14-placeholder"
aria-describedby="react-select-theme-input-placeholder"
aria-expanded="false"
aria-haspopup="true"
autocapitalize="none"
@@ -742,7 +737,7 @@ exports[`preferences - navigation to extension specific preferences given in pre
>
<span
class="css-1f43avz-a11yText-A11yText"
id="react-select-15-live-region"
id="react-select-extension-install-registry-input-live-region"
/>
<span
aria-atomic="false"
@@ -758,7 +753,7 @@ exports[`preferences - navigation to extension specific preferences given in pre
>
<div
class="Select__placeholder css-14el2xx-placeholder"
id="react-select-15-placeholder"
id="react-select-extension-install-registry-input-placeholder"
>
Select...
</div>
@@ -768,7 +763,7 @@ exports[`preferences - navigation to extension specific preferences given in pre
>
<input
aria-autocomplete="list"
aria-describedby="react-select-15-placeholder"
aria-describedby="react-select-extension-install-registry-input-placeholder"
aria-expanded="false"
aria-haspopup="true"
autocapitalize="none"
@@ -814,17 +809,12 @@ exports[`preferences - navigation to extension specific preferences given in pre
<p
class="mt-4 mb-5 leading-relaxed"
>
This setting is to change the registry URL for installing extensions by name.
If you are unable to access the default registry (
https://registry.npmjs.org
)
you can change it in your
This setting is to change the registry URL for installing extensions by name.
If you are unable to access the default registry (https://registry.npmjs.org) you can change it in your
<b>
.npmrc
</b>
 file or in the input below.
file or in the input below.
</p>
<div
class="Input theme round black disabled invalid"
@@ -882,7 +872,7 @@ exports[`preferences - navigation to extension specific preferences given in pre
>
<span
class="css-1f43avz-a11yText-A11yText"
id="react-select-16-live-region"
id="react-select-update-channel-input-live-region"
/>
<span
aria-atomic="false"
@@ -898,7 +888,7 @@ exports[`preferences - navigation to extension specific preferences given in pre
>
<div
class="Select__placeholder css-14el2xx-placeholder"
id="react-select-16-placeholder"
id="react-select-update-channel-input-placeholder"
>
Select...
</div>
@@ -908,7 +898,7 @@ exports[`preferences - navigation to extension specific preferences given in pre
>
<input
aria-autocomplete="list"
aria-describedby="react-select-16-placeholder"
aria-describedby="react-select-update-channel-input-placeholder"
aria-expanded="false"
aria-haspopup="true"
autocapitalize="none"
@@ -967,7 +957,7 @@ exports[`preferences - navigation to extension specific preferences given in pre
>
<span
class="css-1f43avz-a11yText-A11yText"
id="react-select-17-live-region"
id="react-select-timezone-input-live-region"
/>
<span
aria-atomic="false"
@@ -983,7 +973,7 @@ exports[`preferences - navigation to extension specific preferences given in pre
>
<div
class="Select__placeholder css-14el2xx-placeholder"
id="react-select-17-placeholder"
id="react-select-timezone-input-placeholder"
>
Select...
</div>
@@ -993,7 +983,7 @@ exports[`preferences - navigation to extension specific preferences given in pre
>
<input
aria-autocomplete="list"
aria-describedby="react-select-17-placeholder"
aria-describedby="react-select-timezone-input-placeholder"
aria-expanded="false"
aria-haspopup="true"
autocapitalize="none"
@@ -112,7 +112,7 @@ exports[`preferences - navigation to kubernetes preferences given in preferences
>
<span
class="css-1f43avz-a11yText-A11yText"
id="react-select-2-live-region"
id="react-select-theme-input-live-region"
/>
<span
aria-atomic="false"
@@ -128,7 +128,7 @@ exports[`preferences - navigation to kubernetes preferences given in preferences
>
<div
class="Select__placeholder css-14el2xx-placeholder"
id="react-select-2-placeholder"
id="react-select-theme-input-placeholder"
>
Select...
</div>
@@ -138,7 +138,7 @@ exports[`preferences - navigation to kubernetes preferences given in preferences
>
<input
aria-autocomplete="list"
aria-describedby="react-select-2-placeholder"
aria-describedby="react-select-theme-input-placeholder"
aria-expanded="false"
aria-haspopup="true"
autocapitalize="none"
@@ -197,7 +197,7 @@ exports[`preferences - navigation to kubernetes preferences given in preferences
>
<span
class="css-1f43avz-a11yText-A11yText"
id="react-select-3-live-region"
id="react-select-extension-install-registry-input-live-region"
/>
<span
aria-atomic="false"
@@ -213,7 +213,7 @@ exports[`preferences - navigation to kubernetes preferences given in preferences
>
<div
class="Select__placeholder css-14el2xx-placeholder"
id="react-select-3-placeholder"
id="react-select-extension-install-registry-input-placeholder"
>
Select...
</div>
@@ -223,7 +223,7 @@ exports[`preferences - navigation to kubernetes preferences given in preferences
>
<input
aria-autocomplete="list"
aria-describedby="react-select-3-placeholder"
aria-describedby="react-select-extension-install-registry-input-placeholder"
aria-expanded="false"
aria-haspopup="true"
autocapitalize="none"
@@ -269,17 +269,12 @@ exports[`preferences - navigation to kubernetes preferences given in preferences
<p
class="mt-4 mb-5 leading-relaxed"
>
This setting is to change the registry URL for installing extensions by name.
If you are unable to access the default registry (
https://registry.npmjs.org
)
you can change it in your
This setting is to change the registry URL for installing extensions by name.
If you are unable to access the default registry (https://registry.npmjs.org) you can change it in your
<b>
.npmrc
</b>
 file or in the input below.
file or in the input below.
</p>
<div
class="Input theme round black disabled invalid"
@@ -337,7 +332,7 @@ exports[`preferences - navigation to kubernetes preferences given in preferences
>
<span
class="css-1f43avz-a11yText-A11yText"
id="react-select-4-live-region"
id="react-select-update-channel-input-live-region"
/>
<span
aria-atomic="false"
@@ -353,7 +348,7 @@ exports[`preferences - navigation to kubernetes preferences given in preferences
>
<div
class="Select__placeholder css-14el2xx-placeholder"
id="react-select-4-placeholder"
id="react-select-update-channel-input-placeholder"
>
Select...
</div>
@@ -363,7 +358,7 @@ exports[`preferences - navigation to kubernetes preferences given in preferences
>
<input
aria-autocomplete="list"
aria-describedby="react-select-4-placeholder"
aria-describedby="react-select-update-channel-input-placeholder"
aria-expanded="false"
aria-haspopup="true"
autocapitalize="none"
@@ -422,7 +417,7 @@ exports[`preferences - navigation to kubernetes preferences given in preferences
>
<span
class="css-1f43avz-a11yText-A11yText"
id="react-select-5-live-region"
id="react-select-timezone-input-live-region"
/>
<span
aria-atomic="false"
@@ -438,7 +433,7 @@ exports[`preferences - navigation to kubernetes preferences given in preferences
>
<div
class="Select__placeholder css-14el2xx-placeholder"
id="react-select-5-placeholder"
id="react-select-timezone-input-placeholder"
>
Select...
</div>
@@ -448,7 +443,7 @@ exports[`preferences - navigation to kubernetes preferences given in preferences
>
<input
aria-autocomplete="list"
aria-describedby="react-select-5-placeholder"
aria-describedby="react-select-timezone-input-placeholder"
aria-expanded="false"
aria-haspopup="true"
autocapitalize="none"
@@ -664,7 +659,7 @@ exports[`preferences - navigation to kubernetes preferences given in preferences
>
<span
class="css-1f43avz-a11yText-A11yText"
id="react-select-14-live-region"
id="react-select-download-mirror-input-live-region"
/>
<span
aria-atomic="false"
@@ -680,7 +675,7 @@ exports[`preferences - navigation to kubernetes preferences given in preferences
>
<div
class="Select__placeholder css-14el2xx-placeholder"
id="react-select-14-placeholder"
id="react-select-download-mirror-input-placeholder"
>
Download mirror for kubectl
</div>
@@ -690,7 +685,7 @@ exports[`preferences - navigation to kubernetes preferences given in preferences
>
<input
aria-autocomplete="list"
aria-describedby="react-select-14-placeholder"
aria-describedby="react-select-download-mirror-input-placeholder"
aria-expanded="false"
aria-haspopup="true"
autocapitalize="none"
@@ -845,7 +840,7 @@ exports[`preferences - navigation to kubernetes preferences given in preferences
>
<span
class="css-1f43avz-a11yText-A11yText"
id="react-select-15-live-region"
id="react-select-HelmRepoSelect-live-region"
/>
<span
aria-atomic="false"
@@ -861,7 +856,7 @@ exports[`preferences - navigation to kubernetes preferences given in preferences
>
<div
class="Select__placeholder css-14el2xx-placeholder"
id="react-select-15-placeholder"
id="react-select-HelmRepoSelect-placeholder"
>
Repositories
</div>
@@ -871,7 +866,7 @@ exports[`preferences - navigation to kubernetes preferences given in preferences
>
<input
aria-autocomplete="list"
aria-describedby="react-select-15-placeholder"
aria-describedby="react-select-HelmRepoSelect-placeholder"
aria-expanded="false"
aria-haspopup="true"
autocapitalize="none"
@@ -112,7 +112,7 @@ exports[`preferences - navigation to proxy preferences given in preferences, whe
>
<span
class="css-1f43avz-a11yText-A11yText"
id="react-select-2-live-region"
id="react-select-theme-input-live-region"
/>
<span
aria-atomic="false"
@@ -128,7 +128,7 @@ exports[`preferences - navigation to proxy preferences given in preferences, whe
>
<div
class="Select__placeholder css-14el2xx-placeholder"
id="react-select-2-placeholder"
id="react-select-theme-input-placeholder"
>
Select...
</div>
@@ -138,7 +138,7 @@ exports[`preferences - navigation to proxy preferences given in preferences, whe
>
<input
aria-autocomplete="list"
aria-describedby="react-select-2-placeholder"
aria-describedby="react-select-theme-input-placeholder"
aria-expanded="false"
aria-haspopup="true"
autocapitalize="none"
@@ -197,7 +197,7 @@ exports[`preferences - navigation to proxy preferences given in preferences, whe
>
<span
class="css-1f43avz-a11yText-A11yText"
id="react-select-3-live-region"
id="react-select-extension-install-registry-input-live-region"
/>
<span
aria-atomic="false"
@@ -213,7 +213,7 @@ exports[`preferences - navigation to proxy preferences given in preferences, whe
>
<div
class="Select__placeholder css-14el2xx-placeholder"
id="react-select-3-placeholder"
id="react-select-extension-install-registry-input-placeholder"
>
Select...
</div>
@@ -223,7 +223,7 @@ exports[`preferences - navigation to proxy preferences given in preferences, whe
>
<input
aria-autocomplete="list"
aria-describedby="react-select-3-placeholder"
aria-describedby="react-select-extension-install-registry-input-placeholder"
aria-expanded="false"
aria-haspopup="true"
autocapitalize="none"
@@ -269,17 +269,12 @@ exports[`preferences - navigation to proxy preferences given in preferences, whe
<p
class="mt-4 mb-5 leading-relaxed"
>
This setting is to change the registry URL for installing extensions by name.
If you are unable to access the default registry (
https://registry.npmjs.org
)
you can change it in your
This setting is to change the registry URL for installing extensions by name.
If you are unable to access the default registry (https://registry.npmjs.org) you can change it in your
<b>
.npmrc
</b>
 file or in the input below.
file or in the input below.
</p>
<div
class="Input theme round black disabled invalid"
@@ -337,7 +332,7 @@ exports[`preferences - navigation to proxy preferences given in preferences, whe
>
<span
class="css-1f43avz-a11yText-A11yText"
id="react-select-4-live-region"
id="react-select-update-channel-input-live-region"
/>
<span
aria-atomic="false"
@@ -353,7 +348,7 @@ exports[`preferences - navigation to proxy preferences given in preferences, whe
>
<div
class="Select__placeholder css-14el2xx-placeholder"
id="react-select-4-placeholder"
id="react-select-update-channel-input-placeholder"
>
Select...
</div>
@@ -363,7 +358,7 @@ exports[`preferences - navigation to proxy preferences given in preferences, whe
>
<input
aria-autocomplete="list"
aria-describedby="react-select-4-placeholder"
aria-describedby="react-select-update-channel-input-placeholder"
aria-expanded="false"
aria-haspopup="true"
autocapitalize="none"
@@ -422,7 +417,7 @@ exports[`preferences - navigation to proxy preferences given in preferences, whe
>
<span
class="css-1f43avz-a11yText-A11yText"
id="react-select-5-live-region"
id="react-select-timezone-input-live-region"
/>
<span
aria-atomic="false"
@@ -438,7 +433,7 @@ exports[`preferences - navigation to proxy preferences given in preferences, whe
>
<div
class="Select__placeholder css-14el2xx-placeholder"
id="react-select-5-placeholder"
id="react-select-timezone-input-placeholder"
>
Select...
</div>
@@ -448,7 +443,7 @@ exports[`preferences - navigation to proxy preferences given in preferences, whe
>
<input
aria-autocomplete="list"
aria-describedby="react-select-5-placeholder"
aria-describedby="react-select-timezone-input-placeholder"
aria-expanded="false"
aria-haspopup="true"
autocapitalize="none"
@@ -300,7 +300,7 @@ exports[`preferences - navigation to telemetry preferences given in preferences,
>
<span
class="css-1f43avz-a11yText-A11yText"
id="react-select-2-live-region"
id="react-select-theme-input-live-region"
/>
<span
aria-atomic="false"
@@ -316,7 +316,7 @@ exports[`preferences - navigation to telemetry preferences given in preferences,
>
<div
class="Select__placeholder css-14el2xx-placeholder"
id="react-select-2-placeholder"
id="react-select-theme-input-placeholder"
>
Select...
</div>
@@ -326,7 +326,7 @@ exports[`preferences - navigation to telemetry preferences given in preferences,
>
<input
aria-autocomplete="list"
aria-describedby="react-select-2-placeholder"
aria-describedby="react-select-theme-input-placeholder"
aria-expanded="false"
aria-haspopup="true"
autocapitalize="none"
@@ -385,7 +385,7 @@ exports[`preferences - navigation to telemetry preferences given in preferences,
>
<span
class="css-1f43avz-a11yText-A11yText"
id="react-select-3-live-region"
id="react-select-extension-install-registry-input-live-region"
/>
<span
aria-atomic="false"
@@ -401,7 +401,7 @@ exports[`preferences - navigation to telemetry preferences given in preferences,
>
<div
class="Select__placeholder css-14el2xx-placeholder"
id="react-select-3-placeholder"
id="react-select-extension-install-registry-input-placeholder"
>
Select...
</div>
@@ -411,7 +411,7 @@ exports[`preferences - navigation to telemetry preferences given in preferences,
>
<input
aria-autocomplete="list"
aria-describedby="react-select-3-placeholder"
aria-describedby="react-select-extension-install-registry-input-placeholder"
aria-expanded="false"
aria-haspopup="true"
autocapitalize="none"
@@ -457,17 +457,12 @@ exports[`preferences - navigation to telemetry preferences given in preferences,
<p
class="mt-4 mb-5 leading-relaxed"
>
This setting is to change the registry URL for installing extensions by name.
If you are unable to access the default registry (
https://registry.npmjs.org
)
you can change it in your
This setting is to change the registry URL for installing extensions by name.
If you are unable to access the default registry (https://registry.npmjs.org) you can change it in your
<b>
.npmrc
</b>
 file or in the input below.
file or in the input below.
</p>
<div
class="Input theme round black disabled invalid"
@@ -525,7 +520,7 @@ exports[`preferences - navigation to telemetry preferences given in preferences,
>
<span
class="css-1f43avz-a11yText-A11yText"
id="react-select-4-live-region"
id="react-select-update-channel-input-live-region"
/>
<span
aria-atomic="false"
@@ -541,7 +536,7 @@ exports[`preferences - navigation to telemetry preferences given in preferences,
>
<div
class="Select__placeholder css-14el2xx-placeholder"
id="react-select-4-placeholder"
id="react-select-update-channel-input-placeholder"
>
Select...
</div>
@@ -551,7 +546,7 @@ exports[`preferences - navigation to telemetry preferences given in preferences,
>
<input
aria-autocomplete="list"
aria-describedby="react-select-4-placeholder"
aria-describedby="react-select-update-channel-input-placeholder"
aria-expanded="false"
aria-haspopup="true"
autocapitalize="none"
@@ -610,7 +605,7 @@ exports[`preferences - navigation to telemetry preferences given in preferences,
>
<span
class="css-1f43avz-a11yText-A11yText"
id="react-select-5-live-region"
id="react-select-timezone-input-live-region"
/>
<span
aria-atomic="false"
@@ -626,7 +621,7 @@ exports[`preferences - navigation to telemetry preferences given in preferences,
>
<div
class="Select__placeholder css-14el2xx-placeholder"
id="react-select-5-placeholder"
id="react-select-timezone-input-placeholder"
>
Select...
</div>
@@ -636,7 +631,7 @@ exports[`preferences - navigation to telemetry preferences given in preferences,
>
<input
aria-autocomplete="list"
aria-describedby="react-select-5-placeholder"
aria-describedby="react-select-timezone-input-placeholder"
aria-expanded="false"
aria-haspopup="true"
autocapitalize="none"
@@ -845,7 +840,7 @@ exports[`preferences - navigation to telemetry preferences given in preferences,
>
<span
class="css-1f43avz-a11yText-A11yText"
id="react-select-14-live-region"
id="react-select-theme-input-live-region"
/>
<span
aria-atomic="false"
@@ -861,7 +856,7 @@ exports[`preferences - navigation to telemetry preferences given in preferences,
>
<div
class="Select__placeholder css-14el2xx-placeholder"
id="react-select-14-placeholder"
id="react-select-theme-input-placeholder"
>
Select...
</div>
@@ -871,7 +866,7 @@ exports[`preferences - navigation to telemetry preferences given in preferences,
>
<input
aria-autocomplete="list"
aria-describedby="react-select-14-placeholder"
aria-describedby="react-select-theme-input-placeholder"
aria-expanded="false"
aria-haspopup="true"
autocapitalize="none"
@@ -930,7 +925,7 @@ exports[`preferences - navigation to telemetry preferences given in preferences,
>
<span
class="css-1f43avz-a11yText-A11yText"
id="react-select-15-live-region"
id="react-select-extension-install-registry-input-live-region"
/>
<span
aria-atomic="false"
@@ -946,7 +941,7 @@ exports[`preferences - navigation to telemetry preferences given in preferences,
>
<div
class="Select__placeholder css-14el2xx-placeholder"
id="react-select-15-placeholder"
id="react-select-extension-install-registry-input-placeholder"
>
Select...
</div>
@@ -956,7 +951,7 @@ exports[`preferences - navigation to telemetry preferences given in preferences,
>
<input
aria-autocomplete="list"
aria-describedby="react-select-15-placeholder"
aria-describedby="react-select-extension-install-registry-input-placeholder"
aria-expanded="false"
aria-haspopup="true"
autocapitalize="none"
@@ -1002,17 +997,12 @@ exports[`preferences - navigation to telemetry preferences given in preferences,
<p
class="mt-4 mb-5 leading-relaxed"
>
This setting is to change the registry URL for installing extensions by name.
If you are unable to access the default registry (
https://registry.npmjs.org
)
you can change it in your
This setting is to change the registry URL for installing extensions by name.
If you are unable to access the default registry (https://registry.npmjs.org) you can change it in your
<b>
.npmrc
</b>
 file or in the input below.
file or in the input below.
</p>
<div
class="Input theme round black disabled invalid"
@@ -1070,7 +1060,7 @@ exports[`preferences - navigation to telemetry preferences given in preferences,
>
<span
class="css-1f43avz-a11yText-A11yText"
id="react-select-16-live-region"
id="react-select-update-channel-input-live-region"
/>
<span
aria-atomic="false"
@@ -1086,7 +1076,7 @@ exports[`preferences - navigation to telemetry preferences given in preferences,
>
<div
class="Select__placeholder css-14el2xx-placeholder"
id="react-select-16-placeholder"
id="react-select-update-channel-input-placeholder"
>
Select...
</div>
@@ -1096,7 +1086,7 @@ exports[`preferences - navigation to telemetry preferences given in preferences,
>
<input
aria-autocomplete="list"
aria-describedby="react-select-16-placeholder"
aria-describedby="react-select-update-channel-input-placeholder"
aria-expanded="false"
aria-haspopup="true"
autocapitalize="none"
@@ -1155,7 +1145,7 @@ exports[`preferences - navigation to telemetry preferences given in preferences,
>
<span
class="css-1f43avz-a11yText-A11yText"
id="react-select-17-live-region"
id="react-select-timezone-input-live-region"
/>
<span
aria-atomic="false"
@@ -1171,7 +1161,7 @@ exports[`preferences - navigation to telemetry preferences given in preferences,
>
<div
class="Select__placeholder css-14el2xx-placeholder"
id="react-select-17-placeholder"
id="react-select-timezone-input-placeholder"
>
Select...
</div>
@@ -1181,7 +1171,7 @@ exports[`preferences - navigation to telemetry preferences given in preferences,
>
<input
aria-autocomplete="list"
aria-describedby="react-select-17-placeholder"
aria-describedby="react-select-timezone-input-placeholder"
aria-expanded="false"
aria-haspopup="true"
autocapitalize="none"
@@ -112,7 +112,7 @@ exports[`preferences - navigation to terminal preferences given in preferences,
>
<span
class="css-1f43avz-a11yText-A11yText"
id="react-select-2-live-region"
id="react-select-theme-input-live-region"
/>
<span
aria-atomic="false"
@@ -128,7 +128,7 @@ exports[`preferences - navigation to terminal preferences given in preferences,
>
<div
class="Select__placeholder css-14el2xx-placeholder"
id="react-select-2-placeholder"
id="react-select-theme-input-placeholder"
>
Select...
</div>
@@ -138,7 +138,7 @@ exports[`preferences - navigation to terminal preferences given in preferences,
>
<input
aria-autocomplete="list"
aria-describedby="react-select-2-placeholder"
aria-describedby="react-select-theme-input-placeholder"
aria-expanded="false"
aria-haspopup="true"
autocapitalize="none"
@@ -197,7 +197,7 @@ exports[`preferences - navigation to terminal preferences given in preferences,
>
<span
class="css-1f43avz-a11yText-A11yText"
id="react-select-3-live-region"
id="react-select-extension-install-registry-input-live-region"
/>
<span
aria-atomic="false"
@@ -213,7 +213,7 @@ exports[`preferences - navigation to terminal preferences given in preferences,
>
<div
class="Select__placeholder css-14el2xx-placeholder"
id="react-select-3-placeholder"
id="react-select-extension-install-registry-input-placeholder"
>
Select...
</div>
@@ -223,7 +223,7 @@ exports[`preferences - navigation to terminal preferences given in preferences,
>
<input
aria-autocomplete="list"
aria-describedby="react-select-3-placeholder"
aria-describedby="react-select-extension-install-registry-input-placeholder"
aria-expanded="false"
aria-haspopup="true"
autocapitalize="none"
@@ -269,17 +269,12 @@ exports[`preferences - navigation to terminal preferences given in preferences,
<p
class="mt-4 mb-5 leading-relaxed"
>
This setting is to change the registry URL for installing extensions by name.
If you are unable to access the default registry (
https://registry.npmjs.org
)
you can change it in your
This setting is to change the registry URL for installing extensions by name.
If you are unable to access the default registry (https://registry.npmjs.org) you can change it in your
<b>
.npmrc
</b>
 file or in the input below.
file or in the input below.
</p>
<div
class="Input theme round black disabled invalid"
@@ -337,7 +332,7 @@ exports[`preferences - navigation to terminal preferences given in preferences,
>
<span
class="css-1f43avz-a11yText-A11yText"
id="react-select-4-live-region"
id="react-select-update-channel-input-live-region"
/>
<span
aria-atomic="false"
@@ -353,7 +348,7 @@ exports[`preferences - navigation to terminal preferences given in preferences,
>
<div
class="Select__placeholder css-14el2xx-placeholder"
id="react-select-4-placeholder"
id="react-select-update-channel-input-placeholder"
>
Select...
</div>
@@ -363,7 +358,7 @@ exports[`preferences - navigation to terminal preferences given in preferences,
>
<input
aria-autocomplete="list"
aria-describedby="react-select-4-placeholder"
aria-describedby="react-select-update-channel-input-placeholder"
aria-expanded="false"
aria-haspopup="true"
autocapitalize="none"
@@ -422,7 +417,7 @@ exports[`preferences - navigation to terminal preferences given in preferences,
>
<span
class="css-1f43avz-a11yText-A11yText"
id="react-select-5-live-region"
id="react-select-timezone-input-live-region"
/>
<span
aria-atomic="false"
@@ -438,7 +433,7 @@ exports[`preferences - navigation to terminal preferences given in preferences,
>
<div
class="Select__placeholder css-14el2xx-placeholder"
id="react-select-5-placeholder"
id="react-select-timezone-input-placeholder"
>
Select...
</div>
@@ -448,7 +443,7 @@ exports[`preferences - navigation to terminal preferences given in preferences,
>
<input
aria-autocomplete="list"
aria-describedby="react-select-5-placeholder"
aria-describedby="react-select-timezone-input-placeholder"
aria-expanded="false"
aria-haspopup="true"
autocapitalize="none"
@@ -689,7 +684,7 @@ exports[`preferences - navigation to terminal preferences given in preferences,
>
<span
class="css-1f43avz-a11yText-A11yText"
id="react-select-14-live-region"
id="react-select-terminal-theme-input-live-region"
/>
<span
aria-atomic="false"
@@ -705,7 +700,7 @@ exports[`preferences - navigation to terminal preferences given in preferences,
>
<div
class="Select__placeholder css-14el2xx-placeholder"
id="react-select-14-placeholder"
id="react-select-terminal-theme-input-placeholder"
>
Select...
</div>
@@ -715,7 +710,7 @@ exports[`preferences - navigation to terminal preferences given in preferences,
>
<input
aria-autocomplete="list"
aria-describedby="react-select-14-placeholder"
aria-describedby="react-select-terminal-theme-input-placeholder"
aria-expanded="false"
aria-haspopup="true"
autocapitalize="none"
@@ -114,7 +114,7 @@ exports[`preferences - navigation using application menu when navigating to pref
>
<span
class="css-1f43avz-a11yText-A11yText"
id="react-select-2-live-region"
id="react-select-theme-input-live-region"
/>
<span
aria-atomic="false"
@@ -130,7 +130,7 @@ exports[`preferences - navigation using application menu when navigating to pref
>
<div
class="Select__placeholder css-14el2xx-placeholder"
id="react-select-2-placeholder"
id="react-select-theme-input-placeholder"
>
Select...
</div>
@@ -140,7 +140,7 @@ exports[`preferences - navigation using application menu when navigating to pref
>
<input
aria-autocomplete="list"
aria-describedby="react-select-2-placeholder"
aria-describedby="react-select-theme-input-placeholder"
aria-expanded="false"
aria-haspopup="true"
autocapitalize="none"
@@ -199,7 +199,7 @@ exports[`preferences - navigation using application menu when navigating to pref
>
<span
class="css-1f43avz-a11yText-A11yText"
id="react-select-3-live-region"
id="react-select-extension-install-registry-input-live-region"
/>
<span
aria-atomic="false"
@@ -215,7 +215,7 @@ exports[`preferences - navigation using application menu when navigating to pref
>
<div
class="Select__placeholder css-14el2xx-placeholder"
id="react-select-3-placeholder"
id="react-select-extension-install-registry-input-placeholder"
>
Select...
</div>
@@ -225,7 +225,7 @@ exports[`preferences - navigation using application menu when navigating to pref
>
<input
aria-autocomplete="list"
aria-describedby="react-select-3-placeholder"
aria-describedby="react-select-extension-install-registry-input-placeholder"
aria-expanded="false"
aria-haspopup="true"
autocapitalize="none"
@@ -271,17 +271,12 @@ exports[`preferences - navigation using application menu when navigating to pref
<p
class="mt-4 mb-5 leading-relaxed"
>
This setting is to change the registry URL for installing extensions by name.
If you are unable to access the default registry (
https://registry.npmjs.org
)
you can change it in your
This setting is to change the registry URL for installing extensions by name.
If you are unable to access the default registry (https://registry.npmjs.org) you can change it in your
<b>
.npmrc
</b>
 file or in the input below.
file or in the input below.
</p>
<div
class="Input theme round black disabled invalid"
@@ -339,7 +334,7 @@ exports[`preferences - navigation using application menu when navigating to pref
>
<span
class="css-1f43avz-a11yText-A11yText"
id="react-select-4-live-region"
id="react-select-update-channel-input-live-region"
/>
<span
aria-atomic="false"
@@ -355,7 +350,7 @@ exports[`preferences - navigation using application menu when navigating to pref
>
<div
class="Select__placeholder css-14el2xx-placeholder"
id="react-select-4-placeholder"
id="react-select-update-channel-input-placeholder"
>
Select...
</div>
@@ -365,7 +360,7 @@ exports[`preferences - navigation using application menu when navigating to pref
>
<input
aria-autocomplete="list"
aria-describedby="react-select-4-placeholder"
aria-describedby="react-select-update-channel-input-placeholder"
aria-expanded="false"
aria-haspopup="true"
autocapitalize="none"
@@ -424,7 +419,7 @@ exports[`preferences - navigation using application menu when navigating to pref
>
<span
class="css-1f43avz-a11yText-A11yText"
id="react-select-5-live-region"
id="react-select-timezone-input-live-region"
/>
<span
aria-atomic="false"
@@ -440,7 +435,7 @@ exports[`preferences - navigation using application menu when navigating to pref
>
<div
class="Select__placeholder css-14el2xx-placeholder"
id="react-select-5-placeholder"
id="react-select-timezone-input-placeholder"
>
Select...
</div>
@@ -450,7 +445,7 @@ exports[`preferences - navigation using application menu when navigating to pref
>
<input
aria-autocomplete="list"
aria-describedby="react-select-5-placeholder"
aria-describedby="react-select-timezone-input-placeholder"
aria-expanded="false"
aria-haspopup="true"
autocapitalize="none"
@@ -12,8 +12,6 @@ import { routeInjectionToken } from "../../common/front-end-routing/route-inject
import { computed } from "mobx";
import type { UserStore } from "../../common/user-store";
import userStoreInjectable from "../../common/user-store/user-store.injectable";
import type { ThemeStore } from "../../renderer/theme.store";
import themeStoreInjectable from "../../renderer/theme-store.injectable";
import { preferenceNavigationItemInjectionToken } from "../../renderer/components/+preferences/preferences-navigation/preference-navigation-items.injectable";
import routeIsActiveInjectable from "../../renderer/routes/route-is-active.injectable";
import { Preferences } from "../../renderer/components/+preferences";
@@ -26,6 +24,7 @@ import { createObservableHistory } from "mobx-observable-history";
import navigateToPreferenceTabInjectable from "../../renderer/components/+preferences/preferences-navigation/navigate-to-preference-tab.injectable";
import navigateToFrontPageInjectable from "../../common/front-end-routing/navigate-to-front-page.injectable";
import { navigateToRouteInjectionToken } from "../../common/front-end-routing/navigate-to-route-injection-token";
import ipcRendererInjectable from "../../renderer/app-paths/get-value-from-registered-channel/ipc-renderer/ipc-renderer.injectable";
describe("preferences - closing-preferences", () => {
let applicationBuilder: ApplicationBuilder;
@@ -45,10 +44,10 @@ describe("preferences - closing-preferences", () => {
} as unknown as UserStore;
rendererDi.override(userStoreInjectable, () => userStoreStub);
const themeStoreStub = { themeOptions: [] } as unknown as ThemeStore;
rendererDi.override(themeStoreInjectable, () => themeStoreStub);
rendererDi.override(ipcRendererInjectable, () => ({
on: jest.fn(),
invoke: jest.fn(), // TODO: replace with proper mocking via the IPC bridge
} as never));
rendererDi.override(navigateToFrontPageInjectable, (di) => {
const navigateToRoute = di.inject(navigateToRouteInjectionToken);
@@ -7,10 +7,8 @@ import type { ApplicationBuilder } from "../../renderer/components/test-utils/ge
import { getApplicationBuilder } from "../../renderer/components/test-utils/get-application-builder";
import userStoreInjectable from "../../common/user-store/user-store.injectable";
import type { UserStore } from "../../common/user-store";
import themeStoreInjectable from "../../renderer/theme-store.injectable";
import type { ThemeStore } from "../../renderer/theme.store";
import navigateToProxyPreferencesInjectable
from "../../common/front-end-routing/routes/preferences/proxy/navigate-to-proxy-preferences.injectable";
import navigateToProxyPreferencesInjectable from "../../common/front-end-routing/routes/preferences/proxy/navigate-to-proxy-preferences.injectable";
import ipcRendererInjectable from "../../renderer/app-paths/get-value-from-registered-channel/ipc-renderer/ipc-renderer.injectable";
describe("preferences - navigation to application preferences", () => {
let applicationBuilder: ApplicationBuilder;
@@ -24,10 +22,10 @@ describe("preferences - navigation to application preferences", () => {
} as unknown as UserStore;
rendererDi.override(userStoreInjectable, () => userStoreStub);
const themeStoreStub = ({ themeOptions: [] }) as unknown as ThemeStore;
rendererDi.override(themeStoreInjectable, () => themeStoreStub);
rendererDi.override(ipcRendererInjectable, () => ({
on: jest.fn(),
invoke: jest.fn(), // TODO: replace with proper mocking via the IPC bridge
} as never));
});
});
@@ -7,8 +7,7 @@ import type { ApplicationBuilder } from "../../renderer/components/test-utils/ge
import { getApplicationBuilder } from "../../renderer/components/test-utils/get-application-builder";
import userStoreInjectable from "../../common/user-store/user-store.injectable";
import type { UserStore } from "../../common/user-store";
import themeStoreInjectable from "../../renderer/theme-store.injectable";
import type { ThemeStore } from "../../renderer/theme.store";
import ipcRendererInjectable from "../../renderer/app-paths/get-value-from-registered-channel/ipc-renderer/ipc-renderer.injectable";
describe("preferences - navigation to editor preferences", () => {
let applicationBuilder: ApplicationBuilder;
@@ -23,10 +22,10 @@ describe("preferences - navigation to editor preferences", () => {
} as unknown as UserStore;
rendererDi.override(userStoreInjectable, () => userStoreStub);
const themeStoreStub = ({ themeOptions: [] }) as unknown as ThemeStore;
rendererDi.override(themeStoreInjectable, () => themeStoreStub);
rendererDi.override(ipcRendererInjectable, () => ({
on: jest.fn(),
invoke: jest.fn(), // TODO: replace with proper mocking via the IPC bridge
} as never));
});
});
@@ -7,11 +7,10 @@ import type { ApplicationBuilder } from "../../renderer/components/test-utils/ge
import { getApplicationBuilder } from "../../renderer/components/test-utils/get-application-builder";
import userStoreInjectable from "../../common/user-store/user-store.injectable";
import type { UserStore } from "../../common/user-store";
import themeStoreInjectable from "../../renderer/theme-store.injectable";
import type { ThemeStore } from "../../renderer/theme.store";
import type { LensRendererExtension } from "../../extensions/lens-renderer-extension";
import React from "react";
import { getRendererExtensionFake } from "../../renderer/components/test-utils/get-renderer-extension-fake";
import type { FakeExtensionData } from "../../renderer/components/test-utils/get-renderer-extension-fake";
import { getRendererExtensionFakeFor } from "../../renderer/components/test-utils/get-renderer-extension-fake";
import ipcRendererInjectable from "../../renderer/app-paths/get-value-from-registered-channel/ipc-renderer/ipc-renderer.injectable";
describe("preferences - navigation to extension specific preferences", () => {
let applicationBuilder: ApplicationBuilder;
@@ -25,10 +24,10 @@ describe("preferences - navigation to extension specific preferences", () => {
} as unknown as UserStore;
rendererDi.override(userStoreInjectable, () => userStoreStub);
const themeStoreStub = { themeOptions: [] } as unknown as ThemeStore;
rendererDi.override(themeStoreInjectable, () => themeStoreStub);
rendererDi.override(ipcRendererInjectable, () => ({
on: jest.fn(),
invoke: jest.fn(), // TODO: replace with proper mocking via the IPC bridge
} as never));
});
});
@@ -61,6 +60,7 @@ describe("preferences - navigation to extension specific preferences", () => {
describe("when extension with specific preferences is enabled", () => {
beforeEach(() => {
const getRendererExtensionFake = getRendererExtensionFakeFor(applicationBuilder);
const testExtension = getRendererExtensionFake(extensionStubWithExtensionSpecificPreferenceItems);
applicationBuilder.addExtensions(testExtension);
@@ -107,9 +107,9 @@ describe("preferences - navigation to extension specific preferences", () => {
});
});
const extensionStubWithExtensionSpecificPreferenceItems: Partial<LensRendererExtension> = {
id: "some-test-extension-id",
const extensionStubWithExtensionSpecificPreferenceItems: FakeExtensionData = {
id: "some-extension-id",
name: "some-extension-name",
appPreferences: [
{
title: "Some preference item",
@@ -7,9 +7,8 @@ import type { ApplicationBuilder } from "../../renderer/components/test-utils/ge
import { getApplicationBuilder } from "../../renderer/components/test-utils/get-application-builder";
import userStoreInjectable from "../../common/user-store/user-store.injectable";
import type { UserStore } from "../../common/user-store";
import themeStoreInjectable from "../../renderer/theme-store.injectable";
import type { ThemeStore } from "../../renderer/theme.store";
import { observable } from "mobx";
import ipcRendererInjectable from "../../renderer/app-paths/get-value-from-registered-channel/ipc-renderer/ipc-renderer.injectable";
describe("preferences - navigation to kubernetes preferences", () => {
let applicationBuilder: ApplicationBuilder;
@@ -24,10 +23,10 @@ describe("preferences - navigation to kubernetes preferences", () => {
} as unknown as UserStore;
rendererDi.override(userStoreInjectable, () => userStoreStub);
const themeStoreStub = ({ themeOptions: [] }) as unknown as ThemeStore;
rendererDi.override(themeStoreInjectable, () => themeStoreStub);
rendererDi.override(ipcRendererInjectable, () => ({
on: jest.fn(),
invoke: jest.fn(), // TODO: replace with proper mocking via the IPC bridge
} as never));
});
});
@@ -7,8 +7,7 @@ import type { ApplicationBuilder } from "../../renderer/components/test-utils/ge
import { getApplicationBuilder } from "../../renderer/components/test-utils/get-application-builder";
import userStoreInjectable from "../../common/user-store/user-store.injectable";
import type { UserStore } from "../../common/user-store";
import themeStoreInjectable from "../../renderer/theme-store.injectable";
import type { ThemeStore } from "../../renderer/theme.store";
import ipcRendererInjectable from "../../renderer/app-paths/get-value-from-registered-channel/ipc-renderer/ipc-renderer.injectable";
describe("preferences - navigation to proxy preferences", () => {
let applicationBuilder: ApplicationBuilder;
@@ -22,10 +21,10 @@ describe("preferences - navigation to proxy preferences", () => {
} as unknown as UserStore;
rendererDi.override(userStoreInjectable, () => userStoreStub);
const themeStoreStub = ({ themeOptions: [] }) as unknown as ThemeStore;
rendererDi.override(themeStoreInjectable, () => themeStoreStub);
rendererDi.override(ipcRendererInjectable, () => ({
on: jest.fn(),
invoke: jest.fn(), // TODO: replace with proper mocking via the IPC bridge
} as never));
});
});
@@ -6,13 +6,13 @@ import type { RenderResult } from "@testing-library/react";
import React from "react";
import type { ApplicationBuilder } from "../../renderer/components/test-utils/get-application-builder";
import { getApplicationBuilder } from "../../renderer/components/test-utils/get-application-builder";
import { getRendererExtensionFake } from "../../renderer/components/test-utils/get-renderer-extension-fake";
import type { FakeExtensionData } from "../../renderer/components/test-utils/get-renderer-extension-fake";
import { getRendererExtensionFakeFor } from "../../renderer/components/test-utils/get-renderer-extension-fake";
import type { UserStore } from "../../common/user-store";
import userStoreInjectable from "../../common/user-store/user-store.injectable";
import type { ThemeStore } from "../../renderer/theme.store";
import themeStoreInjectable from "../../renderer/theme-store.injectable";
import navigateToTelemetryPreferencesInjectable from "../../common/front-end-routing/routes/preferences/telemetry/navigate-to-telemetry-preferences.injectable";
import sentryDnsUrlInjectable from "../../renderer/components/+preferences/sentry-dns-url.injectable";
import ipcRendererInjectable from "../../renderer/app-paths/get-value-from-registered-channel/ipc-renderer/ipc-renderer.injectable";
describe("preferences - navigation to telemetry preferences", () => {
let applicationBuilder: ApplicationBuilder;
@@ -26,10 +26,10 @@ describe("preferences - navigation to telemetry preferences", () => {
} as unknown as UserStore;
rendererDi.override(userStoreInjectable, () => userStoreStub);
const themeStoreStub = { themeOptions: [] } as unknown as ThemeStore;
rendererDi.override(themeStoreInjectable, () => themeStoreStub);
rendererDi.override(ipcRendererInjectable, () => ({
on: jest.fn(),
invoke: jest.fn(), // TODO: replace with proper mocking via the IPC bridge
} as never));
});
});
@@ -62,8 +62,8 @@ describe("preferences - navigation to telemetry preferences", () => {
describe("when extension with telemetry preference items gets enabled", () => {
beforeEach(() => {
const testExtensionWithTelemetryPreferenceItems =
getRendererExtensionFake(extensionStubWithTelemetryPreferenceItems);
const getRendererExtensionFake = getRendererExtensionFakeFor(applicationBuilder);
const testExtensionWithTelemetryPreferenceItems = getRendererExtensionFake(extensionStubWithTelemetryPreferenceItems);
applicationBuilder.addExtensions(
testExtensionWithTelemetryPreferenceItems,
@@ -106,18 +106,19 @@ describe("preferences - navigation to telemetry preferences", () => {
});
it("given extensions but no telemetry preference items, does not show link for telemetry preferences", () => {
const testExtensionWithTelemetryPreferenceItems =
getRendererExtensionFake({
id: "some-test-extension-id",
appPreferences: [
{
title: "irrelevant",
id: "irrelevant",
showInPreferencesTab: "not-telemetry",
components: { Hint: () => <div />, Input: () => <div /> },
},
],
});
const getRendererExtensionFake = getRendererExtensionFakeFor(applicationBuilder);
const testExtensionWithTelemetryPreferenceItems = getRendererExtensionFake({
id: "some-test-extension-id",
name: "some-test-extension-name",
appPreferences: [
{
title: "irrelevant",
id: "irrelevant",
showInPreferencesTab: "not-telemetry",
components: { Hint: () => <div />, Input: () => <div /> },
},
],
});
applicationBuilder.addExtensions(
testExtensionWithTelemetryPreferenceItems,
@@ -186,8 +187,9 @@ describe("preferences - navigation to telemetry preferences", () => {
});
});
const extensionStubWithTelemetryPreferenceItems = {
const extensionStubWithTelemetryPreferenceItems: FakeExtensionData = {
id: "some-test-extension-id",
name: "some-test-extension-name",
appPreferences: [
{
title: "Some telemetry-preference item",
@@ -7,10 +7,9 @@ import type { ApplicationBuilder } from "../../renderer/components/test-utils/ge
import { getApplicationBuilder } from "../../renderer/components/test-utils/get-application-builder";
import userStoreInjectable from "../../common/user-store/user-store.injectable";
import type { UserStore } from "../../common/user-store";
import themeStoreInjectable from "../../renderer/theme-store.injectable";
import type { ThemeStore } from "../../renderer/theme.store";
import { observable } from "mobx";
import defaultShellInjectable from "../../renderer/components/+preferences/default-shell.injectable";
import ipcRendererInjectable from "../../renderer/app-paths/get-value-from-registered-channel/ipc-renderer/ipc-renderer.injectable";
describe("preferences - navigation to terminal preferences", () => {
let applicationBuilder: ApplicationBuilder;
@@ -26,12 +25,11 @@ describe("preferences - navigation to terminal preferences", () => {
} as unknown as UserStore;
rendererDi.override(userStoreInjectable, () => userStoreStub);
rendererDi.override(defaultShellInjectable, () => "some-default-shell");
const themeStoreStub = ({ themeOptions: [] }) as unknown as ThemeStore;
rendererDi.override(themeStoreInjectable, () => themeStoreStub);
rendererDi.override(ipcRendererInjectable, () => ({
on: jest.fn(),
invoke: jest.fn(), // TODO: replace with proper mocking via the IPC bridge
} as never));
});
});
@@ -9,8 +9,7 @@ import { getApplicationBuilder } from "../../renderer/components/test-utils/get-
import isAutoUpdateEnabledInjectable from "../../main/is-auto-update-enabled.injectable";
import type { UserStore } from "../../common/user-store";
import userStoreInjectable from "../../common/user-store/user-store.injectable";
import type { ThemeStore } from "../../renderer/theme.store";
import themeStoreInjectable from "../../renderer/theme-store.injectable";
import ipcRendererInjectable from "../../renderer/app-paths/get-value-from-registered-channel/ipc-renderer/ipc-renderer.injectable";
describe("preferences - navigation using application menu", () => {
let applicationBuilder: ApplicationBuilder;
@@ -27,10 +26,10 @@ describe("preferences - navigation using application menu", () => {
} as unknown as UserStore;
rendererDi.override(userStoreInjectable, () => userStoreStub);
const themeStoreStub = { themeOptions: [] } as unknown as ThemeStore;
rendererDi.override(themeStoreInjectable, () => themeStoreStub);
rendererDi.override(ipcRendererInjectable, () => ({
on: jest.fn(),
invoke: jest.fn(), // TODO: replace with proper mocking via the IPC bridge
} as never));
});
rendered = await applicationBuilder.render();
+12
View File
@@ -0,0 +1,12 @@
/**
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import type { RenderResult } from "@testing-library/react";
export function getSidebarItem(rendered: RenderResult, itemId: string) {
return rendered
.queryAllByTestId("sidebar-item")
.find((x) => x.dataset.idTest === itemId);
}
@@ -27,16 +27,17 @@ exports[`welcome - navigation using application menu when navigating to welcome
style="width: 320px;"
>
<h2>
Welcome to
OpenLens
5!
Welcome to OpenLens 5!
</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
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://join.slack.com/t/k8slens/shared_invite/zt-wcl8jq3k-68R5Wcmk1o95MLBE5igUDQ"
+3 -4
View File
@@ -30,9 +30,9 @@ interface TestStoreModel {
}
class TestStore extends BaseStore<TestStoreModel> {
@observable a: string;
@observable b: string;
@observable c: string;
@observable a = "";
@observable b = "";
@observable c = "";
constructor() {
super({
@@ -90,7 +90,6 @@ describe("BaseStore", () => {
await mainDi.runSetups();
store = undefined;
TestStore.resetInstance();
const mockOpts = {
+7 -5
View File
@@ -14,16 +14,13 @@ import { stdout, stderr } from "process";
import getCustomKubeConfigDirectoryInjectable from "../app-paths/get-custom-kube-config-directory/get-custom-kube-config-directory.injectable";
import clusterStoreInjectable from "../cluster-store/cluster-store.injectable";
import type { ClusterModel } from "../cluster-types";
import type {
DiContainer,
} from "@ogre-tools/injectable";
import type { DiContainer } from "@ogre-tools/injectable";
import { createClusterInjectionToken } from "../cluster/create-cluster-injection-token";
import directoryForUserDataInjectable from "../app-paths/directory-for-user-data/directory-for-user-data.injectable";
import { getDiForUnitTesting } from "../../main/getDiForUnitTesting";
import getConfigurationFileModelInjectable from "../get-configuration-file-model/get-configuration-file-model.injectable";
import appVersionInjectable from "../get-configuration-file-model/app-version/app-version.injectable";
import assert from "assert";
console = new Console(stdout, stderr);
@@ -148,6 +145,8 @@ describe("cluster-store", () => {
it("adds new cluster to store", async () => {
const storedCluster = clusterStore.getById("foo");
assert(storedCluster);
expect(storedCluster.id).toBe("foo");
expect(storedCluster.preferences.terminalCWD).toBe("/some-directory-for-user-data");
expect(storedCluster.preferences.icon).toBe(
@@ -249,6 +248,8 @@ describe("cluster-store", () => {
it("allows to retrieve a cluster", () => {
const storedCluster = clusterStore.getById("cluster1");
assert(storedCluster);
expect(storedCluster.id).toBe("cluster1");
expect(storedCluster.preferences.terminalCWD).toBe("/foo");
});
@@ -379,6 +380,7 @@ users:
it("migrates to modern format with icon not in file", async () => {
const { icon } = clusterStore.clustersList[0].preferences;
assert(icon);
expect(icon.startsWith("data:;base64,")).toBe(true);
});
});
+4 -3
View File
@@ -5,7 +5,7 @@
import type { AppEvent } from "../app-event-bus/event-bus";
import { appEventBus } from "../app-event-bus/event-bus";
import { Console } from "console";
import { assert, Console } from "console";
import { stdout, stderr } from "process";
console = new Console(stdout, stderr);
@@ -13,14 +13,15 @@ console = new Console(stdout, stderr);
describe("event bus tests", () => {
describe("emit", () => {
it("emits an event", () => {
let event: AppEvent = null;
let event: AppEvent | undefined;
appEventBus.addListener((data) => {
event = data;
});
appEventBus.emit({ name: "foo", action: "bar" });
expect(event.name).toBe("foo");
assert(event);
expect(event?.name).toBe("foo");
});
});
});
+1 -1
View File
@@ -21,7 +21,7 @@ describe("EventEmitter", () => {
let called = false;
const e = new EventEmitter<[]>();
e.addListener(() => 0 as any, {});
e.addListener(() => 0 as never, {});
e.addListener(() => { called = true; }, {});
e.emit();
+96 -135
View File
@@ -5,69 +5,21 @@
import { anyObject } from "jest-mock-extended";
import mockFs from "mock-fs";
import logger from "../../main/logger";
import type { CatalogEntity, CatalogEntityData, CatalogEntityKindData } from "../catalog";
import { getDiForUnitTesting } from "../../main/getDiForUnitTesting";
import getConfigurationFileModelInjectable from "../get-configuration-file-model/get-configuration-file-model.injectable";
import appVersionInjectable from "../get-configuration-file-model/app-version/app-version.injectable";
import type { DiContainer } from "@ogre-tools/injectable";
import hotbarStoreInjectable from "../hotbar-store.injectable";
import { HotbarStore } from "../hotbar-store";
import hotbarStoreInjectable from "../hotbars/store.injectable";
import type { HotbarStore } from "../hotbars/store";
import catalogEntityRegistryInjectable from "../../main/catalog/entity-registry.injectable";
import { computed } from "mobx";
import hasCategoryForEntityInjectable from "../catalog/has-category-for-entity.injectable";
import catalogCatalogEntityInjectable from "../catalog-entities/general-catalog-entities/implementations/catalog-catalog-entity.injectable";
import loggerInjectable from "../logger.injectable";
import type { Logger } from "../logger";
jest.mock("../../main/catalog/catalog-entity-registry", () => ({
catalogEntityRegistry: {
items: [
getMockCatalogEntity({
apiVersion: "v1",
kind: "Cluster",
status: {
phase: "Running",
},
metadata: {
uid: "1dfa26e2ebab15780a3547e9c7fa785c",
name: "mycluster",
source: "local",
labels: {},
},
}),
getMockCatalogEntity({
apiVersion: "v1",
kind: "Cluster",
status: {
phase: "Running",
},
metadata: {
uid: "55b42c3c7ba3b04193416cda405269a5",
name: "my_shiny_cluster",
source: "remote",
labels: {},
},
}),
getMockCatalogEntity({
apiVersion: "v1",
kind: "Cluster",
status: {
phase: "Running",
},
metadata: {
uid: "catalog-entity",
name: "Catalog",
source: "app",
labels: {},
},
}),
],
},
}));
console.log("I am here as reminder against mockfs (and to fix console logging)");
function getMockCatalogEntity(data: Partial<CatalogEntityData> & CatalogEntityKindData): CatalogEntity {
return {
@@ -84,69 +36,91 @@ function getMockCatalogEntity(data: Partial<CatalogEntityData> & CatalogEntityKi
} as CatalogEntity;
}
const testCluster = getMockCatalogEntity({
apiVersion: "v1",
kind: "Cluster",
status: {
phase: "Running",
},
metadata: {
uid: "test",
name: "test",
labels: {},
},
});
const minikubeCluster = getMockCatalogEntity({
apiVersion: "v1",
kind: "Cluster",
status: {
phase: "Running",
},
metadata: {
uid: "minikube",
name: "minikube",
labels: {},
},
});
const awsCluster = getMockCatalogEntity({
apiVersion: "v1",
kind: "Cluster",
status: {
phase: "Running",
},
metadata: {
uid: "aws",
name: "aws",
labels: {},
},
});
describe("HotbarStore", () => {
let di: DiContainer;
let hotbarStore: HotbarStore;
let testCluster: CatalogEntity;
let minikubeCluster: CatalogEntity;
let awsCluster: CatalogEntity;
let loggerMock: jest.Mocked<Logger>;
beforeEach(async () => {
di = getDiForUnitTesting({ doGeneralOverrides: true });
(di as any).unoverride(hotbarStoreInjectable);
testCluster = getMockCatalogEntity({
apiVersion: "v1",
kind: "Cluster",
status: {
phase: "Running",
},
metadata: {
uid: "some-test-id",
name: "my-test-cluster",
source: "local",
labels: {},
},
});
minikubeCluster = getMockCatalogEntity({
apiVersion: "v1",
kind: "Cluster",
status: {
phase: "Running",
},
metadata: {
uid: "some-minikube-id",
name: "my-minikube-cluster",
source: "local",
labels: {},
},
});
awsCluster = getMockCatalogEntity({
apiVersion: "v1",
kind: "Cluster",
status: {
phase: "Running",
},
metadata: {
uid: "some-aws-id",
name: "my-aws-cluster",
source: "local",
labels: {},
},
});
di.override(hasCategoryForEntityInjectable, () => () => true);
loggerMock = {
warn: jest.fn(),
debug: jest.fn(),
error: jest.fn(),
info: jest.fn(),
silly: jest.fn(),
};
di.override(loggerInjectable, () => loggerMock);
const catalogEntityRegistry = di.inject(catalogEntityRegistryInjectable);
const catalogCatalogEntity = di.inject(catalogCatalogEntityInjectable);
catalogEntityRegistry.addComputedSource("some-id", computed(() => [
testCluster,
minikubeCluster,
awsCluster,
catalogCatalogEntity,
]));
di.permitSideEffects(getConfigurationFileModelInjectable);
di.permitSideEffects(appVersionInjectable);
di.override(hotbarStoreInjectable, () => {
HotbarStore.resetInstance();
return HotbarStore.createInstance({
catalogCatalogEntity: di.inject(catalogCatalogEntityInjectable),
});
});
di.permitSideEffects(hotbarStoreInjectable);
});
afterEach(() => {
mockFs.restore();
});
describe("given no migrations", () => {
describe("given no previous data in store, running all migrations", () => {
beforeEach(async () => {
mockFs();
@@ -174,7 +148,7 @@ describe("HotbarStore", () => {
});
it("initially adds catalog entity as first item", () => {
expect(hotbarStore.getActive().items[0].entity.name).toEqual("Catalog");
expect(hotbarStore.getActive().items[0]?.entity.name).toEqual("Catalog");
});
it("adds items", () => {
@@ -186,7 +160,7 @@ describe("HotbarStore", () => {
it("removes items", () => {
hotbarStore.addToHotbar(testCluster);
hotbarStore.removeFromHotbar("test");
hotbarStore.removeFromHotbar("some-test-id");
hotbarStore.removeFromHotbar("catalog-entity");
const items = hotbarStore.getActive().items.filter(Boolean);
@@ -211,7 +185,7 @@ describe("HotbarStore", () => {
hotbarStore.restackItems(1, 5);
expect(hotbarStore.getActive().items[5]).toBeTruthy();
expect(hotbarStore.getActive().items[5].entity.uid).toEqual("test");
expect(hotbarStore.getActive().items[5]?.entity.uid).toEqual("some-test-id");
});
it("moves items down", () => {
@@ -224,7 +198,7 @@ describe("HotbarStore", () => {
const items = hotbarStore.getActive().items.map(item => item?.entity.uid || null);
expect(items.slice(0, 4)).toEqual(["aws", "catalog-entity", "test", "minikube"]);
expect(items.slice(0, 4)).toEqual(["some-aws-id", "catalog-entity", "some-test-id", "some-minikube-id"]);
});
it("moves items up", () => {
@@ -237,28 +211,21 @@ describe("HotbarStore", () => {
const items = hotbarStore.getActive().items.map(item => item?.entity.uid || null);
expect(items.slice(0, 4)).toEqual(["catalog-entity", "minikube", "aws", "test"]);
expect(items.slice(0, 4)).toEqual(["catalog-entity", "some-minikube-id", "some-aws-id", "some-test-id"]);
});
it("logs an error if cellIndex is out of bounds", () => {
hotbarStore.add({ name: "hottest", id: "hottest" });
hotbarStore.setActiveHotbar("hottest");
const { error } = logger;
const mocked = jest.fn();
logger.error = mocked;
hotbarStore.addToHotbar(testCluster, -1);
expect(mocked).toBeCalledWith("[HOTBAR-STORE]: cannot pin entity to hotbar outside of index range", anyObject());
expect(loggerMock.error).toBeCalledWith("[HOTBAR-STORE]: cannot pin entity to hotbar outside of index range", anyObject());
hotbarStore.addToHotbar(testCluster, 12);
expect(mocked).toBeCalledWith("[HOTBAR-STORE]: cannot pin entity to hotbar outside of index range", anyObject());
expect(loggerMock.error).toBeCalledWith("[HOTBAR-STORE]: cannot pin entity to hotbar outside of index range", anyObject());
hotbarStore.addToHotbar(testCluster, 13);
expect(mocked).toBeCalledWith("[HOTBAR-STORE]: cannot pin entity to hotbar outside of index range", anyObject());
logger.error = error;
expect(loggerMock.error).toBeCalledWith("[HOTBAR-STORE]: cannot pin entity to hotbar outside of index range", anyObject());
});
it("throws an error if getId is invalid or returns not a string", () => {
@@ -275,7 +242,7 @@ describe("HotbarStore", () => {
hotbarStore.addToHotbar(testCluster);
hotbarStore.restackItems(1, 1);
expect(hotbarStore.getActive().items[1].entity.uid).toEqual("test");
expect(hotbarStore.getActive().items[1]?.entity.uid).toEqual("some-test-id");
});
it("new items takes first empty cell", () => {
@@ -284,7 +251,7 @@ describe("HotbarStore", () => {
hotbarStore.restackItems(0, 3);
hotbarStore.addToHotbar(minikubeCluster);
expect(hotbarStore.getActive().items[0].entity.uid).toEqual("minikube");
expect(hotbarStore.getActive().items[0]?.entity.uid).toEqual("some-minikube-id");
});
it("throws if invalid arguments provided", () => {
@@ -315,7 +282,7 @@ describe("HotbarStore", () => {
});
});
describe("given pre beta-5 configurations", () => {
describe("given data from 5.0.0-beta.3 and version being 5.0.0-beta.10", () => {
beforeEach(async () => {
const configurationToBeMigrated = {
"some-electron-app-path-for-user-data": {
@@ -332,7 +299,7 @@ describe("HotbarStore", () => {
items: [
{
entity: {
uid: "1dfa26e2ebab15780a3547e9c7fa785c",
uid: "some-aws-id",
},
},
{
@@ -381,15 +348,17 @@ describe("HotbarStore", () => {
mockFs(configurationToBeMigrated);
di.override(appVersionInjectable, () => "5.0.0-beta.10");
await di.runSetups();
hotbarStore = di.inject(hotbarStoreInjectable);
});
it("allows to retrieve a hotbar", () => {
const hotbar = hotbarStore.getById("3caac17f-aec2-4723-9694-ad204465d935");
const hotbar = hotbarStore.findById("3caac17f-aec2-4723-9694-ad204465d935");
expect(hotbar.id).toBe("3caac17f-aec2-4723-9694-ad204465d935");
expect(hotbar?.id).toBe("3caac17f-aec2-4723-9694-ad204465d935");
});
it("clears cells without entity", () => {
@@ -403,17 +372,9 @@ describe("HotbarStore", () => {
expect(items[0]).toEqual({
entity: {
name: "mycluster",
name: "my-aws-cluster",
source: "local",
uid: "1dfa26e2ebab15780a3547e9c7fa785c",
},
});
expect(items[1]).toEqual({
entity: {
name: "my_shiny_cluster",
source: "remote",
uid: "55b42c3c7ba3b04193416cda405269a5",
uid: "some-aws-id",
},
});
});
+4 -2
View File
@@ -6,13 +6,14 @@ import https from "https";
import os from "os";
import { getMacRootCA, getWinRootCA, injectCAs, DSTRootCAX3 } from "../system-ca";
import { dependencies, devDependencies } from "../../../package.json";
import assert from "assert";
const deps = { ...dependencies, ...devDependencies };
// Skip the test if mac-ca is not installed, or os is not darwin
(deps["mac-ca"] && os.platform().includes("darwin") ? describe: describe.skip)("inject CA for Mac", () => {
// for reset https.globalAgent.options.ca after testing
let _ca: string | Buffer | (string | Buffer)[];
let _ca: string | Buffer | (string | Buffer)[] | undefined;
beforeEach(() => {
_ca = https.globalAgent.options.ca;
@@ -44,6 +45,7 @@ const deps = { ...dependencies, ...devDependencies };
injectCAs(osxCAs);
const injected = https.globalAgent.options.ca;
assert(injected);
expect(injected.includes(DSTRootCAX3)).toBeFalsy();
});
});
@@ -51,7 +53,7 @@ const deps = { ...dependencies, ...devDependencies };
// Skip the test if win-ca is not installed, or os is not win32
(deps["win-ca"] && os.platform().includes("win32") ? describe: describe.skip)("inject CA for Windows", () => {
// for reset https.globalAgent.options.ca after testing
let _ca: string | Buffer | (string | Buffer)[];
let _ca: string | Buffer | (string | Buffer)[] | undefined;
beforeEach(() => {
_ca = https.globalAgent.options.ca;
+4 -4
View File
@@ -30,7 +30,7 @@ import userStoreInjectable from "../user-store/user-store.injectable";
import type { DiContainer } from "@ogre-tools/injectable";
import directoryForUserDataInjectable from "../app-paths/directory-for-user-data/directory-for-user-data.injectable";
import type { ClusterStoreModel } from "../cluster-store/cluster-store";
import { defaultTheme } from "../vars";
import { defaultThemeId } from "../vars";
import writeFileInjectable from "../fs/write-file.injectable";
import { getDiForUnitTesting } from "../../main/getDiForUnitTesting";
import getConfigurationFileModelInjectable
@@ -49,7 +49,7 @@ describe("user store tests", () => {
mockFs();
di.override(writeFileInjectable, () => () => undefined);
di.override(writeFileInjectable, () => () => Promise.resolve());
di.override(directoryForUserDataInjectable, () => "some-directory-for-user-data");
di.override(userStoreInjectable, () => UserStore.createInstance());
@@ -80,7 +80,7 @@ describe("user store tests", () => {
userStore.httpsProxy = "abcd://defg";
expect(userStore.httpsProxy).toBe("abcd://defg");
expect(userStore.colorTheme).toBe(defaultTheme);
expect(userStore.colorTheme).toBe(defaultThemeId);
userStore.colorTheme = "light";
expect(userStore.colorTheme).toBe("light");
@@ -89,7 +89,7 @@ describe("user store tests", () => {
it("correctly resets theme to default value", async () => {
userStore.colorTheme = "some other theme";
userStore.resetTheme();
expect(userStore.colorTheme).toBe(defaultTheme);
expect(userStore.colorTheme).toBe(defaultThemeId);
});
it("correctly calculates if the last seen version is an old release", () => {
@@ -2,20 +2,30 @@
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import { kubernetesClusterCategory } from "../kubernetes-cluster";
import { getDiForUnitTesting } from "../../../renderer/getDiForUnitTesting";
import kubernetesClusterCategoryInjectable from "../../catalog/categories/kubernetes-cluster.injectable";
import type { KubernetesClusterCategory } from "../kubernetes-cluster";
describe("kubernetesClusterCategory", () => {
let kubernetesClusterCategory: KubernetesClusterCategory;
beforeEach(() => {
const di = getDiForUnitTesting();
kubernetesClusterCategory = di.inject(kubernetesClusterCategoryInjectable);
});
describe("filteredItems", () => {
const item1 = {
icon: "Icon",
title: "Title",
// eslint-disable-next-line @typescript-eslint/no-empty-function
onClick: () => {},
};
const item2 = {
icon: "Icon 2",
title: "Title 2",
// eslint-disable-next-line @typescript-eslint/no-empty-function
onClick: () => {},
};
+2 -20
View File
@@ -5,8 +5,7 @@
import { navigate } from "../../renderer/navigation";
import type { CatalogEntityMetadata, CatalogEntitySpec, CatalogEntityStatus } from "../catalog";
import { CatalogCategory, CatalogEntity } from "../catalog";
import { catalogCategoryRegistry } from "../catalog/catalog-category-registry";
import { CatalogCategory, CatalogEntity, categoryVersion } from "../catalog/catalog-entity";
interface GeneralEntitySpec extends CatalogEntitySpec {
path: string;
@@ -23,18 +22,6 @@ export class GeneralEntity extends CatalogEntity<CatalogEntityMetadata, CatalogE
async onRun() {
navigate(this.spec.path);
}
public onSettingsOpen(): void {
return;
}
public onDetailsOpen(): void {
return;
}
public onContextMenuOpen(): void {
return;
}
}
export class GeneralCategory extends CatalogCategory {
@@ -47,15 +34,10 @@ export class GeneralCategory extends CatalogCategory {
public spec = {
group: "entity.k8slens.dev",
versions: [
{
name: "v1alpha1",
entityClass: GeneralEntity,
},
categoryVersion("v1alpha1", GeneralEntity),
],
names: {
kind: "General",
},
};
}
catalogCategoryRegistry.add(new GeneralCategory());
@@ -3,13 +3,12 @@
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import { catalogCategoryRegistry } from "../catalog/catalog-category-registry";
import type { CatalogEntityActionContext, CatalogEntityContextMenuContext, CatalogEntityMetadata, CatalogEntityStatus, CatalogCategorySpec } from "../catalog";
import { CatalogEntity, CatalogCategory } from "../catalog";
import { CatalogEntity, CatalogCategory, categoryVersion } from "../catalog/catalog-entity";
import { ClusterStore } from "../cluster-store/cluster-store";
import { broadcastMessage } from "../ipc";
import { app } from "electron";
import type { CatalogEntitySpec } from "../catalog/catalog-entity";
import type { CatalogEntityConstructor, CatalogEntitySpec } from "../catalog/catalog-entity";
import { IpcRendererNavigationEvents } from "../../renderer/navigation/events";
import { requestClusterActivation, requestClusterDisconnection } from "../../renderer/ipc";
import KubeClusterCategoryIcon from "./icons/kubernetes.svg";
@@ -60,6 +59,10 @@ export type KubernetesClusterStatusPhase = "connected" | "connecting" | "disconn
export interface KubernetesClusterStatus extends CatalogEntityStatus {
}
export function isKubernetesCluster(item: unknown): item is KubernetesCluster {
return item instanceof KubernetesCluster;
}
export class KubernetesCluster<
Metadata extends KubernetesClusterMetadata = KubernetesClusterMetadata,
Status extends KubernetesClusterStatus = KubernetesClusterStatus,
@@ -99,7 +102,7 @@ export class KubernetesCluster<
//
}
async onContextMenuOpen(context: CatalogEntityContextMenuContext) {
onContextMenuOpen(context: CatalogEntityContextMenuContext) {
if (!this.metadata.source || this.metadata.source === "local") {
context.menuItems.push({
title: "Settings",
@@ -128,14 +131,10 @@ export class KubernetesCluster<
});
break;
}
catalogCategoryRegistry
.getCategoryForEntity<KubernetesClusterCategory>(this)
?.emit("contextMenuOpen", this, context);
}
}
class KubernetesClusterCategory extends CatalogCategory {
export class KubernetesClusterCategory extends CatalogCategory {
public readonly apiVersion = "catalog.k8slens.dev/v1alpha1";
public readonly kind = "CatalogCategory";
public metadata = {
@@ -145,17 +144,10 @@ class KubernetesClusterCategory extends CatalogCategory {
public spec: CatalogCategorySpec = {
group: "entity.k8slens.dev",
versions: [
{
name: "v1alpha1",
entityClass: KubernetesCluster,
},
categoryVersion("v1alpha1", KubernetesCluster as CatalogEntityConstructor<KubernetesCluster>),
],
names: {
kind: "KubernetesCluster",
},
};
}
export const kubernetesClusterCategory = new KubernetesClusterCategory();
catalogCategoryRegistry.add(kubernetesClusterCategory);
+3 -17
View File
@@ -4,8 +4,7 @@
*/
import type { CatalogEntityContextMenuContext, CatalogEntityMetadata, CatalogEntityStatus } from "../catalog";
import { CatalogCategory, CatalogEntity } from "../catalog";
import { catalogCategoryRegistry } from "../catalog/catalog-category-registry";
import { CatalogCategory, CatalogEntity, categoryVersion } from "../catalog/catalog-entity";
import { productName } from "../vars";
import { WeblinkStore } from "../weblink-store";
@@ -30,11 +29,7 @@ export class WebLink extends CatalogEntity<CatalogEntityMetadata, WebLinkStatus,
window.open(this.spec.url, "_blank");
}
public onSettingsOpen(): void {
return;
}
async onContextMenuOpen(context: CatalogEntityContextMenuContext) {
onContextMenuOpen(context: CatalogEntityContextMenuContext) {
if (this.metadata.source === "local") {
context.menuItems.push({
title: "Delete",
@@ -45,10 +40,6 @@ export class WebLink extends CatalogEntity<CatalogEntityMetadata, WebLinkStatus,
},
});
}
catalogCategoryRegistry
.getCategoryForEntity<WebLinkCategory>(this)
?.emit("contextMenuOpen", this, context);
}
}
@@ -62,15 +53,10 @@ export class WebLinkCategory extends CatalogCategory {
public spec = {
group: "entity.k8slens.dev",
versions: [
{
name: "v1alpha1",
entityClass: WebLink,
},
categoryVersion("v1alpha1", WebLink),
],
names: {
kind: "WebLink",
},
};
}
catalogCategoryRegistry.add(new WebLinkCategory());
@@ -3,96 +3,10 @@
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import { action, computed, observable, makeObservable } from "mobx";
import { once } from "lodash";
import { iter, getOrInsertMap, strictSet } from "../utils";
import type { Disposer } from "../utils";
import type { CatalogCategory, CatalogEntityData, CatalogEntityKindData } from "./catalog-entity";
import { asLegacyGlobalForExtensionApi } from "../../extensions/as-legacy-globals-for-extension-api/as-legacy-global-object-for-extension-api";
import catalogCategoryRegistryInjectable from "./category-registry.injectable";
export type CategoryFilter = (category: CatalogCategory) => any;
export class CatalogCategoryRegistry {
protected categories = observable.set<CatalogCategory>();
protected groupKinds = new Map<string, Map<string, CatalogCategory>>();
protected filters = observable.set<CategoryFilter>([], {
deep: false,
});
constructor() {
makeObservable(this);
}
@action add(category: CatalogCategory): Disposer {
const byGroup = getOrInsertMap(this.groupKinds, category.spec.group);
this.categories.add(category);
strictSet(byGroup, category.spec.names.kind, category);
return () => {
this.categories.delete(category);
byGroup.delete(category.spec.names.kind);
};
}
@computed get items() {
return Array.from(this.categories);
}
@computed get filteredItems() {
return Array.from(
iter.reduce(
this.filters,
iter.filter,
this.items.values(),
),
);
}
getForGroupKind<T extends CatalogCategory>(group: string, kind: string): T | undefined {
return this.groupKinds.get(group)?.get(kind) as T;
}
getEntityForData(data: CatalogEntityData & CatalogEntityKindData) {
const category = this.getCategoryForEntity(data);
if (!category) {
return null;
}
const splitApiVersion = data.apiVersion.split("/");
const version = splitApiVersion[1];
const specVersion = category.spec.versions.find((v) => v.name === version);
if (!specVersion) {
return null;
}
return new specVersion.entityClass(data);
}
getCategoryForEntity<T extends CatalogCategory>(data: CatalogEntityData & CatalogEntityKindData): T | undefined {
const splitApiVersion = data.apiVersion.split("/");
const group = splitApiVersion[0];
return this.getForGroupKind(group, data.kind);
}
getByName(name: string) {
return this.items.find(category => category.metadata?.name == name);
}
/**
* Add a new filter to the set of category filters
* @param fn The function that should return a truthy value if that category should be displayed
* @returns A function to remove that filter
*/
addCatalogCategoryFilter(fn: CategoryFilter): Disposer {
this.filters.add(fn);
return once(() => void this.filters.delete(fn));
}
}
export const catalogCategoryRegistry = new CatalogCategoryRegistry();
/**
* @deprecated use `di.inject(catalogCategoryRegistryInjectable)` instead
*/
export const catalogCategoryRegistry = asLegacyGlobalForExtensionApi(catalogCategoryRegistryInjectable);
+50 -35
View File
@@ -11,19 +11,19 @@ import type { Disposer } from "../utils";
import { iter } from "../utils";
import type { CategoryColumnRegistration } from "../../renderer/components/+catalog/custom-category-columns";
type ExtractEntityMetadataType<Entity> = Entity extends CatalogEntity<infer Metadata> ? Metadata : never;
type ExtractEntityStatusType<Entity> = Entity extends CatalogEntity<any, infer Status> ? Status : never;
type ExtractEntitySpecType<Entity> = Entity extends CatalogEntity<any, any, infer Spec> ? Spec : never;
export type CatalogEntityDataFor<Entity> = Entity extends CatalogEntity<infer Metadata, infer Status, infer Spec>
? CatalogEntityData<Metadata, Status, Spec>
: never;
export type CatalogEntityInstanceFrom<Constructor> = Constructor extends CatalogEntityConstructor<infer Entity>
? Entity
: never;
export type CatalogEntityConstructor<Entity extends CatalogEntity> = (
(new (data: CatalogEntityData<
ExtractEntityMetadataType<Entity>,
ExtractEntityStatusType<Entity>,
ExtractEntitySpecType<Entity>
>) => Entity)
new (data: CatalogEntityDataFor<Entity>) => Entity
);
export interface CatalogCategoryVersion<Entity extends CatalogEntity> {
export interface CatalogCategoryVersion {
/**
* The specific version that the associated constructor is for. This MUST be
* a DNS label and SHOULD be of the form `vN`, `vNalphaY`, or `vNbetaY` where
@@ -35,19 +35,19 @@ export interface CatalogCategoryVersion<Entity extends CatalogEntity> {
* - `v1alpha2`
* - `v3beta2`
*/
name: string;
readonly name: string;
/**
* The constructor for the entities.
*/
entityClass: CatalogEntityConstructor<Entity>;
readonly entityClass: CatalogEntityConstructor<CatalogEntity>;
}
export interface CatalogCategorySpec {
/**
* The grouping for for the category. This MUST be a DNS label.
*/
group: string;
readonly group: string;
/**
* The specific versions of the constructors.
@@ -56,18 +56,18 @@ export interface CatalogCategorySpec {
* For example, if `group = "entity.k8slens.dev"` and there is an entry in `.versions` with
* `name = "v1alpha1"` then the resulting `.apiVersion` MUST be `entity.k8slens.dev/v1alpha1`
*/
versions: CatalogCategoryVersion<CatalogEntity>[];
readonly versions: CatalogCategoryVersion[];
/**
* This is the concerning the category
*/
names: {
readonly names: {
/**
* The kind of entity that this category is for. This value MUST be a DNS
* label and MUST be equal to the `kind` fields that are produced by the
* `.versions.[] | .entityClass` fields.
*/
kind: string;
readonly kind: string;
};
/**
@@ -81,7 +81,7 @@ export interface CatalogCategorySpec {
*
* These columns will not be used in the "Browse" view.
*/
displayColumns?: CategoryColumnRegistration[];
readonly displayColumns?: CategoryColumnRegistration[];
}
/**
@@ -109,6 +109,30 @@ export interface CatalogCategoryEvents {
contextMenuOpen: (entity: CatalogEntity, context: CatalogEntityContextMenuContext) => void;
}
export interface CatalogCategoryMetadata {
/**
* The name of your category. The category can be searched for by this
* value. This will also be used for the catalog menu.
*/
readonly name: string;
/**
* Either an `<svg>` or the name of an icon from {@link IconProps}
*/
readonly icon: string;
}
export function categoryVersion<
T extends CatalogEntity<Metadata, Status, Spec>,
Metadata extends CatalogEntityMetadata,
Status extends CatalogEntityStatus,
Spec extends CatalogEntitySpec,
>(name: string, entityClass: new (data: CatalogEntityData<Metadata, Status, Spec>) => T): CatalogCategoryVersion {
return {
name,
entityClass: entityClass as CatalogEntityConstructor<T>,
};
}
export abstract class CatalogCategory extends (EventEmitter as new () => TypedEmitter<CatalogCategoryEvents>) {
/**
* The version of category that you are wanting to declare.
@@ -131,28 +155,17 @@ export abstract class CatalogCategory extends (EventEmitter as new () => TypedEm
/**
* The data about the category itself
*/
abstract readonly metadata: {
/**
* The name of your category. The category can be searched for by this
* value. This will also be used for the catalog menu.
*/
name: string;
/**
* Either an `<svg>` or the name of an icon from {@link IconProps}
*/
icon: string;
};
abstract readonly metadata: CatalogCategoryMetadata;
/**
* The most important part of a category, as it is where entity versions are declared.
*/
abstract spec: CatalogCategorySpec;
abstract readonly spec: CatalogCategorySpec;
/**
* @internal
*/
protected filters = observable.set<AddMenuFilter>([], {
protected readonly filters = observable.set<AddMenuFilter>([], {
deep: false,
});
@@ -217,14 +230,16 @@ export abstract class CatalogCategory extends (EventEmitter as new () => TypedEm
}
}
export interface CatalogEntityMetadata {
export type EntityMetadataObject = { [Key in string]?: EntityMetadataValue };
export type EntityMetadataValue = string | number | boolean | EntityMetadataObject | undefined;
export interface CatalogEntityMetadata extends EntityMetadataObject {
uid: string;
name: string;
shortName?: string;
description?: string;
source?: string;
labels: Record<string, string>;
[key: string]: string | object;
}
export interface CatalogEntityStatus {
@@ -392,7 +407,7 @@ export abstract class CatalogEntity<
return this.status.enabled ?? true;
}
public abstract onRun?(context: CatalogEntityActionContext): void | Promise<void>;
public abstract onContextMenuOpen(context: CatalogEntityContextMenuContext): void | Promise<void>;
public abstract onSettingsOpen(context: CatalogEntitySettingsContext): void | Promise<void>;
public onRun?(context: CatalogEntityActionContext): void | Promise<void>;
public onContextMenuOpen?(context: CatalogEntityContextMenuContext): void | Promise<void>;
public onSettingsOpen?(context: CatalogEntitySettingsContext): void | Promise<void>;
}
@@ -0,0 +1,15 @@
/**
* 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 { GeneralCategory } from "../../catalog-entities";
import { builtInCategoryInjectionToken } from "../category-registry.injectable";
const generalCategoryInjectable = getInjectable({
id: "general-category",
instantiate: () => new GeneralCategory(),
injectionToken: builtInCategoryInjectionToken,
});
export default generalCategoryInjectable;
@@ -0,0 +1,15 @@
/**
* 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 { KubernetesClusterCategory } from "../../catalog-entities/kubernetes-cluster";
import { builtInCategoryInjectionToken } from "../category-registry.injectable";
const kubernetesClusterCategoryInjectable = getInjectable({
id: "kubernetes-cluster-category",
instantiate: () => new KubernetesClusterCategory(),
injectionToken: builtInCategoryInjectionToken,
});
export default kubernetesClusterCategoryInjectable;
@@ -0,0 +1,15 @@
/**
* 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 { WebLinkCategory } from "../../catalog-entities";
import { builtInCategoryInjectionToken } from "../category-registry.injectable";
const weblinkCategoryInjectable = getInjectable({
id: "weblink-category",
instantiate: () => new WebLinkCategory(),
injectionToken: builtInCategoryInjectionToken,
});
export default weblinkCategoryInjectable;
@@ -0,0 +1,27 @@
/**
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import { getInjectable, getInjectionToken } from "@ogre-tools/injectable";
import type { CatalogCategory } from "./catalog-entity";
import { CatalogCategoryRegistry } from "./category-registry";
export const builtInCategoryInjectionToken = getInjectionToken<CatalogCategory>({
id: "built-in-category-token",
});
const catalogCategoryRegistryInjectable = getInjectable({
id: "catalog-category-registry",
instantiate: (di) => {
const registry = new CatalogCategoryRegistry();
const categories = di.injectMany(builtInCategoryInjectionToken);
for (const category of categories) {
registry.add(category);
}
return registry;
},
});
export default catalogCategoryRegistryInjectable;
+103
View File
@@ -0,0 +1,103 @@
/**
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import { action, computed, observable, makeObservable } from "mobx";
import { once } from "lodash";
import { iter, getOrInsertMap, strictSet } from "../utils";
import type { Disposer } from "../utils";
import type { CatalogCategory, CatalogEntityData, CatalogEntityKindData } from "./catalog-entity";
export type CategoryFilter = (category: CatalogCategory) => any;
export class CatalogCategoryRegistry {
protected readonly categories = observable.set<CatalogCategory>();
protected readonly groupKinds = new Map<string, Map<string, CatalogCategory>>();
protected readonly filters = observable.set<CategoryFilter>([], {
deep: false,
});
constructor() {
makeObservable(this);
}
@action add(category: CatalogCategory): Disposer {
const byGroup = getOrInsertMap(this.groupKinds, category.spec.group);
this.categories.add(category);
strictSet(byGroup, category.spec.names.kind, category);
return () => {
this.categories.delete(category);
byGroup.delete(category.spec.names.kind);
};
}
@computed get items() {
return Array.from(this.categories);
}
@computed get filteredItems() {
return Array.from(
iter.reduce(
this.filters,
iter.filter,
this.items.values(),
),
);
}
getForGroupKind<T extends CatalogCategory>(group: string, kind: string): T | undefined {
return this.groupKinds.get(group)?.get(kind) as T;
}
getEntityForData(data: CatalogEntityData & CatalogEntityKindData) {
const category = this.getCategoryForEntity(data);
if (!category) {
return null;
}
const splitApiVersion = data.apiVersion.split("/");
const version = splitApiVersion[1];
const specVersion = category.spec.versions.find((v) => v.name === version);
if (!specVersion) {
return null;
}
return new specVersion.entityClass(data);
}
hasCategoryForEntity({ kind, apiVersion }: CatalogEntityData & CatalogEntityKindData): boolean {
const splitApiVersion = apiVersion.split("/");
const group = splitApiVersion[0];
return this.groupKinds.get(group)?.has(kind) ?? false;
}
getCategoryForEntity<T extends CatalogCategory>(data: CatalogEntityData & CatalogEntityKindData): T | undefined {
const splitApiVersion = data.apiVersion.split("/");
const group = splitApiVersion[0];
return this.getForGroupKind(group, data.kind);
}
getByName(name: string) {
return this.items.find(category => category.metadata?.name == name);
}
/**
* Add a new filter to the set of category filters
* @param fn The function that should return a truthy value if that category should be displayed
* @returns A function to remove that filter
*/
addCatalogCategoryFilter(fn: CategoryFilter): Disposer {
this.filters.add(fn);
return once(() => void this.filters.delete(fn));
}
}
@@ -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 { CatalogEntityData, CatalogEntityKindData } from "./catalog-entity";
import catalogCategoryRegistryInjectable from "./category-registry.injectable";
export type HasCategoryForEntity = (data: CatalogEntityData & CatalogEntityKindData) => boolean;
const hasCategoryForEntityInjectable = getInjectable({
id: "has-category-for-entity",
instantiate: (di): HasCategoryForEntity => {
const registry = di.inject(catalogCategoryRegistryInjectable);
return (data) => registry.hasCategoryForEntity(data);
},
});
export default hasCategoryForEntityInjectable;
+1
View File
@@ -4,4 +4,5 @@
*/
export * from "./catalog-category-registry";
export * from "./category-registry";
export * from "./catalog-entity";
@@ -0,0 +1,23 @@
/**
* 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, CatalogEntityContextMenuContext } from "./catalog-entity";
import catalogCategoryRegistryInjectable from "./category-registry.injectable";
export type VisitEntityContextMenu = (entity: CatalogEntity, context: CatalogEntityContextMenuContext) => void;
const visitEntityContextMenuInjectable = getInjectable({
id: "visit-entity-context-menu",
instantiate: (di): VisitEntityContextMenu => {
const categoryRegistry = di.inject(catalogCategoryRegistryInjectable);
return (entity, context) => {
entity.onContextMenuOpen?.(context);
categoryRegistry.getCategoryForEntity(entity)?.emit("contextMenuOpen", entity, context);
};
},
});
export default visitEntityContextMenuInjectable;
@@ -12,7 +12,7 @@ const allowedResourcesInjectable = getInjectable({
instantiate: (di) => {
const cluster = di.inject(hostedClusterInjectable);
return computed(() => new Set(cluster.allowedResources), {
return computed(() => new Set(cluster?.allowedResources), {
// This needs to be here so that during refresh changes are only propogated when necessary
equals: (cur, prev) => comparer.structural(cur, prev),
});
+6 -2
View File
@@ -103,8 +103,12 @@ export class ClusterStore extends BaseStore<ClusterStoreModel> {
return this.clusters.size > 0;
}
getById(id: ClusterId): Cluster | null {
return this.clusters.get(id) ?? null;
getById(id: ClusterId | undefined): Cluster | undefined {
if (id) {
return this.clusters.get(id);
}
return undefined;
}
addCluster(clusterOrModel: ClusterModel | Cluster): Cluster {
@@ -3,12 +3,12 @@
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import { getInjectable } from "@ogre-tools/injectable";
import { deploymentStore } from "./deployments.store";
import { getClusterIdFromHost } from "../utils";
const deploymentsStoreInjectable = getInjectable({
id: "deployments-store",
instantiate: () => deploymentStore,
const hostedClusterIdInjectable = getInjectable({
id: "hosted-cluster-id",
instantiate: () => getClusterIdFromHost(location.host),
causesSideEffects: true,
});
export default deploymentsStoreInjectable;
export default hostedClusterIdInjectable;
@@ -3,16 +3,17 @@
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import { getInjectable } from "@ogre-tools/injectable";
import { getHostedClusterId } from "../utils";
import hostedClusterIdInjectable from "./hosted-cluster-id.injectable";
import clusterStoreInjectable from "./cluster-store.injectable";
const hostedClusterInjectable = getInjectable({
id: "hosted-cluster",
instantiate: (di) => {
const hostedClusterId = getHostedClusterId();
const hostedClusterId = di.inject(hostedClusterIdInjectable);
const store = di.inject(clusterStoreInjectable);
return di.inject(clusterStoreInjectable).getById(hostedClusterId);
return store.getById(hostedClusterId);
},
});
+90 -70
View File
@@ -3,10 +3,9 @@
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import { ipcMain } from "electron";
import { action, comparer, computed, makeObservable, observable, reaction, when } from "mobx";
import { broadcastMessage } from "../ipc";
import type { ContextHandler } from "../../main/context-handler/context-handler";
import type { ClusterContextHandler } from "../../main/context-handler/context-handler";
import type { KubeConfig } from "@kubernetes/client-node";
import { HttpError } from "@kubernetes/client-node";
import type { Kubectl } from "../../main/kubectl/kubectl";
@@ -14,22 +13,24 @@ import type { KubeconfigManager } from "../../main/kubeconfig-manager/kubeconfig
import { loadConfigFromFile, loadConfigFromFileSync, validateKubeConfig } from "../kube-helpers";
import type { KubeApiResource, KubeResource } from "../rbac";
import { apiResourceRecord, apiResources } from "../rbac";
import logger from "../../main/logger";
import { VersionDetector } from "../../main/cluster-detectors/version-detector";
import { DetectorRegistry } from "../../main/cluster-detectors/detector-registry";
import plimit from "p-limit";
import type { ClusterState, ClusterRefreshOptions, ClusterMetricsResourceType, ClusterId, ClusterMetadata, ClusterModel, ClusterPreferences, ClusterPrometheusPreferences, UpdateClusterModel, KubeAuthUpdate } from "../cluster-types";
import { ClusterMetadataKey, initialNodeShellImage, ClusterStatus } from "../cluster-types";
import { disposer, toJS } from "../utils";
import { disposer, isDefined, isRequestError, toJS } from "../utils";
import type { Response } from "request";
import { clusterListNamespaceForbiddenChannel } from "../ipc/cluster";
import type { CanI } from "./authorization-review.injectable";
import type { ListNamespaces } from "./list-namespaces.injectable";
import assert from "assert";
import type { Logger } from "../logger";
export interface ClusterDependencies {
readonly directoryForKubeConfigs: string;
createKubeconfigManager: (cluster: Cluster) => KubeconfigManager;
createContextHandler: (cluster: Cluster) => ContextHandler;
readonly logger: Logger;
createKubeconfigManager: (cluster: Cluster) => KubeconfigManager | undefined;
createContextHandler: (cluster: Cluster) => ClusterContextHandler | undefined;
createKubectl: (clusterVersion: string) => Kubectl;
createAuthorizationReview: (config: KubeConfig) => CanI;
createListNamespaces: (config: KubeConfig) => ListNamespaces;
@@ -43,17 +44,31 @@ export interface ClusterDependencies {
export class Cluster implements ClusterModel, ClusterState {
/** Unique id for a cluster */
public readonly id: ClusterId;
private kubeCtl: Kubectl;
private kubeCtl: Kubectl | undefined;
/**
* Context handler
*
* @internal
*/
public contextHandler: ContextHandler;
protected proxyKubeconfigManager: KubeconfigManager;
protected eventsDisposer = disposer();
protected readonly _contextHandler: ClusterContextHandler | undefined;
protected readonly _proxyKubeconfigManager: KubeconfigManager | undefined;
protected readonly eventsDisposer = disposer();
protected activated = false;
private resourceAccessStatuses: Map<KubeApiResource, boolean> = new Map();
private readonly resourceAccessStatuses = new Map<KubeApiResource, boolean>();
public get contextHandler() {
// TODO: remove these once main/renderer are seperate classes
assert(this._contextHandler, "contextHandler is only defined in the main environment");
return this._contextHandler;
}
protected get proxyKubeconfigManager() {
// TODO: remove these once main/renderer are seperate classes
assert(this._proxyKubeconfigManager, "proxyKubeconfigManager is only defined in the main environment");
return this._proxyKubeconfigManager;
}
get whenReady() {
return when(() => this.ready);
@@ -64,21 +79,21 @@ export class Cluster implements ClusterModel, ClusterState {
*
* @observable
*/
@observable contextName: string;
@observable contextName!: string;
/**
* Path to kubeconfig
*
* @observable
*/
@observable kubeConfigPath: string;
@observable kubeConfigPath!: string;
/**
* @deprecated
*/
@observable workspace: string;
@observable workspace?: string;
/**
* @deprecated
*/
@observable workspaces: string[];
@observable workspaces?: string[];
/**
* Kubernetes API server URL
*
@@ -215,7 +230,7 @@ export class Cluster implements ClusterModel, ClusterState {
* @computed
* @internal
*/
@computed get defaultNamespace(): string {
@computed get defaultNamespace(): string | undefined {
return this.preferences.defaultNamespace;
}
@@ -231,19 +246,24 @@ export class Cluster implements ClusterModel, ClusterState {
throw validationError;
}
this.apiUrl = config.getCluster(config.getContextObject(this.contextName).cluster).server;
const context = config.getContextObject(this.contextName);
if (ipcMain) {
// for the time being, until renderer gets its own cluster type
this.contextHandler = this.dependencies.createContextHandler(this);
this.proxyKubeconfigManager = this.dependencies.createKubeconfigManager(this);
assert(context);
logger.debug(`[CLUSTER]: Cluster init success`, {
id: this.id,
context: this.contextName,
apiUrl: this.apiUrl,
});
}
const cluster = config.getCluster(context.cluster);
assert(cluster);
this.apiUrl = cluster.server;
// for the time being, until renderer gets its own cluster type
this._contextHandler = this.dependencies.createContextHandler(this);
this._proxyKubeconfigManager = this.dependencies.createKubeconfigManager(this);
this.dependencies.logger.debug(`[CLUSTER]: Cluster init success`, {
id: this.id,
context: this.contextName,
apiUrl: this.apiUrl,
});
}
/**
@@ -255,6 +275,7 @@ export class Cluster implements ClusterModel, ClusterState {
// Note: do not assign ID as that should never be updated
this.kubeConfigPath = model.kubeConfigPath;
this.contextName = model.contextName;
if (model.workspace) {
this.workspace = model.workspace;
@@ -264,10 +285,6 @@ export class Cluster implements ClusterModel, ClusterState {
this.workspaces = model.workspaces;
}
if (model.contextName) {
this.contextName = model.contextName;
}
if (model.preferences) {
this.preferences = model.preferences;
}
@@ -289,7 +306,7 @@ export class Cluster implements ClusterModel, ClusterState {
* @internal
*/
protected bindEvents() {
logger.info(`[CLUSTER]: bind events`, this.getMeta());
this.dependencies.logger.info(`[CLUSTER]: bind events`, this.getMeta());
const refreshTimer = setInterval(() => !this.disconnected && this.refresh(), 30000); // every 30s
const refreshMetadataTimer = setInterval(() => !this.disconnected && this.refreshMetadata(), 900000); // every 15 minutes
@@ -310,13 +327,13 @@ export class Cluster implements ClusterModel, ClusterState {
* @internal
*/
protected async recreateProxyKubeconfig() {
logger.info("[CLUSTER]: Recreating proxy kubeconfig");
this.dependencies.logger.info("[CLUSTER]: Recreating proxy kubeconfig");
try {
await this.proxyKubeconfigManager.clear();
await this.getProxyKubeconfig();
} catch (error) {
logger.error(`[CLUSTER]: failed to recreate proxy kubeconfig`, error);
this.dependencies.logger.error(`[CLUSTER]: failed to recreate proxy kubeconfig`, error);
}
}
@@ -330,7 +347,7 @@ export class Cluster implements ClusterModel, ClusterState {
return this.pushState();
}
logger.info(`[CLUSTER]: activate`, this.getMeta());
this.dependencies.logger.info(`[CLUSTER]: activate`, this.getMeta());
if (!this.eventsDisposer.length) {
this.bindEvents();
@@ -348,7 +365,7 @@ export class Cluster implements ClusterModel, ClusterState {
await this.refreshAccessibility();
// download kubectl in background, so it's not blocking dashboard
this.ensureKubectl()
.catch(error => logger.warn(`[CLUSTER]: failed to download kubectl for clusterId=${this.id}`, error));
.catch(error => this.dependencies.logger.warn(`[CLUSTER]: failed to download kubectl for clusterId=${this.id}`, error));
this.broadcastConnectUpdate("Connected, waiting for view to load ...");
}
@@ -372,9 +389,8 @@ export class Cluster implements ClusterModel, ClusterState {
*/
@action
async reconnect() {
logger.info(`[CLUSTER]: reconnect`, this.getMeta());
this.contextHandler?.stopServer();
await this.contextHandler?.ensureServer();
this.dependencies.logger.info(`[CLUSTER]: reconnect`, this.getMeta());
await this.contextHandler?.restartServer();
this.disconnected = false;
}
@@ -383,10 +399,10 @@ export class Cluster implements ClusterModel, ClusterState {
*/
@action disconnect(): void {
if (this.disconnected) {
return void logger.debug("[CLUSTER]: already disconnected", { id: this.id });
return void this.dependencies.logger.debug("[CLUSTER]: already disconnected", { id: this.id });
}
logger.info(`[CLUSTER]: disconnecting`, { id: this.id });
this.dependencies.logger.info(`[CLUSTER]: disconnecting`, { id: this.id });
this.eventsDisposer();
this.contextHandler?.stopServer();
this.disconnected = true;
@@ -397,7 +413,7 @@ export class Cluster implements ClusterModel, ClusterState {
this.allowedNamespaces = [];
this.resourceAccessStatuses.clear();
this.pushState();
logger.info(`[CLUSTER]: disconnected`, { id: this.id });
this.dependencies.logger.info(`[CLUSTER]: disconnected`, { id: this.id });
}
/**
@@ -406,7 +422,7 @@ export class Cluster implements ClusterModel, ClusterState {
*/
@action
async refresh(opts: ClusterRefreshOptions = {}) {
logger.info(`[CLUSTER]: refresh`, this.getMeta());
this.dependencies.logger.info(`[CLUSTER]: refresh`, this.getMeta());
await this.refreshConnectionStatus();
if (this.accessible) {
@@ -424,7 +440,7 @@ export class Cluster implements ClusterModel, ClusterState {
*/
@action
async refreshMetadata() {
logger.info(`[CLUSTER]: refreshMetadata`, this.getMeta());
this.dependencies.logger.info(`[CLUSTER]: refreshMetadata`, this.getMeta());
const metadata = await DetectorRegistry.getInstance().detectForCluster(this);
const existingMetadata = this.metadata;
@@ -495,11 +511,31 @@ export class Cluster implements ClusterModel, ClusterState {
return ClusterStatus.AccessGranted;
} catch (error) {
logger.error(`[CLUSTER]: Failed to connect to "${this.contextName}": ${error}`);
this.dependencies.logger.error(`[CLUSTER]: Failed to connect to "${this.contextName}": ${error}`);
if (error.statusCode) {
if (error.statusCode >= 400 && error.statusCode < 500) {
this.broadcastConnectUpdate("Invalid credentials", true);
if (isRequestError(error)) {
if (error.statusCode) {
if (error.statusCode >= 400 && error.statusCode < 500) {
this.broadcastConnectUpdate("Invalid credentials", true);
return ClusterStatus.AccessDenied;
}
const message = String(error.error || error.message) || String(error);
this.broadcastConnectUpdate(message, true);
return ClusterStatus.Offline;
}
if (error.failed === true) {
if (error.timedOut === true) {
this.broadcastConnectUpdate("Connection timed out", true);
return ClusterStatus.Offline;
}
this.broadcastConnectUpdate("Failed to fetch credentials", true);
return ClusterStatus.AccessDenied;
}
@@ -507,26 +543,10 @@ export class Cluster implements ClusterModel, ClusterState {
const message = String(error.error || error.message) || String(error);
this.broadcastConnectUpdate(message, true);
return ClusterStatus.Offline;
} else {
this.broadcastConnectUpdate("Unknown error has occurred", true);
}
if (error.failed === true) {
if (error.timedOut === true) {
this.broadcastConnectUpdate("Connection timed out", true);
return ClusterStatus.Offline;
}
this.broadcastConnectUpdate("Failed to fetch credentials", true);
return ClusterStatus.AccessDenied;
}
const message = String(error.error || error.message) || String(error);
this.broadcastConnectUpdate(message, true);
return ClusterStatus.Offline;
}
}
@@ -575,7 +595,7 @@ export class Cluster implements ClusterModel, ClusterState {
* @param state cluster state
*/
pushState(state = this.getState()) {
logger.silly(`[CLUSTER]: push-state`, state);
this.dependencies.logger.silly(`[CLUSTER]: push-state`, state);
broadcastMessage("cluster:state", this.id, state);
}
@@ -598,7 +618,7 @@ export class Cluster implements ClusterModel, ClusterState {
broadcastConnectUpdate(message: string, isError = false): void {
const update: KubeAuthUpdate = { message, isError };
logger.debug(`[CLUSTER]: broadcasting connection update`, { ...update, meta: this.getMeta() });
this.dependencies.logger.debug(`[CLUSTER]: broadcasting connection update`, { ...update, meta: this.getMeta() });
broadcastMessage(`cluster:${this.id}:connection-update`, update);
}
@@ -613,12 +633,12 @@ export class Cluster implements ClusterModel, ClusterState {
return await listNamespaces();
} catch (error) {
const ctx = proxyConfig.getContextObject(this.contextName);
const namespaceList = [ctx.namespace].filter(Boolean);
const namespaceList = [ctx?.namespace].filter(isDefined);
if (namespaceList.length === 0 && error instanceof HttpError && error.statusCode === 403) {
const { response } = error as HttpError & { response: Response };
logger.info("[CLUSTER]: listing namespaces is forbidden, broadcasting", { clusterId: this.id, error: response.body });
this.dependencies.logger.info("[CLUSTER]: listing namespaces is forbidden, broadcasting", { clusterId: this.id, error: response.body });
broadcastMessage(clusterListNamespaceForbiddenChannel, this.id);
}
@@ -6,5 +6,8 @@ import { getInjectionToken } from "@ogre-tools/injectable";
import type { ClusterModel } from "../cluster-types";
import type { Cluster } from "./cluster";
export const createClusterInjectionToken =
getInjectionToken<(model: ClusterModel) => Cluster>({ id: "create-cluster-token" });
export type CreateCluster = (model: ClusterModel) => Cluster;
export const createClusterInjectionToken = getInjectionToken<CreateCluster>({
id: "create-cluster-token",
});
@@ -5,6 +5,7 @@
import type { KubeConfig } from "@kubernetes/client-node";
import { CoreV1Api } from "@kubernetes/client-node";
import { getInjectable } from "@ogre-tools/injectable";
import { isDefined } from "../utils";
export type ListNamespaces = () => Promise<string[]>;
@@ -14,7 +15,9 @@ export function listNamespaces(config: KubeConfig): ListNamespaces {
return async () => {
const { body: { items }} = await coreApi.listNamespace();
return items.map(ns => ns.metadata.name);
return items
.map(ns => ns.metadata?.name)
.filter(isDefined);
};
}
@@ -14,12 +14,9 @@ describe("verify-that-all-routes-have-component", () => {
it("verify that routes have route component", async () => {
const rendererDi = getDiForUnitTesting({ doGeneralOverrides: true });
rendererDi.override(
clusterStoreInjectable,
() => ({ getById: (): null => null } as unknown as ClusterStore),
);
await rendererDi.runSetups();
rendererDi.override(clusterStoreInjectable, () => ({
getById: () => null,
} as unknown as ClusterStore));
const routes = rendererDi.injectMany(routeInjectionToken);
const routeComponents = rendererDi.injectMany(
@@ -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 hotbarStoreInjectable from "./store.injectable";
import type { CreateHotbarData, CreateHotbarOptions } from "./types";
export type AddHotbar = (data: CreateHotbarData, opts?: CreateHotbarOptions) => void;
const addHotbarInjectable = getInjectable({
id: "add-hotbar",
instantiate: (di): AddHotbar => {
const store = di.inject(hotbarStoreInjectable);
return (data, opts) => store.add(data, opts);
},
});
export default addHotbarInjectable;
@@ -3,8 +3,9 @@
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import { getInjectable } from "@ogre-tools/injectable";
import catalogCatalogEntityInjectable from "./catalog-entities/general-catalog-entities/implementations/catalog-catalog-entity.injectable";
import { HotbarStore } from "./hotbar-store";
import catalogCatalogEntityInjectable from "../catalog-entities/general-catalog-entities/implementations/catalog-catalog-entity.injectable";
import { HotbarStore } from "./store";
import loggerInjectable from "../logger.injectable";
const hotbarStoreInjectable = getInjectable({
id: "hotbar-store",
@@ -14,6 +15,7 @@ const hotbarStoreInjectable = getInjectable({
return HotbarStore.createInstance({
catalogCatalogEntity: di.inject(catalogCatalogEntityInjectable),
logger: di.inject(loggerInjectable),
});
},
@@ -4,22 +4,17 @@
*/
import { action, comparer, observable, makeObservable, computed } from "mobx";
import { BaseStore } from "./base-store";
import migrations from "../migrations/hotbar-store";
import { toJS } from "./utils";
import type { CatalogEntity } from "./catalog";
import logger from "../main/logger";
import { broadcastMessage } from "./ipc";
import type {
Hotbar,
CreateHotbarData,
CreateHotbarOptions } from "./hotbar-types";
import {
defaultHotbarCells,
getEmptyHotbar,
} from "./hotbar-types";
import { hotbarTooManyItemsChannel } from "./ipc/hotbar";
import type { GeneralEntity } from "./catalog-entities";
import { BaseStore } from "../base-store";
import migrations from "../../migrations/hotbar-store";
import { toJS } from "../utils";
import type { CatalogEntity } from "../catalog";
import { broadcastMessage } from "../ipc";
import type { Hotbar, CreateHotbarData, CreateHotbarOptions } from "./types";
import { defaultHotbarCells, getEmptyHotbar } from "./types";
import { hotbarTooManyItemsChannel } from "../ipc/hotbar";
import type { GeneralEntity } from "../catalog-entities";
import type { Logger } from "../logger";
import assert from "assert";
export interface HotbarStoreModel {
hotbars: Hotbar[];
@@ -27,15 +22,16 @@ export interface HotbarStoreModel {
}
interface Dependencies {
catalogCatalogEntity: GeneralEntity;
readonly catalogCatalogEntity: GeneralEntity;
readonly logger: Logger;
}
export class HotbarStore extends BaseStore<HotbarStoreModel> {
readonly displayName = "HotbarStore";
@observable hotbars: Hotbar[] = [];
@observable private _activeHotbarId: string;
@observable private _activeHotbarId!: string;
constructor(private dependencies: Dependencies) {
constructor(private readonly dependencies: Dependencies) {
super({
configName: "lens-hotbar-store",
accessPropertiesByDotNotation: false, // To make dots safe in cluster context names
@@ -62,7 +58,7 @@ export class HotbarStore extends BaseStore<HotbarStoreModel> {
this._activeHotbarId = this.hotbars[hotbar].id;
}
} else if (typeof hotbar === "string") {
if (this.getById(hotbar)) {
if (this.findById(hotbar)) {
this._activeHotbarId = hotbar;
}
} else {
@@ -120,34 +116,35 @@ export class HotbarStore extends BaseStore<HotbarStoreModel> {
return toJS(model);
}
getActive() {
return this.getById(this.activeHotbarId);
getActive(): Hotbar {
const hotbar = this.findById(this.activeHotbarId);
assert(hotbar, "There MUST always be an active hotbar");
return hotbar;
}
getByName(name: string) {
findByName(name: string) {
return this.hotbars.find((hotbar) => hotbar.name === name);
}
getById(id: string) {
findById(id: string) {
return this.hotbars.find((hotbar) => hotbar.id === id);
}
add = action(
(
data: CreateHotbarData,
{ setActive = false }: CreateHotbarOptions = {},
) => {
const hotbar = getEmptyHotbar(data.name, data.id);
@action
add(data: CreateHotbarData, { setActive = false }: CreateHotbarOptions = {}) {
const hotbar = getEmptyHotbar(data.name, data.id);
this.hotbars.push(hotbar);
this.hotbars.push(hotbar);
if (setActive) {
this._activeHotbarId = hotbar.id;
}
},
);
if (setActive) {
this._activeHotbarId = hotbar.id;
}
}
setHotbarName = action((id: string, name: string) => {
@action
setHotbarName(id: string, name: string): void {
const index = this.hotbars.findIndex((hotbar) => hotbar.id === id);
if (index < 0) {
@@ -158,19 +155,18 @@ export class HotbarStore extends BaseStore<HotbarStoreModel> {
}
this.hotbars[index].name = name;
});
}
remove = action((hotbar: Hotbar) => {
if (this.hotbars.length <= 1) {
throw new Error("Cannot remove the last hotbar");
}
@action
remove(hotbar: Hotbar) {
assert(this.hotbars.length >= 2, "Cannot remove the last hotbar");
this.hotbars = this.hotbars.filter((h) => h !== hotbar);
if (this.activeHotbarId === hotbar.id) {
this.setActiveHotbar(0);
}
});
}
@action
addToHotbar(item: CatalogEntity, cellIndex?: number) {
@@ -209,7 +205,7 @@ export class HotbarStore extends BaseStore<HotbarStoreModel> {
} else if (0 <= cellIndex && cellIndex < hotbar.items.length) {
hotbar.items[cellIndex] = newItem;
} else {
logger.error(
this.dependencies.logger.error(
`[HOTBAR-STORE]: cannot pin entity to hotbar outside of index range`,
{ entityId: uid, hotbarId: hotbar.id, cellIndex },
);
@@ -246,8 +242,9 @@ export class HotbarStore extends BaseStore<HotbarStoreModel> {
findClosestEmptyIndex(from: number, direction = 1) {
let index = from;
const hotbar = this.getActive();
while (this.getActive().items[index] != null) {
while (hotbar.items[index] != null) {
index += direction;
}
@@ -314,11 +311,9 @@ export class HotbarStore extends BaseStore<HotbarStoreModel> {
return false;
}
return (
this.getActive().items.findIndex(
(item) => item?.entity.uid === entity.getId(),
) >= 0
);
const indexInActiveHotbar = this.getActive().items.findIndex(item => item?.entity.uid === entity.getId());
return indexInActiveHotbar >= 0;
}
getDisplayLabel(hotbar: Hotbar): string {
@@ -4,13 +4,13 @@
*/
import * as uuid from "uuid";
import type { Tuple } from "./utils";
import { tuple } from "./utils";
import type { Tuple } from "../utils";
import { tuple } from "../utils";
export interface HotbarItem {
entity: {
uid: string;
name?: string;
name: string;
source?: string;
};
params?: {
@@ -6,5 +6,5 @@ import type { Channel } from "../channel";
export const createChannel = <Message>(name: string): Channel<Message> => ({
name,
_template: null,
_template: null as never,
});
@@ -53,7 +53,7 @@ describe("type enforced ipc tests", () => {
const source = new EventEmitter();
const listener = () => called += 1;
const results = [true, false, true];
const verifier = (args: unknown[]): args is [] => results.pop();
const verifier = (args: unknown[]): args is [] => results.pop() ?? false;
const channel = "foobar";
onCorrect({ source, listener, verifier, channel });
+3 -7
View File
@@ -13,8 +13,6 @@ export interface ItemObject {
}
export abstract class ItemStore<Item extends ItemObject> {
abstract loadAll(...args: any[]): Promise<void | Item[]>;
protected defaultSorting = (item: Item) => item.getName();
@observable failedLoading = false;
@@ -44,8 +42,7 @@ export abstract class ItemStore<Item extends ItemObject> {
return this.items.length;
}
getByName(name: string, ...args: any[]): Item;
getByName(name: string): Item {
getByName(name: string): Item | undefined {
return this.items.find(item => item.getName() === name);
}
@@ -115,7 +112,6 @@ export abstract class ItemStore<Item extends ItemObject> {
}
}
protected async loadItem(...args: any[]): Promise<Item>;
@action
protected async loadItem(request: () => Promise<Item>, sortItems = true) {
const item = await Promise.resolve(request()).catch(() => null);
@@ -133,9 +129,9 @@ export abstract class ItemStore<Item extends ItemObject> {
if (sortItems) items = this.sortItems(items);
this.items.replace(items);
}
return item;
}
return item;
}
@action
@@ -3,19 +3,32 @@
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import { ingressStore } from "../../../renderer/components/+network-ingresses/ingress.store";
import { apiManager } from "../api-manager";
import { getDiForUnitTesting } from "../../../renderer/getDiForUnitTesting";
import type { ApiManager } from "../api-manager";
import apiManagerInjectable from "../api-manager/manager.injectable";
import { KubeApi } from "../kube-api";
import { KubeObject } from "../kube-object";
import { KubeObjectStore } from "../kube-object.store";
class TestApi extends KubeApi<KubeObject> {
protected async checkPreferredVersion() {
return;
}
}
class TestStore extends KubeObjectStore<KubeObject, TestApi> {
}
describe("ApiManager", () => {
let apiManager: ApiManager;
beforeEach(() => {
const di = getDiForUnitTesting({ doGeneralOverrides: true });
apiManager = di.inject(apiManagerInjectable);
});
describe("registerApi", () => {
it("re-register store if apiBase changed", async () => {
const apiBase = "apis/v1/foo";
@@ -23,25 +36,27 @@ describe("ApiManager", () => {
const kubeApi = new TestApi({
objectConstructor: KubeObject,
apiBase,
kind: "foo",
fallbackApiBases: [fallbackApiBase],
checkPreferredVersion: true,
});
const kubeStore = new TestStore(kubeApi);
apiManager.registerApi(apiBase, kubeApi);
// Define to use test api for ingress store
Object.defineProperty(ingressStore, "api", { value: kubeApi });
apiManager.registerStore(ingressStore, [kubeApi]);
Object.defineProperty(kubeStore, "api", { value: kubeApi });
apiManager.registerStore(kubeStore, [kubeApi]);
// Test that store is returned with original apiBase
expect(apiManager.getStore(kubeApi)).toBe(ingressStore);
expect(apiManager.getStore(kubeApi)).toBe(kubeStore);
// Change apiBase similar as checkPreferredVersion does
Object.defineProperty(kubeApi, "apiBase", { value: fallbackApiBase });
apiManager.registerApi(fallbackApiBase, kubeApi);
// Test that store is returned with new apiBase
expect(apiManager.getStore(kubeApi)).toBe(ingressStore);
expect(apiManager.getStore(kubeApi)).toBe(kubeStore);
});
});
});
+29
View File
@@ -16,8 +16,15 @@ describe("Crds", () => {
name: "foo",
resourceVersion: "12345",
uid: "12345",
selfLink: "/apis/apiextensions.k8s.io/v1/customresourcedefinitions/foo",
},
spec: {
group: "foo.bar",
names: {
kind: "Foo",
plural: "foos",
},
scope: "Namespaced",
versions: [
{
name: "123",
@@ -44,8 +51,15 @@ describe("Crds", () => {
name: "foo",
resourceVersion: "12345",
uid: "12345",
selfLink: "/apis/apiextensions.k8s.io/v1/customresourcedefinitions/foo",
},
spec: {
group: "foo.bar",
names: {
kind: "Foo",
plural: "foos",
},
scope: "Namespaced",
versions: [
{
name: "123",
@@ -72,8 +86,15 @@ describe("Crds", () => {
name: "foo",
resourceVersion: "12345",
uid: "12345",
selfLink: "/apis/apiextensions.k8s.io/v1/customresourcedefinitions/foo",
},
spec: {
group: "foo.bar",
names: {
kind: "Foo",
plural: "foos",
},
scope: "Namespaced",
versions: [
{
name: "123",
@@ -100,8 +121,15 @@ describe("Crds", () => {
name: "foo",
resourceVersion: "12345",
uid: "12345",
selfLink: "/apis/apiextensions.k8s.io/v1/customresourcedefinitions/foo",
},
spec: {
group: "foo.bar",
names: {
kind: "Foo",
plural: "foos",
},
scope: "Namespaced",
version: "abc",
versions: [
{
@@ -129,6 +157,7 @@ describe("Crds", () => {
name: "foo",
resourceVersion: "12345",
uid: "12345",
selfLink: "/apis/apiextensions.k8s.io/v1/customresourcedefinitions/foo",
},
spec: {
version: "abc",
@@ -3,31 +3,39 @@
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import { Deployment, DeploymentApi } from "../endpoints/deployment.api";
import { getDiForUnitTesting } from "../../../renderer/getDiForUnitTesting";
import storesAndApisCanBeCreatedInjectable from "../../../renderer/stores-apis-can-be-created.injectable";
import apiKubeInjectable from "../../../renderer/k8s/api-kube.injectable";
import type { DeploymentApi } from "../endpoints/deployment.api";
import deploymentApiInjectable from "../endpoints/deployment.api.injectable";
import type { KubeJsonApi } from "../kube-json-api";
class DeploymentApiTest extends DeploymentApi {
public setRequest(request: any) {
this.request = request;
}
}
describe("DeploymentApi", () => {
let deploymentApi: DeploymentApi;
let kubeJsonApi: jest.Mocked<KubeJsonApi>;
beforeEach(() => {
const di = getDiForUnitTesting({ doGeneralOverrides: true });
di.override(storesAndApisCanBeCreatedInjectable, () => true);
kubeJsonApi = {
getResponse: jest.fn(),
get: jest.fn(),
post: jest.fn(),
put: jest.fn(),
patch: jest.fn(),
del: jest.fn(),
} as never;
di.override(apiKubeInjectable, () => kubeJsonApi);
deploymentApi = di.inject(deploymentApiInjectable);
});
describe("scale", () => {
const requestMock = {
patch: () => ({}),
} as unknown as KubeJsonApi;
const sub = new DeploymentApiTest({ objectConstructor: Deployment });
sub.setRequest(requestMock);
it("requests Kubernetes API with PATCH verb and correct amount of replicas", () => {
const patchSpy = jest.spyOn(requestMock, "patch");
deploymentApi.scale({ namespace: "default", name: "deployment-1" }, 5);
sub.scale({ namespace: "default", name: "deployment-1" }, 5);
expect(patchSpy).toHaveBeenCalledWith("/apis/apps/v1/namespaces/default/deployments/deployment-1/scale", {
expect(kubeJsonApi.patch).toHaveBeenCalledWith("/apis/apps/v1/namespaces/default/deployments/deployment-1/scale", {
data: {
spec: {
replicas: 5,
@@ -3,42 +3,26 @@
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import { EndpointSubset } from "../endpoints";
import { formatEndpointSubset } from "../endpoints";
describe("endpoint tests", () => {
describe("EndpointSubset", () => {
it.each([
4,
false,
null,
{},
[],
"ahe",
/a/,
])("should always initialize fields when given %j", (data: any) => {
const sub = new EndpointSubset(data);
expect(sub.addresses).toStrictEqual([]);
expect(sub.notReadyAddresses).toStrictEqual([]);
expect(sub.ports).toStrictEqual([]);
});
it("toString should be addresses X ports", () => {
const sub = new EndpointSubset({
it("formatEndpointSubset should be addresses X ports", () => {
const formatted = formatEndpointSubset({
addresses: [{
ip: "1.1.1.1",
}, {
ip: "1.1.1.2",
}] as any,
}],
notReadyAddresses: [],
ports: [{
port: "81",
port: 81,
}, {
port: "82",
}] as any,
port: 82,
}],
});
expect(sub.toString()).toBe("1.1.1.1:81, 1.1.1.1:82, 1.1.1.2:81, 1.1.1.2:82");
expect(formatted).toBe("1.1.1.1:81, 1.1.1.1:82, 1.1.1.2:81, 1.1.1.2:82");
});
});
});
@@ -9,33 +9,33 @@ import { HelmChart } from "../endpoints/helm-charts.api";
describe("HelmChart tests", () => {
describe("HelmChart.create() tests", () => {
it("should throw on non-object input", () => {
expect(() => HelmChart.create("" as any)).toThrowError('"value" must be of type object');
expect(() => HelmChart.create(1 as any)).toThrowError('"value" must be of type object');
expect(() => HelmChart.create(false as any)).toThrowError('"value" must be of type object');
expect(() => HelmChart.create([] as any)).toThrowError('"value" must be of type object');
expect(() => HelmChart.create(Symbol() as any)).toThrowError('"value" must be of type object');
expect(() => HelmChart.create("" as never)).toThrowError('"value" must be of type object');
expect(() => HelmChart.create(1 as never)).toThrowError('"value" must be of type object');
expect(() => HelmChart.create(false as never)).toThrowError('"value" must be of type object');
expect(() => HelmChart.create([] as never)).toThrowError('"value" must be of type object');
expect(() => HelmChart.create(Symbol() as never)).toThrowError('"value" must be of type object');
});
it("should throw on missing fields", () => {
expect(() => HelmChart.create({} as any)).toThrowError('"apiVersion" is required');
expect(() => HelmChart.create({} as never)).toThrowError('"apiVersion" is required');
expect(() => HelmChart.create({
apiVersion: "!",
} as any)).toThrowError('"name" is required');
} as never)).toThrowError('"name" is required');
expect(() => HelmChart.create({
apiVersion: "!",
name: "!",
} as any)).toThrowError('"version" is required');
} as never)).toThrowError('"version" is required');
expect(() => HelmChart.create({
apiVersion: "!",
name: "!",
version: "!",
} as any)).toThrowError('"repo" is required');
} as never)).toThrowError('"repo" is required');
expect(() => HelmChart.create({
apiVersion: "!",
name: "!",
version: "!",
repo: "!",
} as any)).toThrowError('"created" is required');
} as never)).toThrowError('"created" is required');
});
it("should throw on fields being wrong type", () => {
@@ -46,7 +46,7 @@ describe("HelmChart tests", () => {
repo: "!",
created: "!",
digest: "!",
} as any)).toThrowError('"apiVersion" must be a string');
} as never)).toThrowError('"apiVersion" must be a string');
expect(() => HelmChart.create({
apiVersion: "1",
name: 1,
@@ -54,7 +54,7 @@ describe("HelmChart tests", () => {
repo: "!",
created: "!",
digest: "!",
} as any)).toThrowError('"name" must be a string');
} as never)).toThrowError('"name" must be a string');
expect(() => HelmChart.create({
apiVersion: "!",
name: "!",
@@ -62,7 +62,7 @@ describe("HelmChart tests", () => {
repo: "!",
created: "!",
digest: 1,
} as any)).toThrowError('"digest" must be a string');
} as never)).toThrowError('"digest" must be a string');
expect(() => HelmChart.create({
apiVersion: "1",
name: "",
@@ -70,7 +70,7 @@ describe("HelmChart tests", () => {
repo: "!",
created: "!",
digest: "!",
} as any)).toThrowError('"version" must be a string');
} as never)).toThrowError('"version" must be a string');
expect(() => HelmChart.create({
apiVersion: "1",
name: "1",
@@ -78,7 +78,7 @@ describe("HelmChart tests", () => {
repo: 1,
created: "!",
digest: "!",
} as any)).toThrowError('"repo" must be a string');
} as never)).toThrowError('"repo" must be a string');
expect(() => HelmChart.create({
apiVersion: "1",
name: "1",
@@ -86,7 +86,7 @@ describe("HelmChart tests", () => {
repo: "1",
created: 1,
digest: "a",
} as any)).toThrowError('"created" must be a string');
} as never)).toThrowError('"created" must be a string');
expect(() => HelmChart.create({
apiVersion: "1",
name: "1",
@@ -94,7 +94,7 @@ describe("HelmChart tests", () => {
repo: "1",
created: "!",
digest: 1,
} as any)).toThrowError('"digest" must be a string');
} as never)).toThrowError('"digest" must be a string');
expect(() => HelmChart.create({
apiVersion: "1",
name: "1",
@@ -103,7 +103,7 @@ describe("HelmChart tests", () => {
digest: "1",
created: "!",
kubeVersion: 1,
} as any)).toThrowError('"kubeVersion" must be a string');
} as never)).toThrowError('"kubeVersion" must be a string');
expect(() => HelmChart.create({
apiVersion: "1",
name: "1",
@@ -112,7 +112,7 @@ describe("HelmChart tests", () => {
digest: "1",
created: "!",
description: 1,
} as any)).toThrowError('"description" must be a string');
} as never)).toThrowError('"description" must be a string');
expect(() => HelmChart.create({
apiVersion: "1",
name: "1",
@@ -121,7 +121,7 @@ describe("HelmChart tests", () => {
digest: "1",
created: "!",
home: 1,
} as any)).toThrowError('"home" must be a string');
} as never)).toThrowError('"home" must be a string');
expect(() => HelmChart.create({
apiVersion: "1",
name: "1",
@@ -130,7 +130,7 @@ describe("HelmChart tests", () => {
digest: "1",
created: "!",
engine: 1,
} as any)).toThrowError('"engine" must be a string');
} as never)).toThrowError('"engine" must be a string');
expect(() => HelmChart.create({
apiVersion: "1",
name: "1",
@@ -139,7 +139,7 @@ describe("HelmChart tests", () => {
digest: "1",
created: "!",
icon: 1,
} as any)).toThrowError('"icon" must be a string');
} as never)).toThrowError('"icon" must be a string');
expect(() => HelmChart.create({
apiVersion: "1",
name: "1",
@@ -148,7 +148,7 @@ describe("HelmChart tests", () => {
digest: "1",
created: "!",
appVersion: 1,
} as any)).toThrowError('"appVersion" must be a string');
} as never)).toThrowError('"appVersion" must be a string');
expect(() => HelmChart.create({
apiVersion: "1",
name: "1",
@@ -157,7 +157,7 @@ describe("HelmChart tests", () => {
digest: "1",
created: "!",
tillerVersion: 1,
} as any)).toThrowError('"tillerVersion" must be a string');
} as never)).toThrowError('"tillerVersion" must be a string');
expect(() => HelmChart.create({
apiVersion: "1",
name: "1",
@@ -166,7 +166,7 @@ describe("HelmChart tests", () => {
digest: "1",
created: "!",
deprecated: 1,
} as any)).toThrowError('"deprecated" must be a boolean');
} as never)).toThrowError('"deprecated" must be a boolean');
expect(() => HelmChart.create({
apiVersion: "1",
name: "1",
@@ -175,7 +175,7 @@ describe("HelmChart tests", () => {
digest: "1",
created: "!",
keywords: 1,
} as any)).toThrowError('"keywords" must be an array');
} as never)).toThrowError('"keywords" must be an array');
expect(() => HelmChart.create({
apiVersion: "1",
name: "1",
@@ -184,7 +184,7 @@ describe("HelmChart tests", () => {
digest: "1",
created: "!",
sources: 1,
} as any)).toThrowError('"sources" must be an array');
} as never)).toThrowError('"sources" must be an array');
expect(() => HelmChart.create({
apiVersion: "1",
name: "1",
@@ -193,7 +193,7 @@ describe("HelmChart tests", () => {
digest: "1",
created: "!",
maintainers: 1,
} as any)).toThrowError('"maintainers" must be an array');
} as never)).toThrowError('"maintainers" must be an array');
});
it("should filter non-string keywords", () => {
@@ -204,10 +204,10 @@ describe("HelmChart tests", () => {
repo: "1",
digest: "1",
created: "!",
keywords: [1, "a", false, {}, "b"] as any,
keywords: [1, "a", false, {}, "b"] as never,
});
expect(chart.keywords).toStrictEqual(["a", "b"]);
expect(chart?.keywords).toStrictEqual(["a", "b"]);
});
it("should filter non-string sources", () => {
@@ -218,10 +218,10 @@ describe("HelmChart tests", () => {
repo: "1",
digest: "1",
created: "!",
sources: [1, "a", false, {}, "b"] as any,
sources: [1, "a", false, {}, "b"] as never,
});
expect(chart.sources).toStrictEqual(["a", "b"]);
expect(chart?.sources).toStrictEqual(["a", "b"]);
});
it("should filter invalid maintainers", () => {
@@ -236,10 +236,10 @@ describe("HelmChart tests", () => {
name: "a",
email: "b",
url: "c",
}] as any,
}] as never,
});
expect(chart.maintainers).toStrictEqual([{
expect(chart?.maintainers).toStrictEqual([{
name: "a",
email: "b",
url: "c",
@@ -261,9 +261,9 @@ describe("HelmChart tests", () => {
name: "a",
email: "b",
url: "c",
}] as any,
}] as never,
"asdjhajksdhadjks": 1,
} as any);
} as never);
expect(warnFn).toHaveBeenCalledWith("HelmChart data has unexpected fields", {
original: anyObject(),
@@ -14,6 +14,8 @@ describe("computeRuleDeclarations", () => {
name: "foo",
resourceVersion: "1",
uid: "bar",
namespace: "default",
selfLink: "/apis/networking.k8s.io/v1/ingresses/default/foo",
},
});
@@ -21,6 +23,7 @@ describe("computeRuleDeclarations", () => {
host: "foo.bar",
http: {
paths: [{
pathType: "Exact",
backend: {
service: {
name: "my-service",
@@ -44,6 +47,8 @@ describe("computeRuleDeclarations", () => {
name: "foo",
resourceVersion: "1",
uid: "bar",
namespace: "default",
selfLink: "/apis/networking.k8s.io/v1/ingresses/default/foo",
},
});
@@ -55,6 +60,7 @@ describe("computeRuleDeclarations", () => {
host: "foo.bar",
http: {
paths: [{
pathType: "Exact",
backend: {
service: {
name: "my-service",
@@ -78,6 +84,8 @@ describe("computeRuleDeclarations", () => {
name: "foo",
resourceVersion: "1",
uid: "bar",
namespace: "default",
selfLink: "/apis/networking.k8s.io/v1/ingresses/default/foo",
},
});
@@ -91,6 +99,7 @@ describe("computeRuleDeclarations", () => {
host: "foo.bar",
http: {
paths: [{
pathType: "Exact",
backend: {
service: {
name: "my-service",
@@ -19,7 +19,7 @@ import { parseKubeApi } from "../kube-api-parse";
/**
* [<input-url>, <expected-result>]
*/
type KubeApiParseTestData = [string, Required<IKubeApiParsed>];
type KubeApiParseTestData = [string, IKubeApiParsed];
const tests: KubeApiParseTestData[] = [
["/apis/apiextensions.k8s.io/v1beta1/customresourcedefinitions/prometheuses.monitoring.coreos.com", {
@@ -126,6 +126,6 @@ describe("parseApi unit tests", () => {
});
it.each(throwtests)("testing %j should throw", (url) => {
expect(() => parseKubeApi(url)).toThrowError("invalid apiPath");
expect(() => parseKubeApi(url as never)).toThrowError("invalid apiPath");
});
});
+149 -140
View File
@@ -3,34 +3,36 @@
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import type { Request } from "node-fetch";
import { forRemoteCluster, KubeApi } from "../kube-api";
import { KubeJsonApi } from "../kube-json-api";
import { KubeObject } from "../kube-object";
import AbortController from "abort-controller";
import { delay } from "../../utils/delay";
import { PassThrough } from "stream";
import type { ApiManager } from "../api-manager";
import { apiManager } from "../api-manager";
import { Ingress, Pod } from "../endpoints";
import { ApiManager } from "../api-manager";
import type { FetchMock } from "jest-fetch-mock/types";
import { DeploymentApi, Ingress, IngressApi, Pod, PodApi } from "../endpoints";
import { getDiForUnitTesting } from "../../../renderer/getDiForUnitTesting";
import apiManagerInjectable from "../api-manager/manager.injectable";
import autoRegistrationInjectable from "../api-manager/auto-registration.injectable";
jest.mock("../api-manager");
const mockApiManager = apiManager as jest.Mocked<ApiManager>;
class TestKubeObject extends KubeObject {
static kind = "Pod";
static namespaced = true;
static apiBase = "/api/v1/pods";
}
class TestKubeApi extends KubeApi<TestKubeObject> {
public async checkPreferredVersion() {
return super.checkPreferredVersion();
}
}
const mockFetch = fetch as FetchMock;
describe("forRemoteCluster", () => {
let apiManager: jest.Mocked<ApiManager>;
beforeEach(async () => {
const di = getDiForUnitTesting({ doGeneralOverrides: true });
await di.runSetups();
apiManager = new ApiManager() as jest.Mocked<ApiManager>;
di.override(apiManagerInjectable, () => apiManager);
});
it("builds api client for KubeObject", async () => {
const api = forRemoteCluster({
cluster: {
@@ -39,7 +41,7 @@ describe("forRemoteCluster", () => {
user: {
token: "daa",
},
}, TestKubeObject);
}, Pod);
expect(api).toBeInstanceOf(KubeApi);
});
@@ -52,9 +54,9 @@ describe("forRemoteCluster", () => {
user: {
token: "daa",
},
}, TestKubeObject, TestKubeApi);
}, Pod, PodApi);
expect(api).toBeInstanceOf(TestKubeApi);
expect(api).toBeInstanceOf(PodApi);
});
it("calls right api endpoint", async () => {
@@ -65,9 +67,9 @@ describe("forRemoteCluster", () => {
user: {
token: "daa",
},
}, TestKubeObject);
}, Pod);
(fetch as any).mockResponse(async (request: any) => {
mockFetch.mockResponse(async (request: any) => {
expect(request.url).toEqual("https://127.0.0.1:6443/api/v1/pods");
return {
@@ -83,22 +85,31 @@ describe("forRemoteCluster", () => {
describe("KubeApi", () => {
let request: KubeJsonApi;
let apiManager: jest.Mocked<ApiManager>;
beforeEach(async () => {
const di = getDiForUnitTesting({ doGeneralOverrides: true });
await di.runSetups();
beforeEach(() => {
request = new KubeJsonApi({
serverAddress: `http://127.0.0.1:9999`,
apiBase: "/api-kube",
});
apiManager = new ApiManager() as jest.Mocked<ApiManager>;
di.override(apiManagerInjectable, () => apiManager);
di.inject(autoRegistrationInjectable);
});
it("uses url from apiBase if apiBase contains the resource", async () => {
(fetch as any).mockResponse(async (request: any) => {
mockFetch.mockResponse(async (request: any) => {
if (request.url === "http://127.0.0.1:9999/api-kube/apis/networking.k8s.io/v1") {
return {
body: JSON.stringify({
resources: [{
name: "ingresses",
}] as any[],
}],
}),
};
} else if (request.url === "http://127.0.0.1:9999/api-kube/apis/extensions/v1beta1") {
@@ -107,13 +118,13 @@ describe("KubeApi", () => {
body: JSON.stringify({
resources: [{
name: "ingresses",
}] as any[],
}],
}),
};
} else {
return {
body: JSON.stringify({
resources: [] as any[],
resources: [],
}),
};
}
@@ -121,9 +132,9 @@ describe("KubeApi", () => {
const apiBase = "/apis/networking.k8s.io/v1/ingresses";
const fallbackApiBase = "/apis/extensions/v1beta1/ingresses";
const kubeApi = new KubeApi({
const kubeApi = new IngressApi({
request,
objectConstructor: KubeObject,
objectConstructor: Ingress,
apiBase,
fallbackApiBases: [fallbackApiBase],
checkPreferredVersion: true,
@@ -138,11 +149,11 @@ describe("KubeApi", () => {
});
it("uses url from fallbackApiBases if apiBase lacks the resource", async () => {
(fetch as any).mockResponse(async (request: any) => {
mockFetch.mockResponse(async (request: any) => {
if (request.url === "http://127.0.0.1:9999/api-kube/apis/networking.k8s.io/v1") {
return {
body: JSON.stringify({
resources: [] as any[],
resources: [],
}),
};
} else if (request.url === "http://127.0.0.1:9999/api-kube/apis/extensions/v1beta1") {
@@ -150,13 +161,13 @@ describe("KubeApi", () => {
body: JSON.stringify({
resources: [{
name: "ingresses",
}] as any[],
}],
}),
};
} else {
return {
body: JSON.stringify({
resources: [] as any[],
resources: [],
}),
};
}
@@ -164,9 +175,10 @@ describe("KubeApi", () => {
const apiBase = "apis/networking.k8s.io/v1/ingresses";
const fallbackApiBase = "/apis/extensions/v1beta1/ingresses";
const kubeApi = new KubeApi({
const kubeApi = new IngressApi({
request,
objectConstructor: Object.assign(KubeObject, { apiBase }),
kind: "Ingress",
fallbackApiBases: [fallbackApiBase],
checkPreferredVersion: true,
});
@@ -183,107 +195,100 @@ describe("KubeApi", () => {
it("registers with apiManager if checkPreferredVersion changes apiVersionPreferred", async () => {
expect.hasAssertions();
const api = new TestKubeApi({
const api = new IngressApi({
objectConstructor: Ingress,
checkPreferredVersion: true,
fallbackApiBases: ["/apis/extensions/v1beta1/ingresses"],
request: {
get: jest.fn()
.mockImplementationOnce((path: string) => {
expect(path).toBe("/apis/networking.k8s.io/v1");
throw new Error("no");
})
.mockImplementationOnce((path: string) => {
expect(path).toBe("/apis/extensions/v1beta1");
return {
resources: [
{
name: "ingresses",
},
],
};
})
.mockImplementationOnce((path: string) => {
expect(path).toBe("/apis/extensions");
return {
preferredVersion: {
version: "v1beta1",
},
};
.mockImplementation((path: string) => {
switch (path) {
case "/apis/networking.k8s.io/v1":
throw new Error("no");
case "/apis/extensions/v1beta1":
return {
resources: [
{
name: "ingresses",
},
],
};
case "/apis/extensions":
return {
preferredVersion: {
version: "v1beta1",
},
};
default:
throw new Error("unknown path");
}
}),
} as any,
} as Partial<KubeJsonApi> as KubeJsonApi,
});
await api.checkPreferredVersion();
await (api as any).checkPreferredVersion();
expect(api.apiVersionPreferred).toBe("v1beta1");
expect(mockApiManager.registerApi).toBeCalledWith("/apis/extensions/v1beta1/ingresses", expect.anything());
expect(apiManager.registerApi).toBeCalledWith(api);
});
it("registers with apiManager if checkPreferredVersion changes apiVersionPreferred with non-grouped apis", async () => {
expect.hasAssertions();
const api = new TestKubeApi({
const api = new PodApi({
objectConstructor: Pod,
checkPreferredVersion: true,
fallbackApiBases: ["/api/v1beta1/pods"],
request: {
get: jest.fn()
.mockImplementationOnce((path: string) => {
expect(path).toBe("/api/v1");
throw new Error("no");
})
.mockImplementationOnce((path: string) => {
expect(path).toBe("/api/v1beta1");
return {
resources: [
{
name: "pods",
},
],
};
})
.mockImplementationOnce((path: string) => {
expect(path).toBe("/api");
return {
preferredVersion: {
version: "v1beta1",
},
};
.mockImplementation((path: string) => {
switch (path) {
case "/api/v1":
throw new Error("no");
case "/api/v1beta1":
return {
resources: [
{
name: "pods",
},
],
};
case "/api":
return {
preferredVersion: {
version: "v1beta1",
},
};
default:
throw new Error("unknown path");
}
}),
} as any,
} as Partial<KubeJsonApi> as KubeJsonApi,
});
await api.checkPreferredVersion();
await (api as any).checkPreferredVersion();
expect(api.apiVersionPreferred).toBe("v1beta1");
expect(mockApiManager.registerApi).toBeCalledWith("/api/v1beta1/pods", expect.anything());
expect(apiManager.registerApi).toBeCalledWith(api);
});
});
describe("patch", () => {
let api: TestKubeApi;
let api: DeploymentApi;
beforeEach(() => {
api = new TestKubeApi({
api = new DeploymentApi({
request,
objectConstructor: TestKubeObject,
});
});
it("sends strategic patch by default", async () => {
expect.hasAssertions();
(fetch as any).mockResponse(async (request: Request) => {
mockFetch.mockResponse(async request => {
expect(request.method).toEqual("PATCH");
expect(request.headers.get("content-type")).toMatch("strategic-merge-patch");
expect(request.body.toString()).toEqual(JSON.stringify({ spec: { replicas: 2 }}));
expect(request.body?.toString()).toEqual(JSON.stringify({ spec: { replicas: 2 }}));
return {};
});
@@ -296,10 +301,10 @@ describe("KubeApi", () => {
it("allows to use merge patch", async () => {
expect.hasAssertions();
(fetch as any).mockResponse(async (request: Request) => {
mockFetch.mockResponse(async request => {
expect(request.method).toEqual("PATCH");
expect(request.headers.get("content-type")).toMatch("merge-patch");
expect(request.body.toString()).toEqual(JSON.stringify({ spec: { replicas: 2 }}));
expect(request.body?.toString()).toEqual(JSON.stringify({ spec: { replicas: 2 }}));
return {};
});
@@ -312,10 +317,10 @@ describe("KubeApi", () => {
it("allows to use json patch", async () => {
expect.hasAssertions();
(fetch as any).mockResponse(async (request: Request) => {
mockFetch.mockResponse(async request => {
expect(request.method).toEqual("PATCH");
expect(request.headers.get("content-type")).toMatch("json-patch");
expect(request.body.toString()).toEqual(JSON.stringify([{ op: "replace", path: "/spec/replicas", value: 2 }]));
expect(request.body?.toString()).toEqual(JSON.stringify([{ op: "replace", path: "/spec/replicas", value: 2 }]));
return {};
});
@@ -328,10 +333,10 @@ describe("KubeApi", () => {
it("allows deep partial patch", async () => {
expect.hasAssertions();
(fetch as any).mockResponse(async (request: Request) => {
mockFetch.mockResponse(async request => {
expect(request.method).toEqual("PATCH");
expect(request.headers.get("content-type")).toMatch("merge-patch");
expect(request.body.toString()).toEqual(JSON.stringify({ metadata: { annotations: { provisioned: "true" }}}));
expect(request.body?.toString()).toEqual(JSON.stringify({ metadata: { annotations: { provisioned: "true" }}}));
return {};
});
@@ -345,18 +350,18 @@ describe("KubeApi", () => {
});
describe("delete", () => {
let api: TestKubeApi;
let api: PodApi;
beforeEach(() => {
api = new TestKubeApi({
api = new PodApi({
request,
objectConstructor: TestKubeObject,
objectConstructor: Pod,
});
});
it("sends correct request with empty namespace", async () => {
expect.hasAssertions();
(fetch as any).mockResponse(async (request: Request) => {
mockFetch.mockResponse(async request => {
expect(request.method).toEqual("DELETE");
expect(request.url).toEqual("http://127.0.0.1:9999/api-kube/api/v1/pods/foo?propagationPolicy=Background");
@@ -368,7 +373,7 @@ describe("KubeApi", () => {
it("sends correct request without namespace", async () => {
expect.hasAssertions();
(fetch as any).mockResponse(async (request: Request) => {
mockFetch.mockResponse(async request => {
expect(request.method).toEqual("DELETE");
expect(request.url).toEqual("http://127.0.0.1:9999/api-kube/api/v1/namespaces/default/pods/foo?propagationPolicy=Background");
@@ -380,7 +385,7 @@ describe("KubeApi", () => {
it("sends correct request with namespace", async () => {
expect.hasAssertions();
(fetch as any).mockResponse(async (request: Request) => {
mockFetch.mockResponse(async request => {
expect(request.method).toEqual("DELETE");
expect(request.url).toEqual("http://127.0.0.1:9999/api-kube/api/v1/namespaces/kube-system/pods/foo?propagationPolicy=Background");
@@ -392,7 +397,7 @@ describe("KubeApi", () => {
it("allows to change propagationPolicy", async () => {
expect.hasAssertions();
(fetch as any).mockResponse(async (request: Request) => {
mockFetch.mockResponse(async request => {
expect(request.method).toEqual("DELETE");
expect(request.url).toMatch("propagationPolicy=Orphan");
@@ -404,13 +409,13 @@ describe("KubeApi", () => {
});
describe("watch", () => {
let api: TestKubeApi;
let api: PodApi;
let stream: PassThrough;
beforeEach(() => {
api = new TestKubeApi({
api = new PodApi({
request,
objectConstructor: TestKubeObject,
objectConstructor: Pod,
});
stream = new PassThrough();
});
@@ -423,9 +428,10 @@ describe("KubeApi", () => {
it("sends a valid watch request", () => {
const spy = jest.spyOn(request, "getResponse");
(fetch as any).mockResponse(async () => {
mockFetch.mockResponse(async () => {
return {
body: stream,
// needed for https://github.com/jefflau/jest-fetch-mock/issues/218
body: stream as unknown as string,
};
});
@@ -436,9 +442,10 @@ describe("KubeApi", () => {
it("sends timeout as a query parameter", async () => {
const spy = jest.spyOn(request, "getResponse");
(fetch as any).mockResponse(async () => {
mockFetch.mockResponse(async () => {
return {
body: stream,
// needed for https://github.com/jefflau/jest-fetch-mock/issues/218
body: stream as unknown as string,
};
});
@@ -449,13 +456,14 @@ describe("KubeApi", () => {
it("aborts watch using abortController", async (done) => {
const spy = jest.spyOn(request, "getResponse");
(fetch as any).mockResponse(async (request: Request) => {
(request as any).signal.addEventListener("abort", () => {
mockFetch.mockResponse(async request => {
request.signal.addEventListener("abort", () => {
done();
});
return {
body: stream,
// needed for https://github.com/jefflau/jest-fetch-mock/issues/218
body: stream as unknown as string,
};
});
@@ -478,9 +486,9 @@ describe("KubeApi", () => {
it("if request ended", (done) => {
const spy = jest.spyOn(request, "getResponse");
jest.spyOn(stream, "on").mockImplementation((eventName: string, callback: Function) => {
jest.spyOn(stream, "on").mockImplementation((event: string | symbol, callback: Function) => {
// End the request in 100ms.
if (eventName === "end") {
if (event === "end") {
setTimeout(() => {
callback();
}, 100);
@@ -493,8 +501,8 @@ describe("KubeApi", () => {
jest.spyOn(global, "fetch").mockImplementation(async () => {
return {
ok: true,
body: stream,
} as any;
body: stream as never,
} as Partial<Response> as Response;
});
api.watch({
@@ -512,9 +520,10 @@ describe("KubeApi", () => {
it("if request not closed after timeout", (done) => {
const spy = jest.spyOn(request, "getResponse");
(fetch as any).mockResponse(async () => {
mockFetch.mockResponse(async () => {
return {
body: stream,
// needed for https://github.com/jefflau/jest-fetch-mock/issues/218
body: stream as unknown as string,
};
});
@@ -536,9 +545,9 @@ describe("KubeApi", () => {
it("retries only once if request ends and timeout is set", (done) => {
const spy = jest.spyOn(request, "getResponse");
jest.spyOn(stream, "on").mockImplementation((eventName: string, callback: Function) => {
jest.spyOn(stream, "on").mockImplementation((event: string | symbol, callback: Function) => {
// End the request in 100ms.
if (eventName === "end") {
if (event === "end") {
setTimeout(() => {
callback();
}, 100);
@@ -551,8 +560,8 @@ describe("KubeApi", () => {
jest.spyOn(global, "fetch").mockImplementation(async () => {
return {
ok: true,
body: stream,
} as any;
body: stream as never,
} as Partial<Response> as Response;
});
const timeoutSeconds = 0.5;
@@ -577,21 +586,21 @@ describe("KubeApi", () => {
});
describe("create", () => {
let api: TestKubeApi;
let api: PodApi;
beforeEach(() => {
api = new TestKubeApi({
api = new PodApi({
request,
objectConstructor: TestKubeObject,
objectConstructor: Pod,
});
});
it("should add kind and apiVersion", async () => {
expect.hasAssertions();
(fetch as any).mockResponse(async (request: Request) => {
mockFetch.mockResponse(async request => {
expect(request.method).toEqual("POST");
expect(JSON.parse(request.body.toString())).toEqual({
expect(JSON.parse(String(request.body))).toEqual({
kind: "Pod",
apiVersion: "v1",
metadata: {
@@ -643,9 +652,9 @@ describe("KubeApi", () => {
it("doesn't override metadata.labels", async () => {
expect.hasAssertions();
(fetch as any).mockResponse(async (request: Request) => {
mockFetch.mockResponse(async request => {
expect(request.method).toEqual("POST");
expect(JSON.parse(request.body.toString())).toEqual({
expect(JSON.parse(String(request.body))).toEqual({
kind: "Pod",
apiVersion: "v1",
metadata: {
@@ -674,21 +683,21 @@ describe("KubeApi", () => {
});
describe("update", () => {
let api: TestKubeApi;
let api: PodApi;
beforeEach(() => {
api = new TestKubeApi({
api = new PodApi({
request,
objectConstructor: TestKubeObject,
objectConstructor: Pod,
});
});
it("doesn't override metadata.labels", async () => {
expect.hasAssertions();
(fetch as any).mockResponse(async (request: Request) => {
mockFetch.mockResponse(async request => {
expect(request.method).toEqual("PUT");
expect(JSON.parse(request.body.toString())).toEqual({
expect(JSON.parse(String(request.body))).toEqual({
metadata: {
name: "foobar",
namespace: "default",
@@ -3,6 +3,7 @@
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import type { Cluster } from "../../cluster/cluster";
import type { ClusterContext } from "../cluster-context";
import type { KubeApi } from "../kube-api";
import { KubeObject } from "../kube-object";
@@ -14,6 +15,7 @@ class FakeKubeObjectStore extends KubeObjectStore<KubeObject> {
allNamespaces: [],
contextNamespaces: [],
hasSelectedAll: false,
cluster: {} as Cluster,
} as ClusterContext;
get context() {
@@ -40,6 +42,7 @@ describe("KubeObjectStore", () => {
resourceVersion: "1",
uid: "some-uid",
namespace: "default",
selfLink: "/some/self/link",
},
});
const store = new FakeKubeObjectStore(loadItems, {
@@ -73,6 +76,7 @@ describe("KubeObjectStore", () => {
resourceVersion: "1",
uid: "some-uid",
namespace: "default",
selfLink: "/some/self/link",
},
});
const objNotInDefaultNamespace = new KubeObject({
@@ -83,6 +87,7 @@ describe("KubeObjectStore", () => {
resourceVersion: "1",
uid: "some-uid",
namespace: "not-default",
selfLink: "/some/self/link",
},
});
const store = new FakeKubeObjectStore(loadItems, {
@@ -115,6 +120,7 @@ describe("KubeObjectStore", () => {
name: "some-obj-name",
resourceVersion: "1",
uid: "some-uid",
selfLink: "/some/self/link",
},
});
const clusterScopedObject2 = new KubeObject({
@@ -125,6 +131,7 @@ describe("KubeObjectStore", () => {
resourceVersion: "1",
uid: "some-uid",
namespace: "not-default",
selfLink: "/some/self/link",
},
});
const store = new FakeKubeObjectStore(loadItems, {
@@ -18,6 +18,7 @@ describe("Nodes tests", () => {
name: "bar",
resourceVersion: "1",
uid: "bat",
selfLink: "/api/v1/nodes/bar",
},
});
@@ -33,6 +34,7 @@ describe("Nodes tests", () => {
resourceVersion: "1",
uid: "bat",
labels: {},
selfLink: "/api/v1/nodes/bar",
},
});
@@ -51,6 +53,7 @@ describe("Nodes tests", () => {
"node-role.kubernetes.io/foobar": "bat",
"hellonode-role.kubernetes.io/foobar1": "bat",
},
selfLink: "/api/v1/nodes/bar",
},
});
@@ -69,6 +72,7 @@ describe("Nodes tests", () => {
"node-role.kubernetes.io/foobar": "bat",
"hellonode-role.kubernetes.io//////foobar1": "bat",
},
selfLink: "/api/v1/nodes/bar",
},
});
@@ -86,6 +90,7 @@ describe("Nodes tests", () => {
labels: {
"kubernetes.io/role": "master",
},
selfLink: "/api/v1/nodes/bar",
},
});
@@ -103,6 +108,7 @@ describe("Nodes tests", () => {
labels: {
"node.kubernetes.io/role": "master",
},
selfLink: "/api/v1/nodes/bar",
},
});
@@ -122,6 +128,7 @@ describe("Nodes tests", () => {
"kubernetes.io/role": "master",
"node.kubernetes.io/role": "master-v2-max",
},
selfLink: "/api/v1/nodes/bar",
},
});
@@ -14,6 +14,8 @@ describe("Pod tests", () => {
name: "foobar",
resourceVersion: "foobar",
uid: "foobar",
namespace: "default",
selfLink: "/api/v1/pods/default/foobar",
},
});
+62 -52
View File
@@ -3,6 +3,8 @@
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import assert from "assert";
import type { PodContainer, PodContainerStatus } from "../endpoints";
import { Pod } from "../endpoints";
interface GetDummyPodOptions {
@@ -12,16 +14,18 @@ interface GetDummyPodOptions {
initDead?: number;
}
function getDummyPodDefaultOptions(): Required<GetDummyPodOptions> {
return {
running: 0,
dead: 0,
initDead: 0,
initRunning: 0,
};
}
function getDummyPod(rawOpts: GetDummyPodOptions = {}): Pod {
const {
running = 0,
dead = 0,
initDead = 0,
initRunning = 0,
} = rawOpts;
function getDummyPod(opts: GetDummyPodOptions = getDummyPodDefaultOptions()): Pod {
const containers: PodContainer[] = [];
const initContainers: PodContainer[] = [];
const containerStatuses: PodContainerStatus[] = [];
const initContainerStatuses: PodContainerStatus[] = [];
const pod = new Pod({
apiVersion: "v1",
kind: "Pod",
@@ -29,36 +33,35 @@ function getDummyPod(opts: GetDummyPodOptions = getDummyPodDefaultOptions()): Po
uid: "1",
name: "test",
resourceVersion: "v1",
selfLink: "http",
namespace: "default",
selfLink: "/api/v1/pods/default/test",
},
spec: {
containers,
initContainers,
serviceAccount: "dummy",
serviceAccountName: "dummy",
},
status: {
phase: "Running",
conditions: [],
hostIP: "10.0.0.1",
podIP: "10.0.0.1",
startTime: "now",
containerStatuses,
initContainerStatuses,
},
});
pod.spec = {
containers: [],
initContainers: [],
serviceAccount: "dummy",
serviceAccountName: "dummy",
};
for (let i = 0; i < running; i += 1) {
const name = `container_running_${i}`;
pod.status = {
phase: "Running",
conditions: [],
hostIP: "10.0.0.1",
podIP: "10.0.0.1",
startTime: "now",
containerStatuses: [],
initContainerStatuses: [],
};
for (let i = 0; i < opts.running; i += 1) {
const name = `container_r_${i}`;
pod.spec.containers.push({
containers.push({
image: "dummy",
imagePullPolicy: "dummy",
name,
});
pod.status.containerStatuses.push({
containerStatuses.push({
image: "dummy",
imageID: "dummy",
name,
@@ -72,15 +75,15 @@ function getDummyPod(opts: GetDummyPodOptions = getDummyPodDefaultOptions()): Po
});
}
for (let i = 0; i < opts.dead; i += 1) {
const name = `container_d_${i}`;
for (let i = 0; i < dead; i += 1) {
const name = `container_dead_${i}`;
pod.spec.containers.push({
containers.push({
image: "dummy",
imagePullPolicy: "dummy",
name,
});
pod.status.containerStatuses.push({
containerStatuses.push({
image: "dummy",
imageID: "dummy",
name,
@@ -97,15 +100,15 @@ function getDummyPod(opts: GetDummyPodOptions = getDummyPodDefaultOptions()): Po
});
}
for (let i = 0; i < opts.initRunning; i += 1) {
const name = `container_ir_${i}`;
for (let i = 0; i < initRunning; i += 1) {
const name = `container_init-running_${i}`;
pod.spec.initContainers.push({
initContainers.push({
image: "dummy",
imagePullPolicy: "dummy",
name,
});
pod.status.initContainerStatuses.push({
initContainerStatuses.push({
image: "dummy",
imageID: "dummy",
name,
@@ -119,15 +122,15 @@ function getDummyPod(opts: GetDummyPodOptions = getDummyPodDefaultOptions()): Po
});
}
for (let i = 0; i < opts.initDead; i += 1) {
const name = `container_id_${i}`;
for (let i = 0; i < initDead; i += 1) {
const name = `container_init-dead_${i}`;
pod.spec.initContainers.push({
initContainers.push({
image: "dummy",
imagePullPolicy: "dummy",
name,
});
pod.status.initContainerStatuses.push({
initContainerStatuses.push({
image: "dummy",
imageID: "dummy",
name,
@@ -173,8 +176,8 @@ describe("Pods", () => {
it("getRunningContainers should return only running and init running", () => {
const res = [
...Array.from(new Array(running), (val, index) => getNamedContainer(`container_r_${index}`)),
...Array.from(new Array(initRunning), (val, index) => getNamedContainer(`container_ir_${index}`)),
...Array.from(new Array(running), (val, index) => getNamedContainer(`container_running_${index}`)),
...Array.from(new Array(initRunning), (val, index) => getNamedContainer(`container_init-running_${index}`)),
];
expect(pod.getRunningContainers()).toStrictEqual(res);
@@ -182,10 +185,10 @@ describe("Pods", () => {
it("getAllContainers should return all containers", () => {
const res = [
...Array.from(new Array(running), (val, index) => getNamedContainer(`container_r_${index}`)),
...Array.from(new Array(dead), (val, index) => getNamedContainer(`container_d_${index}`)),
...Array.from(new Array(initRunning), (val, index) => getNamedContainer(`container_ir_${index}`)),
...Array.from(new Array(initDead), (val, index) => getNamedContainer(`container_id_${index}`)),
...Array.from(new Array(running), (val, index) => getNamedContainer(`container_running_${index}`)),
...Array.from(new Array(dead), (val, index) => getNamedContainer(`container_dead_${index}`)),
...Array.from(new Array(initRunning), (val, index) => getNamedContainer(`container_init-running_${index}`)),
...Array.from(new Array(initDead), (val, index) => getNamedContainer(`container_init-dead_${index}`)),
];
expect(pod.getAllContainers()).toStrictEqual(res);
@@ -253,7 +256,7 @@ describe("Pods", () => {
it("should return true if a condition isn't ready", () => {
const pod = getDummyPod({ running: 1 });
pod.status.conditions.push({
pod.status?.conditions.push({
type: "Ready",
status: "foobar",
lastProbeTime: 1,
@@ -266,7 +269,7 @@ describe("Pods", () => {
it("should return false if a condition is non-ready", () => {
const pod = getDummyPod({ running: 1 });
pod.status.conditions.push({
pod.status?.conditions.push({
type: "dummy",
status: "foobar",
lastProbeTime: 1,
@@ -278,8 +281,11 @@ describe("Pods", () => {
it("should return true if a current container is in a crash loop back off", () => {
const pod = getDummyPod({ running: 1 });
const firstStatus = pod.status?.containerStatuses?.[0];
pod.status.containerStatuses[0].state = {
assert(firstStatus);
firstStatus.state = {
waiting: {
reason: "CrashLookBackOff",
message: "too much foobar",
@@ -292,6 +298,8 @@ describe("Pods", () => {
it("should return true if a current phase isn't running", () => {
const pod = getDummyPod({ running: 1 });
assert(pod.status);
pod.status.phase = "not running";
expect(pod.hasIssues()).toStrictEqual(true);
@@ -300,6 +308,8 @@ describe("Pods", () => {
it("should return false if a current phase is running", () => {
const pod = getDummyPod({ running: 1 });
assert(pod.status);
pod.status.phase = "Running";
expect(pod.hasIssues()).toStrictEqual(false);
@@ -3,31 +3,39 @@
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import { StatefulSet, StatefulSetApi } from "../endpoints/stateful-set.api";
import storesAndApisCanBeCreatedInjectable from "../../../renderer/stores-apis-can-be-created.injectable";
import { getDiForUnitTesting } from "../../../renderer/getDiForUnitTesting";
import apiKubeInjectable from "../../../renderer/k8s/api-kube.injectable";
import type { StatefulSetApi } from "../endpoints";
import statefulSetApiInjectable from "../endpoints/stateful-set.api.injectable";
import type { KubeJsonApi } from "../kube-json-api";
class StatefulSetApiTest extends StatefulSetApi {
public setRequest(request: any) {
this.request = request;
}
}
describe("StatefulSetApi", () => {
let statefulSetApi: StatefulSetApi;
let kubeJsonApi: jest.Mocked<KubeJsonApi>;
beforeEach(() => {
const di = getDiForUnitTesting({ doGeneralOverrides: true });
di.override(storesAndApisCanBeCreatedInjectable, () => true);
kubeJsonApi = {
getResponse: jest.fn(),
get: jest.fn(),
post: jest.fn(),
put: jest.fn(),
patch: jest.fn(),
del: jest.fn(),
} as never;
di.override(apiKubeInjectable, () => kubeJsonApi);
statefulSetApi = di.inject(statefulSetApiInjectable);
});
describe("scale", () => {
const requestMock = {
patch: () => ({}),
} as unknown as KubeJsonApi;
const sub = new StatefulSetApiTest({ objectConstructor: StatefulSet });
sub.setRequest(requestMock);
it("requests Kubernetes API with PATCH verb and correct amount of replicas", () => {
const patchSpy = jest.spyOn(requestMock, "patch");
statefulSetApi.scale({ namespace: "default", name: "statefulset-1" }, 5);
sub.scale({ namespace: "default", name: "statefulset-1" }, 5);
expect(patchSpy).toHaveBeenCalledWith("/apis/apps/v1/namespaces/default/statefulsets/statefulset-1/scale", {
expect(kubeJsonApi.patch).toHaveBeenCalledWith("/apis/apps/v1/namespaces/default/statefulsets/statefulset-1/scale", {
data: {
spec: {
replicas: 5,
+8 -14
View File
@@ -3,18 +3,12 @@
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import { isClusterPageContext } from "../utils";
import { KubeJsonApi } from "./kube-json-api";
import { apiKubePrefix, isDevelopment } from "../vars";
import { getInjectionToken } from "@ogre-tools/injectable";
import { asLegacyGlobalForExtensionApi } from "../../extensions/as-legacy-globals-for-extension-api/as-legacy-global-object-for-extension-api";
import type { KubeJsonApi } from "./kube-json-api";
export const apiKube = isClusterPageContext()
? new KubeJsonApi({
serverAddress: `http://127.0.0.1:${window.location.port}`,
apiBase: apiKubePrefix,
debug: isDevelopment,
}, {
headers: {
"Host": window.location.host,
},
})
: undefined;
export const apiKubeInjectionToken = getInjectionToken<KubeJsonApi>({
id: "api-kube-injection-token",
});
export const apiKube = asLegacyGlobalForExtensionApi(apiKubeInjectionToken);
-123
View File
@@ -1,123 +0,0 @@
/**
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import type { KubeObjectStore } from "./kube-object.store";
import { action, observable, makeObservable } from "mobx";
import { autoBind, iter } from "../utils";
import type { KubeApi } from "./kube-api";
import type { KubeObject } from "./kube-object";
import type { IKubeObjectRef } from "./kube-api-parse";
import { parseKubeApi, createKubeApiURL } from "./kube-api-parse";
export class ApiManager {
private apis = observable.map<string, KubeApi<KubeObject>>();
private stores = observable.map<string, KubeObjectStore<KubeObject>>();
constructor() {
makeObservable(this);
autoBind(this);
}
getApi(pathOrCallback: string | ((api: KubeApi<KubeObject>) => boolean)) {
if (typeof pathOrCallback === "string") {
return this.apis.get(pathOrCallback) || this.apis.get(parseKubeApi(pathOrCallback).apiBase);
}
return iter.find(this.apis.values(), pathOrCallback ?? (() => true));
}
getApiByKind(kind: string, apiVersion: string) {
return iter.find(this.apis.values(), api => api.kind === kind && api.apiVersionWithGroup === apiVersion);
}
registerApi<K extends KubeObject>(apiBase: string, api: KubeApi<K>) {
if (!api.apiBase) return;
if (!this.apis.has(apiBase)) {
this.stores.forEach((store) => {
if (store.api === api) {
this.stores.set(apiBase, store);
}
});
this.apis.set(apiBase, api);
}
}
protected resolveApi(api?: string | KubeApi<KubeObject>): KubeApi<KubeObject> | undefined {
if (!api) {
return undefined;
}
if (typeof api === "string") {
return this.getApi(api) as KubeApi<KubeObject>;
}
return api;
}
unregisterApi(api: string | KubeApi<KubeObject>) {
if (typeof api === "string") this.apis.delete(api);
else {
const apis = Array.from(this.apis.entries());
const entry = apis.find(entry => entry[1] === api);
if (entry) this.unregisterApi(entry[0]);
}
}
@action
registerStore<K extends KubeObject>(store: KubeObjectStore<K>, apis: KubeApi<K>[] = [store.api]) {
apis.filter(Boolean).forEach(api => {
if (api.apiBase) this.stores.set(api.apiBase, store);
});
}
getStore<S extends KubeObjectStore<KubeObject>>(api: string | KubeApi<KubeObject>): S | undefined {
return this.stores.get(this.resolveApi(api)?.apiBase) as S;
}
lookupApiLink(ref: IKubeObjectRef, parentObject?: KubeObject): string {
const {
kind, apiVersion, name,
namespace = parentObject?.getNs(),
} = ref;
if (!kind) return "";
// search in registered apis by 'kind' & 'apiVersion'
const api = this.getApi(api => api.kind === kind && api.apiVersionWithGroup == apiVersion);
if (api) {
return api.getUrl({ namespace, name });
}
// lookup api by generated resource link
const apiPrefixes = ["/apis", "/api"];
const resource = kind.endsWith("s") ? `${kind.toLowerCase()}es` : `${kind.toLowerCase()}s`;
for (const apiPrefix of apiPrefixes) {
const apiLink = createKubeApiURL({ apiPrefix, apiVersion, name, namespace, resource });
if (this.getApi(apiLink)) {
return apiLink;
}
}
// resolve by kind only (hpa's might use refs to older versions of resources for example)
const apiByKind = this.getApi(api => api.kind === kind);
if (apiByKind) {
return apiByKind.getUrl({ name, namespace });
}
// otherwise generate link with default prefix
// resource still might exists in k8s, but api is not registered in the app
return createKubeApiURL({ apiVersion, name, namespace, resource });
}
}
export const apiManager = new ApiManager();
@@ -0,0 +1,161 @@
/**
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import type { KubeObjectStore } from "../kube-object.store";
import { action, observable, makeObservable } from "mobx";
import { autoBind, isDefined, iter } from "../../utils";
import type { KubeApi } from "../kube-api";
import type { KubeJsonApiDataFor, KubeObject, ObjectReference } from "../kube-object";
import { parseKubeApi, createKubeApiURL } from "../kube-api-parse";
export type RegisterableStore<Store> = Store extends KubeObjectStore<any, any, any>
? Store
: never;
export type RegisterableApi<Api> = Api extends KubeApi<any, any>
? Api
: never;
export type KubeObjectStoreFrom<Api> = Api extends KubeApi<infer KubeObj, infer ApiData>
? KubeObjectStore<KubeObj, Api, ApiData>
: never;
export class ApiManager {
private readonly apis = observable.map<string, KubeApi>();
private readonly stores = observable.map<string, KubeObjectStore>();
constructor() {
makeObservable(this);
autoBind(this);
}
getApi(pathOrCallback: string | ((api: KubeApi<KubeObject>) => boolean)) {
if (typeof pathOrCallback === "string") {
return this.apis.get(pathOrCallback) || this.apis.get(parseKubeApi(pathOrCallback).apiBase);
}
return iter.find(this.apis.values(), pathOrCallback ?? (() => true));
}
getApiByKind(kind: string, apiVersion: string) {
return iter.find(this.apis.values(), api => api.kind === kind && api.apiVersionWithGroup === apiVersion);
}
registerApi<Api>(api: RegisterableApi<Api>): void;
/**
* @deprecated Just register the `api` by itself
*/
registerApi<Api>(apiBase: string, api: RegisterableApi<Api>): void;
registerApi<Api>(apiBaseRaw: string | RegisterableApi<Api>, apiRaw?: RegisterableApi<Api>) {
const api = typeof apiBaseRaw === "string"
? apiRaw
: apiBaseRaw;
if (!api?.apiBase) {
return;
}
if (!this.apis.has(api.apiBase)) {
this.stores.forEach((store) => {
if (store.api === api) {
this.stores.set(api.apiBase, store);
}
});
this.apis.set(api.apiBase, api);
}
}
protected resolveApi(api: undefined | string | KubeApi): KubeApi | undefined {
if (!api) {
return undefined;
}
if (typeof api === "string") {
return this.getApi(api);
}
return api;
}
unregisterApi(api: string | KubeApi<KubeObject>) {
if (typeof api === "string") this.apis.delete(api);
else {
const apis = Array.from(this.apis.entries());
const entry = apis.find(entry => entry[1] === api);
if (entry) this.unregisterApi(entry[0]);
}
}
registerStore<KubeObj>(store: RegisterableStore<KubeObj>): void;
/**
* @deprecated KubeObjectStore's should only every be about a single KubeApi type
*/
registerStore<KubeObj extends KubeObject>(store: KubeObjectStore<KubeObj, KubeApi<KubeObj>, KubeJsonApiDataFor<KubeObj>>, apis: KubeApi<KubeObj>[]): void;
@action
registerStore<KubeObj extends KubeObject>(store: KubeObjectStore<KubeObj, KubeApi<KubeObj>, KubeJsonApiDataFor<KubeObj>>, apis: KubeApi<KubeObj>[] = [store.api]): void {
for (const api of apis.filter(isDefined)) {
if (api.apiBase) {
this.stores.set(api.apiBase, store as never);
}
}
}
getStore(api: string | undefined): KubeObjectStore | undefined;
getStore<Api>(api: RegisterableApi<Api>): KubeObjectStoreFrom<Api> | undefined;
/**
* @deprecated use an actual cast instead of hiding it with this unused type param
*/
getStore<Store extends KubeObjectStore>(api: string | KubeApi): Store | undefined ;
getStore(api: string | KubeApi | undefined): KubeObjectStore | undefined {
const { apiBase } = this.resolveApi(api) ?? {};
if (apiBase) {
return this.stores.get(apiBase);
}
return undefined;
}
lookupApiLink(ref: ObjectReference, parentObject?: KubeObject): string {
const {
kind, apiVersion = "v1", name,
namespace = parentObject?.getNs(),
} = ref;
if (!kind) return "";
// search in registered apis by 'kind' & 'apiVersion'
const api = this.getApi(api => api.kind === kind && api.apiVersionWithGroup == apiVersion);
if (api) {
return api.getUrl({ namespace, name });
}
// lookup api by generated resource link
const apiPrefixes = ["/apis", "/api"];
const resource = kind.endsWith("s") ? `${kind.toLowerCase()}es` : `${kind.toLowerCase()}s`;
for (const apiPrefix of apiPrefixes) {
const apiLink = createKubeApiURL({ apiPrefix, apiVersion, name, namespace, resource });
if (this.getApi(apiLink)) {
return apiLink;
}
}
// resolve by kind only (hpa's might use refs to older versions of resources for example)
const apiByKind = this.getApi(api => api.kind === kind);
if (apiByKind) {
return apiByKind.getUrl({ name, namespace });
}
// otherwise generate link with default prefix
// resource still might exists in k8s, but api is not registered in the app
return createKubeApiURL({ apiVersion, name, namespace, resource });
}
}
@@ -0,0 +1,27 @@
/**
* 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 EventEmitter from "events";
import type TypedEventEmitter from "typed-emitter";
import type { CustomResourceDefinition } from "../endpoints";
import type { KubeApi } from "../kube-api";
export interface LegacyAutoRegistration {
customResourceDefinition: (crd: CustomResourceDefinition) => void;
kubeApi: (api: KubeApi<any, any>) => void;
}
/**
* This is used to remove dependency cycles from auto registering of instances
*
* - Custom Resource Definitions get their own registered store (will need in the future)
* - All KubeApi's get auto registered (this should be changed in the future)
*/
const autoRegistrationEmitterInjectable = getInjectable({
id: "auto-registration-emitter",
instantiate: (): TypedEventEmitter<LegacyAutoRegistration> => new EventEmitter(),
});
export default autoRegistrationEmitterInjectable;
@@ -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 type { CustomResourceDefinition } from "../endpoints";
import { KubeApi } from "../kube-api";
import { KubeObject } from "../kube-object";
import autoRegistrationEmitterInjectable from "./auto-registration-emitter.injectable";
import apiManagerInjectable from "./manager.injectable";
import { CustomResourceStore } from "./resource.store";
const autoRegistrationInjectable = getInjectable({
id: "api-manager-auto-registration",
instantiate: (di) => {
const autoRegistrationEmitter = di.inject(autoRegistrationEmitterInjectable);
const beforeApiManagerInitializationCrds: CustomResourceDefinition[] = [];
const beforeApiManagerInitializationApis: KubeApi[] = [];
let initialized = false;
const autoInitCustomResourceStore = (crd: CustomResourceDefinition) => {
const objectConstructor = class extends KubeObject {
static readonly kind = crd.getResourceKind();
static readonly namespaced = crd.isNamespaced();
static readonly apiBase = crd.getResourceApiBase();
};
const api = (() => {
const rawApi = apiManager.getApi(objectConstructor.apiBase);
if (rawApi) {
return rawApi;
}
const api = new KubeApi({ objectConstructor });
apiManager.registerApi(api);
return api;
})();
if (!apiManager.getStore(api)) {
apiManager.registerStore(new CustomResourceStore(api));
}
};
const autoInitKubeApi = (api: KubeApi) => {
apiManager.registerApi(api);
};
autoRegistrationEmitter
.on("customResourceDefinition", (crd) => {
if (initialized) {
autoInitCustomResourceStore(crd);
} else {
beforeApiManagerInitializationCrds.push(crd);
}
})
.on("kubeApi", (api) => {
if (initialized) {
autoInitKubeApi(api);
} else {
beforeApiManagerInitializationApis.push(api);
}
});
const apiManager = di.inject(apiManagerInjectable);
beforeApiManagerInitializationCrds.forEach(autoInitCustomResourceStore);
beforeApiManagerInitializationApis.forEach(autoInitKubeApi);
initialized = true;
},
});
export default autoRegistrationInjectable;
@@ -3,8 +3,4 @@
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import fetch from "node-fetch";
export {
fetch,
};
export * from "./api-manager";
@@ -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, getInjectionToken } from "@ogre-tools/injectable";
import { storesAndApisCanBeCreatedInjectionToken } from "../stores-apis-can-be-created.token";
import type { KubeObjectStore } from "../kube-object.store";
import { ApiManager } from "./api-manager";
export const kubeObjectStoreInjectionToken = getInjectionToken<KubeObjectStore<any, any, any>>({
id: "kube-object-store-token",
});
const apiManagerInjectable = getInjectable({
id: "api-manager",
instantiate: (di) => {
const apiManager = new ApiManager();
if (di.inject(storesAndApisCanBeCreatedInjectionToken)) {
const stores = di.injectMany(kubeObjectStoreInjectionToken);
for (const store of stores) {
apiManager.registerStore(store);
}
}
return apiManager;
},
});
export default apiManagerInjectable;
@@ -0,0 +1,14 @@
/**
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import type { KubeApi } from "../kube-api";
import { KubeObjectStore } from "../kube-object.store";
import type { KubeObject } from "../kube-object";
export class CustomResourceStore<K extends KubeObject> extends KubeObjectStore<K, KubeApi<K>> {
constructor(api: KubeApi<K>) {
super(api);
}
}
+1 -1
View File
@@ -6,7 +6,7 @@
import type { Cluster } from "../cluster/cluster";
export interface ClusterContext {
cluster?: Cluster;
cluster: Cluster;
allNamespaces: string[]; // available / allowed namespaces from cluster.ts
contextNamespaces: string[]; // selected by user (see: namespace-select.tsx)
hasSelectedAll: boolean;
@@ -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 assert from "assert";
import { storesAndApisCanBeCreatedInjectionToken } from "../stores-apis-can-be-created.token";
import { ClusterRoleBindingApi } from "./cluster-role-binding.api";
const clusterRoleBindingApiInjectable = getInjectable({
id: "cluster-role-binding-api",
instantiate: (di) => {
assert(di.inject(storesAndApisCanBeCreatedInjectionToken), "clusterRoleBindingApi is only accessible in certain environments");
return new ClusterRoleBindingApi();
},
});
export default clusterRoleBindingApiInjectable;
@@ -2,35 +2,39 @@
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import { isClusterPageContext } from "../../utils/cluster-id-url-parsing";
import type { DerivedKubeApiOptions } from "../kube-api";
import { KubeApi } from "../kube-api";
import type { KubeJsonApiData } from "../kube-json-api";
import type { KubeObjectMetadata, KubeObjectScope } from "../kube-object";
import { KubeObject } from "../kube-object";
import type { RoleRef } from "./types/role-ref";
import type { Subject } from "./types/subject";
export type ClusterRoleBindingSubjectKind = "Group" | "ServiceAccount" | "User";
export interface ClusterRoleBindingSubject {
kind: ClusterRoleBindingSubjectKind;
name: string;
apiGroup?: string;
namespace?: string;
export interface ClusterRoleBindingData extends KubeJsonApiData<KubeObjectMetadata<KubeObjectScope.Cluster>, void, void> {
subjects?: Subject[];
roleRef: RoleRef;
}
export interface ClusterRoleBinding {
subjects?: ClusterRoleBindingSubject[];
roleRef: {
kind: string;
name: string;
apiGroup?: string;
};
}
export class ClusterRoleBinding extends KubeObject {
export class ClusterRoleBinding extends KubeObject<void, void, KubeObjectScope.Cluster> {
static kind = "ClusterRoleBinding";
static namespaced = false;
static apiBase = "/apis/rbac.authorization.k8s.io/v1/clusterrolebindings";
subjects?: Subject[];
roleRef: RoleRef;
constructor({
subjects,
roleRef,
...rest
}: ClusterRoleBindingData) {
super(rest);
this.subjects = subjects;
this.roleRef = roleRef;
}
getSubjects() {
return this.subjects || [];
return this.subjects ?? [];
}
getSubjectNames(): string {
@@ -38,17 +42,11 @@ export class ClusterRoleBinding extends KubeObject {
}
}
/**
* Only available within kubernetes cluster pages
*/
let clusterRoleBindingApi: KubeApi<ClusterRoleBinding>;
if (isClusterPageContext()) {
clusterRoleBindingApi = new KubeApi({
objectConstructor: ClusterRoleBinding,
});
export class ClusterRoleBindingApi extends KubeApi<ClusterRoleBinding, ClusterRoleBindingData> {
constructor(opts: DerivedKubeApiOptions = {}) {
super({
...opts,
objectConstructor: ClusterRoleBinding,
});
}
}
export {
clusterRoleBindingApi,
};
@@ -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 assert from "assert";
import { storesAndApisCanBeCreatedInjectionToken } from "../stores-apis-can-be-created.token";
import { ClusterRoleApi } from "./cluster-role.api";
const clusterRoleApiInjectable = getInjectable({
id: "cluster-role-api",
instantiate: (di) => {
assert(di.inject(storesAndApisCanBeCreatedInjectionToken), "clusterRoleApi is only available in certain environments");
return new ClusterRoleApi();
},
});
export default clusterRoleApiInjectable;
@@ -3,40 +3,43 @@
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import { isClusterPageContext } from "../../utils/cluster-id-url-parsing";
import type { DerivedKubeApiOptions } from "../kube-api";
import { KubeApi } from "../kube-api";
import type { KubeJsonApiData } from "../kube-json-api";
import type { KubeObjectMetadata, KubeObjectScope } from "../kube-object";
import { KubeObject } from "../kube-object";
import type { AggregationRule } from "./types/aggregation-rule";
import type { PolicyRule } from "./types/policy-rule";
export interface ClusterRole {
rules: {
verbs: string[];
apiGroups: string[];
resources: string[];
resourceNames?: string[];
}[];
export interface ClusterRoleData extends KubeJsonApiData<KubeObjectMetadata<KubeObjectScope.Cluster>, void, void> {
rules?: PolicyRule[];
aggregationRule?: AggregationRule;
}
export class ClusterRole extends KubeObject {
export class ClusterRole extends KubeObject<void, void, KubeObjectScope.Cluster> {
static kind = "ClusterRole";
static namespaced = false;
static apiBase = "/apis/rbac.authorization.k8s.io/v1/clusterroles";
rules?: PolicyRule[];
aggregationRule?: AggregationRule;
constructor({ rules, aggregationRule, ...rest }: ClusterRoleData) {
super(rest);
this.rules = rules;
this.aggregationRule = aggregationRule;
}
getRules() {
return this.rules || [];
}
}
/**
* Only available within kubernetes cluster pages
*/
let clusterRoleApi: KubeApi<ClusterRole>;
if (isClusterPageContext()) { // initialize automatically only when within a cluster iframe/context
clusterRoleApi = new KubeApi({
objectConstructor: ClusterRole,
});
export class ClusterRoleApi extends KubeApi<ClusterRole, ClusterRoleData> {
constructor(opts: DerivedKubeApiOptions = {}) {
super({
...opts,
objectConstructor: ClusterRole,
});
}
}
export {
clusterRoleApi,
};
@@ -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 assert from "assert";
import { storesAndApisCanBeCreatedInjectionToken } from "../stores-apis-can-be-created.token";
import { ClusterApi } from "./cluster.api";
const clusterApiInjectable = getInjectable({
id: "cluster-api",
instantiate: (di) => {
assert(di.inject(storesAndApisCanBeCreatedInjectionToken), "clusterApi is only available in certain environments");
return new ClusterApi();
},
});
export default clusterApiInjectable;
+23 -32
View File
@@ -3,18 +3,25 @@
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import type { IMetrics, IMetricsReqParams } from "./metrics.api";
import type { MetricData, IMetricsReqParams } from "./metrics.api";
import { metricsApi } from "./metrics.api";
import { KubeObject } from "../kube-object";
import type { DerivedKubeApiOptions, IgnoredKubeApiOptions } from "../kube-api";
import { KubeApi } from "../kube-api";
import { isClusterPageContext } from "../../utils/cluster-id-url-parsing";
export class ClusterApi extends KubeApi<Cluster> {
static kind = "Cluster";
static namespaced = true;
constructor(opts: DerivedKubeApiOptions & IgnoredKubeApiOptions = {}) {
super({
...opts,
objectConstructor: Cluster,
});
}
}
export function getMetricsByNodeNames(nodeNames: string[], params?: IMetricsReqParams): Promise<IClusterMetrics> {
export function getMetricsByNodeNames(nodeNames: string[], params?: IMetricsReqParams): Promise<ClusterMetricData> {
const nodes = nodeNames.join("|");
const opts = { category: "cluster", nodes };
@@ -45,20 +52,19 @@ export enum ClusterStatus {
ERROR = "Error",
}
export interface IClusterMetrics<T = IMetrics> {
[metric: string]: T;
memoryUsage: T;
memoryRequests: T;
memoryLimits: T;
memoryCapacity: T;
cpuUsage: T;
cpuRequests: T;
cpuLimits: T;
cpuCapacity: T;
podUsage: T;
podCapacity: T;
fsSize: T;
fsUsage: T;
export interface ClusterMetricData extends Partial<Record<string, MetricData>> {
memoryUsage: MetricData;
memoryRequests: MetricData;
memoryLimits: MetricData;
memoryCapacity: MetricData;
cpuUsage: MetricData;
cpuRequests: MetricData;
cpuLimits: MetricData;
cpuCapacity: MetricData;
podUsage: MetricData;
podCapacity: MetricData;
fsSize: MetricData;
fsUsage: MetricData;
}
export interface Cluster {
@@ -107,18 +113,3 @@ export class Cluster extends KubeObject {
return ClusterStatus.ACTIVE;
}
}
/**
* Only available within kubernetes cluster pages
*/
let clusterApi: ClusterApi;
if (isClusterPageContext()) { // initialize automatically only when within a cluster iframe/context
clusterApi = new ClusterApi({
objectConstructor: Cluster,
});
}
export {
clusterApi,
};
@@ -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 assert from "assert";
import { storesAndApisCanBeCreatedInjectionToken } from "../stores-apis-can-be-created.token";
import { ComponentStatusApi } from "./component-status.api";
const componentStatusApiInjectable = getInjectable({
id: "component-status-api",
instantiate: (di) => {
assert(di.inject(storesAndApisCanBeCreatedInjectionToken), "componentStatusApi is only available in certain environments");
return new ComponentStatusApi();
},
});
export default componentStatusApiInjectable;
@@ -4,16 +4,17 @@
*/
import { KubeObject } from "../kube-object";
import type { DerivedKubeApiOptions } from "../kube-api";
import { KubeApi } from "../kube-api";
export interface IComponentStatusCondition {
export interface ComponentStatusCondition {
type: string;
status: string;
message: string;
}
export interface ComponentStatus {
conditions: IComponentStatusCondition[];
conditions: ComponentStatusCondition[];
}
export class ComponentStatus extends KubeObject {
@@ -26,6 +27,11 @@ export class ComponentStatus extends KubeObject {
}
}
export const componentStatusApi = new KubeApi({
objectConstructor: ComponentStatus,
});
export class ComponentStatusApi extends KubeApi<ComponentStatus> {
constructor(opts: DerivedKubeApiOptions = {}) {
super({
...opts,
objectConstructor: ComponentStatus,
});
}
}
@@ -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 assert from "assert";
import { storesAndApisCanBeCreatedInjectionToken } from "../stores-apis-can-be-created.token";
import { ConfigMapApi } from "./config-map.api";
const configMapApiInjectable = getInjectable({
id: "config-map-api",
instantiate: (di) => {
assert(di.inject(storesAndApisCanBeCreatedInjectionToken), "configMapApi is only available in certain environments");
return new ConfigMapApi();
},
});
export default configMapApiInjectable;
@@ -0,0 +1,49 @@
/**
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import type { KubeObjectMetadata, KubeObjectScope } from "../kube-object";
import { KubeObject } from "../kube-object";
import type { KubeJsonApiData } from "../kube-json-api";
import type { DerivedKubeApiOptions } from "../kube-api";
import { KubeApi } from "../kube-api";
import { autoBind } from "../../utils";
export interface ConfigMapData extends KubeJsonApiData<KubeObjectMetadata<KubeObjectScope.Namespace>, void, void> {
data?: Partial<Record<string, string>>;
binaryData?: Partial<Record<string, string>>;
immutable?: boolean;
}
export class ConfigMap extends KubeObject<void, void, KubeObjectScope.Namespace> {
static kind = "ConfigMap";
static namespaced = true;
static apiBase = "/api/v1/configmaps";
data: Partial<Record<string, string>>;
binaryData: Partial<Record<string, string>>;
immutable?: boolean;
constructor({ data, binaryData, immutable, ...rest }: ConfigMapData) {
super(rest);
autoBind(this);
this.data = data ?? {};
this.binaryData = binaryData ?? {};
this.immutable = immutable;
}
getKeys(): string[] {
return Object.keys(this.data);
}
}
export class ConfigMapApi extends KubeApi<ConfigMap, ConfigMapData> {
constructor(opts?: DerivedKubeApiOptions) {
super({
objectConstructor: ConfigMap,
...opts ?? {},
});
}
}
@@ -1,48 +0,0 @@
/**
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import { KubeObject } from "../kube-object";
import type { KubeJsonApiData } from "../kube-json-api";
import { KubeApi } from "../kube-api";
import { autoBind } from "../../utils";
import { isClusterPageContext } from "../../utils/cluster-id-url-parsing";
export interface ConfigMap {
data: {
[param: string]: string;
};
}
export class ConfigMap extends KubeObject {
static kind = "ConfigMap";
static namespaced = true;
static apiBase = "/api/v1/configmaps";
constructor(data: KubeJsonApiData) {
super(data);
autoBind(this);
this.data ??= {};
}
getKeys(): string[] {
return Object.keys(this.data);
}
}
/**
* Only available within kubernetes cluster pages
*/
let configMapApi: KubeApi<ConfigMap>;
if (isClusterPageContext()) {
configMapApi = new KubeApi({
objectConstructor: ConfigMap,
});
}
export {
configMapApi,
};
@@ -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 assert from "assert";
import { storesAndApisCanBeCreatedInjectionToken } from "../stores-apis-can-be-created.token";
import { CronJobApi } from "./cron-job.api";
const cronJobApiInjectable = getInjectable({
id: "cron-job-api",
instantiate: (di) => {
assert(di.inject(storesAndApisCanBeCreatedInjectionToken), "cronJobApi is only available in certain environments");
return new CronJobApi();
},
});
export default cronJobApiInjectable;
Loaded 100 of 1062 files, more files were not shown because too many files have changed in this diff. Show more