Making apiBase injectable (#6022)

* Making apiBase injectable

Signed-off-by: Sebastian Malton <sebastian@malton.name>

* Convert all of Helm functions to be DI

Signed-off-by: Sebastian Malton <sebastian@malton.name>

* Make PortForward's use of apiBase fully injectable

Signed-off-by: Sebastian Malton <sebastian@malton.name>

* Convert all metric requests to be injectable

Signed-off-by: Sebastian Malton <sebastian@malton.name>

* Replace resource applier with injectables

Signed-off-by: Sebastian Malton <sebastian@malton.name>

* Switch KubeJsonApi.forCluster to be injectable but do not use

Signed-off-by: Sebastian Malton <sebastian@malton.name>

* Convert the rest of shell sessions to be DI-ed

- This is a prerequesit for using the new
  createKubeJsonApiForClusterInjectable

Signed-off-by: Sebastian Malton <sebastian@malton.name>

* Use new createKubeJsonApiForClusterInjectable for openNodeShellSession

Signed-off-by: Sebastian Malton <sebastian@malton.name>

* Make KubeconfigDialog injectable

Signed-off-by: Sebastian Malton <sebastian@malton.name>

* Remove jest-fetch-mock and make fetch injectable

Signed-off-by: Sebastian Malton <sebastian@malton.name>

* Fix tests with new global override

Signed-off-by: Sebastian Malton <sebastian@malton.name>

* Add new injectable for create KubeJsonApi and JsonApi instances

Signed-off-by: Sebastian Malton <sebastian@malton.name>

* 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>

* Fix show details and updating helm releases tests

Signed-off-by: Sebastian Malton <sebastian@malton.name>

* Fix residual typing issues related to metrics

Signed-off-by: Sebastian Malton <sebastian@malton.name>

* Fix crash on load due to circular dependency

Signed-off-by: Sebastian Malton <sebastian@malton.name>

* Fix create resource tab not working

Signed-off-by: Sebastian Malton <sebastian@malton.name>

* Remove legacy apiBase global

Signed-off-by: Sebastian Malton <sebastian@malton.name>

* Introduce and use isDebuggingInjectable

Signed-off-by: Sebastian Malton <sebastian@malton.name>

* Introduce and use windowLocationInjectable

Signed-off-by: Sebastian Malton <sebastian@malton.name>

* Remove global legacy apiKube

Signed-off-by: Sebastian Malton <sebastian@malton.name>

* Improve injectable filenames compared to the injectables inside

Signed-off-by: Sebastian Malton <sebastian@malton.name>

* Remove modifying input in requestActivePortForwardInjectable

Signed-off-by: Sebastian Malton <sebastian@malton.name>

* Introduce and use get(Milli)SecondsFromUnixEpochInjectable

Signed-off-by: Sebastian Malton <sebastian@malton.name>

* Switch to non-reactive way of gettting possible helm release versions

Signed-off-by: Sebastian Malton <sebastian@malton.name>

* Fix typo

Signed-off-by: Sebastian Malton <sebastian@malton.name>

* Fix bug in KubeApi constructor

Signed-off-by: Sebastian Malton <sebastian@malton.name>

* Convert all KubeApi related tests to use asyncFn

Signed-off-by: Sebastian Malton <sebastian@malton.name>

* Fix unit tests after introducing new injectables that have side effects

Signed-off-by: Sebastian Malton <sebastian@malton.name>

* Fix bad rebase causing tests to fail

Signed-off-by: Sebastian Malton <sebastian@malton.name>

* Improve expects for multiple field values

Signed-off-by: Sebastian Malton <sebastian@malton.name>

* Fix crash will looking up api refs

Signed-off-by: Sebastian Malton <sebastian@malton.name>

* Fix breaking change on KubeApi.list

Signed-off-by: Sebastian Malton <sebastian@malton.name>

* Better fix for formatting urls

Signed-off-by: Sebastian Malton <sebastian@malton.name>

* Remove injectable for time since we should just use useMockTime

Signed-off-by: Sebastian Malton <sebastian@malton.name>

* Add happy path behavioural tests for upgrade chart tab

Signed-off-by: Sebastian Malton <sebastian@malton.name>

* Remove debug message

Signed-off-by: Sebastian Malton <sebastian@malton.name>

* Update snapshots

Signed-off-by: Sebastian Malton <sebastian@malton.name>

* fix showing-details-for-helm-release tests

Signed-off-by: Sebastian Malton <sebastian@malton.name>

* Fix installing-helm-chart-from-new-tab tests

Signed-off-by: Sebastian Malton <sebastian@malton.name>

* Fix tests relating to hosted cluster id

Signed-off-by: Sebastian Malton <sebastian@malton.name>

* Update snapshots to recent changes in master

Co-authored-by: Janne Savolainen <janne.savolainen@live.fi>

Signed-off-by: Iku-turso <mikko.aspiala@gmail.com>

* Reupdated upgrade chart new tab test snapshots

Signed-off-by: Sebastian Malton <sebastian@malton.name>

* Fix flakiness in unit test when using <Animated>

Signed-off-by: Sebastian Malton <sebastian@malton.name>

* Fix flakiness and improve tests for DeleteClusterDialog

Signed-off-by: Sebastian Malton <sebastian@malton.name>

* Fix kubeconfig-sync tests

Signed-off-by: Sebastian Malton <sebastian@malton.name>

* Fix <Extensions> tests by removing mockFs and making everything injectable

Signed-off-by: Sebastian Malton <sebastian@malton.name>

* Fix build issues

Signed-off-by: Sebastian Malton <sebastian@malton.name>

* Fix getElectronAppPathInjectable override not returning absolute paths

- Also fixes the listing-active-helm-repos-in-prefs tests

Signed-off-by: Sebastian Malton <sebastian@malton.name>

* Replace all uses of getAbsolutePath with joinPaths as it is more correct and less confusing

Signed-off-by: Sebastian Malton <sebastian@malton.name>

* Fix opening application window tests by making override properly absolute

Signed-off-by: Sebastian Malton <sebastian@malton.name>

* Update snapshots relating no longer using getAbsolutePath

Signed-off-by: Sebastian Malton <sebastian@malton.name>

* Fix and add behavioural tests for RenderDelay

Signed-off-by: Sebastian Malton <sebastian@malton.name>

* Fix extension discovery tests

Signed-off-by: Sebastian Malton <sebastian@malton.name>

* Fix test flakiness because of path side effects, propagate uses to as many places

Signed-off-by: Sebastian Malton <sebastian@malton.name>

* Fix extension-discovery tests

Signed-off-by: Sebastian Malton <sebastian@malton.name>

* Add global override to fix some tests

Signed-off-by: Sebastian Malton <sebastian@malton.name>

* Rewrite and fix implementation of KubeconfigManager and its tests

Signed-off-by: Sebastian Malton <sebastian@malton.name>

* Fix tests by global override pathExists

Signed-off-by: Sebastian Malton <sebastian@malton.name>

* Fix unit tests failing on windows by using injectable verions of path functions

Signed-off-by: Sebastian Malton <sebastian@malton.name>

* Attempt to fix test timeout by using runInAction

Signed-off-by: Sebastian Malton <sebastian@malton.name>

* Update snapshots after rebase

Signed-off-by: Sebastian Malton <sebastian@malton.name>

* Update snapshots after rebase

Signed-off-by: Sebastian Malton <sebastian@malton.name>

* Fix tests after rebase

Signed-off-by: Sebastian Malton <sebastian@malton.name>

* Fix setupIpcMainHandlers usage

Signed-off-by: Sebastian Malton <sebastian@malton.name>

* Update snapshots

Signed-off-by: Sebastian Malton <sebastian@malton.name>

Signed-off-by: Sebastian Malton <sebastian@malton.name>
Signed-off-by: Iku-turso <mikko.aspiala@gmail.com>
Co-authored-by: Iku-turso <mikko.aspiala@gmail.com>
This commit is contained in:
authored and GitHub committed 2022-10-05 08:10:36 -04:00
1 parent ed073d6562
commit 76066c5ebf
367 files changed
+16701 -5302

No files matched your search

@@ -1,15 +0,0 @@
/**
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import callForHelmReleasesInjectable from "./call-for-helm-releases.injectable";
import { getGlobalOverride } from "../../../../common/test-utils/get-global-override";
export default getGlobalOverride(
callForHelmReleasesInjectable,
() => () => {
throw new Error(
"Tried to call for helm releases without explicit override.",
);
},
);
@@ -1,24 +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 type { HelmReleaseDto } from "../../../../common/k8s-api/endpoints/helm-releases.api";
import { apiBase } from "../../../../common/k8s-api";
import { endpoint } from "../../../../common/k8s-api/endpoints/helm-releases.api";
export type CallForHelmReleases = (
namespace?: string
) => Promise<HelmReleaseDto[]>;
const callForHelmReleasesInjectable = getInjectable({
id: "call-for-helm-releases",
instantiate: (): CallForHelmReleases => async (namespace) =>
await apiBase.get<HelmReleaseDto[]>(endpoint({ namespace })),
causesSideEffects: true,
});
export default callForHelmReleasesInjectable;
@@ -1,44 +0,0 @@
/**
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import yaml from "js-yaml";
import { getInjectable } from "@ogre-tools/injectable";
import type { HelmReleaseUpdateDetails } from "../../../../common/k8s-api/endpoints/helm-releases.api";
import { endpoint } from "../../../../common/k8s-api/endpoints/helm-releases.api";
import { apiBase } from "../../../../common/k8s-api";
interface HelmReleaseCreatePayload {
name?: string;
repo: string;
chart: string;
namespace: string;
version: string;
values: string;
}
export type CallForCreateHelmRelease = (
payload: HelmReleaseCreatePayload
) => Promise<HelmReleaseUpdateDetails>;
const callForCreateHelmReleaseInjectable = getInjectable({
id: "call-for-create-helm-release",
instantiate: (): CallForCreateHelmRelease => (payload) => {
const { repo, chart: rawChart, values: rawValues, ...data } = payload;
const chart = `${repo}/${rawChart}`;
const values = yaml.load(rawValues);
return apiBase.post(endpoint(), {
data: {
chart,
values,
...data,
},
});
},
causesSideEffects: true,
});
export default callForCreateHelmReleaseInjectable;
@@ -5,15 +5,15 @@
import { getInjectable } from "@ogre-tools/injectable";
import releasesInjectable from "../releases.injectable";
import type { CallForCreateHelmRelease } from "./call-for-create-helm-release.injectable";
import callForCreateHelmReleaseInjectable from "./call-for-create-helm-release.injectable";
import type { RequestCreateHelmRelease } from "../../../../common/k8s-api/endpoints/helm-releases.api/request-create.injectable";
import requestCreateHelmReleaseInjectable from "../../../../common/k8s-api/endpoints/helm-releases.api/request-create.injectable";
const createReleaseInjectable = getInjectable({
id: "create-release",
instantiate: (di): CallForCreateHelmRelease => {
instantiate: (di): RequestCreateHelmRelease => {
const releases = di.inject(releasesInjectable);
const callForCreateRelease = di.inject(callForCreateHelmReleaseInjectable);
const callForCreateRelease = di.inject(requestCreateHelmReleaseInjectable);
return async (payload) => {
const release = await callForCreateRelease(payload);
@@ -3,11 +3,8 @@
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import { getInjectable } from "@ogre-tools/injectable";
import type {
HelmRelease } from "../../../../common/k8s-api/endpoints/helm-releases.api";
import {
deleteRelease,
} from "../../../../common/k8s-api/endpoints/helm-releases.api";
import type { HelmRelease } from "../../../../common/k8s-api/endpoints/helm-releases.api";
import requestDeleteHelmReleaseInjectable from "../../../../common/k8s-api/endpoints/helm-releases.api/request-delete.injectable";
import releasesInjectable from "../releases.injectable";
const deleteReleaseInjectable = getInjectable({
@@ -15,9 +12,10 @@ const deleteReleaseInjectable = getInjectable({
instantiate: (di) => {
const releases = di.inject(releasesInjectable);
const requestDeleteHelmRelease = di.inject(requestDeleteHelmReleaseInjectable);
return async (release: HelmRelease) => {
await deleteRelease(release.getName(), release.getNs());
await requestDeleteHelmRelease(release.getName(), release.getNs());
releases.invalidate();
};
@@ -13,20 +13,23 @@ import type { DialogProps } from "../../dialog";
import { Dialog } from "../../dialog";
import { Wizard, WizardStep } from "../../wizard";
import type { HelmRelease } from "../../../../common/k8s-api/endpoints/helm-releases.api";
import { getReleaseHistory, type HelmReleaseRevision } from "../../../../common/k8s-api/endpoints/helm-releases.api";
import { Select } from "../../select";
import { Notifications } from "../../notifications";
import orderBy from "lodash/orderBy";
import { withInjectables } from "@ogre-tools/injectable-react";
import releaseRollbackDialogStateInjectable from "./state.injectable";
import type { RollbackRelease } from "../rollback-release/rollback-release.injectable";
import rollbackReleaseInjectable from "../rollback-release/rollback-release.injectable";
import type { HelmReleaseRevision, RequestHelmReleaseHistory } from "../../../../common/k8s-api/endpoints/helm-releases.api/request-history.injectable";
import requestHelmReleaseHistoryInjectable from "../../../../common/k8s-api/endpoints/helm-releases.api/request-history.injectable";
export interface ReleaseRollbackDialogProps extends DialogProps {
}
interface Dependencies {
rollbackRelease: (releaseName: string, namespace: string, revisionNumber: number) => Promise<void>;
rollbackRelease: RollbackRelease;
state: IObservableValue<HelmRelease | undefined>;
requestHelmReleaseHistory: RequestHelmReleaseHistory;
}
@observer
@@ -48,7 +51,7 @@ class NonInjectedReleaseRollbackDialog extends React.Component<ReleaseRollbackDi
onOpen = async (release: HelmRelease) => {
this.isLoading.set(true);
const releases = await getReleaseHistory(release.getName(), release.getNs());
const releases = await this.props.requestHelmReleaseHistory(release.getName(), release.getNs());
runInAction(() => {
this.revisions.replace(orderBy(releases, "revision", "desc"));
@@ -134,14 +137,11 @@ class NonInjectedReleaseRollbackDialog extends React.Component<ReleaseRollbackDi
}
}
export const ReleaseRollbackDialog = withInjectables<Dependencies, ReleaseRollbackDialogProps>(
NonInjectedReleaseRollbackDialog,
{
getProps: (di, props) => ({
rollbackRelease: di.inject(rollbackReleaseInjectable),
state: di.inject(releaseRollbackDialogStateInjectable),
...props,
}),
},
);
export const ReleaseRollbackDialog = withInjectables<Dependencies, ReleaseRollbackDialogProps>(NonInjectedReleaseRollbackDialog, {
getProps: (di, props) => ({
...props,
rollbackRelease: di.inject(rollbackReleaseInjectable),
state: di.inject(releaseRollbackDialogStateInjectable),
requestHelmReleaseHistory: di.inject(requestHelmReleaseHistoryInjectable),
}),
});
@@ -1,15 +0,0 @@
/**
* 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 callForHelmReleaseConfigurationInjectable from "./call-for-helm-release-configuration.injectable";
export default getGlobalOverride(
callForHelmReleaseConfigurationInjectable,
() => () => {
throw new Error(
"Tried to call for helm release configuration without explicit override.",
);
},
);
@@ -1,29 +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 { apiBase } from "../../../../../../common/k8s-api";
import { endpoint } from "../../../../../../common/k8s-api/endpoints/helm-releases.api";
export type CallForHelmReleaseConfiguration = (
name: string,
namespace: string,
all: boolean
) => Promise<string>;
const callForHelmReleaseConfigurationInjectable = getInjectable({
id: "call-for-helm-release-configuration",
instantiate:
(): CallForHelmReleaseConfiguration => async (name, namespace, all: boolean) => {
const route = "values";
const path = endpoint({ name, namespace, route }, { all });
return apiBase.get<string>(path);
},
causesSideEffects: true,
});
export default callForHelmReleaseConfigurationInjectable;
@@ -1,15 +0,0 @@
/**
* 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 callForHelmReleaseDetailsInjectable from "./call-for-helm-release-details.injectable";
export default getGlobalOverride(
callForHelmReleaseDetailsInjectable,
() => () => {
throw new Error(
"Tried to call for helm release details without explicit override.",
);
},
);
@@ -1,45 +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 { apiBase } from "../../../../../../../common/k8s-api";
import { endpoint } from "../../../../../../../common/k8s-api/endpoints/helm-releases.api";
import type { KubeJsonApiData } from "../../../../../../../common/k8s-api/kube-json-api";
export interface HelmReleaseDetails {
resources: KubeJsonApiData[];
name: string;
namespace: string;
version: string;
config: string; // release values
manifest: string;
info: {
deleted: string;
description: string;
first_deployed: string;
last_deployed: string;
notes: string;
status: string;
};
}
export type CallForHelmReleaseDetails = (
name: string,
namespace: string
) => Promise<HelmReleaseDetails>;
const callForHelmReleaseDetailsInjectable = getInjectable({
id: "call-for-helm-release-details",
instantiate: (): CallForHelmReleaseDetails => async (name, namespace) => {
const path = endpoint({ name, namespace });
return apiBase.get<HelmReleaseDetails>(path);
},
causesSideEffects: true,
});
export default callForHelmReleaseDetailsInjectable;
@@ -1,51 +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 type { HelmReleaseDto } from "../../../../../../common/k8s-api/endpoints/helm-releases.api";
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;
details: HelmReleaseDetails;
}
export type CallForHelmRelease = (
name: string,
namespace: string
) => Promise<AsyncResult<DetailedHelmRelease | undefined>>;
const callForHelmReleaseInjectable = getInjectable({
id: "call-for-helm-release",
instantiate: (di): CallForHelmRelease => {
const callForHelmReleases = di.inject(callForHelmReleasesInjectable);
const callForHelmReleaseDetails = di.inject(callForHelmReleaseDetailsInjectable);
return async (name, namespace) => {
const [releases, details] = await Promise.all([
callForHelmReleases(namespace),
callForHelmReleaseDetails(name, namespace),
]);
const release = releases.find(
(rel) => rel.name === name && rel.namespace === namespace,
);
if (!release) {
return {
callWasSuccessful: false,
error: `Release ${name} didn't exist in ${namespace} namespace.`,
};
}
return { callWasSuccessful: true, response: { release, details }};
};
},
});
export default callForHelmReleaseInjectable;
@@ -6,18 +6,17 @@ import { getInjectable, lifecycleEnum } from "@ogre-tools/injectable";
import type { IComputedValue, IObservableValue } from "mobx";
import { runInAction, action, observable, computed } from "mobx";
import type { TargetHelmRelease } from "../target-helm-release.injectable";
import type { CallForHelmRelease, DetailedHelmRelease } from "./call-for-helm-release/call-for-helm-release.injectable";
import callForHelmReleaseInjectable from "./call-for-helm-release/call-for-helm-release.injectable";
import type { RequestDetailedHelmRelease, DetailedHelmRelease } from "./request-detailed-helm-release.injectable";
import requestDetailedHelmReleaseInjectable from "./request-detailed-helm-release.injectable";
import type { LensTheme } from "../../../../themes/store";
import type { CallForHelmReleaseConfiguration } from "./call-for-helm-release-configuration/call-for-helm-release-configuration.injectable";
import callForHelmReleaseConfigurationInjectable from "./call-for-helm-release-configuration/call-for-helm-release-configuration.injectable";
import { toHelmRelease } from "../../releases.injectable";
import type { RequestHelmReleaseConfiguration } from "../../../../../common/k8s-api/endpoints/helm-releases.api/request-configuration.injectable";
import requestHelmReleaseConfigurationInjectable from "../../../../../common/k8s-api/endpoints/helm-releases.api/request-configuration.injectable";
import { pipeline } from "@ogre-tools/fp";
import { groupBy, map } from "lodash/fp";
import type { KubeJsonApiData } from "../../../../../common/k8s-api/kube-json-api";
import type { GetResourceDetailsUrl } from "./get-resource-details-url.injectable";
import getResourceDetailsUrlInjectable from "./get-resource-details-url.injectable";
import type { CallForHelmReleaseUpdate } from "../../update-release/call-for-helm-release-update/call-for-helm-release-update.injectable";
import type { RequestHelmReleaseUpdate } from "../../../../../common/k8s-api/endpoints/helm-releases.api/request-update.injectable";
import updateReleaseInjectable from "../../update-release/update-release.injectable";
import type { ShowCheckedErrorNotification } from "../../../notifications/show-checked-error.injectable";
import showCheckedErrorNotificationInjectable from "../../../notifications/show-checked-error.injectable";
@@ -26,35 +25,29 @@ 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 activeThemeInjectable from "../../../../themes/active.injectable";
import type { ToHelmRelease } from "../../to-helm-release.injectable";
import toHelmReleaseInjectable from "../../to-helm-release.injectable";
const releaseDetailsModelInjectable = getInjectable({
id: "release-details-model",
instantiate: async (di, targetRelease: TargetHelmRelease) => {
const callForHelmRelease = di.inject(callForHelmReleaseInjectable);
const callForHelmReleaseConfiguration = di.inject(callForHelmReleaseConfigurationInjectable);
const activeTheme = di.inject(activeThemeInjectable);
const getResourceDetailsUrl = di.inject(getResourceDetailsUrlInjectable);
const updateRelease = di.inject(updateReleaseInjectable);
const showCheckedErrorNotification = di.inject(showCheckedErrorNotificationInjectable);
const showSuccessNotification = di.inject(showSuccessNotificationInjectable);
const createUpgradeChartTab = di.inject(createUpgradeChartTabInjectable);
const navigateToHelmReleases = di.inject(navigateToHelmReleasesInjectable);
const model = new ReleaseDetailsModel({
callForHelmRelease,
requestDetailedHelmRelease: di.inject(requestDetailedHelmReleaseInjectable),
targetRelease,
activeTheme,
callForHelmReleaseConfiguration,
getResourceDetailsUrl,
updateRelease,
showCheckedErrorNotification,
showSuccessNotification,
createUpgradeChartTab,
navigateToHelmReleases,
activeTheme: di.inject(activeThemeInjectable),
requestHelmReleaseConfiguration: di.inject(requestHelmReleaseConfigurationInjectable),
getResourceDetailsUrl: di.inject(getResourceDetailsUrlInjectable),
updateRelease: di.inject(updateReleaseInjectable),
showCheckedErrorNotification: di.inject(showCheckedErrorNotificationInjectable),
showSuccessNotification: di.inject(showSuccessNotificationInjectable),
createUpgradeChartTab: di.inject(createUpgradeChartTabInjectable),
navigateToHelmReleases: di.inject(navigateToHelmReleasesInjectable),
toHelmRelease: di.inject(toHelmReleaseInjectable),
});
await model.load();
@@ -85,20 +78,21 @@ export interface ConfigurationInput {
interface Dependencies {
readonly targetRelease: TargetHelmRelease;
readonly activeTheme: IComputedValue<LensTheme>;
callForHelmRelease: CallForHelmRelease;
callForHelmReleaseConfiguration: CallForHelmReleaseConfiguration;
requestDetailedHelmRelease: RequestDetailedHelmRelease;
requestHelmReleaseConfiguration: RequestHelmReleaseConfiguration;
getResourceDetailsUrl: GetResourceDetailsUrl;
updateRelease: CallForHelmReleaseUpdate;
updateRelease: RequestHelmReleaseUpdate;
showCheckedErrorNotification: ShowCheckedErrorNotification;
showSuccessNotification: ShowNotification;
createUpgradeChartTab: (release: HelmRelease) => string;
navigateToHelmReleases: () => void;
navigateToHelmReleases: NavigateToHelmReleases;
toHelmRelease: ToHelmRelease;
}
export class ReleaseDetailsModel {
readonly id = `${this.dependencies.targetRelease.namespace}/${this.dependencies.targetRelease.name}`;
constructor(private readonly dependencies: Dependencies) {}
constructor(protected readonly dependencies: Dependencies) {}
private readonly detailedRelease = observable.box<DetailedHelmRelease | undefined>();
@@ -171,7 +165,7 @@ export class ReleaseDetailsModel {
load = async () => {
const { name, namespace } = this.dependencies.targetRelease;
const result = await this.dependencies.callForHelmRelease(
const result = await this.dependencies.requestDetailedHelmRelease(
name,
namespace,
);
@@ -199,7 +193,7 @@ export class ReleaseDetailsModel {
const { name, namespace } = this.release;
const configuration =
await this.dependencies.callForHelmReleaseConfiguration(
await this.dependencies.requestHelmReleaseConfiguration(
name,
namespace,
!this.onlyUserSuppliedValuesAreShown.value.get(),
@@ -216,7 +210,7 @@ export class ReleaseDetailsModel {
assert(detailedRelease, "Tried to access release before load");
return toHelmRelease(detailedRelease.release);
return this.dependencies.toHelmRelease(detailedRelease.release);
}
@computed private get details() {
@@ -0,0 +1,51 @@
/**
* 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 { HelmReleaseDto } from "../../../../../common/k8s-api/endpoints/helm-releases.api";
import requestHelmReleasesInjectable from "../../../../../common/k8s-api/endpoints/helm-releases.api/request-releases.injectable";
import type { HelmReleaseDetails } from "../../../../../common/k8s-api/endpoints/helm-releases.api/request-details.injectable";
import requestHelmReleaseDetailsInjectable from "../../../../../common/k8s-api/endpoints/helm-releases.api/request-details.injectable";
import type { AsyncResult } from "../../../../../common/utils/async-result";
export interface DetailedHelmRelease {
release: HelmReleaseDto;
details: HelmReleaseDetails;
}
export type RequestDetailedHelmRelease = (
name: string,
namespace: string
) => Promise<AsyncResult<DetailedHelmRelease>>;
const requestDetailedHelmReleaseInjectable = getInjectable({
id: "request-detailed-helm-release",
instantiate: (di): RequestDetailedHelmRelease => {
const requestHelmReleases = di.inject(requestHelmReleasesInjectable);
const requestHelmReleaseDetails = di.inject(requestHelmReleaseDetailsInjectable);
return async (name, namespace) => {
const [releases, details] = await Promise.all([
requestHelmReleases(namespace),
requestHelmReleaseDetails(name, namespace),
]);
const release = releases.find(
(rel) => rel.name === name && rel.namespace === namespace,
);
if (!release) {
return {
callWasSuccessful: false,
error: `Release ${name} didn't exist in ${namespace} namespace.`,
};
}
return { callWasSuccessful: true, response: { release, details }};
};
},
});
export default requestDetailedHelmReleaseInjectable;
@@ -61,7 +61,10 @@ class NonInjectedHelmReleaseMenu extends React.Component<HelmReleaseMenuProps &
<span className="title">Rollback</span>
</MenuItem>
)}
<MenuItem onClick={this.upgrade}>
<MenuItem
onClick={this.upgrade}
data-testid={`upgrade-chart-menu-item-for-${release.getId()}`}
>
<Icon
material="refresh"
interactive={toolbar}
@@ -79,6 +82,10 @@ class NonInjectedHelmReleaseMenu extends React.Component<HelmReleaseMenuProps &
return (
<MenuActions
id={`menu-actions-for-release-menu-for-${release.getId()}`}
triggerIcon={{
material: "more_vert",
"data-testid": `menu-actions-icon-for-release-menu-for-${release.getId()}`,
}}
{...menuProps}
className={cssNames("HelmReleaseMenu", className)}
removeAction={this.remove}
@@ -4,113 +4,32 @@
*/
import { getInjectable } from "@ogre-tools/injectable";
import { asyncComputed } from "@ogre-tools/injectable-react";
import namespaceStoreInjectable from "../+namespaces/store.injectable";
import clusterFrameContextInjectable from "../../cluster-frame-context/cluster-frame-context.injectable";
import releaseSecretsInjectable from "./release-secrets.injectable";
import callForHelmReleasesInjectable from "./call-for-helm-releases/call-for-helm-releases.injectable";
import type { HelmRelease, HelmReleaseDto } from "../../../common/k8s-api/endpoints/helm-releases.api";
import { formatDuration } from "../../../common/utils";
import { helmChartStore } from "../+helm-charts/helm-chart.store";
import { capitalize } from "lodash/fp";
import requestHelmReleasesInjectable from "../../../common/k8s-api/endpoints/helm-releases.api/request-releases.injectable";
import toHelmReleaseInjectable from "./to-helm-release.injectable";
const releasesInjectable = getInjectable({
id: "releases",
instantiate: (di) => {
const clusterContext = di.inject(clusterFrameContextInjectable);
const namespaceStore = di.inject(namespaceStoreInjectable);
const releaseSecrets = di.inject(releaseSecretsInjectable);
const callForHelmReleases = di.inject(callForHelmReleasesInjectable);
const requestHelmReleases = di.inject(requestHelmReleasesInjectable);
const toHelmRelease = di.inject(toHelmReleaseInjectable);
return asyncComputed(async () => {
const contextNamespaces = namespaceStore.contextNamespaces || [];
void releaseSecrets.get();
const isLoadingAll =
clusterContext.allNamespaces?.length > 1 &&
clusterContext.cluster?.accessibleNamespaces.length === 0 &&
clusterContext.allNamespaces.every((namespace) =>
contextNamespaces.includes(namespace),
);
const releaseArrays = await (isLoadingAll ? callForHelmReleases() : Promise.all(
contextNamespaces.map((namespace) =>
callForHelmReleases(namespace),
),
));
const releaseArrays = await (clusterContext.hasSelectedAll
? requestHelmReleases()
: Promise.all(clusterContext.contextNamespaces.map(namespace => requestHelmReleases(namespace)))
);
return releaseArrays.flat().map(toHelmRelease);
}, []);
},
});
export const toHelmRelease = (release: HelmReleaseDto) : HelmRelease => ({
...release,
getId() {
return `${this.namespace}/${this.name}`;
},
getName() {
return this.name;
},
getNs() {
return this.namespace;
},
getChart(withVersion = false) {
let chart = this.chart;
if (!withVersion && this.getVersion() != "") {
const search = new RegExp(`-${this.getVersion()}`);
chart = chart.replace(search, "");
}
return chart;
},
getRevision() {
return parseInt(this.revision, 10);
},
getStatus() {
return capitalize(this.status);
},
getVersion() {
const versions = this.chart.match(/(?<=-)(v?\d+)[^-].*$/);
return versions?.[0] ?? "";
},
getUpdated(humanize = true, compact = true) {
const updated = this.updated.replace(/\s\w*$/, ""); // 2019-11-26 10:58:09 +0300 MSK -> 2019-11-26 10:58:09 +0300 to pass into Date()
const updatedDate = new Date(updated).getTime();
const diff = Date.now() - updatedDate;
if (humanize) {
return formatDuration(diff, compact);
}
return diff;
},
// 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,
);
return chartVersion ? chartVersion.repo : "";
},
});
export default releasesInjectable;
@@ -3,17 +3,20 @@
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import { getInjectable } from "@ogre-tools/injectable";
import { rollbackRelease } from "../../../../common/k8s-api/endpoints/helm-releases.api";
import requestHelmReleaseRollbackInjectable from "../../../../common/k8s-api/endpoints/helm-releases.api/request-rollback.injectable";
import releasesInjectable from "../releases.injectable";
export type RollbackRelease = (name: string, namespace: string, revision: number) => Promise<void>;
const rollbackReleaseInjectable = getInjectable({
id: "rollback-release",
instantiate: (di) => {
instantiate: (di): RollbackRelease => {
const releases = di.inject(releasesInjectable);
const requestHelmReleaseRollback = di.inject(requestHelmReleaseRollbackInjectable);
return async (name: string, namespace: string, revision: number) => {
await rollbackRelease(name, namespace, revision);
return async (name, namespace, revision) => {
await requestHelmReleaseRollback(name, namespace, revision);
releases.invalidate();
};
@@ -0,0 +1,90 @@
/**
* 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 { capitalize } from "lodash";
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 { getMillisecondsFromUnixEpoch } from "../../../common/utils/date/get-current-date-time";
import { formatDuration } from "../../utils";
export type ToHelmRelease = (release: HelmReleaseDto) => HelmRelease;
const toHelmReleaseInjectable = getInjectable({
id: "to-helm-release",
instantiate: (di): ToHelmRelease => {
const helmChartVersions = (release: HelmRelease) => di.inject(helmChartVersionsInjectable, release);
return (release) => ({
...release,
getId() {
return `${this.namespace}/${this.name}`;
},
getName() {
return this.name;
},
getNs() {
return this.namespace;
},
getChart(withVersion = false) {
let chart = this.chart;
if (!withVersion && this.getVersion() != "") {
const search = new RegExp(`-${this.getVersion()}`);
chart = chart.replace(search, "");
}
return chart;
},
getRevision() {
return parseInt(this.revision, 10);
},
getStatus() {
return capitalize(this.status);
},
getVersion() {
const versions = this.chart.match(/(?<=-)(v?\d+)[^-].*$/);
return versions?.[0] ?? "";
},
getUpdated(humanize = true, compact = true) {
const updated = this.updated.replace(/\s\w*$/, ""); // 2019-11-26 10:58:09 +0300 MSK -> 2019-11-26 10:58:09 +0300 to pass into Date()
const updatedDate = new Date(updated).getTime();
const diff = getMillisecondsFromUnixEpoch() - updatedDate;
if (humanize) {
return formatDuration(diff, compact);
}
return diff;
},
// 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 versionsComputed = helmChartVersions(this);
const version = this.getVersion();
await when(() => !versionsComputed.pending.get());
return versionsComputed.value
.get()
.find((chartVersion) => chartVersion.version === version)?.repo
?? "";
},
});
},
});
export default toHelmReleaseInjectable;
@@ -1,15 +0,0 @@
/**
* 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 callForHelmReleaseUpdateInjectable from "./call-for-helm-release-update.injectable";
export default getGlobalOverride(
callForHelmReleaseUpdateInjectable,
() => () => {
throw new Error(
"Tried to call for helm release update without explicit override.",
);
},
);
@@ -1,50 +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 { apiBase } from "../../../../../common/k8s-api";
import { endpoint } from "../../../../../common/k8s-api/endpoints/helm-releases.api";
import yaml from "js-yaml";
interface HelmReleaseUpdatePayload {
repo: string;
chart: string;
version: string;
values: string;
}
export type CallForHelmReleaseUpdate = (
name: string,
namespace: string,
payload: HelmReleaseUpdatePayload
) => Promise<{ updateWasSuccessful: true } | { updateWasSuccessful: false; error: unknown }>;
const callForHelmReleaseUpdateInjectable = getInjectable({
id: "call-for-helm-release-update",
instantiate:
(): CallForHelmReleaseUpdate => async (name, namespace, payload) => {
const { repo, chart: rawChart, values: rawValues, ...data } = payload;
const chart = `${repo}/${rawChart}`;
const values = yaml.load(rawValues);
try {
await apiBase.put(endpoint({ name, namespace }), {
data: {
chart,
values,
...data,
},
});
} catch (e) {
return { updateWasSuccessful: false, error: e };
}
return { updateWasSuccessful: true };
},
causesSideEffects: true,
});
export default callForHelmReleaseUpdateInjectable;
@@ -4,15 +4,15 @@
*/
import { getInjectable } from "@ogre-tools/injectable";
import releasesInjectable from "../releases.injectable";
import type { CallForHelmReleaseUpdate } from "./call-for-helm-release-update/call-for-helm-release-update.injectable";
import callForHelmReleaseUpdateInjectable from "./call-for-helm-release-update/call-for-helm-release-update.injectable";
import type { RequestHelmReleaseUpdate } from "../../../../common/k8s-api/endpoints/helm-releases.api/request-update.injectable";
import requestHelmReleaseUpdateInjectable from "../../../../common/k8s-api/endpoints/helm-releases.api/request-update.injectable";
const updateReleaseInjectable = getInjectable({
id: "update-release",
instantiate: (di): CallForHelmReleaseUpdate => {
instantiate: (di): RequestHelmReleaseUpdate => {
const releases = di.inject(releasesInjectable);
const callForHelmReleaseUpdate = di.inject(callForHelmReleaseUpdateInjectable);
const callForHelmReleaseUpdate = di.inject(requestHelmReleaseUpdateInjectable);
return async (
name,