mirror of
https://github.com/lensapp/lens.git
synced 2025-05-20 05:10:56 +00:00
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:
1 parent
381d77c633
commit
dfcb7c3330
1062 files changed
+26512
-28689
No files matched your search
@@ -4,7 +4,7 @@
|
||||
*/
|
||||
|
||||
import "@testing-library/jest-dom/extend-expect";
|
||||
import { fireEvent, waitFor } from "@testing-library/react";
|
||||
import { fireEvent, screen, waitFor } from "@testing-library/react";
|
||||
import fse from "fs-extra";
|
||||
import React from "react";
|
||||
import { UserStore } from "../../../../common/user-store";
|
||||
@@ -21,10 +21,14 @@ import { renderFor } from "../../test-utils/renderFor";
|
||||
import extensionDiscoveryInjectable from "../../../../extensions/extension-discovery/extension-discovery.injectable";
|
||||
import directoryForUserDataInjectable from "../../../../common/app-paths/directory-for-user-data/directory-for-user-data.injectable";
|
||||
import directoryForDownloadsInjectable from "../../../../common/app-paths/directory-for-downloads/directory-for-downloads.injectable";
|
||||
import getConfigurationFileModelInjectable
|
||||
from "../../../../common/get-configuration-file-model/get-configuration-file-model.injectable";
|
||||
import appVersionInjectable
|
||||
from "../../../../common/get-configuration-file-model/app-version/app-version.injectable";
|
||||
import getConfigurationFileModelInjectable from "../../../../common/get-configuration-file-model/get-configuration-file-model.injectable";
|
||||
import appVersionInjectable from "../../../../common/get-configuration-file-model/app-version/app-version.injectable";
|
||||
import assert from "assert";
|
||||
import type { InstallFromInput } from "../install-from-input/install-from-input";
|
||||
import installFromInputInjectable from "../install-from-input/install-from-input.injectable";
|
||||
import type { ExtensionInstallationStateStore } from "../../../../extensions/extension-installation-state-store/extension-installation-state-store";
|
||||
import extensionInstallationStateStoreInjectable from "../../../../extensions/extension-installation-state-store/extension-installation-state-store.injectable";
|
||||
import { observable, when } from "mobx";
|
||||
|
||||
mockWindow();
|
||||
|
||||
@@ -50,6 +54,8 @@ jest.mock("../../../../common/utils/tar");
|
||||
describe("Extensions", () => {
|
||||
let extensionLoader: ExtensionLoader;
|
||||
let extensionDiscovery: ExtensionDiscovery;
|
||||
let installFromInput: jest.MockedFunction<InstallFromInput>;
|
||||
let extensionInstallationStateStore: ExtensionInstallationStateStore;
|
||||
let render: DiRender;
|
||||
|
||||
beforeEach(async () => {
|
||||
@@ -69,8 +75,13 @@ describe("Extensions", () => {
|
||||
|
||||
render = renderFor(di);
|
||||
|
||||
installFromInput = jest.fn();
|
||||
|
||||
di.override(installFromInputInjectable, () => installFromInput);
|
||||
|
||||
extensionLoader = di.inject(extensionLoaderInjectable);
|
||||
extensionDiscovery = di.inject(extensionDiscoveryInjectable);
|
||||
extensionInstallationStateStore = di.inject(extensionInstallationStateStoreInjectable);
|
||||
|
||||
extensionLoader.addExtension({
|
||||
id: "extensionId",
|
||||
@@ -99,36 +110,53 @@ describe("Extensions", () => {
|
||||
it("disables uninstall and disable buttons while uninstalling", async () => {
|
||||
extensionDiscovery.isLoaded = true;
|
||||
|
||||
const res = render(<><Extensions /><ConfirmDialog /></>);
|
||||
const table = res.getByTestId("extensions-table");
|
||||
const menuTrigger = table.querySelector("div[role=row]:first-of-type .actions .Icon");
|
||||
render((
|
||||
<>
|
||||
<Extensions />
|
||||
<ConfirmDialog />
|
||||
</>
|
||||
));
|
||||
|
||||
const table = await screen.findByTestId("extensions-table");
|
||||
const menuTrigger = table.querySelector(".table div[role='rowgroup'] .actions .Icon");
|
||||
|
||||
assert(menuTrigger);
|
||||
fireEvent.click(menuTrigger);
|
||||
|
||||
expect(res.getByText("Disable")).toHaveAttribute("aria-disabled", "false");
|
||||
expect(res.getByText("Uninstall")).toHaveAttribute("aria-disabled", "false");
|
||||
expect(await screen.findByText("Disable")).toHaveAttribute("aria-disabled", "false");
|
||||
expect(await screen.findByText("Uninstall")).toHaveAttribute("aria-disabled", "false");
|
||||
|
||||
fireEvent.click(res.getByText("Uninstall"));
|
||||
fireEvent.click(await screen.findByText("Uninstall"));
|
||||
|
||||
// Approve confirm dialog
|
||||
fireEvent.click(res.getByText("Yes"));
|
||||
fireEvent.click(await screen.findByText("Yes"));
|
||||
|
||||
await waitFor(() => {
|
||||
expect(extensionDiscovery.uninstallExtension).toHaveBeenCalled();
|
||||
fireEvent.click(menuTrigger);
|
||||
expect(res.getByText("Disable")).toHaveAttribute("aria-disabled", "true");
|
||||
expect(res.getByText("Uninstall")).toHaveAttribute("aria-disabled", "true");
|
||||
expect(screen.getByText("Disable")).toHaveAttribute("aria-disabled", "true");
|
||||
expect(screen.getByText("Uninstall")).toHaveAttribute("aria-disabled", "true");
|
||||
}, {
|
||||
timeout: 30000,
|
||||
});
|
||||
});
|
||||
|
||||
it("disables install button while installing", async () => {
|
||||
const res = render(<Extensions />);
|
||||
render(<Extensions />);
|
||||
|
||||
(fse.unlink as jest.MockedFunction<typeof fse.unlink>).mockReturnValue(Promise.resolve() as any);
|
||||
const resolveInstall = observable.box(false);
|
||||
|
||||
fireEvent.change(res.getByPlaceholderText("File path or URL", {
|
||||
(fse.unlink as jest.MockedFunction<typeof fse.unlink>).mockReturnValue(Promise.resolve());
|
||||
installFromInput.mockImplementation(async (input) => {
|
||||
expect(input).toBe("https://test.extensionurl/package.tgz");
|
||||
|
||||
const clear = extensionInstallationStateStore.startPreInstall();
|
||||
|
||||
await when(() => resolveInstall.get());
|
||||
clear();
|
||||
});
|
||||
|
||||
fireEvent.change(await screen.findByPlaceholderText("File path or URL", {
|
||||
exact: false,
|
||||
}), {
|
||||
target: {
|
||||
@@ -136,8 +164,9 @@ describe("Extensions", () => {
|
||||
},
|
||||
});
|
||||
|
||||
fireEvent.click(res.getByText("Install"));
|
||||
expect(res.getByText("Install").closest("button")).toBeDisabled();
|
||||
fireEvent.click(await screen.findByText("Install"));
|
||||
expect((await screen.findByText("Install")).closest("button")).toBeDisabled();
|
||||
resolveInstall.set(true);
|
||||
});
|
||||
|
||||
it("displays spinner while extensions are loading", () => {
|
||||
|
||||
@@ -10,7 +10,6 @@ import path from "path";
|
||||
import { SemVer } from "semver";
|
||||
import URLParse from "url-parse";
|
||||
import type { InstallRequest } from "./attempt-install/install-request";
|
||||
import { reduce } from "lodash";
|
||||
import type { ExtensionInstallationStateStore } from "../../../extensions/extension-installation-state-store/extension-installation-state-store";
|
||||
import type { Confirm } from "../confirm-dialog/confirm.injectable";
|
||||
import { getInjectable } from "@ogre-tools/injectable";
|
||||
@@ -18,6 +17,7 @@ import attemptInstallInjectable from "./attempt-install/attempt-install.injectab
|
||||
import getBaseRegistryUrlInjectable from "./get-base-registry-url/get-base-registry-url.injectable";
|
||||
import extensionInstallationStateStoreInjectable from "../../../extensions/extension-installation-state-store/extension-installation-state-store.injectable";
|
||||
import confirmInjectable from "../confirm-dialog/confirm.injectable";
|
||||
import { reduce } from "lodash";
|
||||
|
||||
export interface ExtensionInfo {
|
||||
name: string;
|
||||
@@ -78,7 +78,11 @@ const attemptInstallByInfo = ({
|
||||
} else {
|
||||
Notifications.error((
|
||||
<p>
|
||||
The <em>{name}</em> extension does not have a version or tag <code>{version}</code>.
|
||||
{"The "}
|
||||
<em>{name}</em>
|
||||
{" extension does not have a version or tag "}
|
||||
<code>{version}</code>
|
||||
.
|
||||
</p>
|
||||
));
|
||||
|
||||
@@ -91,19 +95,28 @@ const attemptInstallByInfo = ({
|
||||
// ignore pre-releases for auto picking the version
|
||||
.filter(version => version.prerelease.length === 0);
|
||||
|
||||
finalVersion = reduce(
|
||||
versions,
|
||||
(prev, curr) => prev.compareMain(curr) === -1 ? curr : prev,
|
||||
).format();
|
||||
const latestVersion = reduce(versions, (prev, curr) => prev.compareMain(curr) === -1 ? curr : prev);
|
||||
|
||||
if (!latestVersion) {
|
||||
console.error("No versions supplied for that extension", { name });
|
||||
Notifications.error(`No versions found for ${name}`);
|
||||
|
||||
return disposer();
|
||||
}
|
||||
|
||||
finalVersion = latestVersion.format();
|
||||
}
|
||||
|
||||
if (requireConfirmation) {
|
||||
const proceed = await confirm({
|
||||
message: (
|
||||
<p>
|
||||
Are you sure you want to install{" "}
|
||||
Are you sure you want to install
|
||||
{" "}
|
||||
<b>
|
||||
{name}@{finalVersion}
|
||||
{name}
|
||||
@
|
||||
{finalVersion}
|
||||
</b>
|
||||
?
|
||||
</p>
|
||||
@@ -117,7 +130,8 @@ const attemptInstallByInfo = ({
|
||||
}
|
||||
}
|
||||
|
||||
const url = json.versions[finalVersion].dist.tarball;
|
||||
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
||||
const url = json.versions[finalVersion!].dist.tarball;
|
||||
const fileName = path.basename(url);
|
||||
const { promise: dataP } = downloadFile({ url, timeout: 10 * 60 * 1000 });
|
||||
|
||||
|
||||
@@ -74,7 +74,9 @@ export const attemptInstall =
|
||||
<div className="flex column gaps">
|
||||
<b>Extension Install Collision:</b>
|
||||
<p>
|
||||
The <em>{name}</em> extension is currently {curState.toLowerCase()}.
|
||||
{"The "}
|
||||
<em>{name}</em>
|
||||
{` extension is currently ${curState.toLowerCase()}.`}
|
||||
</p>
|
||||
<p>Will not proceed with this current install request.</p>
|
||||
</div>,
|
||||
@@ -92,21 +94,20 @@ export const attemptInstall =
|
||||
<div className="InstallingExtensionNotification flex gaps align-center">
|
||||
<div className="flex column gaps">
|
||||
<p>
|
||||
Install extension{" "}
|
||||
<b>
|
||||
{name}@{version}
|
||||
</b>
|
||||
?
|
||||
{"Install extension "}
|
||||
<b>{`${name}@${version}`}</b>
|
||||
?
|
||||
</p>
|
||||
<p>
|
||||
Description: <em>{description}</em>
|
||||
{"Description: "}
|
||||
<em>{description}</em>
|
||||
</p>
|
||||
<div
|
||||
className="remove-folder-warning"
|
||||
onClick={() => shell.openPath(extensionFolder)}
|
||||
>
|
||||
<b>Warning:</b> {name}@{oldVersion} will be removed before
|
||||
installation.
|
||||
<b>Warning:</b>
|
||||
{` ${name}@${oldVersion} will be removed before installation.`}
|
||||
</div>
|
||||
</div>
|
||||
<Button
|
||||
|
||||
+5
-2
@@ -73,10 +73,13 @@ export const createTempFilesAndValidate =
|
||||
Notifications.error(
|
||||
<div className="flex column gaps">
|
||||
<p>
|
||||
Installing <em>{fileName}</em> has failed, skipping.
|
||||
{"Installing "}
|
||||
<em>{fileName}</em>
|
||||
{" has failed, skipping."}
|
||||
</p>
|
||||
<p>
|
||||
Reason: <em>{message}</em>
|
||||
{"Reason: "}
|
||||
<em>{message}</em>
|
||||
</p>
|
||||
</div>,
|
||||
);
|
||||
|
||||
+6
-2
@@ -75,7 +75,9 @@ export const unpackExtension =
|
||||
|
||||
Notifications.ok(
|
||||
<p>
|
||||
Extension <b>{displayName}</b> successfully installed!
|
||||
{"Extension "}
|
||||
<b>{displayName}</b>
|
||||
{" successfully installed!"}
|
||||
</p>,
|
||||
);
|
||||
} catch (error) {
|
||||
@@ -87,7 +89,9 @@ export const unpackExtension =
|
||||
);
|
||||
Notifications.error(
|
||||
<p>
|
||||
Installing extension <b>{displayName}</b> has failed:{" "}
|
||||
{"Installing extension "}
|
||||
<b>{displayName}</b>
|
||||
{" has failed: "}
|
||||
<em>{message}</em>
|
||||
</p>,
|
||||
);
|
||||
|
||||
+11
-7
@@ -3,7 +3,7 @@
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
import type { LensExtensionManifest } from "../../../../../extensions/lens-extension";
|
||||
import { listTarEntries, readFileFromTar } from "../../../../../common/utils";
|
||||
import { hasTypedProperty, isObject, isString, listTarEntries, readFileFromTar } from "../../../../../common/utils";
|
||||
import { manifestFilename } from "../../../../../extensions/extension-discovery/extension-discovery";
|
||||
import path from "path";
|
||||
|
||||
@@ -31,17 +31,21 @@ export const validatePackage = async (
|
||||
throw new Error(`invalid extension bundle, ${manifestFilename} not found`);
|
||||
}
|
||||
|
||||
const manifest = await readFileFromTar<LensExtensionManifest>({
|
||||
const manifest = await readFileFromTar({
|
||||
tarPath: filePath,
|
||||
filePath: manifestLocation,
|
||||
parseJson: true,
|
||||
});
|
||||
|
||||
if (!manifest.main && !manifest.renderer) {
|
||||
throw new Error(
|
||||
`${manifestFilename} must specify "main" and/or "renderer" fields`,
|
||||
);
|
||||
if (
|
||||
isObject(manifest)
|
||||
&& (
|
||||
hasTypedProperty(manifest, "main", isString)
|
||||
|| hasTypedProperty(manifest, "renderer", isString)
|
||||
)
|
||||
) {
|
||||
return manifest as unknown as LensExtensionManifest;
|
||||
}
|
||||
|
||||
return manifest;
|
||||
throw new Error(`${manifestFilename} must specify "main" and/or "renderer" fields`);
|
||||
};
|
||||
@@ -30,7 +30,9 @@ const confirmUninstallExtension = ({
|
||||
const confirmed = await confirm({
|
||||
message: (
|
||||
<p>
|
||||
Are you sure you want to uninstall extension <b>{displayName}</b>?
|
||||
{"Are you sure you want to uninstall extension "}
|
||||
<b>{displayName}</b>
|
||||
?
|
||||
</p>
|
||||
),
|
||||
labelOk: "Yes",
|
||||
|
||||
@@ -35,13 +35,14 @@ import installOnDropInjectable from "./install-on-drop/install-on-drop.injectabl
|
||||
import { supportedExtensionFormats } from "./supported-extension-formats";
|
||||
import extensionInstallationStateStoreInjectable from "../../../extensions/extension-installation-state-store/extension-installation-state-store.injectable";
|
||||
import type { ExtensionInstallationStateStore } from "../../../extensions/extension-installation-state-store/extension-installation-state-store";
|
||||
import type { InstallFromInput } from "./install-from-input/install-from-input";
|
||||
|
||||
interface Dependencies {
|
||||
userExtensions: IComputedValue<InstalledExtension[]>;
|
||||
enableExtension: (id: LensExtensionId) => void;
|
||||
disableExtension: (id: LensExtensionId) => void;
|
||||
confirmUninstallExtension: ConfirmUninstallExtension;
|
||||
installFromInput: (input: string) => Promise<void>;
|
||||
installFromInput: InstallFromInput;
|
||||
installFromSelectFileDialog: () => Promise<void>;
|
||||
installOnDrop: (files: File[]) => Promise<void>;
|
||||
extensionInstallationStateStore: ExtensionInstallationStateStore;
|
||||
@@ -73,15 +74,33 @@ class NonInjectedExtensions extends React.Component<Dependencies> {
|
||||
|
||||
return (
|
||||
<DropFileInput onDropFiles={this.props.installOnDrop}>
|
||||
<SettingLayout className="Extensions" contentGaps={false} data-testid="extensions-page">
|
||||
<SettingLayout
|
||||
className="Extensions"
|
||||
contentGaps={false}
|
||||
data-testid="extensions-page"
|
||||
>
|
||||
<section>
|
||||
<h1>Extensions</h1>
|
||||
|
||||
<Notice className="mb-14 mt-3">
|
||||
<p>
|
||||
Add new features via Lens Extensions.{" "}
|
||||
Check out <a href={`${docsUrl}/extensions/`} target="_blank" rel="noreferrer">docs</a>{" "}
|
||||
and list of <a href="https://github.com/lensapp/lens-extensions/blob/main/README.md" target="_blank" rel="noreferrer">available extensions</a>.
|
||||
{"Add new features via Lens Extensions. Check out the "}
|
||||
<a
|
||||
href={`${docsUrl}/extensions/`}
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
>
|
||||
docs
|
||||
</a>
|
||||
{" and list of "}
|
||||
<a
|
||||
href="https://github.com/lensapp/lens-extensions/blob/main/README.md"
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
>
|
||||
available extensions
|
||||
</a>
|
||||
.
|
||||
</p>
|
||||
</Notice>
|
||||
|
||||
|
||||
+13
-6
@@ -5,7 +5,7 @@
|
||||
|
||||
import React from "react";
|
||||
import type { ExtensionRegistry } from "../../../../common/user-store/preferences-helpers";
|
||||
import { defaultExtensionRegistryUrl, ExtensionRegistryLocation } from "../../../../common/user-store/preferences-helpers";
|
||||
import { defaultExtensionRegistryUrl } from "../../../../common/user-store/preferences-helpers";
|
||||
import { promiseExecFile } from "../../../utils";
|
||||
import { Notifications } from "../../notifications";
|
||||
|
||||
@@ -17,10 +17,10 @@ export const getBaseRegistryUrl = ({ getRegistryUrlPreference }: Dependencies) =
|
||||
const extensionRegistryUrl = getRegistryUrlPreference();
|
||||
|
||||
switch (extensionRegistryUrl.location) {
|
||||
case ExtensionRegistryLocation.CUSTOM:
|
||||
case "custom":
|
||||
return extensionRegistryUrl.customUrl;
|
||||
|
||||
case ExtensionRegistryLocation.NPMRC: {
|
||||
case "npmrc": {
|
||||
try {
|
||||
const filteredEnv = Object.fromEntries(
|
||||
Object.entries(process.env)
|
||||
@@ -30,13 +30,20 @@ export const getBaseRegistryUrl = ({ getRegistryUrlPreference }: Dependencies) =
|
||||
|
||||
return stdout.trim();
|
||||
} catch (error) {
|
||||
Notifications.error(<p>Failed to get configured registry from <code>.npmrc</code>. Falling back to default registry</p>);
|
||||
Notifications.error((
|
||||
<p>
|
||||
Failed to get configured registry from
|
||||
<code>.npmrc</code>
|
||||
. Falling back to default registry.
|
||||
</p>
|
||||
));
|
||||
console.warn("[EXTENSIONS]: failed to get configured registry from .npmrc", error);
|
||||
}
|
||||
// fallthrough
|
||||
}
|
||||
|
||||
// fallthrough
|
||||
default:
|
||||
case ExtensionRegistryLocation.DEFAULT:
|
||||
case "default":
|
||||
return defaultExtensionRegistryUrl;
|
||||
}
|
||||
};
|
||||
+7
-4
@@ -2,16 +2,19 @@
|
||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
export function getMessageFromError(error: any): string {
|
||||
|
||||
import { hasTypedProperty, isDefined } from "../../../utils";
|
||||
|
||||
export function getMessageFromError(error: unknown): string {
|
||||
if (!error || typeof error !== "object") {
|
||||
return "an error has occurred";
|
||||
}
|
||||
|
||||
if (error.message) {
|
||||
return String(error.message);
|
||||
if (error instanceof Error) {
|
||||
return error.message;
|
||||
}
|
||||
|
||||
if (error.err) {
|
||||
if (hasTypedProperty(error, "err", isDefined)) {
|
||||
return String(error.err);
|
||||
}
|
||||
|
||||
|
||||
@@ -14,6 +14,9 @@ import { readFileNotify } from "../read-file-notify/read-file-notify";
|
||||
import type { InstallRequest } from "../attempt-install/install-request";
|
||||
import type { ExtensionInfo } from "../attempt-install-by-info.injectable";
|
||||
import type { ExtensionInstallationStateStore } from "../../../../extensions/extension-installation-state-store/extension-installation-state-store";
|
||||
import { AsyncInputValidationError } from "../../input/input_validators";
|
||||
|
||||
export type InstallFromInput = (input: string) => Promise<void>;
|
||||
|
||||
interface Dependencies {
|
||||
attemptInstall: (request: InstallRequest, disposer?: ExtendableDisposer) => Promise<void>;
|
||||
@@ -21,34 +24,59 @@ interface Dependencies {
|
||||
extensionInstallationStateStore: ExtensionInstallationStateStore;
|
||||
}
|
||||
|
||||
export const installFromInput = ({ attemptInstall, attemptInstallByInfo, extensionInstallationStateStore }: Dependencies) => async (input: string) => {
|
||||
let disposer: ExtendableDisposer | undefined = undefined;
|
||||
export const installFromInput = ({
|
||||
attemptInstall,
|
||||
attemptInstallByInfo,
|
||||
extensionInstallationStateStore,
|
||||
}: Dependencies): InstallFromInput => (
|
||||
async (input) => {
|
||||
let disposer: ExtendableDisposer | undefined = undefined;
|
||||
|
||||
try {
|
||||
// fixme: improve error messages for non-tar-file URLs
|
||||
if (InputValidators.isUrl.validate(input)) {
|
||||
// install via url
|
||||
disposer = extensionInstallationStateStore.startPreInstall();
|
||||
const { promise } = downloadFile({ url: input, timeout: 10 * 60 * 1000 });
|
||||
const fileName = path.basename(input);
|
||||
try {
|
||||
// fixme: improve error messages for non-tar-file URLs
|
||||
if (InputValidators.isUrl.validate(input, {})) {
|
||||
// install via url
|
||||
disposer = extensionInstallationStateStore.startPreInstall();
|
||||
const { promise } = downloadFile({ url: input, timeout: 10 * 60 * 1000 });
|
||||
const fileName = path.basename(input);
|
||||
|
||||
await attemptInstall({ fileName, dataP: promise }, disposer);
|
||||
} else if (InputValidators.isPath.validate(input)) {
|
||||
// install from system path
|
||||
const fileName = path.basename(input);
|
||||
return await attemptInstall({ fileName, dataP: promise }, disposer);
|
||||
}
|
||||
|
||||
await attemptInstall({ fileName, dataP: readFileNotify(input) });
|
||||
} else if (InputValidators.isExtensionNameInstall.validate(input)) {
|
||||
const [{ groups: { name, version }}] = [...input.matchAll(InputValidators.isExtensionNameInstallRegex)];
|
||||
try {
|
||||
await InputValidators.isPath.validate(input, {});
|
||||
|
||||
await attemptInstallByInfo({ name, version });
|
||||
// install from system path
|
||||
const fileName = path.basename(input);
|
||||
|
||||
return await attemptInstall({ fileName, dataP: readFileNotify(input) });
|
||||
} catch (error) {
|
||||
if (error instanceof AsyncInputValidationError) {
|
||||
const extNameCaptures = InputValidators.isExtensionNameInstallRegex.captures(input);
|
||||
|
||||
if (extNameCaptures) {
|
||||
const { name, version } = extNameCaptures;
|
||||
|
||||
return await attemptInstallByInfo({ name, version });
|
||||
}
|
||||
} else {
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
throw new Error(`Unknown format of input: ${input}`);
|
||||
} catch (error) {
|
||||
const message = getMessageFromError(error);
|
||||
|
||||
logger.info(`[EXTENSION-INSTALL]: installation has failed: ${message}`, { error, installPath: input });
|
||||
Notifications.error((
|
||||
<p>
|
||||
{"Installation has failed: "}
|
||||
<b>{message}</b>
|
||||
</p>
|
||||
));
|
||||
} finally {
|
||||
disposer?.();
|
||||
}
|
||||
} catch (error) {
|
||||
const message = getMessageFromError(error);
|
||||
|
||||
logger.info(`[EXTENSION-INSTALL]: installation has failed: ${message}`, { error, installPath: input });
|
||||
Notifications.error(<p>Installation has failed: <b>{message}</b></p>);
|
||||
} finally {
|
||||
disposer?.();
|
||||
}
|
||||
};
|
||||
);
|
||||
@@ -9,7 +9,6 @@ import { prevDefault } from "../../utils";
|
||||
import { Button } from "../button";
|
||||
import { Icon } from "../icon";
|
||||
import { observer } from "mobx-react";
|
||||
import type { InputValidator } from "../input";
|
||||
import { Input, InputValidators } from "../input";
|
||||
import { SubTitle } from "../layout/sub-title";
|
||||
import { TooltipPosition } from "../tooltip";
|
||||
@@ -17,6 +16,7 @@ import type { ExtensionInstallationStateStore } from "../../../extensions/extens
|
||||
import extensionInstallationStateStoreInjectable
|
||||
from "../../../extensions/extension-installation-state-store/extension-installation-state-store.injectable";
|
||||
import { withInjectables } from "@ogre-tools/injectable-react";
|
||||
import { inputValidator } from "../input/input_validators";
|
||||
|
||||
export interface InstallProps {
|
||||
installPath: string;
|
||||
@@ -36,12 +36,12 @@ const installInputValidators = [
|
||||
InputValidators.isExtensionNameInstall,
|
||||
];
|
||||
|
||||
const installInputValidator: InputValidator = {
|
||||
const installInputValidator = inputValidator({
|
||||
message: "Invalid URL, absolute path, or extension name",
|
||||
validate: (value: string) => (
|
||||
installInputValidators.some(({ validate }) => validate(value))
|
||||
validate: (value: string, props) => (
|
||||
installInputValidators.some(({ validate }) => validate(value, props))
|
||||
),
|
||||
};
|
||||
});
|
||||
|
||||
const NonInjectedInstall: React.FC<Dependencies & InstallProps> = ({
|
||||
installPath,
|
||||
@@ -71,14 +71,14 @@ const NonInjectedInstall: React.FC<Dependencies & InstallProps> = ({
|
||||
value={installPath}
|
||||
onChange={onChange}
|
||||
onSubmit={installFromInput}
|
||||
iconRight={
|
||||
iconRight={(
|
||||
<Icon
|
||||
className={styles.icon}
|
||||
material="folder_open"
|
||||
onClick={prevDefault(installFromSelectFileDialog)}
|
||||
tooltip="Browse"
|
||||
/>
|
||||
}
|
||||
)}
|
||||
/>
|
||||
</div>
|
||||
<div className="flex-initial">
|
||||
@@ -95,7 +95,8 @@ const NonInjectedInstall: React.FC<Dependencies & InstallProps> = ({
|
||||
</div>
|
||||
</div>
|
||||
<small className="mt-3">
|
||||
<b>Pro-Tip</b>: you can drag-n-drop tarball-file to this area
|
||||
<b>Pro-Tip</b>
|
||||
: you can drag-n-drop tarball-file to this area
|
||||
</small>
|
||||
</section>
|
||||
);
|
||||
|
||||
@@ -46,12 +46,6 @@ function getStatus(extension: InstalledExtension) {
|
||||
}
|
||||
|
||||
const NonInjectedInstalledExtensions = observer(({ extensionDiscovery, extensionInstallationStateStore, extensions, uninstall, enable, disable }: Dependencies & InstalledExtensionsProps) => {
|
||||
const filters = [
|
||||
(extension: InstalledExtension) => extension.manifest.name,
|
||||
(extension: InstalledExtension) => getStatus(extension),
|
||||
(extension: InstalledExtension) => extension.manifest.version,
|
||||
];
|
||||
|
||||
const columns = useMemo(
|
||||
() => [
|
||||
{
|
||||
@@ -87,63 +81,61 @@ const NonInjectedInstalledExtensions = observer(({ extensionDiscovery, extension
|
||||
);
|
||||
|
||||
const data = useMemo(
|
||||
() => {
|
||||
return extensions.map(extension => {
|
||||
const { id, isEnabled, isCompatible, manifest } = extension;
|
||||
const { name, description, version } = manifest;
|
||||
const isUninstalling = extensionInstallationStateStore.isExtensionUninstalling(id);
|
||||
() => extensions.map(extension => {
|
||||
const { id, isEnabled, isCompatible, manifest } = extension;
|
||||
const { name, description, version } = manifest;
|
||||
const isUninstalling = extensionInstallationStateStore.isExtensionUninstalling(id);
|
||||
|
||||
return {
|
||||
extension: (
|
||||
<div className={"flex items-start"}>
|
||||
<div>
|
||||
<div className={styles.extensionName}>{name}</div>
|
||||
<div className={styles.extensionDescription}>{description}</div>
|
||||
</div>
|
||||
return {
|
||||
extension: (
|
||||
<div className={"flex items-start"}>
|
||||
<div>
|
||||
<div className={styles.extensionName}>{name}</div>
|
||||
<div className={styles.extensionDescription}>{description}</div>
|
||||
</div>
|
||||
),
|
||||
version,
|
||||
status: (
|
||||
<div className={cssNames({ [styles.enabled]: isEnabled, [styles.invalid]: !isCompatible })}>
|
||||
{getStatus(extension)}
|
||||
</div>
|
||||
),
|
||||
actions: (
|
||||
<MenuActions usePortal toolbar={false}>
|
||||
{ isCompatible && (
|
||||
<>
|
||||
{isEnabled ? (
|
||||
<MenuItem
|
||||
disabled={isUninstalling}
|
||||
onClick={() => disable(id)}
|
||||
>
|
||||
<Icon material="unpublished"/>
|
||||
<span className="title" aria-disabled={isUninstalling}>Disable</span>
|
||||
</MenuItem>
|
||||
) : (
|
||||
<MenuItem
|
||||
disabled={isUninstalling}
|
||||
onClick={() => enable(id)}
|
||||
>
|
||||
<Icon material="check_circle"/>
|
||||
<span className="title" aria-disabled={isUninstalling}>Enable</span>
|
||||
</MenuItem>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
),
|
||||
version,
|
||||
status: (
|
||||
<div className={cssNames({ [styles.enabled]: isEnabled, [styles.invalid]: !isCompatible })}>
|
||||
{getStatus(extension)}
|
||||
</div>
|
||||
),
|
||||
actions: (
|
||||
<MenuActions usePortal toolbar={false}>
|
||||
{isCompatible && (
|
||||
<>
|
||||
{isEnabled ? (
|
||||
<MenuItem
|
||||
disabled={isUninstalling}
|
||||
onClick={() => disable(id)}
|
||||
>
|
||||
<Icon material="unpublished" />
|
||||
<span className="title" aria-disabled={isUninstalling}>Disable</span>
|
||||
</MenuItem>
|
||||
) : (
|
||||
<MenuItem
|
||||
disabled={isUninstalling}
|
||||
onClick={() => enable(id)}
|
||||
>
|
||||
<Icon material="check_circle" />
|
||||
<span className="title" aria-disabled={isUninstalling}>Enable</span>
|
||||
</MenuItem>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
|
||||
<MenuItem
|
||||
disabled={isUninstalling}
|
||||
onClick={() => uninstall(extension)}
|
||||
>
|
||||
<Icon material="delete"/>
|
||||
<span className="title" aria-disabled={isUninstalling}>Uninstall</span>
|
||||
</MenuItem>
|
||||
</MenuActions>
|
||||
),
|
||||
};
|
||||
});
|
||||
}, [extensions, extensionInstallationStateStore.anyUninstalling],
|
||||
<MenuItem
|
||||
disabled={isUninstalling}
|
||||
onClick={() => uninstall(extension)}
|
||||
>
|
||||
<Icon material="delete" />
|
||||
<span className="title" aria-disabled={isUninstalling}>Uninstall</span>
|
||||
</MenuItem>
|
||||
</MenuActions>
|
||||
),
|
||||
};
|
||||
}), [extensions, extensionInstallationStateStore.anyUninstalling],
|
||||
);
|
||||
|
||||
if (!extensionDiscovery.isLoaded) {
|
||||
@@ -169,7 +161,11 @@ const NonInjectedInstalledExtensions = observer(({ extensionDiscovery, extension
|
||||
columns={columns}
|
||||
data={data}
|
||||
items={extensions}
|
||||
filters={filters}
|
||||
filters={[
|
||||
(extension) => extension.manifest.name,
|
||||
(extension) => getStatus(extension),
|
||||
(extension) => extension.manifest.version,
|
||||
]}
|
||||
/>
|
||||
</section>
|
||||
);
|
||||
|
||||
@@ -19,45 +19,61 @@ interface Dependencies {
|
||||
extensionInstallationStateStore: ExtensionInstallationStateStore;
|
||||
}
|
||||
|
||||
export const uninstallExtension =
|
||||
({ extensionLoader, extensionDiscovery, extensionInstallationStateStore }: Dependencies) =>
|
||||
async (extensionId: LensExtensionId): Promise<boolean> => {
|
||||
const { manifest } = extensionLoader.getExtension(extensionId);
|
||||
const displayName = extensionDisplayName(manifest.name, manifest.version);
|
||||
export const uninstallExtension = ({
|
||||
extensionLoader,
|
||||
extensionDiscovery,
|
||||
extensionInstallationStateStore,
|
||||
}: Dependencies) => (
|
||||
async (extensionId: LensExtensionId): Promise<boolean> => {
|
||||
const ext = extensionLoader.getExtension(extensionId);
|
||||
|
||||
try {
|
||||
logger.debug(`[EXTENSIONS]: trying to uninstall ${extensionId}`);
|
||||
extensionInstallationStateStore.setUninstalling(extensionId);
|
||||
if (!ext) {
|
||||
logger.debug(`[EXTENSIONS]: cannot uninstall ${extensionId}, was not installed`);
|
||||
|
||||
await extensionDiscovery.uninstallExtension(extensionId);
|
||||
return true;
|
||||
}
|
||||
|
||||
// wait for the ExtensionLoader to actually uninstall the extension
|
||||
await when(() => !extensionLoader.userExtensions.has(extensionId));
|
||||
const { manifest } = ext;
|
||||
const displayName = extensionDisplayName(manifest.name, manifest.version);
|
||||
|
||||
Notifications.ok(
|
||||
<p>
|
||||
Extension <b>{displayName}</b> successfully uninstalled!
|
||||
</p>,
|
||||
);
|
||||
try {
|
||||
logger.debug(`[EXTENSIONS]: trying to uninstall ${extensionId}`);
|
||||
extensionInstallationStateStore.setUninstalling(extensionId);
|
||||
|
||||
return true;
|
||||
} catch (error) {
|
||||
const message = getMessageFromError(error);
|
||||
await extensionDiscovery.uninstallExtension(extensionId);
|
||||
|
||||
logger.info(
|
||||
`[EXTENSION-UNINSTALL]: uninstalling ${displayName} has failed: ${error}`,
|
||||
{ error },
|
||||
);
|
||||
Notifications.error(
|
||||
<p>
|
||||
Uninstalling extension <b>{displayName}</b> has failed:{" "}
|
||||
<em>{message}</em>
|
||||
</p>,
|
||||
);
|
||||
// wait for the ExtensionLoader to actually uninstall the extension
|
||||
await when(() => !extensionLoader.userExtensions.has(extensionId));
|
||||
|
||||
return false;
|
||||
} finally {
|
||||
Notifications.ok(
|
||||
<p>
|
||||
{"Extension "}
|
||||
<b>{displayName}</b>
|
||||
{" successfully uninstalled!"}
|
||||
</p>,
|
||||
);
|
||||
|
||||
return true;
|
||||
} catch (error) {
|
||||
const message = getMessageFromError(error);
|
||||
|
||||
logger.info(
|
||||
`[EXTENSION-UNINSTALL]: uninstalling ${displayName} has failed: ${error}`,
|
||||
{ error },
|
||||
);
|
||||
Notifications.error(
|
||||
<p>
|
||||
{"Uninstalling extension "}
|
||||
<b>{displayName}</b>
|
||||
{" has failed: "}
|
||||
<em>{message}</em>
|
||||
</p>,
|
||||
);
|
||||
|
||||
return false;
|
||||
} finally {
|
||||
// Remove uninstall state on uninstall failure
|
||||
extensionInstallationStateStore.clearUninstalling(extensionId);
|
||||
}
|
||||
};
|
||||
extensionInstallationStateStore.clearUninstalling(extensionId);
|
||||
}
|
||||
}
|
||||
);
|
||||
Reference in new issue
Block a user