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

Reorganize code to get rid of orphan promise

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-08-19 10:45:20 +03:00 committed by Sebastian Malton
parent 2e6178760f
commit 2120915bf3
10 changed files with 10222 additions and 9999 deletions

View File

@ -288,15 +288,42 @@ describe("showing details for helm release", () => {
});
});
it("when release resolve with no data, renders", async () => {
await callForHelmReleaseMock.resolve(undefined);
expect(rendered.baseElement).toMatchSnapshot();
describe("when call for release resolves with error", () => {
beforeEach(async () => {
await callForHelmReleaseMock.resolve({
callWasSuccessful: false,
error: "some-error",
});
});
it("renders", async () => {
expect(rendered.baseElement).toMatchSnapshot();
});
it("does not show spinner anymore", () => {
expect(
rendered.queryByTestId("helm-release-detail-content-spinner"),
).not.toBeInTheDocument();
});
it("shows error message about missing release", () => {
expect(
rendered.getByTestId("helm-release-detail-error"),
).toBeInTheDocument();
});
it("does not call for release configuration", () => {
expect(callForHelmReleaseConfigurationMock).not.toHaveBeenCalled();
});
});
describe("when details resolve", () => {
describe("when call for release resolve with release", () => {
beforeEach(async () => {
await callForHelmReleaseMock.resolve(detailedReleaseFake);
await callForHelmReleaseMock.resolve({
callWasSuccessful: true,
response: detailedReleaseFake,
});
});
it("renders", () => {

View File

@ -8,8 +8,8 @@ import "./release-details.scss";
import React from "react";
import { Link } from "react-router-dom";
import { Drawer, DrawerItem, DrawerTitle } from "../../drawer";
import { cssNames, stopPropagation } from "../../../utils";
import { DrawerItem, DrawerTitle } from "../../drawer";
import { stopPropagation } from "../../../utils";
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";
@ -20,7 +20,6 @@ import { Badge } from "../../badge";
import { SubTitle } from "../../layout/sub-title";
import { Table, TableCell, TableHead, TableRow } from "../../table";
import { ReactiveDuration } from "../../duration/reactive-duration";
import { HelmReleaseMenu } from "../release-menu";
import { Checkbox } from "../../checkbox";
import { MonacoEditor } from "../../monaco-editor";
import { Spinner } from "../../spinner";
@ -36,96 +35,85 @@ interface Dependencies {
const NonInjectedReleaseDetailsContent = observer(({ model }: Dependencies & ReleaseDetailsContentProps) => {
const isLoading = model.isLoading.get();
const failedToLoad = model.failedToLoad.get();
if (failedToLoad) {
return <div data-testid="helm-release-detail-error">Failed to load release</div>;
}
return (
<Drawer
className={cssNames("ReleaseDetails", model.activeTheme)}
usePortal={true}
open={true}
title={isLoading ? "" : model.release.getName()}
onClose={model.close}
testIdForClose="close-helm-release-detail"
toolbar={
!isLoading && (
<HelmReleaseMenu
release={model.release}
toolbar
hideDetails={model.close}
/>
)
}
data-testid={`helm-release-details-for-${model.id}`}
>
{isLoading ? (
<Spinner center data-testid="helm-release-detail-content-spinner" />
) : (
<div>
<DrawerItem name="Chart" className="chart">
<div className="flex gaps align-center">
<span>{model.release.chart}</span>
(isLoading ? (
<Spinner center data-testid="helm-release-detail-content-spinner" />
) : (
<div>
<DrawerItem name="Chart" className="chart">
<div className="flex gaps align-center">
<span>{model.release.chart}</span>
<Button
primary
label="Upgrade"
className="box right upgrade"
onClick={model.startUpgradeProcess}
data-testid="helm-release-upgrade-button"
/>
</div>
</DrawerItem>
<DrawerItem name="Updated">
{model.release.getUpdated()}
{` ago (${model.release.updated})`}
</DrawerItem>
<DrawerItem name="Namespace">{model.release.getNs()}</DrawerItem>
<DrawerItem name="Version" onClick={stopPropagation}>
<div className="version flex gaps align-center">
<span>{model.release.getVersion()}</span>
</div>
</DrawerItem>
<DrawerItem
name="Status"
className="status"
labelsOnly>
<Badge
label={model.release.getStatus()}
className={kebabCase(model.release.getStatus())}
<Button
primary
label="Upgrade"
className="box right upgrade"
onClick={model.startUpgradeProcess}
data-testid="helm-release-upgrade-button"
/>
</DrawerItem>
</div>
</DrawerItem>
<ReleaseValues
configuration={model.configuration}
onlyUserSuppliedValuesAreShown={
model.onlyUserSuppliedValuesAreShown
}
<DrawerItem name="Updated">
{model.release.getUpdated()}
{` ago (${model.release.updated})`}
</DrawerItem>
<DrawerItem name="Namespace">{model.release.getNs()}</DrawerItem>
<DrawerItem name="Version" onClick={stopPropagation}>
<div className="version flex gaps align-center">
<span>{model.release.getVersion()}</span>
</div>
</DrawerItem>
<DrawerItem
name="Status"
className="status"
labelsOnly>
<Badge
label={model.release.getStatus()}
className={kebabCase(model.release.getStatus())}
/>
</DrawerItem>
<DrawerTitle>Notes</DrawerTitle>
<ReleaseValues
configuration={model.configuration}
onlyUserSuppliedValuesAreShown={
model.onlyUserSuppliedValuesAreShown
}
/>
{model.notes && <div className="notes">{model.notes}</div>}
<DrawerTitle>Notes</DrawerTitle>
<DrawerTitle>Resources</DrawerTitle>
{model.notes && <div className="notes">{model.notes}</div>}
{model.groupedResources.length > 0 && (
<div className="resources">
{model.groupedResources.map((group) => (
<ResourceGroup key={group.kind} group={group} />
))}
</div>
)}
</div>
)}
</Drawer>
<DrawerTitle>Resources</DrawerTitle>
{model.groupedResources.length > 0 && (
<div className="resources">
{model.groupedResources.map((group) => (
<ResourceGroup key={group.kind} group={group} />
))}
</div>
)}
</div>
))
);
});
export const ReleaseDetailsContent = withInjectables<Dependencies, ReleaseDetailsContentProps>(NonInjectedReleaseDetailsContent, {
getProps: (di, props) => ({
model: di.inject(releaseDetailsModelInjectable, props.targetRelease),
getPlaceholder: () => <Spinner center data-testid="helm-release-detail-content-spinner" />,
getProps: async (di, props) => ({
model: await di.inject(releaseDetailsModelInjectable, props.targetRelease),
...props,
}),
});

View File

@ -0,0 +1,49 @@
/**
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import "./release-details.scss";
import React from "react";
import { observer } from "mobx-react";
import { withInjectables } from "@ogre-tools/injectable-react";
import type { TargetHelmRelease } from "./target-helm-release.injectable";
import navigateToHelmReleasesInjectable from "../../../../common/front-end-routing/routes/cluster/helm/releases/navigate-to-helm-releases.injectable";
import type { ReleaseDetailsModel } from "./release-details-model/release-details-model.injectable";
import releaseDetailsModelInjectable from "./release-details-model/release-details-model.injectable";
import { HelmReleaseMenu } from "../release-menu";
interface ReleaseDetailsDrawerProps {
targetRelease: TargetHelmRelease;
}
interface Dependencies {
model: ReleaseDetailsModel;
closeDrawer: () => void;
}
const NonInjectedReleaseDetailsDrawerToolbar = observer(
({ model, closeDrawer }: Dependencies & ReleaseDetailsDrawerProps) =>
model.failedToLoad.get() ? null : (
<HelmReleaseMenu
release={model.release}
toolbar
hideDetails={closeDrawer}
/>
),
);
export const ReleaseDetailsDrawerToolbar = withInjectables<
Dependencies,
ReleaseDetailsDrawerProps
>(NonInjectedReleaseDetailsDrawerToolbar, {
getPlaceholder: () => <></>,
getProps: async (di, props) => ({
model: await di.inject(releaseDetailsModelInjectable, props.targetRelease),
closeDrawer: di.inject(navigateToHelmReleasesInjectable),
...props,
}),
});

View File

@ -0,0 +1,58 @@
/**
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import "./release-details.scss";
import React from "react";
import { Drawer } from "../../drawer";
import { cssNames } from "../../../utils";
import { observer } from "mobx-react";
import { withInjectables } from "@ogre-tools/injectable-react";
import type { TargetHelmRelease } from "./target-helm-release.injectable";
import type { ActiveThemeType } from "../../../themes/active-type.injectable";
import activeThemeTypeInjectable from "../../../themes/active-type.injectable";
import { ReleaseDetailsContent } from "./release-details-content";
import navigateToHelmReleasesInjectable from "../../../../common/front-end-routing/routes/cluster/helm/releases/navigate-to-helm-releases.injectable";
import { ReleaseDetailsDrawerToolbar } from "./release-details-drawer-toolbar";
interface ReleaseDetailsDrawerProps {
targetRelease: TargetHelmRelease;
}
interface Dependencies {
activeThemeType: ActiveThemeType;
closeDrawer: () => void;
}
const NonInjectedReleaseDetailsDrawer = observer(({
activeThemeType,
closeDrawer,
targetRelease,
}: Dependencies & ReleaseDetailsDrawerProps) => (
<Drawer
className={cssNames("ReleaseDetails", activeThemeType.get())}
usePortal={true}
open={true}
title={targetRelease.name}
onClose={closeDrawer}
testIdForClose="close-helm-release-detail"
toolbar={<ReleaseDetailsDrawerToolbar targetRelease={targetRelease} />}
data-testid={`helm-release-details-for-${targetRelease.namespace}/${targetRelease.name}`}
>
<ReleaseDetailsContent targetRelease={targetRelease} />
</Drawer>
));
export const ReleaseDetailsDrawer = withInjectables<
Dependencies,
ReleaseDetailsDrawerProps
>(NonInjectedReleaseDetailsDrawer, {
getProps: (di, props) => ({
activeThemeType: di.inject(activeThemeTypeInjectable),
closeDrawer: di.inject(navigateToHelmReleasesInjectable),
...props,
}),
});

View File

@ -7,6 +7,7 @@ import type { HelmReleaseDto } from "../../../../../../common/k8s-api/endpoints/
import callForHelmReleasesInjectable from "../../../call-for-helm-releases/call-for-helm-releases.injectable";
import type { HelmReleaseDetails } from "./call-for-helm-release-details/call-for-helm-release-details.injectable";
import callForHelmReleaseDetailsInjectable from "./call-for-helm-release-details/call-for-helm-release-details.injectable";
import type { AsyncResult } from "../../../../../../common/utils/async-result";
export interface DetailedHelmRelease {
release: HelmReleaseDto;
@ -16,7 +17,7 @@ export interface DetailedHelmRelease {
export type CallForHelmRelease = (
name: string,
namespace: string
) => Promise<DetailedHelmRelease | undefined>;
) => Promise<AsyncResult<DetailedHelmRelease | undefined>>;
const callForHelmReleaseInjectable = getInjectable({
id: "call-for-helm-release",
@ -36,10 +37,13 @@ const callForHelmReleaseInjectable = getInjectable({
);
if (!release) {
return undefined;
return {
callWasSuccessful: false,
error: `Release ${name} didn't exist in ${namespace} namespace.`,
};
}
return { release, details };
return { callWasSuccessful: true, response: { release, details }};
};
},
});

View File

@ -26,16 +26,14 @@ import showSuccessNotificationInjectable from "../../../notifications/show-succe
import React from "react";
import createUpgradeChartTabInjectable from "../../../dock/upgrade-chart/create-upgrade-chart-tab.injectable";
import type { HelmRelease } from "../../../../../common/k8s-api/endpoints/helm-releases.api";
import type { NavigateToHelmReleases } from "../../../../../common/front-end-routing/routes/cluster/helm/releases/navigate-to-helm-releases.injectable";
import navigateToHelmReleasesInjectable from "../../../../../common/front-end-routing/routes/cluster/helm/releases/navigate-to-helm-releases.injectable";
import assert from "assert";
import withOrphanPromiseInjectable from "../../../../../common/utils/with-orphan-promise/with-orphan-promise.injectable";
import activeThemeInjectable from "../../../../themes/active.injectable";
const releaseDetailsModelInjectable = getInjectable({
id: "release-details-model",
instantiate: (di, targetRelease: TargetHelmRelease) => {
instantiate: async (di, targetRelease: TargetHelmRelease) => {
const callForHelmRelease = di.inject(callForHelmReleaseInjectable);
const callForHelmReleaseConfiguration = di.inject(callForHelmReleaseConfigurationInjectable);
const activeTheme = di.inject(activeThemeInjectable);
@ -45,7 +43,6 @@ const releaseDetailsModelInjectable = getInjectable({
const showSuccessNotification = di.inject(showSuccessNotificationInjectable);
const createUpgradeChartTab = di.inject(createUpgradeChartTabInjectable);
const navigateToHelmReleases = di.inject(navigateToHelmReleasesInjectable);
const withOrphanPromise = di.inject(withOrphanPromiseInjectable);
const model = new ReleaseDetailsModel({
callForHelmRelease,
@ -57,13 +54,10 @@ const releaseDetailsModelInjectable = getInjectable({
showCheckedErrorNotification,
showSuccessNotification,
createUpgradeChartTab,
navigateToHelmReleases,
closeDrawer: navigateToHelmReleases,
});
const load = withOrphanPromise(model.load);
// TODO: Reorganize Drawer to allow setting of header-bar in children to make "getPlaceholder" from injectable usable.
load();
await model.load();
return model;
},
@ -99,7 +93,7 @@ interface Dependencies {
showCheckedErrorNotification: ShowCheckedErrorNotification;
showSuccessNotification: ShowNotification;
createUpgradeChartTab: (release: HelmRelease) => string;
navigateToHelmReleases: NavigateToHelmReleases;
closeDrawer: () => void;
}
export class ReleaseDetailsModel {
@ -110,6 +104,7 @@ export class ReleaseDetailsModel {
private detailedRelease = observable.box<DetailedHelmRelease | undefined>();
readonly isLoading = observable.box(false);
readonly failedToLoad = observable.box(false);
readonly configuration: ConfigurationInput = {
nonSavedValue: observable.box(""),
@ -182,13 +177,21 @@ export class ReleaseDetailsModel {
const { name, namespace } = this.dependencies.targetRelease;
const detailedRelease = await this.dependencies.callForHelmRelease(
const result = await this.dependencies.callForHelmRelease(
name,
namespace,
);
if (!result.callWasSuccessful) {
runInAction(() => {
this.failedToLoad.set(true);
});
return;
}
runInAction(() => {
this.detailedRelease.set(detailedRelease);
this.detailedRelease.set(result.response);
});
await this.loadConfiguration();
@ -263,13 +266,13 @@ export class ReleaseDetailsModel {
}
close = () => {
this.dependencies.navigateToHelmReleases();
this.dependencies.closeDrawer();
};
startUpgradeProcess = () => {
this.dependencies.createUpgradeChartTab(this.release);
this.close();
this.dependencies.closeDrawer();
};
}

View File

@ -11,9 +11,9 @@ import { observer } from "mobx-react";
import { withInjectables } from "@ogre-tools/injectable-react";
import type { IComputedValue } from "mobx";
import { ReleaseDetailsContent } from "./release-details-content";
import type { TargetHelmRelease } from "./target-helm-release.injectable";
import targetHelmReleaseInjectable from "./target-helm-release.injectable";
import { ReleaseDetailsDrawer } from "./release-details-drawer";
interface Dependencies {
targetRelease: IComputedValue<
@ -25,7 +25,7 @@ const NonInjectedReleaseDetails = observer(
({ targetRelease }: Dependencies) => {
const release = targetRelease.get();
return release ? <ReleaseDetailsContent targetRelease={release} /> : null;
return release ? <ReleaseDetailsDrawer targetRelease={release} /> : null;
},
);

View File

@ -0,0 +1,23 @@
/**
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import { getInjectable } from "@ogre-tools/injectable";
import type { IComputedValue } from "mobx";
import { computed } from "mobx";
import type { LensThemeType } from "./store";
import themeStoreInjectable from "./store.injectable";
export type ActiveThemeType = IComputedValue<LensThemeType>;
const activeThemeTypeInjectable = getInjectable({
id: "active-theme-type",
instantiate: (di) => {
const store = di.inject(themeStoreInjectable);
return computed(() => store.activeTheme.type);
},
});
export default activeThemeTypeInjectable;

View File

@ -17,10 +17,10 @@ import type { ReadonlyDeep } from "type-fest/source/readonly-deep";
import assert from "assert";
export type ThemeId = string;
export type LensThemeType = "dark" | "light";
export interface LensTheme {
name: string;
type: "dark" | "light";
type: LensThemeType;
colors: Record<string, string>;
description: string;
author: string;