From d81449b2a31be85592a4cb6a4e8417a464b540b7 Mon Sep 17 00:00:00 2001 From: Janne Savolainen Date: Thu, 20 Jan 2022 11:44:26 +0200 Subject: [PATCH] Make data required for opening of release details a dependency to make sure it's present Co-authored-by: Mikko Aspiala Signed-off-by: Janne Savolainen --- .../release-details.injectable.ts | 21 ++ .../release-details.scss | 2 +- .../{ => release-details}/release-details.tsx | 194 +++++++----------- .../release-route-parameters.injectable.ts | 31 +++ .../release-values.injectable.ts | 30 +++ .../release-details/release.injectable.ts | 26 +++ ...er-supplied-values-are-shown.injectable.ts | 27 +++ .../components/+apps-releases/releases.tsx | 20 +- 8 files changed, 215 insertions(+), 136 deletions(-) create mode 100644 src/renderer/components/+apps-releases/release-details/release-details.injectable.ts rename src/renderer/components/+apps-releases/{ => release-details}/release-details.scss (97%) rename src/renderer/components/+apps-releases/{ => release-details}/release-details.tsx (52%) create mode 100644 src/renderer/components/+apps-releases/release-details/release-route-parameters.injectable.ts create mode 100644 src/renderer/components/+apps-releases/release-details/release-values.injectable.ts create mode 100644 src/renderer/components/+apps-releases/release-details/release.injectable.ts create mode 100644 src/renderer/components/+apps-releases/release-details/user-supplied-values-are-shown.injectable.ts diff --git a/src/renderer/components/+apps-releases/release-details/release-details.injectable.ts b/src/renderer/components/+apps-releases/release-details/release-details.injectable.ts new file mode 100644 index 0000000000..1dac3b716f --- /dev/null +++ b/src/renderer/components/+apps-releases/release-details/release-details.injectable.ts @@ -0,0 +1,21 @@ +/** + * Copyright (c) OpenLens Authors. All rights reserved. + * Licensed under MIT License. See LICENSE in root directory for more information. + */ +import { getInjectable, lifecycleEnum } from "@ogre-tools/injectable"; +import { getRelease } from "../../../../common/k8s-api/endpoints/helm-releases.api"; +import { asyncComputed } from "@ogre-tools/injectable-react"; +import releaseInjectable from "./release.injectable"; + +const releaseDetailsInjectable = getInjectable({ + instantiate: (di) => + asyncComputed(async () => { + const release = di.inject(releaseInjectable).value.get(); + + return await getRelease(release.name, release.namespace); + }), + + lifecycle: lifecycleEnum.singleton, +}); + +export default releaseDetailsInjectable; diff --git a/src/renderer/components/+apps-releases/release-details.scss b/src/renderer/components/+apps-releases/release-details/release-details.scss similarity index 97% rename from src/renderer/components/+apps-releases/release-details.scss rename to src/renderer/components/+apps-releases/release-details/release-details.scss index 20ce0a2cef..0a4acfcca3 100644 --- a/src/renderer/components/+apps-releases/release-details.scss +++ b/src/renderer/components/+apps-releases/release-details/release-details.scss @@ -3,7 +3,7 @@ * Licensed under MIT License. See LICENSE in root directory for more information. */ -@import "release.mixins"; +@import "../release.mixins"; .ReleaseDetails { .DrawerItem { diff --git a/src/renderer/components/+apps-releases/release-details.tsx b/src/renderer/components/+apps-releases/release-details/release-details.tsx similarity index 52% rename from src/renderer/components/+apps-releases/release-details.tsx rename to src/renderer/components/+apps-releases/release-details/release-details.tsx index 4d4a56a601..c69b967a6f 100644 --- a/src/renderer/components/+apps-releases/release-details.tsx +++ b/src/renderer/components/+apps-releases/release-details/release-details.tsx @@ -7,126 +7,79 @@ import "./release-details.scss"; import React, { Component } from "react"; import groupBy from "lodash/groupBy"; -import isEqual from "lodash/isEqual"; -import { makeObservable, observable, reaction } from "mobx"; +import { computed, makeObservable, observable } from "mobx"; import { Link } from "react-router-dom"; import kebabCase from "lodash/kebabCase"; -import { - getRelease, - getReleaseValues, - HelmRelease, - IReleaseDetails, IReleaseUpdateDetails, - IReleaseUpdatePayload, -} from "../../../common/k8s-api/endpoints/helm-releases.api"; -import { HelmReleaseMenu } from "./release-menu"; -import { Drawer, DrawerItem, DrawerTitle } from "../drawer"; -import { Badge } from "../badge"; -import { cssNames, stopPropagation } from "../../utils"; -import { disposeOnUnmount, observer } from "mobx-react"; -import { Spinner } from "../spinner"; -import { Table, TableCell, TableHead, TableRow } from "../table"; -import { Button } from "../button"; -import { Notifications } from "../notifications"; -import { ThemeStore } from "../../theme.store"; -import { apiManager } from "../../../common/k8s-api/api-manager"; -import { SubTitle } from "../layout/sub-title"; -import { secretsStore } from "../+config-secrets/secrets.store"; -import { Secret } from "../../../common/k8s-api/endpoints"; -import { getDetailsUrl } from "../kube-detail-params"; -import { Checkbox } from "../checkbox"; -import { MonacoEditor } from "../monaco-editor"; -import { withInjectables } from "@ogre-tools/injectable-react"; -import createUpgradeChartTabInjectable - from "../dock/create-upgrade-chart-tab/create-upgrade-chart-tab.injectable"; -import updateReleaseInjectable from "./update-release/update-release.injectable"; -import getReleaseSecretInjectable from "./get-release-secret/get-release-secret.injectable"; +import type { HelmRelease, IReleaseDetails, IReleaseUpdateDetails, IReleaseUpdatePayload } from "../../../../common/k8s-api/endpoints/helm-releases.api"; +import { HelmReleaseMenu } from "../release-menu"; +import { Drawer, DrawerItem, DrawerTitle } from "../../drawer"; +import { Badge } from "../../badge"; +import { cssNames, stopPropagation } from "../../../utils"; +import { observer } from "mobx-react"; +import { Spinner } from "../../spinner"; +import { Table, TableCell, TableHead, TableRow } from "../../table"; +import { Button } from "../../button"; +import { Notifications } from "../../notifications"; +import { ThemeStore } from "../../../theme.store"; +import { apiManager } from "../../../../common/k8s-api/api-manager"; +import { SubTitle } from "../../layout/sub-title"; +import { getDetailsUrl } from "../../kube-detail-params"; +import { Checkbox } from "../../checkbox"; +import { MonacoEditor } from "../../monaco-editor"; +import { IAsyncComputed, withInjectables } from "@ogre-tools/injectable-react"; +import createUpgradeChartTabInjectable from "../../dock/create-upgrade-chart-tab/create-upgrade-chart-tab.injectable"; +import updateReleaseInjectable from "../update-release/update-release.injectable"; +import releaseInjectable from "./release.injectable"; +import releaseDetailsInjectable from "./release-details.injectable"; +import releaseValuesInjectable from "./release-values.injectable"; +import userSuppliedValuesAreShownInjectable from "./user-supplied-values-are-shown.injectable"; interface Props { - release: HelmRelease; hideDetails(): void; } interface Dependencies { - getReleaseSecret: (release: HelmRelease) => Secret + release: IAsyncComputed + releaseDetails: IAsyncComputed + releaseValues: IAsyncComputed updateRelease: (name: string, namespace: string, payload: IReleaseUpdatePayload) => Promise createUpgradeChartTab: (release: HelmRelease) => void + userSuppliedValuesAreShown: { toggle: () => void, value: boolean } } @observer class NonInjectedReleaseDetails extends Component { - @observable details: IReleaseDetails | null = null; - @observable values = ""; - @observable valuesLoading = false; - @observable showOnlyUserSuppliedValues = true; + @observable saving = false; - @observable releaseSecret: Secret; @observable error?: string = undefined; - componentDidMount() { - disposeOnUnmount(this, [ - reaction(() => this.props.release, release => { - if (!release) return; - this.loadDetails(); - this.loadValues(); - this.releaseSecret = null; - }), - reaction(() => secretsStore.getItems(), () => { - if (!this.props.release) return; - - const { release } = this.props; - const secret = this.props.getReleaseSecret(release); - - if (this.releaseSecret) { - if (isEqual(this.releaseSecret.getLabels(), secret.getLabels())) return; - this.loadDetails(); - } - this.releaseSecret = secret; - }), - reaction(() => this.showOnlyUserSuppliedValues, () => { - this.loadValues(); - }), - ]); - } + private nonSavedValues: string; constructor(props: Props & Dependencies) { super(props); makeObservable(this); } - async loadDetails() { - const { release } = this.props; - - try { - this.details = null; - this.details = await getRelease(release.getName(), release.getNs()); - } catch (error) { - this.error = `Failed to get release details: ${error}`; - } + @computed get release() { + return this.props.release.value.get(); } - async loadValues() { - const { release } = this.props; + @computed get details() { + return this.props.releaseDetails.value.get(); + } - try { - this.valuesLoading = true; - this.values = (await getReleaseValues(release.getName(), release.getNs(), !this.showOnlyUserSuppliedValues)) ?? ""; - } catch (error) { - Notifications.error(`Failed to load values for ${release.getName()}: ${error}`); - this.values = ""; - } finally { - this.valuesLoading = false; - } + @computed get values() { + return this.props.releaseValues.value.get(); } updateValues = async () => { - const { release } = this.props; - const name = release.getName(); - const namespace = release.getNs(); + const name = this.release.getName(); + const namespace = this.release.getNs(); const data = { - chart: release.getChart(), - repo: await release.getRepo(), - version: release.getVersion(), - values: this.values, + chart: this.release.getChart(), + repo: await this.release.getRepo(), + version: this.release.getVersion(), + values: this.nonSavedValues, }; this.saving = true; @@ -136,6 +89,8 @@ class NonInjectedReleaseDetails extends Component { Notifications.ok(

Release {name} successfully updated!

, ); + + this.props.releaseValues.invalidate(); } catch (err) { Notifications.error(err); } @@ -143,14 +98,18 @@ class NonInjectedReleaseDetails extends Component { }; upgradeVersion = () => { - const { release, hideDetails } = this.props; + const { hideDetails } = this.props; - this.props.createUpgradeChartTab(release); + this.props.createUpgradeChartTab(this.release); hideDetails(); }; renderValues() { - const { values, valuesLoading, saving } = this; + const { saving } = this; + + const releaseValuesArePending = this.props.releaseValues.pending.get(); + + this.nonSavedValues = this.values; return (
@@ -158,24 +117,20 @@ class NonInjectedReleaseDetails extends Component {
this.showOnlyUserSuppliedValues = value} - disabled={valuesLoading} + value={this.props.userSuppliedValuesAreShown.value} + onChange={this.props.userSuppliedValuesAreShown.toggle} + disabled={releaseValuesArePending} /> this.values = text} - > - {valuesLoading && } - + value={this.values} + onChange={text => this.nonSavedValues = text} + />
@@ -238,9 +193,7 @@ class NonInjectedReleaseDetails extends Component { } renderContent() { - const { release } = this.props; - - if (!release) return null; + if (!this.release) return null; if (this.error) { return ( @@ -258,7 +211,7 @@ class NonInjectedReleaseDetails extends Component {
- {release.getChart()} + {this.release.getChart()}
- {release.getUpdated()} ago ({release.updated}) + {this.release.getUpdated()} ago ({this.release.updated}) - {release.getNs()} + {this.release.getNs()}
- {release.getVersion()} + {this.release.getVersion()}
{this.renderValues()} @@ -296,15 +249,15 @@ class NonInjectedReleaseDetails extends Component { } render() { - const { release, hideDetails } = this.props; - const title = release ? `Release: ${release.getName()}` : ""; - const toolbar = ; + const { hideDetails } = this.props; + const title = this.release ? `Release: ${this.release.getName()}` : ""; + const toolbar = ; return ( ( { getProps: (di, props) => ({ - getReleaseSecret: di.inject(getReleaseSecretInjectable), + release: di.inject(releaseInjectable), + releaseDetails: di.inject(releaseDetailsInjectable), + releaseValues: di.inject(releaseValuesInjectable), + + userSuppliedValuesAreShown: di.inject(userSuppliedValuesAreShownInjectable), + updateRelease: di.inject(updateReleaseInjectable), createUpgradeChartTab: di.inject(createUpgradeChartTabInjectable), ...props, diff --git a/src/renderer/components/+apps-releases/release-details/release-route-parameters.injectable.ts b/src/renderer/components/+apps-releases/release-details/release-route-parameters.injectable.ts new file mode 100644 index 0000000000..d179c539be --- /dev/null +++ b/src/renderer/components/+apps-releases/release-details/release-route-parameters.injectable.ts @@ -0,0 +1,31 @@ +/** + * Copyright (c) OpenLens Authors. All rights reserved. + * Licensed under MIT License. See LICENSE in root directory for more information. + */ +import { getInjectable, lifecycleEnum } from "@ogre-tools/injectable"; +import { computed } from "mobx"; +import { matchPath } from "react-router"; +import observableHistoryInjectable from "../../../navigation/observable-history.injectable"; +import { releaseRoute, ReleaseRouteParams } from "../../../../common/routes"; + +const releaseRouteParametersInjectable = getInjectable({ + instantiate: (di) => { + const observableHistory = di.inject(observableHistoryInjectable); + + return computed(() => { + const releasePathParameters = matchPath(observableHistory.location.pathname, { + path: releaseRoute.path, + }); + + if (!releasePathParameters) { + return {}; + } + + return releasePathParameters.params; + }); + }, + + lifecycle: lifecycleEnum.singleton, +}); + +export default releaseRouteParametersInjectable; diff --git a/src/renderer/components/+apps-releases/release-details/release-values.injectable.ts b/src/renderer/components/+apps-releases/release-details/release-values.injectable.ts new file mode 100644 index 0000000000..d7cc2c965c --- /dev/null +++ b/src/renderer/components/+apps-releases/release-details/release-values.injectable.ts @@ -0,0 +1,30 @@ +/** + * Copyright (c) OpenLens Authors. All rights reserved. + * Licensed under MIT License. See LICENSE in root directory for more information. + */ +import { getInjectable, lifecycleEnum } from "@ogre-tools/injectable"; +import { getReleaseValues } from "../../../../common/k8s-api/endpoints/helm-releases.api"; +import { asyncComputed } from "@ogre-tools/injectable-react"; +import releaseInjectable from "./release.injectable"; +import { Notifications } from "../../notifications"; +import userSuppliedValuesAreShownInjectable from "./user-supplied-values-are-shown.injectable"; + +const releaseValuesInjectable = getInjectable({ + instantiate: (di) => + asyncComputed(async () => { + const release = di.inject(releaseInjectable).value.get(); + const userSuppliedValuesAreShown = di.inject(userSuppliedValuesAreShownInjectable).value; + + try { + return await getReleaseValues(release.getName(), release.getNs(), !userSuppliedValuesAreShown) ?? ""; + } catch (error) { + Notifications.error(`Failed to load values for ${release.getName()}: ${error}`); + + return ""; + } + }), + + lifecycle: lifecycleEnum.singleton, +}); + +export default releaseValuesInjectable; diff --git a/src/renderer/components/+apps-releases/release-details/release.injectable.ts b/src/renderer/components/+apps-releases/release-details/release.injectable.ts new file mode 100644 index 0000000000..3319a39bc9 --- /dev/null +++ b/src/renderer/components/+apps-releases/release-details/release.injectable.ts @@ -0,0 +1,26 @@ +/** + * Copyright (c) OpenLens Authors. All rights reserved. + * Licensed under MIT License. See LICENSE in root directory for more information. + */ +import { getInjectable, lifecycleEnum } from "@ogre-tools/injectable"; +import { matches } from "lodash/fp"; +import releasesInjectable from "../releases.injectable"; +import releaseRouteParametersInjectable from "./release-route-parameters.injectable"; +import { asyncComputed } from "@ogre-tools/injectable-react"; + +const releaseInjectable = getInjectable({ + instantiate: (di) => { + const releases = di.inject(releasesInjectable); + const releaseRouteParameters = di.inject(releaseRouteParametersInjectable); + + return asyncComputed(async () => { + const { name, namespace } = releaseRouteParameters.get(); + + return releases.value.get().find(matches({ name, namespace })); + }); + }, + + lifecycle: lifecycleEnum.singleton, +}); + +export default releaseInjectable; diff --git a/src/renderer/components/+apps-releases/release-details/user-supplied-values-are-shown.injectable.ts b/src/renderer/components/+apps-releases/release-details/user-supplied-values-are-shown.injectable.ts new file mode 100644 index 0000000000..374f982882 --- /dev/null +++ b/src/renderer/components/+apps-releases/release-details/user-supplied-values-are-shown.injectable.ts @@ -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, lifecycleEnum } from "@ogre-tools/injectable"; +import { observable } from "mobx"; + +const userSuppliedValuesAreShownInjectable = getInjectable({ + instantiate: () => { + const state = observable.box(false); + + return { + get value() { + return state.get(); + }, + + toggle: () => { + state.set(!state.get()); + }, + }; + }, + + lifecycle: lifecycleEnum.singleton, +}); + +export default userSuppliedValuesAreShownInjectable; + diff --git a/src/renderer/components/+apps-releases/releases.tsx b/src/renderer/components/+apps-releases/releases.tsx index 98f68df173..a71cc8e7a1 100644 --- a/src/renderer/components/+apps-releases/releases.tsx +++ b/src/renderer/components/+apps-releases/releases.tsx @@ -20,7 +20,7 @@ import { kebabCase } from "lodash/fp"; import { HelmReleaseMenu } from "./release-menu"; import type { ItemStore } from "../../../common/item.store"; import { ReleaseRollbackDialog } from "./release-rollback-dialog"; -import { ReleaseDetails } from "./release-details"; +import { ReleaseDetails } from "./release-details/release-details"; import removableReleasesInjectable from "./removable-releases.injectable"; import type { RemovableHelmRelease } from "./removable-releases"; import { observer } from "mobx-react"; @@ -58,20 +58,8 @@ class NonInjectedHelmReleases extends Component { } } - get selectedRelease() { - const { match: { params: { name, namespace }}} = this.props; - - return this.props.releases.get().find(release => { - return release.getName() == name && release.getNs() == namespace; - }); - } - onDetails = (item: HelmRelease) => { - if (item === this.selectedRelease) { - this.hideDetails(); - } else { - this.showDetails(item); - } + this.showDetails(item); }; showDetails = (item: HelmRelease) => { @@ -210,12 +198,10 @@ class NonInjectedHelmReleases extends Component { customizeRemoveDialog={selectedItems => ({ message: this.renderRemoveDialogMessage(selectedItems), })} - detailsItem={this.selectedRelease} onDetails={this.onDetails} /> - +