mirror of
https://github.com/lensapp/lens.git
synced 2025-05-20 05:10:56 +00:00
Add OnClickDecorator for UI components
Signed-off-by: Juho Heikka <juho.heikka@gmail.com>
This commit is contained in:
parent
17ddee2bde
commit
396d08051f
@ -262,7 +262,7 @@ export class MetricsSettings extends React.Component<MetricsSettingsProps> {
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<Button
|
||||
<OpenLensButton
|
||||
label={this.buttonLabel}
|
||||
waiting={this.inProgress}
|
||||
onClick={() => this.save()}
|
||||
|
||||
@ -24,8 +24,8 @@ describe("add custom helm repository in preferences", () => {
|
||||
let showErrorNotificationMock: jest.Mock;
|
||||
let rendered: RenderResult;
|
||||
let execFileMock: AsyncFnMock<
|
||||
ReturnType<typeof execFileInjectable["instantiate"]>
|
||||
>;
|
||||
ReturnType<typeof execFileInjectable["instantiate"]>
|
||||
>;
|
||||
let getActiveHelmRepositoriesMock: AsyncFnMock<() => Promise<AsyncResult<HelmRepo[]>>>;
|
||||
|
||||
beforeEach(async () => {
|
||||
|
||||
@ -10,7 +10,7 @@ import type { IComputedValue } from "mobx";
|
||||
import { observer } from "mobx-react";
|
||||
import installUpdateCountdownInjectable from "./install-update-countdown.injectable";
|
||||
import { Dialog } from "../../components/dialog";
|
||||
import { Button } from "../../components/button";
|
||||
import { OpenLensButton } from "../../components/button";
|
||||
import styles from "./force-update-modal.module.scss";
|
||||
|
||||
interface Dependencies {
|
||||
@ -36,7 +36,7 @@ const NonInjectedForceUpdateModal = observer(
|
||||
</div>
|
||||
|
||||
<div className={styles.footer}>
|
||||
<Button
|
||||
<OpenLensButton
|
||||
primary
|
||||
data-testid="update-now-from-must-update-immediately-modal"
|
||||
onClick={restartAndInstallUpdate}
|
||||
@ -49,7 +49,7 @@ const NonInjectedForceUpdateModal = observer(
|
||||
data-testid="countdown-to-automatic-update"
|
||||
/>
|
||||
)
|
||||
</Button>
|
||||
</OpenLensButton>
|
||||
</div>
|
||||
</div>
|
||||
</Dialog>
|
||||
|
||||
@ -17,7 +17,7 @@ import { appEventBus } from "../../../common/app-event-bus/event-bus";
|
||||
import { loadConfigFromString, splitConfig } from "../../../common/kube-helpers";
|
||||
import { docsUrl } from "../../../common/vars";
|
||||
import { isDefined, iter } from "../../utils";
|
||||
import { Button } from "../button";
|
||||
import { OpenLensButton } from "../button";
|
||||
import { Notifications } from "../notifications";
|
||||
import { SettingLayout } from "../layout/setting-layout";
|
||||
import { MonacoEditor } from "../monaco-editor";
|
||||
@ -138,7 +138,7 @@ class NonInjectedAddCluster extends React.Component<Dependencies> {
|
||||
</>
|
||||
)}
|
||||
<div className="actions-panel">
|
||||
<Button
|
||||
<OpenLensButton
|
||||
primary
|
||||
disabled={this.kubeContexts.size === 0}
|
||||
label={this.kubeContexts.size === 1 ? "Add cluster" : "Add clusters"}
|
||||
|
||||
@ -11,7 +11,7 @@ import { disposeOnUnmount, observer } from "mobx-react";
|
||||
import { DrawerTitle } from "../drawer";
|
||||
import { Notifications } from "../notifications";
|
||||
import { Input } from "../input";
|
||||
import { Button } from "../button";
|
||||
import { OpenLensButton } from "../button";
|
||||
import { configMapStore } from "./legacy-store";
|
||||
import type { KubeObjectDetailsProps } from "../kube-object-details";
|
||||
import { ConfigMap } from "../../../common/k8s-api/endpoints";
|
||||
@ -104,7 +104,7 @@ export class ConfigMapDetails extends React.Component<ConfigMapDetailsProps> {
|
||||
</div>
|
||||
))
|
||||
}
|
||||
<Button
|
||||
<OpenLensButton
|
||||
primary
|
||||
label="Save"
|
||||
waiting={this.isSaving}
|
||||
|
||||
@ -17,7 +17,7 @@ import type { IResourceQuotaValues } from "../../../common/k8s-api/endpoints";
|
||||
import { resourceQuotaApi } from "../../../common/k8s-api/endpoints";
|
||||
import { Select } from "../select";
|
||||
import { Icon } from "../icon";
|
||||
import { Button } from "../button";
|
||||
import { OpenLensButton } from "../button";
|
||||
import { Notifications } from "../notifications";
|
||||
import { NamespaceSelect } from "../+namespaces/namespace-select";
|
||||
import { SubTitle } from "../layout/sub-title";
|
||||
@ -218,7 +218,7 @@ export class AddQuotaDialog extends React.Component<AddQuotaDialogProps> {
|
||||
onKeyDown={this.onInputQuota}
|
||||
className="box grow"
|
||||
/>
|
||||
<Button
|
||||
<OpenLensButton
|
||||
round
|
||||
primary
|
||||
onClick={this.setQuota}
|
||||
@ -227,7 +227,7 @@ export class AddQuotaDialog extends React.Component<AddQuotaDialogProps> {
|
||||
material={this.quotaSelectValue && this.quotas[this.quotaSelectValue] ? "edit" : "add"}
|
||||
tooltip="Set quota"
|
||||
/>
|
||||
</Button>
|
||||
</OpenLensButton>
|
||||
</div>
|
||||
<div className="quota-entries">
|
||||
{this.quotaEntries.map(([quota, value]) => (
|
||||
|
||||
@ -4,9 +4,12 @@
|
||||
*/
|
||||
|
||||
import React from "react";
|
||||
import { render } from "@testing-library/react";
|
||||
import { SecretDetails } from "../secret-details";
|
||||
import { Secret, SecretType } from "../../../../common/k8s-api/endpoints";
|
||||
import { getDiForUnitTesting } from "../../../getDiForUnitTesting";
|
||||
import type { DiRender } from "../../test-utils/renderFor";
|
||||
import { renderFor } from "../../test-utils/renderFor";
|
||||
import type { DiContainer } from "@ogre-tools/injectable";
|
||||
|
||||
jest.mock("../../kube-object-meta/kube-object-meta", () => ({
|
||||
KubeObjectMeta: () => null,
|
||||
@ -14,6 +17,14 @@ jest.mock("../../kube-object-meta/kube-object-meta", () => ({
|
||||
|
||||
|
||||
describe("SecretDetails tests", () => {
|
||||
let di: DiContainer;
|
||||
let render: DiRender;
|
||||
|
||||
beforeEach(() => {
|
||||
di = getDiForUnitTesting({ doGeneralOverrides: true });
|
||||
render = renderFor(di);
|
||||
});
|
||||
|
||||
it("should show the visibility toggle when the secret value is ''", () => {
|
||||
const secret = new Secret({
|
||||
apiVersion: "v1",
|
||||
@ -30,6 +41,7 @@ describe("SecretDetails tests", () => {
|
||||
},
|
||||
type: SecretType.Opaque,
|
||||
});
|
||||
|
||||
const result = render(<SecretDetails object={secret}/>);
|
||||
|
||||
expect(result.getByTestId("foobar-secret-entry").querySelector(".Icon")).toBeDefined();
|
||||
|
||||
@ -10,7 +10,7 @@ import { autorun, observable, makeObservable } from "mobx";
|
||||
import { disposeOnUnmount, observer } from "mobx-react";
|
||||
import { DrawerItem, DrawerTitle } from "../drawer";
|
||||
import { Input } from "../input";
|
||||
import { Button } from "../button";
|
||||
import { OpenLensButton } from "../button";
|
||||
import { Notifications } from "../notifications";
|
||||
import { base64, toggle } from "../../utils";
|
||||
import { Icon } from "../icon";
|
||||
@ -122,7 +122,7 @@ export class SecretDetails extends React.Component<SecretDetailsProps> {
|
||||
<>
|
||||
<DrawerTitle>Data</DrawerTitle>
|
||||
{secrets.map(this.renderSecret)}
|
||||
<Button
|
||||
<OpenLensButton
|
||||
primary
|
||||
label="Save"
|
||||
waiting={this.isSaving}
|
||||
|
||||
@ -9,7 +9,7 @@ import {
|
||||
disposer,
|
||||
} from "../../../../common/utils";
|
||||
import { Notifications } from "../../notifications";
|
||||
import { Button } from "../../button";
|
||||
import { OpenLensButton } from "../../button";
|
||||
import type { ExtensionLoader } from "../../../../extensions/extension-loader";
|
||||
import type { LensExtensionId } from "../../../../extensions/lens-extension";
|
||||
import React from "react";
|
||||
@ -110,7 +110,7 @@ export const attemptInstall =
|
||||
{` ${name}@${oldVersion} will be removed before installation.`}
|
||||
</div>
|
||||
</div>
|
||||
<Button
|
||||
<OpenLensButton
|
||||
autoFocus
|
||||
label="Install"
|
||||
onClick={async () => {
|
||||
|
||||
@ -6,7 +6,7 @@
|
||||
import styles from "./install.module.scss";
|
||||
import React from "react";
|
||||
import { prevDefault } from "../../utils";
|
||||
import { Button } from "../button";
|
||||
import { OpenLensButton } from "../button";
|
||||
import { Icon } from "../icon";
|
||||
import { observer } from "mobx-react";
|
||||
import { Input, InputValidators } from "../input";
|
||||
@ -77,7 +77,7 @@ const NonInjectedInstall: React.FC<Dependencies & InstallProps> = ({
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<Button
|
||||
<OpenLensButton
|
||||
className={styles.button}
|
||||
primary
|
||||
label="Install"
|
||||
|
||||
@ -12,7 +12,7 @@ import { Drawer, DrawerItem } from "../drawer";
|
||||
import { autoBind, stopPropagation } from "../../utils";
|
||||
import { MarkdownViewer } from "../markdown-viewer";
|
||||
import { Spinner } from "../spinner";
|
||||
import { Button } from "../button";
|
||||
import { OpenLensButton } from "../button";
|
||||
import { Select } from "../select";
|
||||
import { Badge } from "../badge";
|
||||
import { Tooltip, withStyles } from "@material-ui/core";
|
||||
@ -76,7 +76,7 @@ class NonInjectedHelmChartDetails extends Component<HelmChartDetailsProps & Depe
|
||||
<div className="intro-contents box grow">
|
||||
<div className="description flex align-center justify-space-between" data-testid="selected-chart-description">
|
||||
{selectedChart.getDescription()}
|
||||
<Button
|
||||
<OpenLensButton
|
||||
primary
|
||||
label="Install"
|
||||
onClick={this.install}
|
||||
|
||||
@ -14,7 +14,7 @@ import { observer } from "mobx-react";
|
||||
import { withInjectables } from "@ogre-tools/injectable-react";
|
||||
import type { ConfigurationInput, MinimalResourceGroup, OnlyUserSuppliedValuesAreShownToggle, ReleaseDetailsModel } from "./release-details-model/release-details-model.injectable";
|
||||
import releaseDetailsModelInjectable from "./release-details-model/release-details-model.injectable";
|
||||
import { Button } from "../../button";
|
||||
import { OpenLensButton } from "../../button";
|
||||
import { kebabCase } from "lodash/fp";
|
||||
import { Badge } from "../../badge";
|
||||
import { SubTitle } from "../../layout/sub-title";
|
||||
@ -64,7 +64,7 @@ const NonInjectedReleaseDetailsContent = observer(({ model }: Dependencies & Rel
|
||||
<div className="flex gaps align-center">
|
||||
<span>{model.release.chart}</span>
|
||||
|
||||
<Button
|
||||
<OpenLensButton
|
||||
primary
|
||||
label="Upgrade"
|
||||
className="box right upgrade"
|
||||
@ -196,7 +196,7 @@ const ReleaseValues = observer(({ configuration, onlyUserSuppliedValuesAreShown
|
||||
onChange={configuration.onChange}
|
||||
/>
|
||||
|
||||
<Button
|
||||
<OpenLensButton
|
||||
primary
|
||||
label="Save"
|
||||
waiting={configuration.isSaving.get()}
|
||||
|
||||
@ -11,7 +11,7 @@ import type { Service, ServicePort } from "../../../common/k8s-api/endpoints";
|
||||
import { action, makeObservable, observable, reaction } from "mobx";
|
||||
import { cssNames } from "../../utils";
|
||||
import { Notifications } from "../notifications";
|
||||
import { Button } from "../button";
|
||||
import { OpenLensButton } from "../button";
|
||||
import type { ForwardedPort, PortForwardStore } from "../../port-forward";
|
||||
import { predictProtocol } from "../../port-forward";
|
||||
import { Spinner } from "../spinner";
|
||||
@ -183,11 +183,11 @@ class NonInjectedServicePortComponent extends React.Component<ServicePortCompone
|
||||
<span title="Open in a browser" onClick={() => this.portForward()}>
|
||||
{port.toString()}
|
||||
</span>
|
||||
<Button primary onClick={portForwardAction}>
|
||||
<OpenLensButton primary onClick={portForwardAction}>
|
||||
{" "}
|
||||
{this.isPortForwarded ? (this.isActive ? "Stop/Remove" : "Remove") : "Forward..."}
|
||||
{" "}
|
||||
</Button>
|
||||
</OpenLensButton>
|
||||
{this.waiting && (
|
||||
<Spinner />
|
||||
)}
|
||||
|
||||
@ -17,7 +17,7 @@ import type { IObservableValue } from "mobx";
|
||||
import { action } from "mobx";
|
||||
import submitCustomHelmRepositoryInjectable from "./submit-custom-helm-repository.injectable";
|
||||
import hideDialogForAddingCustomHelmRepositoryInjectable from "./dialog-visibility/hide-dialog-for-adding-custom-helm-repository.injectable";
|
||||
import { Button } from "../../../../button";
|
||||
import { OpenLensButton } from "../../../../button";
|
||||
import { Icon } from "../../../../icon";
|
||||
import maximalCustomHelmRepoOptionsAreShownInjectable from "./maximal-custom-helm-repo-options-are-shown.injectable";
|
||||
import { SubTitle } from "../../../../layout/sub-title";
|
||||
@ -59,7 +59,7 @@ const NonInjectedActivationOfCustomHelmRepositoryDialogContent = observer(({ hel
|
||||
onChange={action(v => helmRepo.url = v)}
|
||||
data-testid="custom-helm-repository-url-input"
|
||||
/>
|
||||
<Button
|
||||
<OpenLensButton
|
||||
plain
|
||||
className="accordion"
|
||||
data-testid="toggle-maximal-options-for-custom-helm-repository-button"
|
||||
@ -71,7 +71,7 @@ const NonInjectedActivationOfCustomHelmRepositoryDialogContent = observer(({ hel
|
||||
tooltip="More"
|
||||
material={maximalOptionsAreShown.get() ? "remove" : "add"}
|
||||
/>
|
||||
</Button>
|
||||
</OpenLensButton>
|
||||
|
||||
{maximalOptionsAreShown.get() && (
|
||||
<div data-testid="maximal-options-for-custom-helm-repository-dialog">
|
||||
|
||||
@ -4,7 +4,7 @@
|
||||
*/
|
||||
import { withInjectables } from "@ogre-tools/injectable-react";
|
||||
import React from "react";
|
||||
import { Button } from "../../../../button";
|
||||
import { OpenLensButton } from "../../../../button";
|
||||
import showDialogForAddingCustomHelmRepositoryInjectable from "./dialog-visibility/show-dialog-for-adding-custom-helm-repository.injectable";
|
||||
|
||||
interface Dependencies {
|
||||
@ -12,7 +12,7 @@ interface Dependencies {
|
||||
}
|
||||
|
||||
const NonInjectedActivationOfCustomHelmRepositoryOpenButton = ({ showDialog }: Dependencies) => (
|
||||
<Button
|
||||
<OpenLensButton
|
||||
primary
|
||||
label="Add Custom Helm Repo"
|
||||
onClick={showDialog}
|
||||
|
||||
@ -11,7 +11,7 @@ import type { ContainerPort, Pod } from "../../../common/k8s-api/endpoints";
|
||||
import { action, makeObservable, observable, reaction } from "mobx";
|
||||
import { cssNames } from "../../utils";
|
||||
import { Notifications } from "../notifications";
|
||||
import { Button } from "../button";
|
||||
import { OpenLensButton } from "../button";
|
||||
import type { ForwardedPort, PortForwardStore } from "../../port-forward";
|
||||
import { predictProtocol } from "../../port-forward";
|
||||
import { Spinner } from "../spinner";
|
||||
@ -181,11 +181,11 @@ class NonInjectedPodContainerPort extends React.Component<PodContainerPortProps
|
||||
<span title="Open in a browser" onClick={() => this.portForward()}>
|
||||
{text}
|
||||
</span>
|
||||
<Button primary onClick={portForwardAction}>
|
||||
<OpenLensButton primary onClick={portForwardAction}>
|
||||
{" "}
|
||||
{this.isPortForwarded ? (this.isActive ? "Stop/Remove" : "Remove") : "Forward..."}
|
||||
{" "}
|
||||
</Button>
|
||||
</OpenLensButton>
|
||||
{this.waiting && (
|
||||
<Spinner />
|
||||
)}
|
||||
|
||||
@ -7,7 +7,7 @@ import "./add-remove-buttons.scss";
|
||||
|
||||
import React from "react";
|
||||
import { cssNames } from "../../utils";
|
||||
import { Button } from "../button";
|
||||
import { OpenLensButton } from "../button";
|
||||
import { Icon } from "../icon";
|
||||
|
||||
export interface AddRemoveButtonsProps extends React.HTMLAttributes<any> {
|
||||
@ -37,7 +37,7 @@ export class AddRemoveButtons extends React.PureComponent<AddRemoveButtonsProps>
|
||||
]
|
||||
.filter(button => button.onClick)
|
||||
.map(({ icon, ...props }) => (
|
||||
<Button
|
||||
<OpenLensButton
|
||||
key={icon}
|
||||
big
|
||||
round
|
||||
@ -45,7 +45,7 @@ export class AddRemoveButtons extends React.PureComponent<AddRemoveButtonsProps>
|
||||
{...props}
|
||||
>
|
||||
<Icon material={icon} />
|
||||
</Button>
|
||||
</OpenLensButton>
|
||||
));
|
||||
}
|
||||
|
||||
|
||||
@ -11,6 +11,7 @@ import randomColor from "randomcolor";
|
||||
import GraphemeSplitter from "grapheme-splitter";
|
||||
import type { SingleOrMany } from "../../utils";
|
||||
import { cssNames, isDefined, iter } from "../../utils";
|
||||
import { OpenLensButton } from "../button";
|
||||
|
||||
export interface AvatarProps {
|
||||
title: string;
|
||||
@ -77,7 +78,7 @@ export const Avatar = ({
|
||||
onClick,
|
||||
"data-testid": dataTestId,
|
||||
}: AvatarProps) => (
|
||||
<div
|
||||
<OpenLensButton
|
||||
className={cssNames(styles.Avatar, {
|
||||
[styles.circle]: variant == "circle",
|
||||
[styles.rounded]: variant == "rounded",
|
||||
@ -105,5 +106,5 @@ export const Avatar = ({
|
||||
/>
|
||||
)
|
||||
: children || getLabelFromTitle(title)}
|
||||
</div>
|
||||
</OpenLensButton>
|
||||
);
|
||||
|
||||
@ -7,7 +7,9 @@ import "./button.scss";
|
||||
import type { ButtonHTMLAttributes } from "react";
|
||||
import React from "react";
|
||||
import { cssNames } from "../../utils";
|
||||
import type { TooltipDecoratorProps } from "../tooltip";
|
||||
import { withTooltip } from "../tooltip";
|
||||
import { OnClickDecorated } from "../on-click-decorated/on-click-decorated";
|
||||
|
||||
export interface ButtonProps extends ButtonHTMLAttributes<any> {
|
||||
label?: React.ReactNode;
|
||||
@ -23,16 +25,22 @@ export interface ButtonProps extends ButtonHTMLAttributes<any> {
|
||||
round?: boolean;
|
||||
href?: string; // render as hyperlink
|
||||
target?: "_blank"; // in case of using @href
|
||||
Component?: React.ComponentType<any>;
|
||||
}
|
||||
|
||||
const PlainButton = (props: React.DetailedHTMLProps<React.ButtonHTMLAttributes<HTMLButtonElement>, HTMLButtonElement>) => <button {...props} />;
|
||||
const PlainAnchor = (props: React.DetailedHTMLProps<React.AnchorHTMLAttributes<HTMLAnchorElement>, HTMLAnchorElement>) => <a {...props} />;
|
||||
|
||||
export const Button = withTooltip((props: ButtonProps) => {
|
||||
const {
|
||||
waiting, label, primary, accent, plain, hidden, active, big,
|
||||
round, outlined, light, children, ...btnProps
|
||||
round, outlined, light, children, Component, ...btnProps
|
||||
} = props;
|
||||
|
||||
if (hidden) return null;
|
||||
|
||||
const ButtonComponent = Component ?? (props.href ? PlainAnchor : PlainButton);
|
||||
|
||||
btnProps.className = cssNames("Button", btnProps.className, {
|
||||
waiting, primary, accent, plain, active, big, round, outlined, light,
|
||||
});
|
||||
@ -40,18 +48,25 @@ export const Button = withTooltip((props: ButtonProps) => {
|
||||
// render as link
|
||||
if (props.href) {
|
||||
return (
|
||||
<a {...btnProps}>
|
||||
<ButtonComponent
|
||||
{...btnProps}>
|
||||
{label}
|
||||
{children}
|
||||
</a>
|
||||
</ButtonComponent>
|
||||
);
|
||||
}
|
||||
|
||||
// render as button
|
||||
return (
|
||||
<button type="button" {...btnProps}>
|
||||
<ButtonComponent
|
||||
type="button"
|
||||
{...btnProps}>
|
||||
{label}
|
||||
{children}
|
||||
</button>
|
||||
</ButtonComponent>
|
||||
);
|
||||
});
|
||||
|
||||
export const OpenLensButton = (props: ButtonProps & TooltipDecoratorProps) => {
|
||||
return <Button {...props} Component={(props) => <OnClickDecorated {...props} tagName={props.href ? "a" : "button"} />} />;
|
||||
};
|
||||
|
||||
@ -12,7 +12,7 @@ import { ipcRendererOn } from "../../../common/ipc";
|
||||
import type { Cluster } from "../../../common/cluster/cluster";
|
||||
import type { IClassName } from "../../utils";
|
||||
import { isBoolean, hasTypedProperty, isObject, isString, cssNames } from "../../utils";
|
||||
import { Button } from "../button";
|
||||
import { OpenLensButton } from "../button";
|
||||
import { Icon } from "../icon";
|
||||
import { Spinner } from "../spinner";
|
||||
import type { KubeAuthUpdate } from "../../../common/cluster-types";
|
||||
@ -134,7 +134,7 @@ class NonInjectedClusterStatus extends React.Component<ClusterStatusProps & Depe
|
||||
if (this.hasErrors && !this.isReconnecting) {
|
||||
return (
|
||||
<>
|
||||
<Button
|
||||
<OpenLensButton
|
||||
primary
|
||||
label="Reconnect"
|
||||
className="box center"
|
||||
|
||||
@ -7,7 +7,7 @@ import React from "react";
|
||||
import { disposeOnUnmount, observer } from "mobx-react";
|
||||
import { onMultiSelectFor, Select } from "../../select/select";
|
||||
import { Icon } from "../../icon/icon";
|
||||
import { Button } from "../../button/button";
|
||||
import { OpenLensButton } from "../../button/button";
|
||||
import { SubTitle } from "../../layout/sub-title";
|
||||
import type { Cluster } from "../../../../common/cluster/cluster";
|
||||
import { observable, reaction, makeObservable } from "mobx";
|
||||
@ -85,12 +85,12 @@ export class ClusterMetricsSetting extends React.Component<ClusterMetricsSetting
|
||||
)}
|
||||
themeName="lens"
|
||||
/>
|
||||
<Button
|
||||
<OpenLensButton
|
||||
primary
|
||||
label="Hide all metrics"
|
||||
onClick={this.onChangeButton}
|
||||
/>
|
||||
<Button
|
||||
<OpenLensButton
|
||||
primary
|
||||
label="Reset"
|
||||
onClick={this.reset}
|
||||
|
||||
@ -12,7 +12,7 @@ import { observable, makeObservable, computed } from "mobx";
|
||||
import { observer } from "mobx-react";
|
||||
import { cssNames, noop, prevDefault } from "../../utils";
|
||||
import type { ButtonProps } from "../button";
|
||||
import { Button } from "../button";
|
||||
import { OpenLensButton } from "../button";
|
||||
import type { DialogProps } from "../dialog";
|
||||
import { Dialog } from "../dialog";
|
||||
import { Icon } from "../icon";
|
||||
@ -140,14 +140,14 @@ class NonInjectedConfirmDialog extends React.Component<ConfirmDialogProps & Depe
|
||||
{message}
|
||||
</div>
|
||||
<div className="confirm-buttons">
|
||||
<Button
|
||||
<OpenLensButton
|
||||
plain
|
||||
className="cancel"
|
||||
label={labelCancel}
|
||||
onClick={prevDefault(this.close)}
|
||||
{...cancelButtonProps}
|
||||
/>
|
||||
<Button
|
||||
<OpenLensButton
|
||||
autoFocus
|
||||
primary
|
||||
className="ok"
|
||||
|
||||
@ -9,7 +9,7 @@ import { action, observable } from "mobx";
|
||||
import { observer } from "mobx-react";
|
||||
import React from "react";
|
||||
|
||||
import { Button } from "../button";
|
||||
import { OpenLensButton } from "../button";
|
||||
import { saveKubeconfig } from "./save-config";
|
||||
import { Notifications } from "../notifications";
|
||||
import { Dialog } from "../dialog";
|
||||
@ -226,12 +226,12 @@ class NonInjectedDeleteClusterDialog extends React.Component<Dependencies> {
|
||||
)}
|
||||
</div>
|
||||
<div className={styles.dialogButtons}>
|
||||
<Button
|
||||
<OpenLensButton
|
||||
onClick={this.close}
|
||||
plain
|
||||
label="Cancel"
|
||||
/>
|
||||
<Button
|
||||
<OpenLensButton
|
||||
onClick={() => this.onDelete(state)}
|
||||
autoFocus
|
||||
accent
|
||||
|
||||
@ -10,7 +10,7 @@ import type { DialogProps } from "../dialog";
|
||||
import { Dialog } from "../dialog";
|
||||
import { Wizard, WizardStep } from "../wizard";
|
||||
import { Notifications } from "../notifications";
|
||||
import { Button } from "../button";
|
||||
import { OpenLensButton } from "../button";
|
||||
import { Icon } from "../icon";
|
||||
import { clipboard } from "electron";
|
||||
import { kebabCase } from "lodash/fp";
|
||||
@ -35,7 +35,7 @@ export function LogsDialog({ title, logs, ...dialogProps }: LogsDialogProps) {
|
||||
scrollable={false}
|
||||
customButtons={(
|
||||
<div className="buttons flex gaps align-center justify-space-between">
|
||||
<Button
|
||||
<OpenLensButton
|
||||
plain
|
||||
onClick={() => {
|
||||
clipboard.writeText(logs);
|
||||
@ -44,10 +44,10 @@ export function LogsDialog({ title, logs, ...dialogProps }: LogsDialogProps) {
|
||||
>
|
||||
<Icon material="assignment"/>
|
||||
{" Copy to clipboard"}
|
||||
</Button>
|
||||
<Button plain onClick={dialogProps.close}>
|
||||
</OpenLensButton>
|
||||
<OpenLensButton plain onClick={dialogProps.close}>
|
||||
Close
|
||||
</Button>
|
||||
</OpenLensButton>
|
||||
</div>
|
||||
)}
|
||||
>
|
||||
|
||||
@ -10,7 +10,7 @@ import React, { Component } from "react";
|
||||
import { computed, observable, reaction, makeObservable } from "mobx";
|
||||
import { disposeOnUnmount, observer } from "mobx-react";
|
||||
import { cssNames } from "../../utils";
|
||||
import { Button } from "../button";
|
||||
import { OpenLensButton } from "../button";
|
||||
import { Icon } from "../icon";
|
||||
import { Spinner } from "../spinner";
|
||||
import type { DockStore, TabId } from "./dock/store";
|
||||
@ -146,13 +146,13 @@ class NonInjectedInfoPanel extends Component<InfoPanelProps & Dependencies> {
|
||||
)}
|
||||
{showButtons && (
|
||||
<>
|
||||
<Button
|
||||
<OpenLensButton
|
||||
plain
|
||||
label="Cancel"
|
||||
onClick={close}
|
||||
data-testid={this.props.cancelTestId}
|
||||
/>
|
||||
<Button
|
||||
<OpenLensButton
|
||||
active
|
||||
outlined={showSubmitClose}
|
||||
primary={!showSubmitClose}// one button always should be primary (blue)
|
||||
@ -162,7 +162,7 @@ class NonInjectedInfoPanel extends Component<InfoPanelProps & Dependencies> {
|
||||
data-testid={this.props.submitTestId}
|
||||
/>
|
||||
{showSubmitClose && (
|
||||
<Button
|
||||
<OpenLensButton
|
||||
primary
|
||||
active
|
||||
label={`${submitLabel} & Close`}
|
||||
|
||||
@ -13,7 +13,7 @@ import { Badge } from "../../badge";
|
||||
import { NamespaceSelect } from "../../+namespaces/namespace-select";
|
||||
import { prevDefault } from "../../../utils";
|
||||
import { Icon } from "../../icon";
|
||||
import { Button } from "../../button";
|
||||
import { OpenLensButton } from "../../button";
|
||||
import { LogsDialog } from "../../dialog/logs-dialog";
|
||||
import { Select } from "../../select";
|
||||
import { Input } from "../../input";
|
||||
@ -46,14 +46,14 @@ const NonInjectedInstallChart = observer(
|
||||
</p>
|
||||
<p>Installation complete!</p>
|
||||
<div className="flex gaps align-center">
|
||||
<Button
|
||||
<OpenLensButton
|
||||
autoFocus
|
||||
primary
|
||||
label="View Helm Release"
|
||||
onClick={prevDefault(model.navigateToInstalledRelease)}
|
||||
data-testid={`show-release-${installed.release.name}-for-${tabId}`}
|
||||
/>
|
||||
<Button
|
||||
<OpenLensButton
|
||||
plain
|
||||
active
|
||||
label="Show Notes"
|
||||
|
||||
@ -8,7 +8,7 @@ import "./error-boundary.scss";
|
||||
import type { ErrorInfo } from "react";
|
||||
import React from "react";
|
||||
import { observer } from "mobx-react";
|
||||
import { Button } from "../button";
|
||||
import { OpenLensButton } from "../button";
|
||||
import { navigation } from "../../navigation";
|
||||
import { issuesTrackerUrl, slackUrl } from "../../../common/vars";
|
||||
import type { SingleOrMany } from "../../utils";
|
||||
@ -75,7 +75,7 @@ export class ErrorBoundary extends React.Component<ErrorBoundaryProps, State> {
|
||||
{error.stack}
|
||||
</code>
|
||||
</div>
|
||||
<Button
|
||||
<OpenLensButton
|
||||
className="box self-flex-start"
|
||||
primary
|
||||
label="Back"
|
||||
|
||||
@ -10,7 +10,7 @@ import { observer } from "mobx-react";
|
||||
import yaml from "js-yaml";
|
||||
import type { ServiceAccount } from "../../../common/k8s-api/endpoints";
|
||||
import { cssNames, saveFileDialog } from "../../utils";
|
||||
import { Button } from "../button";
|
||||
import { OpenLensButton } from "../button";
|
||||
import type { DialogProps } from "../dialog";
|
||||
import { Dialog } from "../dialog";
|
||||
import { Icon } from "../icon";
|
||||
@ -81,21 +81,21 @@ export class KubeConfigDialog extends React.Component<KubeConfigDialogProps> {
|
||||
<WizardStep
|
||||
customButtons={(
|
||||
<div className="actions flex gaps">
|
||||
<Button plain onClick={this.copyToClipboard}>
|
||||
<OpenLensButton plain onClick={this.copyToClipboard}>
|
||||
<Icon material="assignment"/>
|
||||
{" Copy to clipboard"}
|
||||
</Button>
|
||||
<Button plain onClick={this.download}>
|
||||
</OpenLensButton>
|
||||
<OpenLensButton plain onClick={this.download}>
|
||||
<Icon material="cloud_download"/>
|
||||
{" Download file"}
|
||||
</Button>
|
||||
<Button
|
||||
</OpenLensButton>
|
||||
<OpenLensButton
|
||||
plain
|
||||
className="box right"
|
||||
onClick={this.close}
|
||||
>
|
||||
Close
|
||||
</Button>
|
||||
</OpenLensButton>
|
||||
</div>
|
||||
)}
|
||||
prev={this.close}
|
||||
|
||||
@ -0,0 +1,11 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`foobar given A -element 1`] = `
|
||||
<body>
|
||||
<div>
|
||||
<a
|
||||
data-testid="some-anchor"
|
||||
/>
|
||||
</div>
|
||||
</body>
|
||||
`;
|
||||
@ -0,0 +1,11 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`foobar given A -element 1`] = `
|
||||
<body>
|
||||
<div>
|
||||
<a
|
||||
data-testid="some-anchor"
|
||||
/>
|
||||
</div>
|
||||
</body>
|
||||
`;
|
||||
@ -0,0 +1,104 @@
|
||||
/**
|
||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
import { getDiForUnitTesting } from "../../getDiForUnitTesting";
|
||||
import type { DiRender } from "../test-utils/renderFor";
|
||||
import { renderFor } from "../test-utils/renderFor";
|
||||
import { OpenLensButton } from "../button";
|
||||
import React from "react";
|
||||
import { fireEvent } from "@testing-library/dom";
|
||||
import type { RenderResult } from "@testing-library/react";
|
||||
import { onClickDecoratorInjectionToken } from "./on-click-decorator-injection-token";
|
||||
import type { DiContainer } from "@ogre-tools/injectable";
|
||||
import { getInjectable } from "@ogre-tools/injectable";
|
||||
import { OnClickDecorated } from "./on-click-decorated";
|
||||
|
||||
describe("foobar", () => {
|
||||
let di: DiContainer;
|
||||
let render: DiRender;
|
||||
|
||||
beforeEach(() => {
|
||||
di = getDiForUnitTesting({ doGeneralOverrides: true });
|
||||
render = renderFor(di);
|
||||
});
|
||||
|
||||
it("given A -element", () => {
|
||||
const rendered = render(
|
||||
<OnClickDecorated
|
||||
tagName="a"
|
||||
onClick={() => {}}
|
||||
data-testid="some-anchor"
|
||||
/>,
|
||||
);
|
||||
|
||||
expect(rendered.baseElement).toMatchSnapshot();
|
||||
});
|
||||
|
||||
|
||||
describe("given button", () => {
|
||||
let onClickMock: jest.Mock;
|
||||
let firstHigherOrderFunctionMock: jest.Mock;
|
||||
let secondHigherOrderFunctionMock: jest.Mock;
|
||||
let rendered: RenderResult;
|
||||
|
||||
beforeEach(() => {
|
||||
onClickMock = jest.fn();
|
||||
firstHigherOrderFunctionMock = jest.fn((x) => x);
|
||||
secondHigherOrderFunctionMock = jest.fn((x) => x);
|
||||
|
||||
const someInjectable = getInjectable({
|
||||
id: "some-injectable",
|
||||
|
||||
instantiate: () => ({
|
||||
onClick: firstHigherOrderFunctionMock,
|
||||
}),
|
||||
|
||||
injectionToken: onClickDecoratorInjectionToken,
|
||||
});
|
||||
|
||||
const someOtherInjectable = getInjectable({
|
||||
id: "some-other-injectable",
|
||||
|
||||
instantiate: () => ({
|
||||
onClick: secondHigherOrderFunctionMock,
|
||||
}),
|
||||
|
||||
injectionToken: onClickDecoratorInjectionToken,
|
||||
});
|
||||
|
||||
di.register(someInjectable, someOtherInjectable);
|
||||
|
||||
rendered = render(
|
||||
<OpenLensButton
|
||||
primary
|
||||
label="Add Custom Helm Repo"
|
||||
onClick={onClickMock}
|
||||
data-testid="add-custom-helm-repo-button"
|
||||
/>,
|
||||
);
|
||||
});
|
||||
|
||||
describe("when button is clicked", () => {
|
||||
beforeEach(() => {
|
||||
fireEvent.click(rendered.getByTestId("add-custom-helm-repo-button"));
|
||||
});
|
||||
|
||||
it("calls the original onClick", () => {
|
||||
expect(onClickMock).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("calls the original onClick with an argument", () => {
|
||||
expect(onClickMock).toHaveBeenCalledWith(expect.any(Object));
|
||||
});
|
||||
|
||||
it("calls the first higher order function", () => {
|
||||
expect(firstHigherOrderFunctionMock).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("calls the second higher order function", () => {
|
||||
expect(secondHigherOrderFunctionMock).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
@ -0,0 +1,38 @@
|
||||
/**
|
||||
* 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 { flow } from "lodash/fp";
|
||||
import type { HTMLAttributes, MouseEventHandler } from "react";
|
||||
import React from "react";
|
||||
import type { OnClickDecorator } from "./on-click-decorator-injection-token";
|
||||
import { onClickDecoratorInjectionToken } from "./on-click-decorator-injection-token";
|
||||
|
||||
interface Dependencies {
|
||||
decorators: OnClickDecorator[];
|
||||
}
|
||||
|
||||
interface ClickDecoratedProps extends HTMLAttributes<any> {
|
||||
onClick?: MouseEventHandler<any>;
|
||||
tagName: "button" | "a";
|
||||
}
|
||||
|
||||
const NonInjectedOnClickDecorated = ({ decorators, tagName: TagName, onClick, ...props }: Dependencies & ClickDecoratedProps) => {
|
||||
const onClickDecorators = decorators.map(decorator => decorator.onClick);
|
||||
|
||||
const decoratedOnClick = onClick ? flow(...onClickDecorators)(onClick) : undefined;
|
||||
|
||||
return <TagName {...props} onClick={decoratedOnClick} />;
|
||||
};
|
||||
|
||||
export const OnClickDecorated = withInjectables<Dependencies, ClickDecoratedProps>(
|
||||
NonInjectedOnClickDecorated,
|
||||
|
||||
{
|
||||
getProps: (di, props) => ({
|
||||
decorators: di.injectMany(onClickDecoratorInjectionToken),
|
||||
...props,
|
||||
}),
|
||||
},
|
||||
);
|
||||
@ -0,0 +1,17 @@
|
||||
/**
|
||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
import { getInjectionToken } from "@ogre-tools/injectable";
|
||||
import type { MouseEventHandler } from "react";
|
||||
import type React from "react";
|
||||
|
||||
type OnClick = (toBeDecorated: MouseEventHandler<any>) => (event: React.MouseEvent) => void;
|
||||
|
||||
export interface OnClickDecorator {
|
||||
onClick: OnClick;
|
||||
}
|
||||
|
||||
export const onClickDecoratorInjectionToken = getInjectionToken<OnClickDecorator>({
|
||||
id: "onclick-decorator-injection-token",
|
||||
});
|
||||
@ -0,0 +1,67 @@
|
||||
/**
|
||||
* 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 { AppEvent } from "../../../common/app-event-bus/event-bus";
|
||||
import type { EventEmitter } from "../../../common/event-emitter";
|
||||
import capitalize from "lodash/capitalize";
|
||||
import { onClickDecoratorInjectionToken } from "./on-click-decorator-injection-token";
|
||||
import appEventBusInjectable from "../../../common/app-event-bus/app-event-bus.injectable";
|
||||
|
||||
function getEventName(el: HTMLElement, pathname: string, parentLevels = 3) {
|
||||
let headers: string[] = [];
|
||||
let parent = el;
|
||||
|
||||
const path = pathname.split("/");
|
||||
|
||||
headers.push(capitalize(path[path.length-1]));
|
||||
|
||||
for (let i = 0; i < parentLevels; i++) {
|
||||
if (parent.parentElement) {
|
||||
parent = parent.parentElement;
|
||||
}
|
||||
}
|
||||
|
||||
const nodelist = parent.querySelectorAll("h1, h2, h3, h4, h5, h6, .header");
|
||||
|
||||
nodelist.forEach(node => node.textContent && headers.push(node.textContent));
|
||||
|
||||
headers = [...new Set(headers)];
|
||||
|
||||
if (el?.textContent) {
|
||||
headers.push(el.textContent);
|
||||
}
|
||||
const eventName = headers.join(" ");
|
||||
|
||||
return eventName;
|
||||
}
|
||||
|
||||
function captureMouseEvent(eventBus: EventEmitter<[AppEvent]>, event: React.MouseEvent) {
|
||||
const name = getEventName(event.target as HTMLElement, window.location.pathname);
|
||||
|
||||
eventBus.emit({
|
||||
name,
|
||||
action: capitalize(event.type),
|
||||
destination: "AutoCapture",
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
const onClickTelemetryDecoratorInjectable = getInjectable({
|
||||
id: "on-click-telemetry-decorator",
|
||||
|
||||
instantiate: (di) => ({
|
||||
onClick: (toBeDecorated) => {
|
||||
return (event) => {
|
||||
captureMouseEvent(di.inject(appEventBusInjectable), event);
|
||||
|
||||
return toBeDecorated(event);
|
||||
};
|
||||
},
|
||||
}),
|
||||
|
||||
injectionToken: onClickDecoratorInjectionToken,
|
||||
});
|
||||
|
||||
export default onClickTelemetryDecoratorInjectable;
|
||||
@ -7,7 +7,7 @@ import type { FileFilter, OpenDialogOptions } from "electron";
|
||||
import { observer } from "mobx-react";
|
||||
import React from "react";
|
||||
import { cssNames } from "../../utils";
|
||||
import { Button } from "../button";
|
||||
import { OpenLensButton } from "../button";
|
||||
import { requestOpenFilePickingDialog } from "../../ipc";
|
||||
|
||||
export interface PathPickOpts {
|
||||
@ -53,7 +53,7 @@ export class PathPicker extends React.Component<PathPickerProps> {
|
||||
const { className, label, disabled } = this.props;
|
||||
|
||||
return (
|
||||
<Button
|
||||
<OpenLensButton
|
||||
primary
|
||||
label={label}
|
||||
disabled={disabled}
|
||||
|
||||
@ -6,7 +6,7 @@
|
||||
import "./wizard.scss";
|
||||
import React from "react";
|
||||
import { cssNames, prevDefault } from "../../utils";
|
||||
import { Button } from "../button";
|
||||
import { OpenLensButton } from "../button";
|
||||
import { Stepper } from "../stepper";
|
||||
import { SubTitle } from "../layout/sub-title";
|
||||
import { Spinner } from "../spinner";
|
||||
@ -238,7 +238,7 @@ export class WizardStep<D> extends React.Component<WizardStepProps<D>, WizardSte
|
||||
{customButtons ?? (
|
||||
<div className="buttons flex gaps align-center">
|
||||
{moreButtons}
|
||||
<Button
|
||||
<OpenLensButton
|
||||
className="back-btn"
|
||||
plain
|
||||
label={prevLabel || (isFirst?.() ? "Cancel" : "Back")}
|
||||
@ -246,7 +246,7 @@ export class WizardStep<D> extends React.Component<WizardStepProps<D>, WizardSte
|
||||
onClick={this.prev}
|
||||
data-testid={testIdForPrev}
|
||||
/>
|
||||
<Button
|
||||
<OpenLensButton
|
||||
primary
|
||||
type="submit"
|
||||
label={nextLabel || (isLast?.() ? "Submit" : "Next")}
|
||||
|
||||
@ -73,6 +73,9 @@ import forceUpdateModalRootFrameComponentInjectable from "./application-update/f
|
||||
import legacyOnChannelListenInjectable from "./ipc/legacy-channel-listen.injectable";
|
||||
import getEntitySettingCommandsInjectable from "./components/command-palette/registered-commands/get-entity-setting-commands.injectable";
|
||||
import storageSaveDelayInjectable from "./utils/create-storage/storage-save-delay.injectable";
|
||||
import appEventBusInjectable from "../common/app-event-bus/app-event-bus.injectable";
|
||||
import { EventEmitter } from "./utils";
|
||||
import type { AppEvent } from "../common/app-event-bus/event-bus";
|
||||
|
||||
export const getDiForUnitTesting = (opts: { doGeneralOverrides?: boolean } = {}) => {
|
||||
const {
|
||||
@ -227,6 +230,8 @@ export const getDiForUnitTesting = (opts: { doGeneralOverrides?: boolean } = {})
|
||||
info: noop,
|
||||
silly: noop,
|
||||
}));
|
||||
|
||||
di.override(appEventBusInjectable, () => new EventEmitter<[AppEvent]>());
|
||||
}
|
||||
|
||||
return di;
|
||||
|
||||
@ -7,7 +7,7 @@ import navigateToEntitySettingsInjectable from "../../common/front-end-routing/r
|
||||
import type { ListNamespaceForbiddenArgs } from "../../common/ipc/cluster";
|
||||
import { Notifications } from "../components/notifications";
|
||||
import { ClusterStore } from "../../common/cluster-store/cluster-store";
|
||||
import { Button } from "../components/button";
|
||||
import { OpenLensButton } from "../components/button";
|
||||
import type { IpcRendererEvent } from "electron";
|
||||
import React from "react";
|
||||
import notificationsStoreInjectable from "../components/notifications/notifications-store.injectable";
|
||||
@ -56,7 +56,7 @@ const listNamespacesForbiddenHandlerInjectable = getInjectable({
|
||||
{" does not have permissions to list namespaces. Please add the namespaces you have access to."}
|
||||
</p>
|
||||
<div className="flex gaps row align-left box grow">
|
||||
<Button
|
||||
<OpenLensButton
|
||||
active
|
||||
outlined
|
||||
label="Go to Accessible Namespaces Settings"
|
||||
|
||||
@ -4,7 +4,7 @@
|
||||
*/
|
||||
|
||||
import React from "react";
|
||||
import { Button } from "../components/button";
|
||||
import { OpenLensButton } from "../components/button";
|
||||
import { Notifications } from "../components/notifications";
|
||||
import type { NavigateToPortForwards } from "../../common/front-end-routing/routes/cluster/network/port-forwards/navigate-to-port-forwards.injectable";
|
||||
import type { NotificationsStore } from "../components/notifications/notifications.store";
|
||||
@ -30,7 +30,7 @@ export const aboutPortForwarding = ({
|
||||
You can manage your port forwards on the Port Forwarding Page.
|
||||
</p>
|
||||
<div className="flex gaps row align-left box grow">
|
||||
<Button
|
||||
<OpenLensButton
|
||||
active
|
||||
outlined
|
||||
label="Go to Port Forwarding"
|
||||
@ -71,7 +71,7 @@ export const notifyErrorPortForwarding = ({
|
||||
{msg}
|
||||
</p>
|
||||
<div className="flex gaps row align-left box grow">
|
||||
<Button
|
||||
<OpenLensButton
|
||||
active
|
||||
outlined
|
||||
label="Check Port Forwarding"
|
||||
|
||||
Loading…
Reference in New Issue
Block a user