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

Introduce competition for activating custom helm repository in preferences

Co-authored-by: Mikko Aspiala <mikko.aspiala@gmail.com>

Signed-off-by: Janne Savolainen <janne.savolainen@live.fi>
This commit is contained in:
Janne Savolainen 2022-06-06 16:00:42 +03:00
parent 467698bb83
commit 2e51e33853
No known key found for this signature in database
GPG Key ID: 8C6CFB2FFFE8F68A
17 changed files with 8342 additions and 1 deletions

View File

@ -0,0 +1,330 @@
/**
* 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";
import { fireEvent } 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 type { AsyncFnMock } from "@async-fn/jest";
import asyncFn from "@async-fn/jest";
import execFileInjectable from "../../common/fs/exec-file.injectable";
import helmBinaryPathInjectable from "../../main/helm/helm-binary-path.injectable";
import getActiveHelmRepositoriesInjectable from "../../main/helm/repositories/get-active-helm-repositories/get-active-helm-repositories.injectable";
import type { HelmRepo } from "../../common/helm-repo";
import callForPublicHelmRepositoriesInjectable from "../../renderer/components/+preferences/kubernetes/helm-charts/activation-of-public-helm-repository/public-helm-repositories/call-for-public-helm-repositories.injectable";
import isPathInjectable from "../../renderer/components/input/validators/is-path.injectable";
// TODO: Make tooltips free of side effects by making it deterministic
jest.mock("../../renderer/components/tooltip/withTooltip", () => ({
withTooltip: (target: any) => target,
}));
describe("activate custom helm repository in preferences", () => {
let applicationBuilder: ApplicationBuilder;
let rendered: RenderResult;
let execFileMock: AsyncFnMock<
ReturnType<typeof execFileInjectable["instantiate"]>
>;
let getActiveHelmRepositoriesMock: AsyncFnMock<() => Promise<HelmRepo[]>>;
beforeEach(async () => {
jest.useFakeTimers("modern");
applicationBuilder = getApplicationBuilder();
execFileMock = asyncFn();
getActiveHelmRepositoriesMock = asyncFn();
applicationBuilder.beforeApplicationStart(({ mainDi, rendererDi }) => {
rendererDi.override(callForPublicHelmRepositoriesInjectable, () => async () => []);
rendererDi.override(isPathInjectable, () => ({ debounce: 0, validate: async () => {} }));
mainDi.override(
getActiveHelmRepositoriesInjectable,
() => getActiveHelmRepositoriesMock,
);
mainDi.override(execFileInjectable, () => execFileMock);
mainDi.override(helmBinaryPathInjectable, () => "some-helm-binary-path");
});
rendered = await applicationBuilder.render();
});
describe("when navigating to preferences containing helm repositories", () => {
beforeEach(async () => {
applicationBuilder.preferences.navigate();
applicationBuilder.preferences.navigation.click("kubernetes");
});
it("renders", () => {
expect(rendered.baseElement).toMatchSnapshot();
});
describe("when active repositories resolve", () => {
beforeEach(async () => {
await Promise.all([
getActiveHelmRepositoriesMock.resolve([
{ name: "Some active repository", url: "some-url" },
]),
]);
});
it("renders", () => {
expect(rendered.baseElement).toMatchSnapshot();
});
describe("when selecting to add custom repository", () => {
beforeEach(() => {
const button = rendered.getByTestId("add-custom-helm-repo-button");
fireEvent.click(button);
});
it("renders", () => {
expect(rendered.baseElement).toMatchSnapshot();
});
it("shows dialog", () => {
expect(
rendered.queryByTestId("activate-custom-helm-repository-dialog"),
).toBeInTheDocument();
});
// TODO: Figure out how to close dialog by clicking outside of it
xdescribe("when closing the dialog by clicking outside", () => {
beforeEach(() => {});
it("renders", () => {
expect(rendered.baseElement).toMatchSnapshot();
});
it("does not show dialog anymore", () => {
expect(
rendered.queryByTestId("activate-custom-helm-repository-dialog"),
).not.toBeInTheDocument();
});
});
describe("when closing the dialog by clicking cancel", () => {
beforeEach(() => {
const button = rendered.getByTestId("custom-helm-repository-cancel-button");
fireEvent.click(button);
});
it("renders", () => {
expect(rendered.baseElement).toMatchSnapshot();
});
it("does not show dialog anymore", () => {
expect(
rendered.queryByTestId("activate-custom-helm-repository-dialog"),
).not.toBeInTheDocument();
});
});
describe("when inputted minimal options for the repository", () => {
beforeEach(() => {
getActiveHelmRepositoriesMock.mockClear();
const nameInput = rendered.getByTestId("custom-helm-repository-name-input");
fireEvent.change(nameInput, { target: { value: "some-custom-repository" }});
const urlInput = rendered.getByTestId("custom-helm-repository-url-input");
fireEvent.change(urlInput, { target: { value: "http://some.url" }});
});
it("renders", () => {
expect(rendered.baseElement).toMatchSnapshot();
});
describe("when submitted and some time passes", () => {
beforeEach(() => {
const submitButton = rendered.getByTestId("custom-helm-repository-submit-button");
fireEvent.click(submitButton);
// TODO: Remove when debounce is removed from WizardStep.submit
jest.runOnlyPendingTimers();
});
it("renders", () => {
expect(rendered.baseElement).toMatchSnapshot();
});
it("activates the repository", () => {
expect(execFileMock).toHaveBeenCalledWith(
"some-helm-binary-path",
["repo", "add", "some-custom-repository", "http://some.url"],
);
});
it("does not reload active repositories yet", () => {
expect(getActiveHelmRepositoriesMock).not.toHaveBeenCalled();
});
describe("when activating resolves", () => {
beforeEach(async () => {
await execFileMock.resolveSpecific(
[
"some-helm-binary-path",
["repo", "add", "some-custom-repository", "http://some.url"],
],
"",
);
});
it("renders", () => {
expect(rendered.baseElement).toMatchSnapshot();
});
it("does not show dialog anymore", () => {
expect(rendered.queryByTestId("activate-custom-helm-repository-dialog")).not.toBeInTheDocument();
});
it("reloads active repositories", () => {
expect(getActiveHelmRepositoriesMock).toHaveBeenCalled();
});
describe("when adding custom repository again", () => {
beforeEach(() => {
const button = rendered.getByTestId("add-custom-helm-repo-button");
fireEvent.click(button);
});
it("renders", () => {
expect(rendered.baseElement).toMatchSnapshot();
});
it("repository name is empty", () => {
const input = rendered.getByTestId("custom-helm-repository-name-input") as HTMLInputElement;
expect(input.value).toBe("");
});
it("repository url is empty", () => {
const input = rendered.getByTestId("custom-helm-repository-url-input") as HTMLInputElement;
expect(input.value).toBe("");
});
});
});
});
describe("when showing the maximal options", () => {
beforeEach(() => {
const button = rendered.getByTestId("toggle-maximal-options-for-custom-helm-repository-button");
fireEvent.click(button);
});
it("shows maximal options", () => {
const maximalOptions = rendered.getByTestId("maximal-options-for-custom-helm-repository-dialog");
expect(maximalOptions).toBeInTheDocument();
});
it("renders", () => {
expect(rendered.baseElement).toMatchSnapshot();
});
it("given closing the dialog, when reopening the dialog, still shows maximal options", () => {
const cancelButton = rendered.getByTestId("custom-helm-repository-cancel-button");
fireEvent.click(cancelButton);
const openButton = rendered.getByTestId("add-custom-helm-repo-button");
fireEvent.click(openButton);
const maximalOptions = rendered.getByTestId("maximal-options-for-custom-helm-repository-dialog");
expect(maximalOptions).toBeInTheDocument();
});
describe("when hiding maximal options", () => {
beforeEach(() => {
const button = rendered.getByTestId("toggle-maximal-options-for-custom-helm-repository-button");
fireEvent.click(button);
});
it("renders", () => {
expect(rendered.baseElement).toMatchSnapshot();
});
it("does not show maximal options anymore", () => {
const maximalOptions = rendered.queryByTestId("maximal-options-for-custom-helm-repository-dialog");
expect(maximalOptions).not.toBeInTheDocument();
});
});
describe("when inputted maximal options", () => {
beforeEach(async () => {
[
{ selector: "username-input", value: "some-username" },
{ selector: "password-input", value: "some-password" },
{ selector: "ca-cert-file-input", value: "some-ca-cert-file" },
{ selector: "cert-file-input", value: "some-cert-file" },
{ selector: "key-file-input", value: "some-key-file" },
].forEach(({ selector, value }) => {
const input = rendered.getByTestId(`custom-helm-repository-${selector}`);
fireEvent.change(input, { target: { value }});
});
const checkbox = rendered.getByTestId(`custom-helm-repository-verify-tls-input`);
fireEvent.click(checkbox);
jest.runOnlyPendingTimers();
});
it("renders", () => {
expect(rendered.baseElement).toMatchSnapshot();
});
it("when submitted and some time passes, activates the repository with maximal options", () => {
const submitButton = rendered.getByTestId("custom-helm-repository-submit-button");
fireEvent.click(submitButton);
// TODO: Remove when debounce is removed from WizardStep.submit
jest.runOnlyPendingTimers();
expect(execFileMock).toHaveBeenCalledWith(
"some-helm-binary-path",
[
"repo",
"add",
"some-custom-repository",
"http://some.url",
"--insecure-skip-tls-verify",
"--username",
"some-username",
"--password",
"some-password",
"--ca-file",
"some-ca-cert-file",
"--key-file",
"some-key-file",
"--cert-file",
"some-cert-file",
],
);
});
});
});
});
});
});
});
});

View File

@ -0,0 +1,152 @@
/**
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import "../../../add-helm-repo-dialog.scss";
import React from "react";
import { Wizard, WizardStep } from "../../../../wizard";
import { Input } from "../../../../input";
import { systemName, isUrl } from "../../../../input/input_validators";
import { withInjectables } from "@ogre-tools/injectable-react";
import customHelmRepoInjectable from "./custom-helm-repo.injectable";
import type { HelmRepo } from "../../../../../../common/helm-repo";
import { observer } from "mobx-react";
import type { IObservableValue } from "mobx";
import { action } from "mobx";
import submitCustomHelmRepositoryInjectable from "./submit-custom-helm-repository.injectable";
import hideDialogForActivatingCustomHelmRepositoryInjectable from "./dialog-visibility/hide-dialog-for-activating-custom-helm-repository.injectable";
import { Button } from "../../../../button";
import { Icon } from "../../../../icon";
import maximalCustomHelmRepoOptionsAreShownInjectable from "./maximal-custom-helm-repo-options-are-shown.injectable";
import { SubTitle } from "../../../../layout/sub-title";
import { Checkbox } from "../../../../checkbox";
import { HelmFileInput } from "./helm-file-input/helm-file-input";
interface Dependencies {
helmRepo: HelmRepo;
hideDialog: () => void;
submitCustomRepository: (repository: HelmRepo) => Promise<void>;
maximalOptionsAreShown: IObservableValue<boolean>;
}
const NonInjectedActivationOfCustomHelmRepositoryDialogContent = observer(({ helmRepo, submitCustomRepository, maximalOptionsAreShown, hideDialog } : Dependencies) => (
<Wizard header={<h5>Add custom Helm Repo</h5>} done={hideDialog}>
<WizardStep
contentClass="flow column"
nextLabel="Add"
next={() => submitCustomRepository(helmRepo)}
testIdForNext="custom-helm-repository-submit-button"
testIdForPrev="custom-helm-repository-cancel-button"
>
<div className="flex column gaps" data-testid="activate-custom-helm-repository-dialog">
<Input
autoFocus
required
placeholder="Helm repo name"
trim
validators={systemName}
value={helmRepo.name}
onChange={action(v => helmRepo.name = v)}
data-testid="custom-helm-repository-name-input"
/>
<Input
required
placeholder="URL"
validators={isUrl}
value={helmRepo.url}
onChange={action(v => helmRepo.url = v)}
data-testid="custom-helm-repository-url-input"
/>
<Button
plain
className="accordion"
data-testid="toggle-maximal-options-for-custom-helm-repository-button"
onClick={action(() => maximalOptionsAreShown.set(!maximalOptionsAreShown.get()))}
>
More
<Icon
small
tooltip="More"
material={maximalOptionsAreShown.get() ? "remove" : "add"}
/>
</Button>
{maximalOptionsAreShown.get() && (
<div data-testid="maximal-options-for-custom-helm-repository-dialog">
<SubTitle title="Security settings" />
<Checkbox
label="Skip TLS certificate checks for the repository"
value={helmRepo.insecureSkipTlsVerify}
onChange={action(v => {
helmRepo.insecureSkipTlsVerify = v;
})}
data-testid="custom-helm-repository-verify-tls-input"
/>
<HelmFileInput
placeholder="Key file"
value={helmRepo.keyFile || ""}
setValue={action((value) => helmRepo.keyFile = value)}
fileExtensions={keyExtensions}
data-testid="custom-helm-repository-key-file-input"
/>
<HelmFileInput
placeholder="Ca file"
value={helmRepo.caFile || ""}
setValue={action((value) => helmRepo.caFile = value)}
fileExtensions={certExtensions}
data-testid="custom-helm-repository-ca-cert-file-input"
/>
<HelmFileInput
placeholder="Certificate file"
value={helmRepo.certFile || ""}
setValue={action((value) => helmRepo.certFile = value)}
fileExtensions={certExtensions}
data-testid="custom-helm-repository-cert-file-input"
/>
<SubTitle title="Chart Repository Credentials" />
<Input
placeholder="Username"
value={helmRepo.username}
onChange= {action(v => helmRepo.username = v)}
data-testid="custom-helm-repository-username-input"
/>
<Input
type="password"
placeholder="Password"
value={helmRepo.password}
onChange={action(v => helmRepo.password = v)}
data-testid="custom-helm-repository-password-input"
/>
</div>
)}
</div>
</WizardStep>
</Wizard>
));
export const ActivationOfCustomHelmRepositoryDialogContent = withInjectables<Dependencies>(
NonInjectedActivationOfCustomHelmRepositoryDialogContent,
{
getProps: (di) => ({
helmRepo: di.inject(customHelmRepoInjectable),
hideDialog: di.inject(hideDialogForActivatingCustomHelmRepositoryInjectable),
submitCustomRepository: di.inject(submitCustomHelmRepositoryInjectable),
maximalOptionsAreShown: di.inject(maximalCustomHelmRepoOptionsAreShownInjectable),
}),
},
);
const keyExtensions = ["key", "keystore", "jks", "p12", "pfx", "pem"];
const certExtensions = ["crt", "cer", "ca-bundle", "p7b", "p7c", "p7s", "p12", "pfx", "pem"];

View File

@ -0,0 +1,47 @@
/**
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import "../../../add-helm-repo-dialog.scss";
import React from "react";
import { Dialog } from "../../../../dialog";
import { withInjectables } from "@ogre-tools/injectable-react";
import { ActivationOfCustomHelmRepositoryDialogContent } from "./activation-of-custom-helm-repository-dialog-content";
import activationOfCustomHelmRepositoryDialogIsVisibleInjectable from "./dialog-visibility/activation-of-custom-helm-repository-dialog-is-visible.injectable";
import type { IObservableValue } from "mobx";
import { observer } from "mobx-react";
import hideDialogForActivatingCustomHelmRepositoryInjectable from "./dialog-visibility/hide-dialog-for-activating-custom-helm-repository.injectable";
interface Dependencies {
contentIsVisible: IObservableValue<boolean>;
hideDialog: () => void;
}
const NonInjectedActivationOfCustomHelmRepositoryDialog = observer(({
contentIsVisible,
hideDialog,
}: Dependencies) => (
<div>
<Dialog
className="AddHelmRepoDialog"
isOpen={contentIsVisible.get()}
close={hideDialog}
>
{contentIsVisible.get() && <ActivationOfCustomHelmRepositoryDialogContent />}
</Dialog>
</div>
));
export const ActivationOfCustomHelmRepositoryDialog = withInjectables<Dependencies>(
NonInjectedActivationOfCustomHelmRepositoryDialog,
{
getProps: (di) => ({
contentIsVisible: di.inject(activationOfCustomHelmRepositoryDialogIsVisibleInjectable),
hideDialog: di.inject(hideDialogForActivatingCustomHelmRepositoryInjectable),
}),
},
);

View File

@ -0,0 +1,32 @@
/**
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import { withInjectables } from "@ogre-tools/injectable-react";
import React from "react";
import { Button } from "../../../../button";
import showDialogForActivatingCustomHelmRepositoryInjectable from "./dialog-visibility/show-dialog-for-activating-custom-helm-repository.injectable";
interface Dependencies {
showDialog: () => void;
}
const NonInjectedActivationOfCustomHelmRepositoryOpenButton = ({ showDialog }: Dependencies) => (
<Button
primary
label="Add Custom Helm Repo"
onClick={showDialog}
data-testid="add-custom-helm-repo-button"
/>
);
export const ActivationOfCustomHelmRepositoryOpenButton = withInjectables<Dependencies>(
NonInjectedActivationOfCustomHelmRepositoryOpenButton,
{
getProps: (di) => ({
showDialog: di.inject(showDialogForActivatingCustomHelmRepositoryInjectable),
}),
},
);

View File

@ -0,0 +1,25 @@
/**
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import { getInjectable, lifecycleEnum } from "@ogre-tools/injectable";
import { observable } from "mobx";
const customHelmRepoInjectable = getInjectable({
id: "custom-helm-repo",
instantiate: () => observable({
name: "",
url: "",
username: "",
password: "",
insecureSkipTlsVerify: false,
caFile: "",
keyFile: "",
certFile: "",
}),
lifecycle: lifecycleEnum.transient,
});
export default customHelmRepoInjectable;

View File

@ -0,0 +1,13 @@
/**
* 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 { observable } from "mobx";
const activationOfCustomHelmRepositoryDialogIsVisibleInjectable = getInjectable({
id: "add-custom-helm-repository-dialog-is-visible",
instantiate: () => observable.box(false),
});
export default activationOfCustomHelmRepositoryDialogIsVisibleInjectable;

View File

@ -0,0 +1,21 @@
/**
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import { getInjectable } from "@ogre-tools/injectable";
import { action } from "mobx";
import activationOfCustomHelmRepositoryDialogIsVisibleInjectable from "./activation-of-custom-helm-repository-dialog-is-visible.injectable";
const hideDialogForActivatingCustomHelmRepositoryInjectable = getInjectable({
id: "hide-dialog-for-activating-custom-helm-repository",
instantiate: (di) => {
const state = di.inject(activationOfCustomHelmRepositoryDialogIsVisibleInjectable);
return action(() => {
state.set(false);
});
},
});
export default hideDialogForActivatingCustomHelmRepositoryInjectable;

View File

@ -0,0 +1,21 @@
/**
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import { getInjectable } from "@ogre-tools/injectable";
import { action } from "mobx";
import activationOfCustomHelmRepositoryDialogIsVisibleInjectable from "./activation-of-custom-helm-repository-dialog-is-visible.injectable";
const showDialogForActivatingCustomHelmRepositoryInjectable = getInjectable({
id: "show-dialog-for-activating-custom-helm-repository",
instantiate: (di) => {
const state = di.inject(activationOfCustomHelmRepositoryDialogIsVisibleInjectable);
return action(() => {
state.set(true);
});
},
});
export default showDialogForActivatingCustomHelmRepositoryInjectable;

View File

@ -0,0 +1,24 @@
/**
* 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 { FileFilter } from "electron";
import { requestOpenFilePickingDialog } from "../../../../../../ipc";
const getFilePathsInjectable = getInjectable({
id: "get-file-paths",
instantiate: () => async (fileFilter: FileFilter) =>
await requestOpenFilePickingDialog({
// defaultPath: this.getFilePath(type),
properties: ["openFile", "showHiddenFiles"],
message: `Select file`,
buttonLabel: `Use file`,
filters: [fileFilter, { name: "Any", extensions: ["*"] }],
}),
causesSideEffects: true,
});
export default getFilePathsInjectable;

View File

@ -0,0 +1,74 @@
/**
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import type { InputValidator } from "../../../../../input";
import { Input } from "../../../../../input";
import { Icon } from "../../../../../icon";
import { withInjectables } from "@ogre-tools/injectable-react";
import React from "react";
import getFilePathsInjectable from "./get-file-paths.injectable";
import type { FileFilter } from "electron";
import isPathInjectable from "../../../../../input/validators/is-path.injectable";
interface HelmFileInputProps {
placeholder: string;
fileExtensions: string[];
value: string;
setValue: (value: string) => void;
"data-testid"?: string;
}
interface Dependencies {
getFilePaths: (fileFilter: FileFilter) => Promise<{ canceled: boolean; filePaths: string[] }>;
isPath: InputValidator<true>;
}
const NonInjectedHelmFileInput = ({
placeholder,
value,
setValue,
fileExtensions,
getFilePaths,
isPath,
"data-testid": testId,
}: Dependencies & HelmFileInputProps) => (
<div className="flex gaps align-center">
<Input
placeholder={placeholder}
validators={isPath}
className="box grow"
value={value}
onChange={(v) => setValue(v)}
data-testid={testId}
/>
<Icon
material="folder"
onClick={async () => {
const { canceled, filePaths } = await getFilePaths({
name: placeholder,
extensions: fileExtensions,
});
if (!canceled && filePaths.length) {
setValue(filePaths[0]);
}
}}
tooltip="Browse"
/>
</div>
);
export const HelmFileInput = withInjectables<Dependencies, HelmFileInputProps>(
NonInjectedHelmFileInput,
{
getProps: (di, props) => ({
getFilePaths: di.inject(getFilePathsInjectable),
isPath: di.inject(isPathInjectable),
...props,
}),
},
);

View File

@ -0,0 +1,13 @@
/**
* 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 { observable } from "mobx";
const maximalCustomHelmRepoOptionsAreShownInjectable = getInjectable({
id: "maximal-custom-helm-repo-options-are-shown",
instantiate: () => observable.box(false),
});
export default maximalCustomHelmRepoOptionsAreShownInjectable;

View File

@ -0,0 +1,25 @@
/**
* 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 { HelmRepo } from "../../../../../../common/helm-repo";
import activateHelmRepositoryInjectable from "../activation-of-public-helm-repository/select-helm-repository/activate-helm-repository.injectable";
import hideDialogForActivatingCustomHelmRepositoryInjectable from "./dialog-visibility/hide-dialog-for-activating-custom-helm-repository.injectable";
const submitCustomHelmRepositoryInjectable = getInjectable({
id: "submit-custom-helm-repository",
instantiate: (di) => {
const activateHelmRepository = di.inject(activateHelmRepositoryInjectable);
const hideDialog = di.inject(hideDialogForActivatingCustomHelmRepositoryInjectable);
return async (repository: HelmRepo) => {
await activateHelmRepository(repository);
hideDialog();
};
},
});
export default submitCustomHelmRepositoryInjectable;

View File

@ -8,14 +8,20 @@ import React from "react";
import { HelmRepositories } from "./helm-repositories"; import { HelmRepositories } from "./helm-repositories";
import { ActivationOfPublicHelmRepository } from "./activation-of-public-helm-repository/activation-of-public-helm-repository"; import { ActivationOfPublicHelmRepository } from "./activation-of-public-helm-repository/activation-of-public-helm-repository";
import { ActivationOfCustomHelmRepositoryOpenButton } from "./activation-of-custom-helm-repository/activation-of-custom-helm-repository-open-button";
import { ActivationOfCustomHelmRepositoryDialog } from "./activation-of-custom-helm-repository/activation-of-custom-helm-repository-dialog";
export const HelmCharts = () => ( export const HelmCharts = () => (
<div> <div>
<div className="flex gaps"> <div className="flex gaps">
<ActivationOfPublicHelmRepository /> <ActivationOfPublicHelmRepository />
<ActivationOfCustomHelmRepositoryOpenButton />
</div> </div>
<HelmRepositories /> <HelmRepositories />
<ActivationOfCustomHelmRepositoryDialog />
</div> </div>
); );

View File

@ -0,0 +1,32 @@
/**
* 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 { AsyncInputValidationError, inputValidator } from "../input_validators";
import pathExistsInjectable from "../../../../common/fs/path-exists.injectable";
const isPathInjectable = getInjectable({
id: "is-path",
instantiate: (di) => {
const pathExists = di.inject(pathExistsInjectable);
return inputValidator<true>({
debounce: 100,
condition: ({ type }) => type === "text",
validate: async (value) => {
try {
await pathExists(value);
} catch {
throw new AsyncInputValidationError(
`${value} is not a valid file path`,
);
}
},
});
},
});
export default isPathInjectable;

View File

@ -129,6 +129,8 @@ export interface WizardStepProps<D> extends WizardCommonProps<D> {
skip?: boolean; // don't render the step skip?: boolean; // don't render the step
scrollable?: boolean; scrollable?: boolean;
children?: React.ReactNode | React.ReactNode[]; children?: React.ReactNode | React.ReactNode[];
testIdForNext?: string;
testIdForPrev?: string;
} }
interface WizardStepState { interface WizardStepState {
@ -214,7 +216,7 @@ export class WizardStep<D> extends React.Component<WizardStepProps<D>, WizardSte
step, isFirst, isLast, children, step, isFirst, isLast, children,
loading, customButtons, disabledNext, scrollable, loading, customButtons, disabledNext, scrollable,
hideNextBtn, hideBackBtn, beforeContent, afterContent, noValidate, skip, moreButtons, hideNextBtn, hideBackBtn, beforeContent, afterContent, noValidate, skip, moreButtons,
waiting, className, contentClass, prevLabel, nextLabel, waiting, className, contentClass, prevLabel, nextLabel, testIdForNext, testIdForPrev,
} = this.props; } = this.props;
if (skip) { if (skip) {
@ -242,6 +244,7 @@ export class WizardStep<D> extends React.Component<WizardStepProps<D>, WizardSte
label={prevLabel || (isFirst?.() ? "Cancel" : "Back")} label={prevLabel || (isFirst?.() ? "Cancel" : "Back")}
hidden={hideBackBtn} hidden={hideBackBtn}
onClick={this.prev} onClick={this.prev}
data-testid={testIdForPrev}
/> />
<Button <Button
primary primary
@ -250,6 +253,7 @@ export class WizardStep<D> extends React.Component<WizardStepProps<D>, WizardSte
hidden={hideNextBtn} hidden={hideNextBtn}
waiting={waiting ?? this.state.waiting} waiting={waiting ?? this.state.waiting}
disabled={disabledNext} disabled={disabledNext}
data-testid={testIdForNext}
/> />
</div> </div>
)} )}

View File

@ -45,6 +45,7 @@ import appVersionInjectable from "../common/get-configuration-file-model/app-ver
import provideInitialValuesForSyncBoxesInjectable from "./utils/sync-box/provide-initial-values-for-sync-boxes.injectable"; import provideInitialValuesForSyncBoxesInjectable from "./utils/sync-box/provide-initial-values-for-sync-boxes.injectable";
import requestAnimationFrameInjectable from "./components/animate/request-animation-frame.injectable"; import requestAnimationFrameInjectable from "./components/animate/request-animation-frame.injectable";
import getRandomIdInjectable from "../common/utils/get-random-id.injectable"; import getRandomIdInjectable from "../common/utils/get-random-id.injectable";
import getFilePathsInjectable from "./components/+preferences/kubernetes/helm-charts/activation-of-custom-helm-repository/helm-file-input/get-file-paths.injectable";
export const getDiForUnitTesting = (opts: { doGeneralOverrides?: boolean } = {}) => { export const getDiForUnitTesting = (opts: { doGeneralOverrides?: boolean } = {}) => {
const { const {
@ -95,6 +96,10 @@ export const getDiForUnitTesting = (opts: { doGeneralOverrides?: boolean } = {})
throw new Error("Tried to broadcast message over IPC without explicit override."); throw new Error("Tried to broadcast message over IPC without explicit override.");
}); });
di.override(getFilePathsInjectable, () => () => {
throw new Error("Tried to get file paths without explicit override.");
});
// eslint-disable-next-line unused-imports/no-unused-vars-ts // eslint-disable-next-line unused-imports/no-unused-vars-ts
di.override(extensionsStoreInjectable, () => ({ isEnabled: ({ id, isBundled }) => false }) as ExtensionsStore); di.override(extensionsStoreInjectable, () => ({ isEnabled: ({ id, isBundled }) => false }) as ExtensionsStore);