mirror of
https://github.com/lensapp/lens.git
synced 2025-05-20 05:10:56 +00:00
Fix crash in HelmChartDetails
Signed-off-by: Sebastian Malton <sebastian@malton.name>
This commit is contained in:
parent
fc770b4b44
commit
a623c59b7e
@ -315,6 +315,8 @@ export class HelmChart implements HelmChartData {
|
|||||||
autoBind(this);
|
autoBind(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static create(data: RawHelmChart): HelmChart;
|
||||||
|
static create(data: RawHelmChart, opts?: HelmChartCreateOpts): HelmChart | undefined;
|
||||||
static create(data: RawHelmChart, { onError = "throw" }: HelmChartCreateOpts = {}): HelmChart | undefined {
|
static create(data: RawHelmChart, { onError = "throw" }: HelmChartCreateOpts = {}): HelmChart | undefined {
|
||||||
const result = helmChartValidator.validate(data, {
|
const result = helmChartValidator.validate(data, {
|
||||||
abortEarly: false,
|
abortEarly: false,
|
||||||
|
|||||||
@ -0,0 +1,387 @@
|
|||||||
|
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||||
|
|
||||||
|
exports[`<HelmChartDetails /> before getChartDetails resolves renders 1`] = `
|
||||||
|
<body>
|
||||||
|
<div>
|
||||||
|
<div
|
||||||
|
class="Notifications flex column align-flex-end"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
class="Animate slide-right Drawer HelmChartDetails right enter"
|
||||||
|
style="--size: 725px; --enter-duration: 100ms; --leave-duration: 100ms;"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="drawer-wrapper flex column"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="drawer-title flex align-center"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="drawer-title-text flex gaps align-center"
|
||||||
|
>
|
||||||
|
Chart: a galaxy far far away/a name
|
||||||
|
<i
|
||||||
|
class="Icon material interactive focusable"
|
||||||
|
id="tooltip_target_1"
|
||||||
|
tabindex="0"
|
||||||
|
>
|
||||||
|
<span
|
||||||
|
class="icon"
|
||||||
|
data-icon-name="content_copy"
|
||||||
|
>
|
||||||
|
content_copy
|
||||||
|
</span>
|
||||||
|
<div />
|
||||||
|
</i>
|
||||||
|
</div>
|
||||||
|
<i
|
||||||
|
class="Icon material interactive focusable"
|
||||||
|
id="tooltip_target_2"
|
||||||
|
tabindex="0"
|
||||||
|
>
|
||||||
|
<span
|
||||||
|
class="icon"
|
||||||
|
data-icon-name="close"
|
||||||
|
>
|
||||||
|
close
|
||||||
|
</span>
|
||||||
|
<div />
|
||||||
|
</i>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
class="drawer-content flex column box grow"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="Spinner singleColor center"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
class="ResizingAnchor horizontal leading"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</body>
|
||||||
|
`;
|
||||||
|
|
||||||
|
exports[`<HelmChartDetails /> before getChartDetails resolves when getChartDetails resolves with one version renders 1`] = `
|
||||||
|
<body>
|
||||||
|
<div>
|
||||||
|
<div
|
||||||
|
class="Notifications flex column align-flex-end"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
class="Animate slide-right Drawer HelmChartDetails right enter"
|
||||||
|
style="--size: 725px; --enter-duration: 100ms; --leave-duration: 100ms;"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="drawer-wrapper flex column"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="drawer-title flex align-center"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="drawer-title-text flex gaps align-center"
|
||||||
|
>
|
||||||
|
Chart: a galaxy far far away/a name
|
||||||
|
<i
|
||||||
|
class="Icon material interactive focusable"
|
||||||
|
id="tooltip_target_3"
|
||||||
|
tabindex="0"
|
||||||
|
>
|
||||||
|
<span
|
||||||
|
class="icon"
|
||||||
|
data-icon-name="content_copy"
|
||||||
|
>
|
||||||
|
content_copy
|
||||||
|
</span>
|
||||||
|
<div />
|
||||||
|
</i>
|
||||||
|
</div>
|
||||||
|
<i
|
||||||
|
class="Icon material interactive focusable"
|
||||||
|
id="tooltip_target_4"
|
||||||
|
tabindex="0"
|
||||||
|
>
|
||||||
|
<span
|
||||||
|
class="icon"
|
||||||
|
data-icon-name="close"
|
||||||
|
>
|
||||||
|
close
|
||||||
|
</span>
|
||||||
|
<div />
|
||||||
|
</i>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
class="drawer-content flex column box grow"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="box grow"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="introduction flex align-flex-start"
|
||||||
|
>
|
||||||
|
<img
|
||||||
|
class="intro-logo"
|
||||||
|
src=""
|
||||||
|
/>
|
||||||
|
<div
|
||||||
|
class="intro-contents box grow"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="description flex align-center justify-space-between"
|
||||||
|
data-testid="selected-chart-description"
|
||||||
|
>
|
||||||
|
|
||||||
|
<button
|
||||||
|
class="Button primary"
|
||||||
|
type="button"
|
||||||
|
>
|
||||||
|
Install
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
class="DrawerItem version"
|
||||||
|
>
|
||||||
|
<span
|
||||||
|
class="name"
|
||||||
|
>
|
||||||
|
Version
|
||||||
|
</span>
|
||||||
|
<span
|
||||||
|
class="value"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="Select theme-outlined css-b62m3t-container"
|
||||||
|
>
|
||||||
|
<span
|
||||||
|
class="css-1f43avz-a11yText-A11yText"
|
||||||
|
id="react-select-chart-version-input-live-region"
|
||||||
|
/>
|
||||||
|
<span
|
||||||
|
aria-atomic="false"
|
||||||
|
aria-live="polite"
|
||||||
|
aria-relevant="additions text"
|
||||||
|
class="css-1f43avz-a11yText-A11yText"
|
||||||
|
/>
|
||||||
|
<div
|
||||||
|
class="Select__control css-1s2u09g-control"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="Select__value-container Select__value-container--has-value css-319lph-ValueContainer"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="Select__single-value css-qc6sy-singleValue"
|
||||||
|
>
|
||||||
|
1
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
class="Select__input-container css-6j8wv5-Input"
|
||||||
|
data-value=""
|
||||||
|
>
|
||||||
|
<input
|
||||||
|
aria-autocomplete="list"
|
||||||
|
aria-expanded="false"
|
||||||
|
aria-haspopup="true"
|
||||||
|
autocapitalize="none"
|
||||||
|
autocomplete="off"
|
||||||
|
autocorrect="off"
|
||||||
|
class="Select__input"
|
||||||
|
id="chart-version-input"
|
||||||
|
role="combobox"
|
||||||
|
spellcheck="false"
|
||||||
|
style="opacity: 1; width: 100%; grid-area: 1 / 2; min-width: 2px; border: 0px; margin: 0px; outline: 0; padding: 0px;"
|
||||||
|
tabindex="0"
|
||||||
|
type="text"
|
||||||
|
value=""
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
class="Select__indicators css-1hb7zxy-IndicatorsContainer"
|
||||||
|
>
|
||||||
|
<span
|
||||||
|
class="Select__indicator-separator css-1okebmr-indicatorSeparator"
|
||||||
|
/>
|
||||||
|
<div
|
||||||
|
aria-hidden="true"
|
||||||
|
class="Select__indicator Select__dropdown-indicator css-tlfecz-indicatorContainer"
|
||||||
|
>
|
||||||
|
<svg
|
||||||
|
aria-hidden="true"
|
||||||
|
class="css-tj5bde-Svg"
|
||||||
|
focusable="false"
|
||||||
|
height="20"
|
||||||
|
viewBox="0 0 20 20"
|
||||||
|
width="20"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
d="M4.516 7.548c0.436-0.446 1.043-0.481 1.576 0l3.908 3.747 3.908-3.747c0.533-0.481 1.141-0.446 1.574 0 0.436 0.445 0.408 1.197 0 1.615-0.406 0.418-4.695 4.502-4.695 4.502-0.217 0.223-0.502 0.335-0.787 0.335s-0.57-0.112-0.789-0.335c0 0-4.287-4.084-4.695-4.502s-0.436-1.17 0-1.615z"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
class="DrawerItem"
|
||||||
|
>
|
||||||
|
<span
|
||||||
|
class="name"
|
||||||
|
>
|
||||||
|
Home
|
||||||
|
</span>
|
||||||
|
<span
|
||||||
|
class="value"
|
||||||
|
>
|
||||||
|
<a
|
||||||
|
rel="noreferrer"
|
||||||
|
target="_blank"
|
||||||
|
/>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
class="DrawerItem maintainers"
|
||||||
|
>
|
||||||
|
<span
|
||||||
|
class="name"
|
||||||
|
>
|
||||||
|
Maintainers
|
||||||
|
</span>
|
||||||
|
<span
|
||||||
|
class="value"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
class="chart-description"
|
||||||
|
data-testid="helmchart-readme"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="MarkDownViewer"
|
||||||
|
>
|
||||||
|
<p>
|
||||||
|
I am a readme
|
||||||
|
</p>
|
||||||
|
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
class="ResizingAnchor horizontal leading"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</body>
|
||||||
|
`;
|
||||||
|
|
||||||
|
exports[`<HelmChartDetails /> before getChartDetails resolves with getChartDetails rejects renders 1`] = `
|
||||||
|
<body>
|
||||||
|
<div>
|
||||||
|
<div
|
||||||
|
class="Notifications flex column align-flex-end"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="Animate opacity notification flex error enter"
|
||||||
|
style="--enter-duration: 100ms; --leave-duration: 100ms;"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="box"
|
||||||
|
>
|
||||||
|
<i
|
||||||
|
class="Icon material focusable"
|
||||||
|
>
|
||||||
|
<span
|
||||||
|
class="icon"
|
||||||
|
data-icon-name="info_outline"
|
||||||
|
>
|
||||||
|
info_outline
|
||||||
|
</span>
|
||||||
|
</i>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
class="message box grow"
|
||||||
|
>
|
||||||
|
Error: some error
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
class="box"
|
||||||
|
>
|
||||||
|
<i
|
||||||
|
class="Icon close material interactive focusable"
|
||||||
|
data-testid="close-notification-for-notification_20"
|
||||||
|
tabindex="0"
|
||||||
|
>
|
||||||
|
<span
|
||||||
|
class="icon"
|
||||||
|
data-icon-name="close"
|
||||||
|
>
|
||||||
|
close
|
||||||
|
</span>
|
||||||
|
</i>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
class="Animate slide-right Drawer HelmChartDetails right enter"
|
||||||
|
style="--size: 725px; --enter-duration: 100ms; --leave-duration: 100ms;"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="drawer-wrapper flex column"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="drawer-title flex align-center"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="drawer-title-text flex gaps align-center"
|
||||||
|
>
|
||||||
|
Chart: a galaxy far far away/a name
|
||||||
|
<i
|
||||||
|
class="Icon material interactive focusable"
|
||||||
|
id="tooltip_target_18"
|
||||||
|
tabindex="0"
|
||||||
|
>
|
||||||
|
<span
|
||||||
|
class="icon"
|
||||||
|
data-icon-name="content_copy"
|
||||||
|
>
|
||||||
|
content_copy
|
||||||
|
</span>
|
||||||
|
<div />
|
||||||
|
</i>
|
||||||
|
</div>
|
||||||
|
<i
|
||||||
|
class="Icon material interactive focusable"
|
||||||
|
id="tooltip_target_19"
|
||||||
|
tabindex="0"
|
||||||
|
>
|
||||||
|
<span
|
||||||
|
class="icon"
|
||||||
|
data-icon-name="close"
|
||||||
|
>
|
||||||
|
close
|
||||||
|
</span>
|
||||||
|
<div />
|
||||||
|
</i>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
class="drawer-content flex column box grow"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="Spinner singleColor center"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
class="ResizingAnchor horizontal leading"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</body>
|
||||||
|
`;
|
||||||
@ -0,0 +1,17 @@
|
|||||||
|
/**
|
||||||
|
* 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 { GetChartDetailsOptions, IHelmChartDetails } from "../../../common/k8s-api/endpoints/helm-charts.api";
|
||||||
|
import { getChartDetails } from "../../../common/k8s-api/endpoints/helm-charts.api";
|
||||||
|
|
||||||
|
export type GetChartDetails = (repo: string, name: string, opts?: GetChartDetailsOptions) => Promise<IHelmChartDetails>;
|
||||||
|
|
||||||
|
const getChartDetailsInjectable = getInjectable({
|
||||||
|
id: "get-chart-details",
|
||||||
|
instantiate: (): GetChartDetails => getChartDetails,
|
||||||
|
causesSideEffects: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
export default getChartDetailsInjectable;
|
||||||
@ -0,0 +1,92 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||||
|
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import type { AsyncFnMock } from "@async-fn/jest";
|
||||||
|
import asyncFn from "@async-fn/jest";
|
||||||
|
import type { DiContainer } from "@ogre-tools/injectable";
|
||||||
|
import type { RenderResult } from "@testing-library/react";
|
||||||
|
import React from "react";
|
||||||
|
import directoryForLensLocalStorageInjectable from "../../../common/directory-for-lens-local-storage/directory-for-lens-local-storage.injectable";
|
||||||
|
import { HelmChart } from "../../../common/k8s-api/endpoints/helm-charts.api";
|
||||||
|
import { getDiForUnitTesting } from "../../getDiForUnitTesting";
|
||||||
|
import { noop } from "../../utils";
|
||||||
|
import type { CreateInstallChartTab } from "../dock/install-chart/create-install-chart-tab.injectable";
|
||||||
|
import createInstallChartTabInjectable from "../dock/install-chart/create-install-chart-tab.injectable";
|
||||||
|
import { Notifications } from "../notifications";
|
||||||
|
import type { DiRender } from "../test-utils/renderFor";
|
||||||
|
import { renderFor } from "../test-utils/renderFor";
|
||||||
|
import type { GetChartDetails } from "./get-char-details.injectable";
|
||||||
|
import getChartDetailsInjectable from "./get-char-details.injectable";
|
||||||
|
import { HelmChartDetails } from "./helm-chart-details";
|
||||||
|
|
||||||
|
describe("<HelmChartDetails />", () => {
|
||||||
|
let di: DiContainer;
|
||||||
|
let getChartDetails: AsyncFnMock<GetChartDetails>;
|
||||||
|
let chart: HelmChart;
|
||||||
|
let render: DiRender;
|
||||||
|
let result: RenderResult;
|
||||||
|
let createInstallChartTab: jest.MockedFunction<CreateInstallChartTab>;
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
di = getDiForUnitTesting({ doGeneralOverrides: true });
|
||||||
|
getChartDetails = asyncFn<GetChartDetails>();
|
||||||
|
createInstallChartTab = jest.fn();
|
||||||
|
chart = HelmChart.create({
|
||||||
|
apiVersion: "some-api-version",
|
||||||
|
created: "a long time ago",
|
||||||
|
name: "a name",
|
||||||
|
repo: "a galaxy far far away",
|
||||||
|
version: "1",
|
||||||
|
});
|
||||||
|
|
||||||
|
di.override(directoryForLensLocalStorageInjectable, () => "some-directory-for-lens-local-storage");
|
||||||
|
di.override(getChartDetailsInjectable, () => getChartDetails);
|
||||||
|
di.override(createInstallChartTabInjectable, () => createInstallChartTab);
|
||||||
|
render = renderFor(di);
|
||||||
|
result = render((
|
||||||
|
<>
|
||||||
|
<HelmChartDetails chart={chart} hideDetails={noop} />
|
||||||
|
<Notifications />
|
||||||
|
</>
|
||||||
|
));
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("before getChartDetails resolves", () => {
|
||||||
|
it("renders", () => {
|
||||||
|
expect(result.baseElement).toMatchSnapshot();
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("when getChartDetails resolves with one version", () => {
|
||||||
|
beforeEach(async () => {
|
||||||
|
await getChartDetails.resolve({
|
||||||
|
readme: "I am a readme",
|
||||||
|
versions: [chart],
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it("renders", () => {
|
||||||
|
expect(result.baseElement).toMatchSnapshot();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("shows the readme", () => {
|
||||||
|
expect(result.queryByTestId("helmchart-readme")).not.toBeNull();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("shows the selected chart", () => {
|
||||||
|
expect(result.queryByTestId("selected-chart-description")).not.toBeNull();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("with getChartDetails rejects", () => {
|
||||||
|
beforeEach(async () => {
|
||||||
|
await getChartDetails.reject(new Error("some error"));
|
||||||
|
});
|
||||||
|
|
||||||
|
it("renders", () => {
|
||||||
|
expect(result.baseElement).toMatchSnapshot();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
@ -7,7 +7,6 @@ import "./helm-chart-details.scss";
|
|||||||
|
|
||||||
import React, { Component } from "react";
|
import React, { Component } from "react";
|
||||||
import type { HelmChart } from "../../../common/k8s-api/endpoints/helm-charts.api";
|
import type { HelmChart } from "../../../common/k8s-api/endpoints/helm-charts.api";
|
||||||
import { getChartDetails } from "../../../common/k8s-api/endpoints/helm-charts.api";
|
|
||||||
import { computed, observable, reaction, runInAction } from "mobx";
|
import { computed, observable, reaction, runInAction } from "mobx";
|
||||||
import { disposeOnUnmount, observer } from "mobx-react";
|
import { disposeOnUnmount, observer } from "mobx-react";
|
||||||
import { Drawer, DrawerItem } from "../drawer";
|
import { Drawer, DrawerItem } from "../drawer";
|
||||||
@ -20,10 +19,13 @@ import { Badge } from "../badge";
|
|||||||
import { Tooltip, withStyles } from "@material-ui/core";
|
import { Tooltip, withStyles } from "@material-ui/core";
|
||||||
import { withInjectables } from "@ogre-tools/injectable-react";
|
import { withInjectables } from "@ogre-tools/injectable-react";
|
||||||
import createInstallChartTabInjectable from "../dock/install-chart/create-install-chart-tab.injectable";
|
import createInstallChartTabInjectable from "../dock/install-chart/create-install-chart-tab.injectable";
|
||||||
import { Notifications } from "../notifications";
|
import type { ShowCheckedErrorNotification } from "../notifications";
|
||||||
import HelmLogoPlaceholder from "./helm-placeholder.svg";
|
import HelmLogoPlaceholder from "./helm-placeholder.svg";
|
||||||
import type { SingleValue } from "react-select";
|
import type { SingleValue } from "react-select";
|
||||||
import { AbortController } from "abort-controller";
|
import AbortController from "abort-controller";
|
||||||
|
import showCheckedErrorNotificationInjectable from "../notifications/show-checked-error.injectable";
|
||||||
|
import type { GetChartDetails } from "./get-char-details.injectable";
|
||||||
|
import getChartDetailsInjectable from "./get-char-details.injectable";
|
||||||
|
|
||||||
export interface HelmChartDetailsProps {
|
export interface HelmChartDetailsProps {
|
||||||
chart: HelmChart;
|
chart: HelmChart;
|
||||||
@ -38,6 +40,8 @@ const LargeTooltip = withStyles({
|
|||||||
|
|
||||||
interface Dependencies {
|
interface Dependencies {
|
||||||
createInstallChartTab: (helmChart: HelmChart) => void;
|
createInstallChartTab: (helmChart: HelmChart) => void;
|
||||||
|
showCheckedErrorNotification: ShowCheckedErrorNotification;
|
||||||
|
getChartDetails: GetChartDetails;
|
||||||
}
|
}
|
||||||
|
|
||||||
@observer
|
@observer
|
||||||
@ -73,7 +77,7 @@ class NonInjectedHelmChartDetails extends Component<HelmChartDetailsProps & Depe
|
|||||||
});
|
});
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const { readme, versions } = await getChartDetails(repo, name, { version });
|
const { readme, versions } = await this.props.getChartDetails(repo, name, { version });
|
||||||
|
|
||||||
runInAction(() => {
|
runInAction(() => {
|
||||||
this.readme.set(readme);
|
this.readme.set(readme);
|
||||||
@ -81,7 +85,7 @@ class NonInjectedHelmChartDetails extends Component<HelmChartDetailsProps & Depe
|
|||||||
this.selectedChart.set(versions[0]);
|
this.selectedChart.set(versions[0]);
|
||||||
});
|
});
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
Notifications.checkedError(error, "Unknown error occured while getting chart details");
|
this.props.showCheckedErrorNotification(error, "Unknown error occured while getting chart details");
|
||||||
}
|
}
|
||||||
}, {
|
}, {
|
||||||
fireImmediately: true,
|
fireImmediately: true,
|
||||||
@ -101,11 +105,11 @@ class NonInjectedHelmChartDetails extends Component<HelmChartDetailsProps & Depe
|
|||||||
this.abortController.abort();
|
this.abortController.abort();
|
||||||
this.abortController = new AbortController();
|
this.abortController = new AbortController();
|
||||||
const { chart: { name, repo }} = this.props;
|
const { chart: { name, repo }} = this.props;
|
||||||
const { readme } = await getChartDetails(repo, name, { version: chart.version, reqInit: { signal: this.abortController.signal }});
|
const { readme } = await this.props.getChartDetails(repo, name, { version: chart.version, reqInit: { signal: this.abortController.signal }});
|
||||||
|
|
||||||
this.readme.set(readme);
|
this.readme.set(readme);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
Notifications.checkedError(error, "Unknown error occured while getting chart details");
|
this.props.showCheckedErrorNotification(error, "Unknown error occured while getting chart details");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -123,7 +127,7 @@ class NonInjectedHelmChartDetails extends Component<HelmChartDetailsProps & Depe
|
|||||||
onError={(event) => event.currentTarget.src = HelmLogoPlaceholder}
|
onError={(event) => event.currentTarget.src = HelmLogoPlaceholder}
|
||||||
/>
|
/>
|
||||||
<div className="intro-contents box grow">
|
<div className="intro-contents box grow">
|
||||||
<div className="description flex align-center justify-space-between">
|
<div className="description flex align-center justify-space-between" data-testid="selected-chart-description">
|
||||||
{selectedChart.getDescription()}
|
{selectedChart.getDescription()}
|
||||||
<Button
|
<Button
|
||||||
primary
|
primary
|
||||||
@ -194,7 +198,7 @@ class NonInjectedHelmChartDetails extends Component<HelmChartDetailsProps & Depe
|
|||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="chart-description">
|
<div className="chart-description" data-testid="helmchart-readme">
|
||||||
<MarkdownViewer markdown={readme} />
|
<MarkdownViewer markdown={readme} />
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
@ -232,13 +236,11 @@ class NonInjectedHelmChartDetails extends Component<HelmChartDetailsProps & Depe
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export const HelmChartDetails = withInjectables<Dependencies, HelmChartDetailsProps>(
|
export const HelmChartDetails = withInjectables<Dependencies, HelmChartDetailsProps>(NonInjectedHelmChartDetails, {
|
||||||
NonInjectedHelmChartDetails,
|
|
||||||
|
|
||||||
{
|
|
||||||
getProps: (di, props) => ({
|
getProps: (di, props) => ({
|
||||||
createInstallChartTab: di.inject(createInstallChartTabInjectable),
|
|
||||||
...props,
|
...props,
|
||||||
|
createInstallChartTab: di.inject(createInstallChartTabInjectable),
|
||||||
|
showCheckedErrorNotification: di.inject(showCheckedErrorNotificationInjectable),
|
||||||
|
getChartDetails: di.inject(getChartDetailsInjectable),
|
||||||
}),
|
}),
|
||||||
},
|
});
|
||||||
);
|
|
||||||
|
|||||||
@ -9,9 +9,7 @@ import type {
|
|||||||
DockTab,
|
DockTab,
|
||||||
DockTabCreate,
|
DockTabCreate,
|
||||||
DockTabCreateSpecific } from "../dock/store";
|
DockTabCreateSpecific } from "../dock/store";
|
||||||
import {
|
import { TabKind } from "../dock/store";
|
||||||
TabKind,
|
|
||||||
} from "../dock/store";
|
|
||||||
import type { InstallChartTabStore } from "./store";
|
import type { InstallChartTabStore } from "./store";
|
||||||
import createDockTabInjectable from "../dock/create-dock-tab.injectable";
|
import createDockTabInjectable from "../dock/create-dock-tab.injectable";
|
||||||
|
|
||||||
@ -20,6 +18,8 @@ interface Dependencies {
|
|||||||
installChartStore: InstallChartTabStore;
|
installChartStore: InstallChartTabStore;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export type CreateInstallChartTab = (chart: HelmChart, tabParams?: DockTabCreateSpecific) => DockTab;
|
||||||
|
|
||||||
const createInstallChartTab = ({ createDockTab, installChartStore }: Dependencies) => (chart: HelmChart, tabParams: DockTabCreateSpecific = {}) => {
|
const createInstallChartTab = ({ createDockTab, installChartStore }: Dependencies) => (chart: HelmChart, tabParams: DockTabCreateSpecific = {}) => {
|
||||||
const { name, repo, version } = chart;
|
const { name, repo, version } = chart;
|
||||||
|
|
||||||
|
|||||||
@ -5,6 +5,7 @@
|
|||||||
|
|
||||||
import type React from "react";
|
import type React from "react";
|
||||||
import { action, observable, makeObservable } from "mobx";
|
import { action, observable, makeObservable } from "mobx";
|
||||||
|
import type { Disposer } from "../../utils";
|
||||||
import { autoBind } from "../../utils";
|
import { autoBind } from "../../utils";
|
||||||
import uniqueId from "lodash/uniqueId";
|
import uniqueId from "lodash/uniqueId";
|
||||||
import type { JsonApiErrorParsed } from "../../../common/k8s-api/json-api";
|
import type { JsonApiErrorParsed } from "../../../common/k8s-api/json-api";
|
||||||
@ -19,6 +20,15 @@ export enum NotificationStatus {
|
|||||||
INFO = "info",
|
INFO = "info",
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export type ShowNotification = (message: NotificationMessage, opts?: CreateNotificationOptions) => Disposer;
|
||||||
|
export type ShowCheckedErrorNotification = (message: unknown, fallback: string, opts?: CreateNotificationOptions) => Disposer;
|
||||||
|
|
||||||
|
export interface CreateNotificationOptions {
|
||||||
|
id?: NotificationId;
|
||||||
|
timeout?: number;
|
||||||
|
onClose?(): void;
|
||||||
|
}
|
||||||
|
|
||||||
export interface Notification {
|
export interface Notification {
|
||||||
id?: NotificationId;
|
id?: NotificationId;
|
||||||
message: NotificationMessage;
|
message: NotificationMessage;
|
||||||
|
|||||||
@ -10,13 +10,17 @@ import { reaction } from "mobx";
|
|||||||
import { disposeOnUnmount, observer } from "mobx-react";
|
import { disposeOnUnmount, observer } from "mobx-react";
|
||||||
import { JsonApiErrorParsed } from "../../../common/k8s-api/json-api";
|
import { JsonApiErrorParsed } from "../../../common/k8s-api/json-api";
|
||||||
import { cssNames, prevDefault } from "../../utils";
|
import { cssNames, prevDefault } from "../../utils";
|
||||||
import type { Notification, NotificationMessage, NotificationsStore } from "./notifications.store";
|
import type { Notification, NotificationsStore, ShowCheckedErrorNotification, ShowNotification } from "./notifications.store";
|
||||||
import { NotificationStatus } from "./notifications.store";
|
|
||||||
import { Animate } from "../animate";
|
import { Animate } from "../animate";
|
||||||
import { Icon } from "../icon";
|
import { Icon } from "../icon";
|
||||||
import { withInjectables } from "@ogre-tools/injectable-react";
|
import { withInjectables } from "@ogre-tools/injectable-react";
|
||||||
import { asLegacyGlobalForExtensionApi } from "../../../extensions/as-legacy-globals-for-extension-api/as-legacy-global-object-for-extension-api";
|
|
||||||
import notificationsStoreInjectable from "./notifications-store.injectable";
|
import notificationsStoreInjectable from "./notifications-store.injectable";
|
||||||
|
import { asLegacyGlobalFunctionForExtensionApi } from "../../../extensions/as-legacy-globals-for-extension-api/as-legacy-global-function-for-extension-api";
|
||||||
|
import showSuccessNotificationInjectable from "./show-success-notification.injectable";
|
||||||
|
import showCheckedErrorNotificationInjectable from "./show-checked-error.injectable";
|
||||||
|
import showErrorNotificationInjectable from "./show-error-notification.injectable";
|
||||||
|
import showInfoNotificationInjectable from "./show-info-notification.injectable";
|
||||||
|
import showShortInfoNotificationInjectable from "./show-short-info.injectable";
|
||||||
|
|
||||||
interface Dependencies {
|
interface Dependencies {
|
||||||
store: NotificationsStore;
|
store: NotificationsStore;
|
||||||
@ -103,58 +107,16 @@ export const Notifications = withInjectables<Dependencies>(
|
|||||||
}),
|
}),
|
||||||
},
|
},
|
||||||
) as React.FC & {
|
) as React.FC & {
|
||||||
ok: (message: NotificationMessage) => () => void;
|
ok: ShowNotification;
|
||||||
checkedError: (message: unknown, fallback: string, customOpts?: Partial<Omit<Notification, "message">>) => () => void;
|
checkedError: ShowCheckedErrorNotification;
|
||||||
error: (message: NotificationMessage, customOpts?: Partial<Omit<Notification, "message">>) => () => void;
|
error: ShowNotification;
|
||||||
shortInfo: (message: NotificationMessage, customOpts?: Partial<Omit<Notification, "message">>) => () => void;
|
shortInfo: ShowNotification;
|
||||||
info: (message: NotificationMessage, customOpts?: Partial<Omit<Notification, "message">>) => () => void;
|
info: ShowNotification;
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
Notifications.ok = asLegacyGlobalFunctionForExtensionApi(showSuccessNotificationInjectable);
|
||||||
* @deprecated
|
Notifications.error = asLegacyGlobalFunctionForExtensionApi(showErrorNotificationInjectable);
|
||||||
*/
|
Notifications.checkedError = asLegacyGlobalFunctionForExtensionApi(showCheckedErrorNotificationInjectable);
|
||||||
const _notificationStore = asLegacyGlobalForExtensionApi(notificationsStoreInjectable);
|
Notifications.info = asLegacyGlobalFunctionForExtensionApi(showInfoNotificationInjectable);
|
||||||
|
Notifications.shortInfo = asLegacyGlobalFunctionForExtensionApi(showShortInfoNotificationInjectable);
|
||||||
Notifications.ok = (message: NotificationMessage) => {
|
|
||||||
return _notificationStore.add({
|
|
||||||
message,
|
|
||||||
timeout: 2_500,
|
|
||||||
status: NotificationStatus.OK,
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
Notifications.checkedError = (message, fallback, customOpts = {}) => {
|
|
||||||
if (typeof message === "string" || message instanceof Error || message instanceof JsonApiErrorParsed) {
|
|
||||||
return Notifications.error(message, customOpts);
|
|
||||||
}
|
|
||||||
|
|
||||||
console.warn("Unknown notification error message, falling back to default", message);
|
|
||||||
|
|
||||||
return Notifications.error(fallback, customOpts);
|
|
||||||
};
|
|
||||||
|
|
||||||
Notifications.error = (message, customOpts= {}) => {
|
|
||||||
return _notificationStore.add({
|
|
||||||
message,
|
|
||||||
timeout: 10_000,
|
|
||||||
status: NotificationStatus.ERROR,
|
|
||||||
...customOpts,
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
Notifications.shortInfo = (message, customOpts = {}) => {
|
|
||||||
return Notifications.info(message, {
|
|
||||||
timeout: 5_000,
|
|
||||||
...customOpts,
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
Notifications.info = (message, customOpts = {}) => {
|
|
||||||
return _notificationStore.add({
|
|
||||||
status: NotificationStatus.INFO,
|
|
||||||
timeout: 0,
|
|
||||||
message,
|
|
||||||
...customOpts,
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|||||||
@ -0,0 +1,27 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||||
|
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||||
|
*/
|
||||||
|
import { getInjectable } from "@ogre-tools/injectable";
|
||||||
|
import { JsonApiErrorParsed } from "../../../common/k8s-api/json-api";
|
||||||
|
import type { ShowCheckedErrorNotification } from "./notifications.store";
|
||||||
|
import showErrorNotificationInjectable from "./show-error-notification.injectable";
|
||||||
|
|
||||||
|
const showCheckedErrorNotificationInjectable = getInjectable({
|
||||||
|
id: "show-checked-error-notififcation",
|
||||||
|
instantiate: (di): ShowCheckedErrorNotification => {
|
||||||
|
const showErrorNotification = di.inject(showErrorNotificationInjectable);
|
||||||
|
|
||||||
|
return (message, fallback, opts) => {
|
||||||
|
if (typeof message === "string" || message instanceof Error || message instanceof JsonApiErrorParsed) {
|
||||||
|
return showErrorNotification(message, opts);
|
||||||
|
}
|
||||||
|
|
||||||
|
console.warn("Unknown notification error message, falling back to default", message);
|
||||||
|
|
||||||
|
return showErrorNotification(fallback, opts);
|
||||||
|
};
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
export default showCheckedErrorNotificationInjectable;
|
||||||
@ -3,17 +3,17 @@
|
|||||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||||
*/
|
*/
|
||||||
import { getInjectable } from "@ogre-tools/injectable";
|
import { getInjectable } from "@ogre-tools/injectable";
|
||||||
import type { NotificationMessage, Notification } from "./notifications.store";
|
import type { ShowNotification } from "./notifications.store";
|
||||||
import { NotificationStatus } from "./notifications.store";
|
import { NotificationStatus } from "./notifications.store";
|
||||||
import notificationsStoreInjectable from "./notifications-store.injectable";
|
import notificationsStoreInjectable from "./notifications-store.injectable";
|
||||||
|
|
||||||
const showErrorNotificationInjectable = getInjectable({
|
const showErrorNotificationInjectable = getInjectable({
|
||||||
id: "show-error-notification",
|
id: "show-error-notification",
|
||||||
|
|
||||||
instantiate: (di) => {
|
instantiate: (di): ShowNotification => {
|
||||||
const notificationsStore = di.inject(notificationsStoreInjectable);
|
const notificationsStore = di.inject(notificationsStoreInjectable);
|
||||||
|
|
||||||
return (message: NotificationMessage, customOpts: Partial<Omit<Notification, "message">> = {}) =>
|
return (message, customOpts = {}) =>
|
||||||
notificationsStore.add({
|
notificationsStore.add({
|
||||||
status: NotificationStatus.ERROR,
|
status: NotificationStatus.ERROR,
|
||||||
timeout: 5000,
|
timeout: 5000,
|
||||||
|
|||||||
@ -3,17 +3,17 @@
|
|||||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||||
*/
|
*/
|
||||||
import { getInjectable } from "@ogre-tools/injectable";
|
import { getInjectable } from "@ogre-tools/injectable";
|
||||||
import type { NotificationMessage, Notification } from "./notifications.store";
|
import type { ShowNotification } from "./notifications.store";
|
||||||
import { NotificationStatus } from "./notifications.store";
|
import { NotificationStatus } from "./notifications.store";
|
||||||
import notificationsStoreInjectable from "./notifications-store.injectable";
|
import notificationsStoreInjectable from "./notifications-store.injectable";
|
||||||
|
|
||||||
const showInfoNotificationInjectable = getInjectable({
|
const showInfoNotificationInjectable = getInjectable({
|
||||||
id: "show-info-notification",
|
id: "show-info-notification",
|
||||||
|
|
||||||
instantiate: (di) => {
|
instantiate: (di): ShowNotification => {
|
||||||
const notificationsStore = di.inject(notificationsStoreInjectable);
|
const notificationsStore = di.inject(notificationsStoreInjectable);
|
||||||
|
|
||||||
return (message: NotificationMessage, customOpts: Partial<Omit<Notification, "message">> = {}) =>
|
return (message, customOpts = {}) =>
|
||||||
notificationsStore.add({
|
notificationsStore.add({
|
||||||
status: NotificationStatus.INFO,
|
status: NotificationStatus.INFO,
|
||||||
timeout: 5000,
|
timeout: 5000,
|
||||||
|
|||||||
@ -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 { ShowNotification } from "./notifications.store";
|
||||||
|
import showInfoNotificationInjectable from "./show-info-notification.injectable";
|
||||||
|
|
||||||
|
const showShortInfoNotificationInjectable = getInjectable({
|
||||||
|
id: "show-short-info-notification",
|
||||||
|
instantiate: (di): ShowNotification => {
|
||||||
|
const showInfoNotification = di.inject(showInfoNotificationInjectable);
|
||||||
|
|
||||||
|
return (message, customOpts = {}) => {
|
||||||
|
return showInfoNotification(message, {
|
||||||
|
timeout: 5_000,
|
||||||
|
...customOpts,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
export default showShortInfoNotificationInjectable;
|
||||||
@ -3,17 +3,17 @@
|
|||||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||||
*/
|
*/
|
||||||
import { getInjectable } from "@ogre-tools/injectable";
|
import { getInjectable } from "@ogre-tools/injectable";
|
||||||
import type { NotificationMessage, Notification } from "./notifications.store";
|
import type { ShowNotification } from "./notifications.store";
|
||||||
import { NotificationStatus } from "./notifications.store";
|
import { NotificationStatus } from "./notifications.store";
|
||||||
import notificationsStoreInjectable from "./notifications-store.injectable";
|
import notificationsStoreInjectable from "./notifications-store.injectable";
|
||||||
|
|
||||||
const showSuccessNotificationInjectable = getInjectable({
|
const showSuccessNotificationInjectable = getInjectable({
|
||||||
id: "show-success-notification",
|
id: "show-success-notification",
|
||||||
|
|
||||||
instantiate: (di) => {
|
instantiate: (di): ShowNotification => {
|
||||||
const notificationsStore = di.inject(notificationsStoreInjectable);
|
const notificationsStore = di.inject(notificationsStoreInjectable);
|
||||||
|
|
||||||
return (message: NotificationMessage, customOpts: Partial<Omit<Notification, "message">> = {}) =>
|
return (message, customOpts = {}) =>
|
||||||
notificationsStore.add({
|
notificationsStore.add({
|
||||||
status: NotificationStatus.OK,
|
status: NotificationStatus.OK,
|
||||||
timeout: 5000,
|
timeout: 5000,
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user