mirror of
https://github.com/lensapp/lens.git
synced 2025-05-20 05:10:56 +00:00
Fix showing-details-for-helm-release behavioural tests
- Remove HelmChartStore in favour of all injectables - Create a model for UpgradeChartDockTab Signed-off-by: Sebastian Malton <sebastian@malton.name>
This commit is contained in:
parent
4bef236f8c
commit
042b679ca6
@ -434,14 +434,8 @@ exports[`opening dock tab for installing helm chart given application is started
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="NoItems flex box grow"
|
||||
>
|
||||
<div
|
||||
class="box center"
|
||||
>
|
||||
Item list is empty
|
||||
</div>
|
||||
</div>
|
||||
class="Spinner singleColor center"
|
||||
/>
|
||||
</div>
|
||||
<div
|
||||
class="AddRemoveButtons flex gaps"
|
||||
|
||||
@ -12295,8 +12295,147 @@ exports[`showing details for helm release given application is started when navi
|
||||
style="flex-basis: 300px;"
|
||||
>
|
||||
<div
|
||||
class="Spinner singleColor center"
|
||||
/>
|
||||
class="UpgradeChart flex column"
|
||||
>
|
||||
<div
|
||||
class="InfoPanel flex gaps align-center"
|
||||
>
|
||||
<div
|
||||
class="controls"
|
||||
>
|
||||
<div
|
||||
class="upgrade flex gaps align-center"
|
||||
>
|
||||
<span>
|
||||
Release
|
||||
</span>
|
||||
|
||||
<div
|
||||
class="badge"
|
||||
>
|
||||
some-name
|
||||
</div>
|
||||
<span>
|
||||
Namespace
|
||||
</span>
|
||||
|
||||
<div
|
||||
class="badge"
|
||||
>
|
||||
some-namespace
|
||||
</div>
|
||||
<span>
|
||||
Version
|
||||
</span>
|
||||
|
||||
<div
|
||||
class="badge"
|
||||
/>
|
||||
<span>
|
||||
Upgrade version
|
||||
</span>
|
||||
<div
|
||||
class="Select theme-outlined chart-version css-b62m3t-container"
|
||||
>
|
||||
<span
|
||||
class="css-1f43avz-a11yText-A11yText"
|
||||
id="react-select-char-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 css-319lph-ValueContainer"
|
||||
>
|
||||
<div
|
||||
class="Select__placeholder css-14el2xx-placeholder"
|
||||
id="react-select-char-version-input-placeholder"
|
||||
>
|
||||
Select...
|
||||
</div>
|
||||
<div
|
||||
class="Select__input-container css-6j8wv5-Input"
|
||||
data-value=""
|
||||
>
|
||||
<input
|
||||
aria-autocomplete="list"
|
||||
aria-describedby="react-select-char-version-input-placeholder"
|
||||
aria-expanded="false"
|
||||
aria-haspopup="true"
|
||||
autocapitalize="none"
|
||||
autocomplete="off"
|
||||
autocorrect="off"
|
||||
class="Select__input"
|
||||
id="char-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>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="flex gaps align-center"
|
||||
/>
|
||||
<button
|
||||
class="Button plain"
|
||||
type="button"
|
||||
>
|
||||
Cancel
|
||||
</button>
|
||||
<button
|
||||
class="Button active outlined"
|
||||
type="button"
|
||||
>
|
||||
Upgrade
|
||||
</button>
|
||||
<button
|
||||
class="Button primary active"
|
||||
type="button"
|
||||
>
|
||||
Upgrade & Close
|
||||
</button>
|
||||
</div>
|
||||
<textarea
|
||||
data-testid="monaco-editor-for-some-tab-id"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@ -23,6 +23,14 @@ import requestHelmReleaseInjectable from "../../renderer/components/+helm-releas
|
||||
import showSuccessNotificationInjectable from "../../renderer/components/notifications/show-success-notification.injectable";
|
||||
import showCheckedErrorInjectable from "../../renderer/components/notifications/show-checked-error.injectable";
|
||||
import getRandomUpgradeChartTabIdInjectable from "../../renderer/components/dock/upgrade-chart/get-random-upgrade-chart-tab-id.injectable";
|
||||
import type { RequestHelmCharts } from "../../common/k8s-api/endpoints/helm-charts.api/list.injectable";
|
||||
import type { RequestHelmChartVersions } from "../../common/k8s-api/endpoints/helm-charts.api/get-versions.injectable";
|
||||
import type { RequestHelmChartReadme } from "../../common/k8s-api/endpoints/helm-charts.api/get-readme.injectable";
|
||||
import type { RequestHelmChartValues } from "../../common/k8s-api/endpoints/helm-charts.api/get-values.injectable";
|
||||
import requestHelmChartsInjectable from "../../common/k8s-api/endpoints/helm-charts.api/list.injectable";
|
||||
import requestHelmChartVersionsInjectable from "../../common/k8s-api/endpoints/helm-charts.api/get-versions.injectable";
|
||||
import requestHelmChartReadmeInjectable from "../../common/k8s-api/endpoints/helm-charts.api/get-readme.injectable";
|
||||
import requestHelmChartValuesInjectable from "../../common/k8s-api/endpoints/helm-charts.api/get-values.injectable";
|
||||
|
||||
describe("showing details for helm release", () => {
|
||||
let builder: ApplicationBuilder;
|
||||
@ -30,6 +38,10 @@ describe("showing details for helm release", () => {
|
||||
let requestHelmReleaseMock: AsyncFnMock<RequestHelmRelease>;
|
||||
let requestHelmReleaseConfigurationMock: AsyncFnMock<RequestHelmReleaseConfiguration>;
|
||||
let requestHelmReleaseUpdateMock: AsyncFnMock<RequestHelmReleaseUpdate>;
|
||||
let requestHelmChartsMock: AsyncFnMock<RequestHelmCharts>;
|
||||
let requestHelmChartVersionsMock: AsyncFnMock<RequestHelmChartVersions>;
|
||||
let requestHelmChartReadmeMock: AsyncFnMock<RequestHelmChartReadme>;
|
||||
let requestHelmChartValuesMock: AsyncFnMock<RequestHelmChartValues>;
|
||||
let showSuccessNotificationMock: jest.Mock;
|
||||
let showCheckedErrorNotificationMock: jest.Mock;
|
||||
|
||||
@ -44,6 +56,10 @@ describe("showing details for helm release", () => {
|
||||
requestHelmReleaseMock = asyncFn();
|
||||
requestHelmReleaseConfigurationMock = asyncFn();
|
||||
requestHelmReleaseUpdateMock = asyncFn();
|
||||
requestHelmChartsMock = asyncFn();
|
||||
requestHelmChartVersionsMock = asyncFn();
|
||||
requestHelmChartReadmeMock = asyncFn();
|
||||
requestHelmChartValuesMock = asyncFn();
|
||||
|
||||
showSuccessNotificationMock = jest.fn();
|
||||
showCheckedErrorNotificationMock = jest.fn();
|
||||
@ -56,6 +72,10 @@ describe("showing details for helm release", () => {
|
||||
windowDi.override(requestHelmReleaseInjectable, () => requestHelmReleaseMock);
|
||||
windowDi.override(requestHelmReleaseConfigurationInjectable, () => requestHelmReleaseConfigurationMock);
|
||||
windowDi.override(requestHelmReleaseUpdateInjectable, () => requestHelmReleaseUpdateMock);
|
||||
windowDi.override(requestHelmChartsInjectable, () => requestHelmChartsMock);
|
||||
windowDi.override(requestHelmChartVersionsInjectable, () => requestHelmChartVersionsMock);
|
||||
windowDi.override(requestHelmChartReadmeInjectable, () => requestHelmChartReadmeMock);
|
||||
windowDi.override(requestHelmChartValuesInjectable, () => requestHelmChartValuesMock);
|
||||
windowDi.override(
|
||||
namespaceStoreInjectable,
|
||||
() =>
|
||||
@ -449,6 +469,16 @@ describe("showing details for helm release", () => {
|
||||
expect(rendered.baseElement).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it("shows spinner", () => {
|
||||
const saveButton = rendered.getByTestId(
|
||||
"helm-release-configuration-save-button",
|
||||
);
|
||||
|
||||
expect(saveButton).toHaveClass("waiting");
|
||||
});
|
||||
|
||||
|
||||
|
||||
it("calls for update", () => {
|
||||
expect(requestHelmReleaseUpdateMock).toHaveBeenCalledWith(
|
||||
"some-name",
|
||||
@ -463,14 +493,6 @@ describe("showing details for helm release", () => {
|
||||
);
|
||||
});
|
||||
|
||||
it("shows spinner", () => {
|
||||
const saveButton = rendered.getByTestId(
|
||||
"helm-release-configuration-save-button",
|
||||
);
|
||||
|
||||
expect(saveButton).toHaveClass("waiting");
|
||||
});
|
||||
|
||||
describe("when update resolves with success", () => {
|
||||
beforeEach(async () => {
|
||||
requestHelmReleasesMock.mockClear();
|
||||
|
||||
@ -0,0 +1,11 @@
|
||||
/**
|
||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
|
||||
import { getGlobalOverride } from "../../../common/test-utils/get-global-override";
|
||||
import getHelmChartReadmeInjectable from "./get-helm-chart-readme.injectable";
|
||||
|
||||
export default getGlobalOverride(getHelmChartReadmeInjectable, () => () => {
|
||||
throw new Error("tried to get a helm chart's readme without overriding");
|
||||
});
|
||||
@ -20,8 +20,7 @@ import navigateToHelmChartsInjectable from "../../../common/front-end-routing/ro
|
||||
import { HelmChartIcon } from "./icon";
|
||||
import helmChartsInjectable from "./helm-charts/helm-charts.injectable";
|
||||
import selectedHelmChartInjectable from "./helm-charts/selected-helm-chart.injectable";
|
||||
import type { HelmChartStore } from "./store";
|
||||
import helmChartStoreInjectable from "./store.injectable";
|
||||
import { noop } from "lodash";
|
||||
|
||||
enum columnId {
|
||||
name = "name",
|
||||
@ -39,7 +38,6 @@ interface Dependencies {
|
||||
navigateToHelmCharts: NavigateToHelmCharts;
|
||||
charts: IAsyncComputed<HelmChart[]>;
|
||||
selectedChart: IComputedValue<HelmChart | undefined>;
|
||||
helmChartStore: HelmChartStore;
|
||||
}
|
||||
|
||||
@observer
|
||||
@ -69,17 +67,31 @@ class NonInjectedHelmCharts extends Component<Dependencies> {
|
||||
};
|
||||
|
||||
render() {
|
||||
const { charts } = this.props;
|
||||
const selectedChart = this.props.selectedChart.get();
|
||||
|
||||
return (
|
||||
<SiblingsInTabLayout>
|
||||
<div data-testid="page-for-helm-charts" style={{ display: "none" }}/>
|
||||
|
||||
<ItemListLayout
|
||||
<ItemListLayout<HelmChart, false>
|
||||
isConfigurable
|
||||
tableId="helm_charts"
|
||||
className="HelmCharts"
|
||||
store={this.props.helmChartStore}
|
||||
store={{
|
||||
get isLoaded() {
|
||||
return !charts.pending.get();
|
||||
},
|
||||
failedLoading: false,
|
||||
getTotalCount: () => charts.value.get().length,
|
||||
isSelected: (item) => item === selectedChart,
|
||||
toggleSelection: noop,
|
||||
isSelectedAll: () => false,
|
||||
toggleSelectionAll: () => false,
|
||||
pickOnlySelected: () => [],
|
||||
removeSelectedItems: async () => {},
|
||||
}}
|
||||
preloadStores={false}
|
||||
getItems={() => this.props.charts.value.get()}
|
||||
isSelectable={false}
|
||||
sortingCallbacks={{
|
||||
@ -138,8 +150,6 @@ export const HelmCharts = withInjectables<Dependencies>(NonInjectedHelmCharts, {
|
||||
navigateToHelmCharts: di.inject(navigateToHelmChartsInjectable),
|
||||
charts: di.inject(helmChartsInjectable),
|
||||
selectedChart: di.inject(selectedHelmChartInjectable),
|
||||
helmChartStore: di.inject(helmChartStoreInjectable),
|
||||
}),
|
||||
},
|
||||
);
|
||||
});
|
||||
|
||||
|
||||
@ -0,0 +1,47 @@
|
||||
/**
|
||||
* 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 { asyncComputed } from "@ogre-tools/injectable-react";
|
||||
import { coerce } from "semver";
|
||||
import requestHelmChartVersionsInjectable from "../../../../common/k8s-api/endpoints/helm-charts.api/get-versions.injectable";
|
||||
import type { HelmRelease } from "../../../../common/k8s-api/endpoints/helm-releases.api";
|
||||
import { sortCompareChartVersions } from "../../../utils";
|
||||
import helmChartsInjectable from "./helm-charts.injectable";
|
||||
|
||||
export interface ChartVersion {
|
||||
repo: string;
|
||||
version: string;
|
||||
}
|
||||
|
||||
const sortChartVersions = (versions: ChartVersion[]) => (
|
||||
versions
|
||||
.map(chartVersion => ({ ...chartVersion, __version: coerce(chartVersion.version, { loose: true }) }))
|
||||
.sort(sortCompareChartVersions)
|
||||
.map(({ __version, ...chartVersion }) => chartVersion)
|
||||
);
|
||||
|
||||
const helmChartVersionsInjectable = getInjectable({
|
||||
id: "helm-chart-versions-loader",
|
||||
instantiate: (di, release) => {
|
||||
const requestHelmChartVersions = di.inject(requestHelmChartVersionsInjectable);
|
||||
const helmCharts = di.inject(helmChartsInjectable);
|
||||
|
||||
return asyncComputed(async () => {
|
||||
const rawVersions = await Promise.all((
|
||||
helmCharts.value.get()
|
||||
.filter(chart => chart.getName() === release.getChart())
|
||||
.map(chart => chart.getRepository())
|
||||
.map(repo => requestHelmChartVersions(repo, release.getChart()))
|
||||
));
|
||||
|
||||
return sortChartVersions(rawVersions.flat());
|
||||
}, []);
|
||||
},
|
||||
lifecycle: lifecycleEnum.keyedSingleton({
|
||||
getInstanceKey: (di, release: HelmRelease) => release.getName(),
|
||||
}),
|
||||
});
|
||||
|
||||
export default helmChartVersionsInjectable;
|
||||
@ -1,18 +0,0 @@
|
||||
/**
|
||||
* 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 requestHelmChartVersionsInjectable from "../../../common/k8s-api/endpoints/helm-charts.api/get-versions.injectable";
|
||||
import requestHelmChartsInjectable from "../../../common/k8s-api/endpoints/helm-charts.api/list.injectable";
|
||||
import { HelmChartStore } from "./store";
|
||||
|
||||
const helmChartStoreInjectable = getInjectable({
|
||||
id: "helm-chart-store",
|
||||
instantiate: (di) => new HelmChartStore({
|
||||
requestHelmCharts: di.inject(requestHelmChartsInjectable),
|
||||
requestHelmChartVersions: di.inject(requestHelmChartVersionsInjectable),
|
||||
}),
|
||||
});
|
||||
|
||||
export default helmChartStoreInjectable;
|
||||
@ -1,112 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
|
||||
import semver from "semver";
|
||||
import { observable, makeObservable } from "mobx";
|
||||
import { autoBind, sortCompareChartVersions } from "../../utils";
|
||||
import type { HelmChart } from "../../../common/k8s-api/endpoints/helm-charts.api";
|
||||
import { ItemStore } from "../../../common/item.store";
|
||||
import flatten from "lodash/flatten";
|
||||
import type { RequestHelmCharts } from "../../../common/k8s-api/endpoints/helm-charts.api/list.injectable";
|
||||
import type { RequestHelmChartVersions } from "../../../common/k8s-api/endpoints/helm-charts.api/get-versions.injectable";
|
||||
|
||||
export interface ChartVersion {
|
||||
repo: string;
|
||||
version: string;
|
||||
}
|
||||
|
||||
interface Dependencies {
|
||||
requestHelmCharts: RequestHelmCharts;
|
||||
requestHelmChartVersions: RequestHelmChartVersions;
|
||||
}
|
||||
|
||||
export class HelmChartStore extends ItemStore<HelmChart> {
|
||||
@observable versions = observable.map<string, ChartVersion[]>();
|
||||
|
||||
constructor(protected readonly dependencies: Dependencies) {
|
||||
super();
|
||||
|
||||
makeObservable(this);
|
||||
autoBind(this);
|
||||
}
|
||||
|
||||
async loadAll() {
|
||||
try {
|
||||
const res = await this.loadItems(() => this.dependencies.requestHelmCharts());
|
||||
|
||||
this.failedLoading = false;
|
||||
|
||||
return res;
|
||||
} catch (error) {
|
||||
this.failedLoading = true;
|
||||
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
getByName(name: string, repo?: string) {
|
||||
if (typeof repo !== "string") {
|
||||
/**
|
||||
* FIXME:
|
||||
* This is here because in strict mode `getByName` MUST be 100% compatiable if called in the
|
||||
* situation where it is only a "ItemStore"
|
||||
*/
|
||||
throw new TypeError("repo must be provided");
|
||||
}
|
||||
|
||||
return this.items.find(chart => chart.getName() === name && chart.getRepository() === repo);
|
||||
}
|
||||
|
||||
protected sortVersions = (versions: ChartVersion[]) => {
|
||||
return versions
|
||||
.map(chartVersion => ({ ...chartVersion, __version: semver.coerce(chartVersion.version, { loose: true }) }))
|
||||
.sort(sortCompareChartVersions)
|
||||
.map(({ __version, ...chartVersion }) => chartVersion);
|
||||
};
|
||||
|
||||
async getVersions(chartName: string, force?: boolean): Promise<ChartVersion[]> {
|
||||
const versions = this.versions.get(chartName);
|
||||
|
||||
if (versions && !force) {
|
||||
return versions;
|
||||
}
|
||||
|
||||
const loadVersions = async (repo: string) => {
|
||||
const versions = await this.dependencies.requestHelmChartVersions(repo, chartName);
|
||||
|
||||
return versions.map(chart => ({
|
||||
repo,
|
||||
version: chart.getVersion(),
|
||||
}));
|
||||
};
|
||||
|
||||
if (!this.isLoaded) {
|
||||
await this.loadAll();
|
||||
}
|
||||
const repos = this.items
|
||||
.filter(chart => chart.getName() === chartName)
|
||||
.map(chart => chart.getRepository());
|
||||
|
||||
const newVersions = await Promise.all(repos.map(loadVersions))
|
||||
.then(flatten)
|
||||
.then(this.sortVersions);
|
||||
|
||||
this.versions.set(chartName, newVersions);
|
||||
|
||||
return newVersions;
|
||||
}
|
||||
|
||||
reset() {
|
||||
super.reset();
|
||||
this.versions.clear();
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated Not supported
|
||||
*/
|
||||
removeItems(): Promise<void> {
|
||||
throw new Error("removeItems is not supported");
|
||||
}
|
||||
}
|
||||
@ -4,7 +4,8 @@
|
||||
*/
|
||||
import { getInjectable } from "@ogre-tools/injectable";
|
||||
import { capitalize } from "lodash";
|
||||
import helmChartStoreInjectable from "../+helm-charts/store.injectable";
|
||||
import { when } from "mobx";
|
||||
import helmChartVersionsInjectable from "../+helm-charts/helm-charts/versions.injectable";
|
||||
import type { HelmRelease, HelmReleaseDto } from "../../../common/k8s-api/endpoints/helm-releases.api";
|
||||
import { formatDuration } from "../../utils";
|
||||
|
||||
@ -13,7 +14,7 @@ export type ToHelmRelease = (release: HelmReleaseDto) => HelmRelease;
|
||||
const toHelmReleaseInjectable = getInjectable({
|
||||
id: "to-helm-release",
|
||||
instantiate: (di): ToHelmRelease => {
|
||||
const helmChartStore = di.inject(helmChartStoreInjectable);
|
||||
const helmChartVersions = (release: HelmRelease) => di.inject(helmChartVersionsInjectable, release);
|
||||
|
||||
return (release) => ({
|
||||
...release,
|
||||
@ -71,14 +72,14 @@ const toHelmReleaseInjectable = getInjectable({
|
||||
// Helm does not store from what repository the release is installed,
|
||||
// so we have to try to guess it by searching charts
|
||||
async getRepo() {
|
||||
const chartName = this.getChart();
|
||||
const version = this.getVersion();
|
||||
const versions = await helmChartStore.getVersions(chartName);
|
||||
const chartVersion = versions.find(
|
||||
(chartVersion) => chartVersion.version === version,
|
||||
);
|
||||
const versionsComputed = helmChartVersions(this);
|
||||
|
||||
return chartVersion ? chartVersion.repo : "";
|
||||
await when(() => !versionsComputed.pending.get());
|
||||
|
||||
const version = this.getVersion();
|
||||
const versions = versionsComputed.value.get();
|
||||
|
||||
return versions.find((chartVersion) => chartVersion.version === version)?.repo ?? "";
|
||||
},
|
||||
});
|
||||
},
|
||||
|
||||
@ -3,23 +3,27 @@
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
import { getInjectable } from "@ogre-tools/injectable";
|
||||
import type { IChartUpgradeData } from "./store.injectable";
|
||||
import upgradeChartTabStoreInjectable from "./store.injectable";
|
||||
import dockStoreInjectable from "../dock/store.injectable";
|
||||
import type { HelmRelease } from "../../../../common/k8s-api/endpoints/helm-releases.api";
|
||||
import type { DockStore, DockTabCreateSpecific, TabId } from "../dock/store";
|
||||
import { TabKind } from "../dock/store";
|
||||
import type { UpgradeChartTabStore } from "./store";
|
||||
import { runInAction } from "mobx";
|
||||
import getRandomUpgradeChartTabIdInjectable from "./get-random-upgrade-chart-tab-id.injectable";
|
||||
import type { DockTabStore } from "../dock-tab-store/dock-tab.store";
|
||||
|
||||
interface Dependencies {
|
||||
upgradeChartStore: UpgradeChartTabStore;
|
||||
upgradeChartStore: DockTabStore<IChartUpgradeData>;
|
||||
dockStore: DockStore;
|
||||
getRandomId: () => string;
|
||||
}
|
||||
|
||||
const createUpgradeChartTab = ({ upgradeChartStore, dockStore, getRandomId }: Dependencies) => (release: HelmRelease, tabParams: DockTabCreateSpecific = {}): TabId => {
|
||||
const tabId = upgradeChartStore.getTabIdByRelease(release.getName());
|
||||
const tabId = upgradeChartStore.findTabIdFromData(val => (
|
||||
val.releaseName === release.getName()
|
||||
&& val.releaseNamespace === release.getNs()
|
||||
));
|
||||
|
||||
if (tabId) {
|
||||
dockStore.open();
|
||||
|
||||
@ -3,10 +3,12 @@
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
import { getInjectable } from "@ogre-tools/injectable";
|
||||
import { UpgradeChartTabStore } from "./store";
|
||||
import createDockTabStoreInjectable from "../dock-tab-store/create-dock-tab-store.injectable";
|
||||
import createStorageInjectable from "../../../utils/create-storage/create-storage.injectable";
|
||||
import requestHelmReleaseConfigurationInjectable from "../../../../common/k8s-api/endpoints/helm-releases.api/get-configuration.injectable";
|
||||
|
||||
export interface IChartUpgradeData {
|
||||
releaseName: string;
|
||||
releaseNamespace: string;
|
||||
}
|
||||
|
||||
const upgradeChartTabStoreInjectable = getInjectable({
|
||||
id: "upgrade-chart-tab-store",
|
||||
@ -14,10 +16,8 @@ const upgradeChartTabStoreInjectable = getInjectable({
|
||||
instantiate: (di) => {
|
||||
const createDockTabStore = di.inject(createDockTabStoreInjectable);
|
||||
|
||||
return new UpgradeChartTabStore({
|
||||
createStorage: di.inject(createStorageInjectable),
|
||||
valuesStore: createDockTabStore<string>(),
|
||||
requestHelmReleaseConfiguration: di.inject(requestHelmReleaseConfigurationInjectable),
|
||||
return createDockTabStore<IChartUpgradeData>({
|
||||
storageKey: "chart_releases",
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
@ -1,56 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
|
||||
import { action, computed, makeObservable } from "mobx";
|
||||
import type { TabId } from "../dock/store";
|
||||
import type { DockTabStoreDependencies } from "../dock-tab-store/dock-tab.store";
|
||||
import { DockTabStore } from "../dock-tab-store/dock-tab.store";
|
||||
import assert from "assert";
|
||||
import type { RequestHelmReleaseConfiguration } from "../../../../common/k8s-api/endpoints/helm-releases.api/get-configuration.injectable";
|
||||
|
||||
export interface IChartUpgradeData {
|
||||
releaseName: string;
|
||||
releaseNamespace: string;
|
||||
}
|
||||
|
||||
export interface UpgradeChartTabStoreDependencies extends DockTabStoreDependencies {
|
||||
valuesStore: DockTabStore<string>;
|
||||
requestHelmReleaseConfiguration: RequestHelmReleaseConfiguration;
|
||||
}
|
||||
|
||||
export class UpgradeChartTabStore extends DockTabStore<IChartUpgradeData> {
|
||||
@computed private get releaseNameReverseLookup(): Map<string, string> {
|
||||
return new Map(this.getAllData().map(([id, { releaseName }]) => [releaseName, id]));
|
||||
}
|
||||
|
||||
get values() {
|
||||
return this.dependencies.valuesStore;
|
||||
}
|
||||
|
||||
constructor(protected readonly dependencies: UpgradeChartTabStoreDependencies) {
|
||||
super(dependencies, {
|
||||
storageKey: "chart_releases",
|
||||
});
|
||||
|
||||
makeObservable(this);
|
||||
}
|
||||
|
||||
@action
|
||||
async reloadValues(tabId: TabId) {
|
||||
this.values.clearData(tabId); // reset
|
||||
const data = this.getData(tabId);
|
||||
|
||||
assert(data, "cannot reload values if no data");
|
||||
|
||||
const { releaseName, releaseNamespace } = data;
|
||||
const values = await this.dependencies.requestHelmReleaseConfiguration(releaseName, releaseNamespace, true);
|
||||
|
||||
this.values.setData(tabId, values);
|
||||
}
|
||||
|
||||
getTabIdByRelease(releaseName: string) {
|
||||
return this.releaseNameReverseLookup.get(releaseName);
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,121 @@
|
||||
/**
|
||||
* 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 { asyncComputed } from "@ogre-tools/injectable-react";
|
||||
import type { IComputedValue } from "mobx";
|
||||
import { action, computed, observable, when } from "mobx";
|
||||
import type { SingleValue } from "react-select";
|
||||
import type { ChartVersion } from "../../+helm-charts/helm-charts/versions.injectable";
|
||||
import helmChartVersionsInjectable from "../../+helm-charts/helm-charts/versions.injectable";
|
||||
import releasesInjectable from "../../+helm-releases/releases.injectable";
|
||||
import updateReleaseInjectable from "../../+helm-releases/update-release/update-release.injectable";
|
||||
import type { HelmRelease } from "../../../../common/k8s-api/endpoints/helm-releases.api";
|
||||
import requestHelmReleaseConfigurationInjectable from "../../../../common/k8s-api/endpoints/helm-releases.api/get-configuration.injectable";
|
||||
import { waitUntilDefined } from "../../../utils";
|
||||
import type { SelectOption } from "../../select";
|
||||
import type { DockTab } from "../dock/store";
|
||||
import upgradeChartTabStoreInjectable from "./store.injectable";
|
||||
|
||||
export interface UpgradeChartModel {
|
||||
readonly release: HelmRelease;
|
||||
readonly versionOptions: IComputedValue<SelectOption<ChartVersion>[]>;
|
||||
readonly configration: {
|
||||
readonly value: IComputedValue<string>;
|
||||
set: (value: string) => void;
|
||||
readonly error: IComputedValue<string | undefined>;
|
||||
setError: (error: unknown) => void;
|
||||
};
|
||||
readonly version: {
|
||||
readonly value: IComputedValue<ChartVersion | undefined>;
|
||||
set: (value: SingleValue<SelectOption<ChartVersion>>) => void;
|
||||
};
|
||||
submit: () => Promise<UpgradeChartSubmitResult>;
|
||||
}
|
||||
|
||||
export interface UpgradeChartSubmitResult {
|
||||
completedSuccessfully: boolean;
|
||||
}
|
||||
|
||||
const upgradeChartModelInjectable = getInjectable({
|
||||
id: "upgrade-chart-model",
|
||||
instantiate: async (di, tab): Promise<UpgradeChartModel> => {
|
||||
const upgradeChartTabStore = di.inject(upgradeChartTabStoreInjectable);
|
||||
const releases = di.inject(releasesInjectable);
|
||||
const requestHelmReleaseConfiguration = di.inject(requestHelmReleaseConfigurationInjectable);
|
||||
const updateRelease = di.inject(updateReleaseInjectable);
|
||||
|
||||
const tabData = await waitUntilDefined(() => upgradeChartTabStore.getData(tab.id));
|
||||
const release = await waitUntilDefined(() => releases.value.get().find(release => release.getName() === tabData.releaseName));
|
||||
const versions = di.inject(helmChartVersionsInjectable, release);
|
||||
const storedConfigration = asyncComputed(() => requestHelmReleaseConfiguration(
|
||||
release.getName(),
|
||||
release.getNs(),
|
||||
true,
|
||||
), "");
|
||||
|
||||
await when(() => !versions.pending.get());
|
||||
|
||||
const configrationValue = observable.box<string>();
|
||||
const configrationEditError = observable.box<string>();
|
||||
const configration: UpgradeChartModel["configration"] = {
|
||||
value: computed(() => configrationValue.get() ?? storedConfigration.value.get()),
|
||||
set: action((value) => {
|
||||
configrationValue.set(value);
|
||||
configrationEditError.set(undefined);
|
||||
}),
|
||||
error: computed(() => configrationEditError.get()),
|
||||
setError: action((error) => configrationEditError.set(String(error))),
|
||||
};
|
||||
const versionValue = observable.box<ChartVersion | undefined>(versions.value.get()[0]);
|
||||
const version: UpgradeChartModel["version"] = {
|
||||
value: computed(() => versionValue.get()),
|
||||
set: action((option) => versionValue.set(option?.value)),
|
||||
};
|
||||
const versionOptions = computed(() => (
|
||||
versions.value
|
||||
.get()
|
||||
.map(version => ({
|
||||
value: version,
|
||||
label: `${version.repo}/${release.getChart()}-${version.version}`,
|
||||
}))
|
||||
));
|
||||
|
||||
return {
|
||||
release,
|
||||
versionOptions,
|
||||
configration,
|
||||
version,
|
||||
submit: async () => {
|
||||
const version = versionValue.get();
|
||||
|
||||
if (!version || configrationEditError.get()) {
|
||||
return {
|
||||
completedSuccessfully: false,
|
||||
};
|
||||
}
|
||||
|
||||
await updateRelease(
|
||||
release.getName(),
|
||||
release.getNs(),
|
||||
{
|
||||
chart: release.getChart(),
|
||||
values: configration.value.get(),
|
||||
...version,
|
||||
},
|
||||
);
|
||||
storedConfigration.invalidate();
|
||||
|
||||
return {
|
||||
completedSuccessfully: true,
|
||||
};
|
||||
},
|
||||
};
|
||||
},
|
||||
lifecycle: lifecycleEnum.keyedSingleton({
|
||||
getInstanceKey: (di, tab: DockTab) => tab.id,
|
||||
}),
|
||||
});
|
||||
|
||||
export default upgradeChartModelInjectable;
|
||||
@ -6,27 +6,20 @@
|
||||
import "./upgrade-chart.scss";
|
||||
|
||||
import React from "react";
|
||||
import { action, makeObservable, observable, reaction } from "mobx";
|
||||
import { disposeOnUnmount, observer } from "mobx-react";
|
||||
import { makeObservable, observable } from "mobx";
|
||||
import { observer } from "mobx-react";
|
||||
import { cssNames } from "../../../utils";
|
||||
import type { DockTab } from "../dock/store";
|
||||
import { InfoPanel } from "../info-panel";
|
||||
import type { UpgradeChartTabStore } from "./store";
|
||||
import { Spinner } from "../../spinner";
|
||||
import { Badge } from "../../badge";
|
||||
import { EditorPanel } from "../editor-panel";
|
||||
import type { HelmChartStore, ChartVersion } from "../../+helm-charts/store";
|
||||
import type { HelmRelease } from "../../../../common/k8s-api/endpoints/helm-releases.api";
|
||||
import type { SelectOption } from "../../select";
|
||||
import { Select } from "../../select";
|
||||
import type { IAsyncComputed } from "@ogre-tools/injectable-react";
|
||||
import { withInjectables } from "@ogre-tools/injectable-react";
|
||||
import upgradeChartTabStoreInjectable from "./store.injectable";
|
||||
import updateReleaseInjectable from "../../+helm-releases/update-release/update-release.injectable";
|
||||
import releasesInjectable from "../../+helm-releases/releases.injectable";
|
||||
import type { RequestHelmReleaseUpdate } from "../../../../common/k8s-api/endpoints/helm-releases.api/update.injectable";
|
||||
import { first } from "lodash/fp";
|
||||
import helmChartStoreInjectable from "../../+helm-charts/store.injectable";
|
||||
import type { ChartVersion } from "../../+helm-charts/helm-charts/versions.injectable";
|
||||
import type { UpgradeChartModel } from "./upgrade-chart-model.injectable";
|
||||
import upgradeChartModelInjectable from "./upgrade-chart-model.injectable";
|
||||
|
||||
export interface UpgradeChartProps {
|
||||
className?: string;
|
||||
@ -34,160 +27,78 @@ export interface UpgradeChartProps {
|
||||
}
|
||||
|
||||
interface Dependencies {
|
||||
releases: IAsyncComputed<HelmRelease[]>;
|
||||
upgradeChartTabStore: UpgradeChartTabStore;
|
||||
updateRelease: RequestHelmReleaseUpdate;
|
||||
helmChartStore: HelmChartStore;
|
||||
model: UpgradeChartModel;
|
||||
}
|
||||
|
||||
@observer
|
||||
export class NonInjectedUpgradeChart extends React.Component<UpgradeChartProps & Dependencies> {
|
||||
@observable error?: string;
|
||||
@observable versions = observable.array<ChartVersion>();
|
||||
@observable version: ChartVersion | undefined = undefined;
|
||||
|
||||
constructor(props: UpgradeChartProps & Dependencies) {
|
||||
super(props);
|
||||
makeObservable(this);
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
disposeOnUnmount(this, [
|
||||
reaction(
|
||||
() => this.release,
|
||||
release => this.reloadVersions(release),
|
||||
{
|
||||
fireImmediately: true,
|
||||
},
|
||||
),
|
||||
reaction(
|
||||
() => this.release?.getRevision(),
|
||||
() => this.reloadValues(),
|
||||
{
|
||||
fireImmediately: true,
|
||||
},
|
||||
),
|
||||
]);
|
||||
}
|
||||
|
||||
get tabId() {
|
||||
return this.props.tab.id;
|
||||
}
|
||||
|
||||
get release() {
|
||||
const tabData = this.props.upgradeChartTabStore.getData(this.tabId);
|
||||
|
||||
if (!tabData) return null;
|
||||
|
||||
return this.props.releases.value.get().find(release => release.getName() === tabData.releaseName);
|
||||
}
|
||||
|
||||
get value() {
|
||||
return this.props.upgradeChartTabStore.values.getData(this.tabId);
|
||||
}
|
||||
|
||||
async reloadValues() {
|
||||
this.props.upgradeChartTabStore.reloadValues(this.props.tab.id);
|
||||
}
|
||||
|
||||
async reloadVersions(release: HelmRelease | null | undefined) {
|
||||
if (!release) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.version = undefined;
|
||||
this.versions.clear();
|
||||
const versions = await this.props.helmChartStore.getVersions(release.getChart());
|
||||
|
||||
this.versions.replace(versions);
|
||||
this.version = first(this.versions);
|
||||
}
|
||||
|
||||
onChange = action((value: string) => {
|
||||
this.error = "";
|
||||
this.props.upgradeChartTabStore.values.setData(this.tabId, value);
|
||||
});
|
||||
|
||||
onError = action((error: Error | string) => {
|
||||
this.error = error.toString();
|
||||
});
|
||||
|
||||
upgrade = async () => {
|
||||
if (this.error || !this.release || !this.version || !this.value) {
|
||||
return null;
|
||||
const { model } = this.props;
|
||||
const { completedSuccessfully } = await model.submit();
|
||||
|
||||
if (completedSuccessfully) {
|
||||
return (
|
||||
<p>
|
||||
{"Release "}
|
||||
<b>{model.release.getName()}</b>
|
||||
{" successfully upgraded to version "}
|
||||
<b>{model.version.value.get()}</b>
|
||||
</p>
|
||||
);
|
||||
}
|
||||
|
||||
const { version, repo } = this.version;
|
||||
const releaseName = this.release.getName();
|
||||
const releaseNs = this.release.getNs();
|
||||
|
||||
await this.props.updateRelease(releaseName, releaseNs, {
|
||||
chart: this.release.getChart(),
|
||||
values: this.value,
|
||||
repo, version,
|
||||
});
|
||||
|
||||
return (
|
||||
<p>
|
||||
{"Release "}
|
||||
<b>{releaseName}</b>
|
||||
{" successfully upgraded to version "}
|
||||
<b>{version}</b>
|
||||
</p>
|
||||
);
|
||||
return null;
|
||||
};
|
||||
|
||||
render() {
|
||||
const { tabId, release, value, error, onChange, onError, upgrade, versions, version } = this;
|
||||
const { className } = this.props;
|
||||
|
||||
if (!release || !this.props.upgradeChartTabStore.isReady(tabId) || !version) {
|
||||
return <Spinner center />;
|
||||
}
|
||||
const currentVersion = release.getVersion();
|
||||
const versionOptions = versions.map(version => ({
|
||||
value: version,
|
||||
label: `${version.repo}/${release.getChart()}-${version.version}`,
|
||||
}));
|
||||
const controlsAndInfo = (
|
||||
<div className="upgrade flex gaps align-center">
|
||||
<span>Release</span>
|
||||
{" "}
|
||||
<Badge label={release.getName()} />
|
||||
<span>Namespace</span>
|
||||
{" "}
|
||||
<Badge label={release.getNs()} />
|
||||
<span>Version</span>
|
||||
{" "}
|
||||
<Badge label={currentVersion} />
|
||||
<span>Upgrade version</span>
|
||||
<Select<ChartVersion, SelectOption<ChartVersion>, false>
|
||||
id="char-version-input"
|
||||
className="chart-version"
|
||||
menuPlacement="top"
|
||||
themeName="outlined"
|
||||
value={version}
|
||||
options={versionOptions}
|
||||
onChange={option => this.version = option?.value}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
const { model, className, tab } = this.props;
|
||||
const tabId = tab.id;
|
||||
const { release } = model;
|
||||
|
||||
return (
|
||||
<div className={cssNames("UpgradeChart flex column", className)}>
|
||||
<InfoPanel
|
||||
tabId={tabId}
|
||||
error={error}
|
||||
submit={upgrade}
|
||||
error={model.configration.error.get()}
|
||||
submit={this.upgrade}
|
||||
submitLabel="Upgrade"
|
||||
submittingMessage="Updating.."
|
||||
controls={controlsAndInfo}
|
||||
controls={(
|
||||
<div className="upgrade flex gaps align-center">
|
||||
<span>Release</span>
|
||||
{" "}
|
||||
<Badge label={release.getName()} />
|
||||
<span>Namespace</span>
|
||||
{" "}
|
||||
<Badge label={release.getNs()} />
|
||||
<span>Version</span>
|
||||
{" "}
|
||||
<Badge label={model.version.value.get()} />
|
||||
<span>Upgrade version</span>
|
||||
<Select<ChartVersion, SelectOption<ChartVersion>, false>
|
||||
id="char-version-input"
|
||||
className="chart-version"
|
||||
menuPlacement="top"
|
||||
themeName="outlined"
|
||||
value={model.version.value.get()}
|
||||
options={model.versionOptions.get()}
|
||||
onChange={model.version.set}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
/>
|
||||
<EditorPanel
|
||||
tabId={tabId}
|
||||
value={value ?? ""}
|
||||
onChange={onChange}
|
||||
onError={onError}
|
||||
value={model.configration.value.get()}
|
||||
onChange={model.configration.set}
|
||||
onError={model.configration.setError}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
@ -195,11 +106,9 @@ export class NonInjectedUpgradeChart extends React.Component<UpgradeChartProps &
|
||||
}
|
||||
|
||||
export const UpgradeChart = withInjectables<Dependencies, UpgradeChartProps>(NonInjectedUpgradeChart, {
|
||||
getProps: (di, props) => ({
|
||||
getPlaceholder: () => <Spinner center />,
|
||||
getProps: async (di, props) => ({
|
||||
...props,
|
||||
releases: di.inject(releasesInjectable),
|
||||
updateRelease: di.inject(updateReleaseInjectable),
|
||||
upgradeChartTabStore: di.inject(upgradeChartTabStoreInjectable),
|
||||
helmChartStore: di.inject(helmChartStoreInjectable),
|
||||
model: await di.inject(upgradeChartModelInjectable, props.tab),
|
||||
}),
|
||||
});
|
||||
|
||||
Loading…
Reference in New Issue
Block a user