mirror of
https://github.com/lensapp/lens.git
synced 2025-05-20 05:10:56 +00:00
Merge branch 'master' into fix-extension-preferences-title-same-as-tab-title
This commit is contained in:
commit
9ac0ef87c3
@ -3,7 +3,7 @@
|
|||||||
"productName": "OpenLens",
|
"productName": "OpenLens",
|
||||||
"description": "OpenLens - Open Source IDE for Kubernetes",
|
"description": "OpenLens - Open Source IDE for Kubernetes",
|
||||||
"homepage": "https://github.com/lensapp/lens",
|
"homepage": "https://github.com/lensapp/lens",
|
||||||
"version": "5.6.0-alpha.7",
|
"version": "6.0.0-alpha.0",
|
||||||
"main": "static/build/main.js",
|
"main": "static/build/main.js",
|
||||||
"copyright": "© 2022 OpenLens Authors",
|
"copyright": "© 2022 OpenLens Authors",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
|
|||||||
@ -83,7 +83,6 @@ if (basename(process.cwd()) === "scripts") {
|
|||||||
|
|
||||||
|
|
||||||
const currentVersion = new SemVer(readJsonSync("./package.json").version);
|
const currentVersion = new SemVer(readJsonSync("./package.json").version);
|
||||||
const currentVersionMilestone = `${currentVersion.major}.${currentVersion.minor}.${currentVersion.patch}`;
|
|
||||||
|
|
||||||
console.log(`current version: ${currentVersion.format()}`);
|
console.log(`current version: ${currentVersion.format()}`);
|
||||||
console.log("fetching tags...");
|
console.log("fetching tags...");
|
||||||
@ -111,6 +110,9 @@ npmVersionArgs.push("--git-tag-version false");
|
|||||||
execSync(npmVersionArgs.join(" "), { stdio: "ignore" });
|
execSync(npmVersionArgs.join(" "), { stdio: "ignore" });
|
||||||
|
|
||||||
const newVersion = new SemVer(readJsonSync("./package.json").version);
|
const newVersion = new SemVer(readJsonSync("./package.json").version);
|
||||||
|
const newVersionMilestone = `${newVersion.major}.${newVersion.minor}.${newVersion.patch}`;
|
||||||
|
|
||||||
|
console.log(`new version: ${newVersion.format()}`);
|
||||||
|
|
||||||
const getMergedPrsArgs = [
|
const getMergedPrsArgs = [
|
||||||
"gh",
|
"gh",
|
||||||
@ -124,7 +126,7 @@ const getMergedPrsArgs = [
|
|||||||
|
|
||||||
console.log("retreiving last 500 PRs to create release PR body...");
|
console.log("retreiving last 500 PRs to create release PR body...");
|
||||||
const mergedPrs = JSON.parse(execSync(getMergedPrsArgs.join(" "), { encoding: "utf-8" }));
|
const mergedPrs = JSON.parse(execSync(getMergedPrsArgs.join(" "), { encoding: "utf-8" }));
|
||||||
const milestoneRelevantPrs = mergedPrs.filter(pr => pr.milestone && pr.milestone.title === currentVersionMilestone);
|
const milestoneRelevantPrs = mergedPrs.filter(pr => pr.milestone?.title === newVersionMilestone);
|
||||||
const relaventPrsQuery = await Promise.all(
|
const relaventPrsQuery = await Promise.all(
|
||||||
milestoneRelevantPrs.map(async pr => ({
|
milestoneRelevantPrs.map(async pr => ({
|
||||||
pr,
|
pr,
|
||||||
|
|||||||
@ -16,7 +16,7 @@ import processCheckingForUpdatesInjectable from "../../main/application-update/c
|
|||||||
import type { DownloadPlatformUpdate } from "../../main/application-update/download-platform-update/download-platform-update.injectable";
|
import type { DownloadPlatformUpdate } from "../../main/application-update/download-platform-update/download-platform-update.injectable";
|
||||||
import downloadPlatformUpdateInjectable from "../../main/application-update/download-platform-update/download-platform-update.injectable";
|
import downloadPlatformUpdateInjectable from "../../main/application-update/download-platform-update/download-platform-update.injectable";
|
||||||
import quitAndInstallUpdateInjectable from "../../main/application-update/quit-and-install-update.injectable";
|
import quitAndInstallUpdateInjectable from "../../main/application-update/quit-and-install-update.injectable";
|
||||||
import appVersionInjectable from "../../common/get-configuration-file-model/app-version/app-version.injectable";
|
import appVersionInjectable from "../../common/vars/app-version.injectable";
|
||||||
import periodicalCheckForUpdatesInjectable from "../../main/application-update/periodical-check-for-updates/periodical-check-for-updates.injectable";
|
import periodicalCheckForUpdatesInjectable from "../../main/application-update/periodical-check-for-updates/periodical-check-for-updates.injectable";
|
||||||
import { advanceFakeTime, useFakeTime } from "../../common/test-utils/use-fake-time";
|
import { advanceFakeTime, useFakeTime } from "../../common/test-utils/use-fake-time";
|
||||||
|
|
||||||
|
|||||||
@ -13,7 +13,7 @@ import checkForPlatformUpdatesInjectable from "../../main/application-update/che
|
|||||||
import processCheckingForUpdatesInjectable from "../../main/application-update/check-for-updates/process-checking-for-updates.injectable";
|
import processCheckingForUpdatesInjectable from "../../main/application-update/check-for-updates/process-checking-for-updates.injectable";
|
||||||
import selectedUpdateChannelInjectable from "../../common/application-update/selected-update-channel/selected-update-channel.injectable";
|
import selectedUpdateChannelInjectable from "../../common/application-update/selected-update-channel/selected-update-channel.injectable";
|
||||||
import type { DiContainer } from "@ogre-tools/injectable";
|
import type { DiContainer } from "@ogre-tools/injectable";
|
||||||
import appVersionInjectable from "../../common/get-configuration-file-model/app-version/app-version.injectable";
|
import appVersionInjectable from "../../common/vars/app-version.injectable";
|
||||||
import { updateChannels } from "../../common/application-update/update-channels";
|
import { updateChannels } from "../../common/application-update/update-channels";
|
||||||
|
|
||||||
describe("downgrading version update", () => {
|
describe("downgrading version update", () => {
|
||||||
|
|||||||
@ -21,7 +21,7 @@ import type { IComputedValue } from "mobx";
|
|||||||
import setUpdateOnQuitInjectable from "../../main/electron-app/features/set-update-on-quit.injectable";
|
import setUpdateOnQuitInjectable from "../../main/electron-app/features/set-update-on-quit.injectable";
|
||||||
import showInfoNotificationInjectable from "../../renderer/components/notifications/show-info-notification.injectable";
|
import showInfoNotificationInjectable from "../../renderer/components/notifications/show-info-notification.injectable";
|
||||||
import processCheckingForUpdatesInjectable from "../../main/application-update/check-for-updates/process-checking-for-updates.injectable";
|
import processCheckingForUpdatesInjectable from "../../main/application-update/check-for-updates/process-checking-for-updates.injectable";
|
||||||
import appVersionInjectable from "../../common/get-configuration-file-model/app-version/app-version.injectable";
|
import appVersionInjectable from "../../common/vars/app-version.injectable";
|
||||||
|
|
||||||
describe("selection of update stability", () => {
|
describe("selection of update stability", () => {
|
||||||
let applicationBuilder: ApplicationBuilder;
|
let applicationBuilder: ApplicationBuilder;
|
||||||
|
|||||||
@ -11123,6 +11123,49 @@ exports[`installing helm chart from new tab given tab for installing chart was n
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<div
|
||||||
|
class="Animate slide-right Drawer ReleaseDetails dark right enter"
|
||||||
|
data-testid="helm-release-details-for-default/some-release"
|
||||||
|
style="--size: 725px; --enter-duration: 100ms; --leave-duration: 100ms;"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="drawer-wrapper flex column"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="drawer-title flex align-center"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="drawer-title-text flex gaps align-center"
|
||||||
|
>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
<i
|
||||||
|
class="Icon material interactive focusable"
|
||||||
|
data-testid="close-helm-release-detail"
|
||||||
|
tabindex="0"
|
||||||
|
tooltip="Close"
|
||||||
|
>
|
||||||
|
<span
|
||||||
|
class="icon"
|
||||||
|
data-icon-name="close"
|
||||||
|
>
|
||||||
|
close
|
||||||
|
</span>
|
||||||
|
</i>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
class="drawer-content flex column box grow"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="Spinner singleColor center"
|
||||||
|
data-testid="helm-release-detail-content-spinner"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
class="ResizingAnchor horizontal leading"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
</body>
|
</body>
|
||||||
`;
|
`;
|
||||||
|
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,604 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||||
|
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||||
|
*/
|
||||||
|
import type { ApplicationBuilder } from "../../renderer/components/test-utils/get-application-builder";
|
||||||
|
import { getApplicationBuilder } from "../../renderer/components/test-utils/get-application-builder";
|
||||||
|
import navigateToHelmReleasesInjectable from "../../common/front-end-routing/routes/cluster/helm/releases/navigate-to-helm-releases.injectable";
|
||||||
|
import type { RenderResult } from "@testing-library/react";
|
||||||
|
import { fireEvent } from "@testing-library/react";
|
||||||
|
import type { AsyncFnMock } from "@async-fn/jest";
|
||||||
|
import asyncFn from "@async-fn/jest";
|
||||||
|
import type { CallForHelmReleases } from "../../renderer/components/+helm-releases/call-for-helm-releases/call-for-helm-releases.injectable";
|
||||||
|
import callForHelmReleasesInjectable from "../../renderer/components/+helm-releases/call-for-helm-releases/call-for-helm-releases.injectable";
|
||||||
|
import namespaceStoreInjectable from "../../renderer/components/+namespaces/store.injectable";
|
||||||
|
import type { NamespaceStore } from "../../renderer/components/+namespaces/store";
|
||||||
|
import type { CallForHelmReleaseConfiguration } from "../../renderer/components/+helm-releases/release-details/release-details-model/call-for-helm-release-configuration/call-for-helm-release-configuration.injectable";
|
||||||
|
import callForHelmReleaseConfigurationInjectable from "../../renderer/components/+helm-releases/release-details/release-details-model/call-for-helm-release-configuration/call-for-helm-release-configuration.injectable";
|
||||||
|
import type { CallForHelmReleaseUpdate } from "../../renderer/components/+helm-releases/update-release/call-for-helm-release-update/call-for-helm-release-update.injectable";
|
||||||
|
import callForHelmReleaseUpdateInjectable from "../../renderer/components/+helm-releases/update-release/call-for-helm-release-update/call-for-helm-release-update.injectable";
|
||||||
|
import { useFakeTime } from "../../common/test-utils/use-fake-time";
|
||||||
|
import type { CallForHelmRelease, DetailedHelmRelease } from "../../renderer/components/+helm-releases/release-details/release-details-model/call-for-helm-release/call-for-helm-release.injectable";
|
||||||
|
import callForHelmReleaseInjectable from "../../renderer/components/+helm-releases/release-details/release-details-model/call-for-helm-release/call-for-helm-release.injectable";
|
||||||
|
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";
|
||||||
|
|
||||||
|
// TODO: Make tooltips free of side effects by making it deterministic
|
||||||
|
jest.mock("../../renderer/components/tooltip/withTooltip", () => ({
|
||||||
|
withTooltip: (target: any) => target,
|
||||||
|
}));
|
||||||
|
|
||||||
|
describe("showing details for helm release", () => {
|
||||||
|
let builder: ApplicationBuilder;
|
||||||
|
let callForHelmReleasesMock: AsyncFnMock<CallForHelmReleases>;
|
||||||
|
let callForHelmReleaseMock: AsyncFnMock<CallForHelmRelease>;
|
||||||
|
let callForHelmReleaseConfigurationMock: AsyncFnMock<CallForHelmReleaseConfiguration>;
|
||||||
|
let callForHelmReleaseUpdateMock: AsyncFnMock<CallForHelmReleaseUpdate>;
|
||||||
|
let showSuccessNotificationMock: jest.Mock;
|
||||||
|
let showCheckedErrorNotificationMock: jest.Mock;
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
useFakeTime("2015-10-21T07:28:00Z");
|
||||||
|
|
||||||
|
builder = getApplicationBuilder();
|
||||||
|
|
||||||
|
builder.setEnvironmentToClusterFrame();
|
||||||
|
|
||||||
|
callForHelmReleasesMock = asyncFn();
|
||||||
|
callForHelmReleaseMock = asyncFn();
|
||||||
|
callForHelmReleaseConfigurationMock = asyncFn();
|
||||||
|
callForHelmReleaseUpdateMock = asyncFn();
|
||||||
|
|
||||||
|
showSuccessNotificationMock = jest.fn();
|
||||||
|
showCheckedErrorNotificationMock = jest.fn();
|
||||||
|
|
||||||
|
builder.beforeApplicationStart(({ rendererDi }) => {
|
||||||
|
rendererDi.override(
|
||||||
|
getRandomUpgradeChartTabIdInjectable,
|
||||||
|
() => () => "some-tab-id",
|
||||||
|
);
|
||||||
|
|
||||||
|
rendererDi.override(
|
||||||
|
showSuccessNotificationInjectable,
|
||||||
|
() => showSuccessNotificationMock,
|
||||||
|
);
|
||||||
|
|
||||||
|
rendererDi.override(
|
||||||
|
showCheckedErrorInjectable,
|
||||||
|
() => showCheckedErrorNotificationMock,
|
||||||
|
);
|
||||||
|
|
||||||
|
rendererDi.override(
|
||||||
|
callForHelmReleasesInjectable,
|
||||||
|
() => callForHelmReleasesMock,
|
||||||
|
);
|
||||||
|
|
||||||
|
rendererDi.override(
|
||||||
|
callForHelmReleaseInjectable,
|
||||||
|
() => callForHelmReleaseMock,
|
||||||
|
);
|
||||||
|
|
||||||
|
rendererDi.override(
|
||||||
|
callForHelmReleaseConfigurationInjectable,
|
||||||
|
() => callForHelmReleaseConfigurationMock,
|
||||||
|
);
|
||||||
|
|
||||||
|
rendererDi.override(
|
||||||
|
callForHelmReleaseUpdateInjectable,
|
||||||
|
() => callForHelmReleaseUpdateMock,
|
||||||
|
);
|
||||||
|
|
||||||
|
rendererDi.override(
|
||||||
|
namespaceStoreInjectable,
|
||||||
|
() =>
|
||||||
|
({
|
||||||
|
contextNamespaces: ["some-namespace", "some-other-namespace"],
|
||||||
|
items: [],
|
||||||
|
selectNamespaces: () => {},
|
||||||
|
} as unknown as NamespaceStore),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("given application is started", () => {
|
||||||
|
let rendered: RenderResult;
|
||||||
|
|
||||||
|
beforeEach(async () => {
|
||||||
|
rendered = await builder.render();
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("when navigating to helm releases", () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
const rendererDi = builder.dis.rendererDi;
|
||||||
|
|
||||||
|
const navigateToHelmReleases = rendererDi.inject(
|
||||||
|
navigateToHelmReleasesInjectable,
|
||||||
|
);
|
||||||
|
|
||||||
|
navigateToHelmReleases();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("renders", () => {
|
||||||
|
expect(rendered.baseElement).toMatchSnapshot();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("calls for releases for each selected namespace", () => {
|
||||||
|
expect(callForHelmReleasesMock.mock.calls).toEqual([
|
||||||
|
["some-namespace"],
|
||||||
|
["some-other-namespace"],
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("shows spinner", () => {
|
||||||
|
expect(
|
||||||
|
rendered.getByTestId("helm-releases-spinner"),
|
||||||
|
).toBeInTheDocument();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("when releases resolve but there is none, renders", async () => {
|
||||||
|
await callForHelmReleasesMock.resolve([]);
|
||||||
|
await callForHelmReleasesMock.resolve([]);
|
||||||
|
|
||||||
|
expect(rendered.baseElement).toMatchSnapshot();
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("when releases resolve", () => {
|
||||||
|
beforeEach(async () => {
|
||||||
|
await callForHelmReleasesMock.resolveSpecific(
|
||||||
|
([namespace]) => namespace === "some-namespace",
|
||||||
|
[
|
||||||
|
{
|
||||||
|
appVersion: "some-app-version",
|
||||||
|
name: "some-name",
|
||||||
|
namespace: "some-namespace",
|
||||||
|
chart: "some-chart",
|
||||||
|
status: "some-status",
|
||||||
|
updated: "some-updated",
|
||||||
|
revision: "some-revision",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
);
|
||||||
|
|
||||||
|
await callForHelmReleasesMock.resolveSpecific(
|
||||||
|
([namespace]) => namespace === "some-other-namespace",
|
||||||
|
[
|
||||||
|
{
|
||||||
|
appVersion: "some-other-app-version",
|
||||||
|
name: "some-other-name",
|
||||||
|
namespace: "some-other-namespace",
|
||||||
|
chart: "some-other-chart",
|
||||||
|
status: "some-other-status",
|
||||||
|
updated: "some-other-updated",
|
||||||
|
revision: "some-other-revision",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("renders", () => {
|
||||||
|
expect(rendered.baseElement).toMatchSnapshot();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("does not show spinner anymore", () => {
|
||||||
|
expect(
|
||||||
|
rendered.queryByTestId("helm-releases-spinner"),
|
||||||
|
).not.toBeInTheDocument();
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("when selecting release to see details", () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
const row = rendered.getByTestId(
|
||||||
|
"helm-release-row-for-some-namespace/some-name",
|
||||||
|
);
|
||||||
|
|
||||||
|
fireEvent.click(row);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("renders", () => {
|
||||||
|
expect(rendered.baseElement).toMatchSnapshot();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("opens the details", () => {
|
||||||
|
expect(
|
||||||
|
rendered.getByTestId("helm-release-details-for-some-namespace/some-name"),
|
||||||
|
).toBeInTheDocument();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("calls for release", () => {
|
||||||
|
expect(callForHelmReleaseMock).toHaveBeenCalledWith(
|
||||||
|
"some-name",
|
||||||
|
"some-namespace",
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("shows spinner", () => {
|
||||||
|
expect(
|
||||||
|
rendered.getByTestId("helm-release-detail-content-spinner"),
|
||||||
|
).toBeInTheDocument();
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("when opening details for second release", () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
callForHelmReleaseMock.mockClear();
|
||||||
|
|
||||||
|
const row = rendered.getByTestId(
|
||||||
|
"helm-release-row-for-some-other-namespace/some-other-name",
|
||||||
|
);
|
||||||
|
|
||||||
|
fireEvent.click(row);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("renders", () => {
|
||||||
|
expect(rendered.baseElement).toMatchSnapshot();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("calls for another release", () => {
|
||||||
|
expect(callForHelmReleaseMock).toHaveBeenCalledWith(
|
||||||
|
"some-other-name",
|
||||||
|
"some-other-namespace",
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("closes details for first release", () => {
|
||||||
|
expect(
|
||||||
|
rendered.queryByTestId("helm-release-details-for-some-namespace/some-name"),
|
||||||
|
).not.toBeInTheDocument();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("opens details for second release", () => {
|
||||||
|
expect(
|
||||||
|
rendered.getByTestId("helm-release-details-for-some-other-namespace/some-other-name"),
|
||||||
|
).toBeInTheDocument();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("when details is closed", () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
const closeButton = rendered.getByTestId(
|
||||||
|
"close-helm-release-detail",
|
||||||
|
);
|
||||||
|
|
||||||
|
fireEvent.click(closeButton);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("renders", () => {
|
||||||
|
expect(rendered.baseElement).toMatchSnapshot();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("closes the details", () => {
|
||||||
|
expect(
|
||||||
|
rendered.queryByTestId("helm-release-details-for-some-namespace/some-name"),
|
||||||
|
).not.toBeInTheDocument();
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("when opening details for same release", () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
callForHelmReleaseMock.mockClear();
|
||||||
|
|
||||||
|
const row = rendered.getByTestId(
|
||||||
|
"helm-release-row-for-some-namespace/some-name",
|
||||||
|
);
|
||||||
|
|
||||||
|
fireEvent.click(row);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("renders", () => {
|
||||||
|
expect(rendered.baseElement).toMatchSnapshot();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("does not reload", () => {
|
||||||
|
expect(callForHelmReleaseMock).not.toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it("when release resolve with no data, renders", async () => {
|
||||||
|
await callForHelmReleaseMock.resolve(undefined);
|
||||||
|
|
||||||
|
expect(rendered.baseElement).toMatchSnapshot();
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("when details resolve", () => {
|
||||||
|
beforeEach(async () => {
|
||||||
|
await callForHelmReleaseMock.resolve(detailedReleaseFake);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("renders", () => {
|
||||||
|
expect(rendered.baseElement).toMatchSnapshot();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("calls for release configuration", () => {
|
||||||
|
expect(callForHelmReleaseConfigurationMock).toHaveBeenCalledWith(
|
||||||
|
"some-name",
|
||||||
|
"some-namespace",
|
||||||
|
true,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("when configuration resolves", () => {
|
||||||
|
beforeEach(async () => {
|
||||||
|
await callForHelmReleaseConfigurationMock.resolve(
|
||||||
|
"some-configuration",
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("renders", () => {
|
||||||
|
expect(rendered.baseElement).toMatchSnapshot();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("does not have tab for upgrading chart yet", () => {
|
||||||
|
expect(
|
||||||
|
rendered.queryByTestId("dock-tab-for-some-tab-id"),
|
||||||
|
).not.toBeInTheDocument();
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("when selecting to upgrade chart", () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
const upgradeButton = rendered.getByTestId(
|
||||||
|
"helm-release-upgrade-button",
|
||||||
|
);
|
||||||
|
|
||||||
|
fireEvent.click(upgradeButton);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("renders", () => {
|
||||||
|
expect(rendered.baseElement).toMatchSnapshot();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("opens tab for upgrading chart", () => {
|
||||||
|
expect(
|
||||||
|
rendered.getByTestId("dock-tab-for-some-tab-id"),
|
||||||
|
).toBeInTheDocument();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("closes the details", () => {
|
||||||
|
expect(
|
||||||
|
rendered.queryByTestId("helm-release-details-for-some-namespace/some-name"),
|
||||||
|
).not.toBeInTheDocument();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("when changing the configuration", () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
const configuration = rendered.getByTestId(
|
||||||
|
"monaco-editor-for-helm-release-configuration",
|
||||||
|
);
|
||||||
|
|
||||||
|
fireEvent.change(configuration, {
|
||||||
|
target: { value: "some-new-configuration" },
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it("renders", () => {
|
||||||
|
expect(rendered.baseElement).toMatchSnapshot();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("has the configuration", () => {
|
||||||
|
const input = rendered.getByTestId(
|
||||||
|
"monaco-editor-for-helm-release-configuration",
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(input).toHaveValue("some-new-configuration");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("does not save changes yet", () => {
|
||||||
|
expect(callForHelmReleaseUpdateMock).not.toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("when toggling to see only user defined values", () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
callForHelmReleaseConfigurationMock.mockClear();
|
||||||
|
|
||||||
|
const toggle = rendered.getByTestId(
|
||||||
|
"user-supplied-values-only-checkbox",
|
||||||
|
);
|
||||||
|
|
||||||
|
fireEvent.click(toggle);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("calls for only user defined configuration", () => {
|
||||||
|
expect(callForHelmReleaseConfigurationMock).toHaveBeenCalledWith(
|
||||||
|
"some-name",
|
||||||
|
"some-namespace",
|
||||||
|
false,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("when configuration resolves", () => {
|
||||||
|
beforeEach(async () => {
|
||||||
|
await callForHelmReleaseConfigurationMock.resolve(
|
||||||
|
"some-other-configuration",
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("renders", () => {
|
||||||
|
expect(rendered.baseElement).toMatchSnapshot();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("overrides the user inputted configuration with new configuration", () => {
|
||||||
|
const input = rendered.getByTestId(
|
||||||
|
"monaco-editor-for-helm-release-configuration",
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(input).toHaveValue("some-other-configuration");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("when toggling again, calls for all configuration", () => {
|
||||||
|
callForHelmReleaseConfigurationMock.mockClear();
|
||||||
|
|
||||||
|
const toggle = rendered.getByTestId(
|
||||||
|
"user-supplied-values-only-checkbox",
|
||||||
|
);
|
||||||
|
|
||||||
|
fireEvent.click(toggle);
|
||||||
|
|
||||||
|
expect(callForHelmReleaseConfigurationMock).toHaveBeenCalledWith(
|
||||||
|
"some-name",
|
||||||
|
"some-namespace",
|
||||||
|
true,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("when saving", () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
const saveButton = rendered.getByTestId(
|
||||||
|
"helm-release-configuration-save-button",
|
||||||
|
);
|
||||||
|
|
||||||
|
fireEvent.click(saveButton);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("renders", () => {
|
||||||
|
expect(rendered.baseElement).toMatchSnapshot();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("calls for update", () => {
|
||||||
|
expect(callForHelmReleaseUpdateMock).toHaveBeenCalledWith(
|
||||||
|
"some-name",
|
||||||
|
"some-namespace",
|
||||||
|
|
||||||
|
{
|
||||||
|
chart: "some-chart",
|
||||||
|
repo: "",
|
||||||
|
values: "some-new-configuration",
|
||||||
|
version: "",
|
||||||
|
},
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("shows spinner", () => {
|
||||||
|
const saveButton = rendered.getByTestId(
|
||||||
|
"helm-release-configuration-save-button",
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(saveButton).toHaveClass("waiting");
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("when update resolves with success", () => {
|
||||||
|
beforeEach(async () => {
|
||||||
|
callForHelmReleasesMock.mockClear();
|
||||||
|
callForHelmReleaseConfigurationMock.mockClear();
|
||||||
|
|
||||||
|
await callForHelmReleaseUpdateMock.resolve({
|
||||||
|
updateWasSuccessful: true,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it("renders", () => {
|
||||||
|
expect(rendered.baseElement).toMatchSnapshot();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("does not show spinner anymore", () => {
|
||||||
|
const saveButton = rendered.getByTestId(
|
||||||
|
"helm-release-configuration-save-button",
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(saveButton).not.toHaveClass("waiting");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("reloads the configuration", () => {
|
||||||
|
expect(callForHelmReleaseConfigurationMock).toHaveBeenCalledWith(
|
||||||
|
"some-name",
|
||||||
|
"some-namespace",
|
||||||
|
true,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("shows success notification", () => {
|
||||||
|
expect(showSuccessNotificationMock).toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("does not show error notification", () => {
|
||||||
|
expect(showCheckedErrorNotificationMock).not.toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("when update resolves with failure", () => {
|
||||||
|
beforeEach(async () => {
|
||||||
|
callForHelmReleasesMock.mockClear();
|
||||||
|
callForHelmReleaseConfigurationMock.mockClear();
|
||||||
|
|
||||||
|
await callForHelmReleaseUpdateMock.resolve({
|
||||||
|
updateWasSuccessful: false,
|
||||||
|
error: "some-error",
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it("renders", () => {
|
||||||
|
expect(rendered.baseElement).toMatchSnapshot();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("does not show spinner anymore", () => {
|
||||||
|
const saveButton = rendered.getByTestId(
|
||||||
|
"helm-release-configuration-save-button",
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(saveButton).not.toHaveClass("waiting");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("does not reload the configuration", () => {
|
||||||
|
expect(callForHelmReleaseConfigurationMock).not.toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("does not show success notification", () => {
|
||||||
|
expect(showSuccessNotificationMock).not.toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("shows error notification", () => {
|
||||||
|
expect(showCheckedErrorNotificationMock).toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
const detailedReleaseFake: DetailedHelmRelease = {
|
||||||
|
release: {
|
||||||
|
appVersion: "some-app-version",
|
||||||
|
chart: "some-chart",
|
||||||
|
status: "some-status",
|
||||||
|
updated: "some-updated",
|
||||||
|
revision: "some-revision",
|
||||||
|
name: "some-name",
|
||||||
|
namespace: "some-namespace",
|
||||||
|
},
|
||||||
|
|
||||||
|
details: {
|
||||||
|
name: "some-name",
|
||||||
|
namespace: "some-namespace",
|
||||||
|
version: "some-version",
|
||||||
|
config: "some-config",
|
||||||
|
manifest: "some-manifest",
|
||||||
|
|
||||||
|
info: {
|
||||||
|
deleted: "some-deleted",
|
||||||
|
description: "some-description",
|
||||||
|
first_deployed: "some-first-deployed",
|
||||||
|
last_deployed: "some-last-deployed",
|
||||||
|
notes: "some-notes",
|
||||||
|
status: "some-status",
|
||||||
|
},
|
||||||
|
|
||||||
|
resources: [
|
||||||
|
{
|
||||||
|
kind: "some-kind",
|
||||||
|
apiVersion: "some-api-version",
|
||||||
|
metadata: {
|
||||||
|
uid: "some-uid",
|
||||||
|
name: "some-resource",
|
||||||
|
namespace: "some-namespace",
|
||||||
|
creationTimestamp: "2015-10-22T07:28:00Z",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
};
|
||||||
@ -7,14 +7,9 @@ import mockFs from "mock-fs";
|
|||||||
import { BaseStore } from "../base-store";
|
import { BaseStore } from "../base-store";
|
||||||
import { action, comparer, makeObservable, observable, toJS } from "mobx";
|
import { action, comparer, makeObservable, observable, toJS } from "mobx";
|
||||||
import { readFileSync } from "fs";
|
import { readFileSync } from "fs";
|
||||||
|
import directoryForUserDataInjectable from "../app-paths/directory-for-user-data/directory-for-user-data.injectable";
|
||||||
import directoryForUserDataInjectable
|
|
||||||
from "../app-paths/directory-for-user-data/directory-for-user-data.injectable";
|
|
||||||
import { getDiForUnitTesting } from "../../main/getDiForUnitTesting";
|
import { getDiForUnitTesting } from "../../main/getDiForUnitTesting";
|
||||||
import getConfigurationFileModelInjectable
|
import getConfigurationFileModelInjectable from "../get-configuration-file-model/get-configuration-file-model.injectable";
|
||||||
from "../get-configuration-file-model/get-configuration-file-model.injectable";
|
|
||||||
import appVersionInjectable
|
|
||||||
from "../get-configuration-file-model/app-version/app-version.injectable";
|
|
||||||
|
|
||||||
jest.mock("electron", () => ({
|
jest.mock("electron", () => ({
|
||||||
ipcMain: {
|
ipcMain: {
|
||||||
@ -86,7 +81,6 @@ describe("BaseStore", () => {
|
|||||||
|
|
||||||
mainDi.override(directoryForUserDataInjectable, () => "some-user-data-directory");
|
mainDi.override(directoryForUserDataInjectable, () => "some-user-data-directory");
|
||||||
mainDi.permitSideEffects(getConfigurationFileModelInjectable);
|
mainDi.permitSideEffects(getConfigurationFileModelInjectable);
|
||||||
mainDi.permitSideEffects(appVersionInjectable);
|
|
||||||
|
|
||||||
TestStore.resetInstance();
|
TestStore.resetInstance();
|
||||||
|
|
||||||
|
|||||||
@ -18,7 +18,7 @@ import { createClusterInjectionToken } from "../cluster/create-cluster-injection
|
|||||||
import directoryForUserDataInjectable from "../app-paths/directory-for-user-data/directory-for-user-data.injectable";
|
import directoryForUserDataInjectable from "../app-paths/directory-for-user-data/directory-for-user-data.injectable";
|
||||||
import { getDiForUnitTesting } from "../../main/getDiForUnitTesting";
|
import { getDiForUnitTesting } from "../../main/getDiForUnitTesting";
|
||||||
import getConfigurationFileModelInjectable from "../get-configuration-file-model/get-configuration-file-model.injectable";
|
import getConfigurationFileModelInjectable from "../get-configuration-file-model/get-configuration-file-model.injectable";
|
||||||
import appVersionInjectable from "../get-configuration-file-model/app-version/app-version.injectable";
|
import appVersionInjectable from "../vars/app-version.injectable";
|
||||||
import assert from "assert";
|
import assert from "assert";
|
||||||
import directoryForTempInjectable from "../app-paths/directory-for-temp/directory-for-temp.injectable";
|
import directoryForTempInjectable from "../app-paths/directory-for-temp/directory-for-temp.injectable";
|
||||||
import kubectlBinaryNameInjectable from "../../main/kubectl/binary-name.injectable";
|
import kubectlBinaryNameInjectable from "../../main/kubectl/binary-name.injectable";
|
||||||
@ -93,7 +93,6 @@ describe("cluster-store", () => {
|
|||||||
mainDi.override(normalizedPlatformInjectable, () => "darwin");
|
mainDi.override(normalizedPlatformInjectable, () => "darwin");
|
||||||
|
|
||||||
mainDi.permitSideEffects(getConfigurationFileModelInjectable);
|
mainDi.permitSideEffects(getConfigurationFileModelInjectable);
|
||||||
mainDi.permitSideEffects(appVersionInjectable);
|
|
||||||
mainDi.permitSideEffects(clusterStoreInjectable);
|
mainDi.permitSideEffects(clusterStoreInjectable);
|
||||||
mainDi.permitSideEffects(fsInjectable);
|
mainDi.permitSideEffects(fsInjectable);
|
||||||
|
|
||||||
|
|||||||
@ -8,7 +8,7 @@ import mockFs from "mock-fs";
|
|||||||
import type { CatalogEntity, CatalogEntityData, CatalogEntityKindData } from "../catalog";
|
import type { CatalogEntity, CatalogEntityData, CatalogEntityKindData } from "../catalog";
|
||||||
import { getDiForUnitTesting } from "../../main/getDiForUnitTesting";
|
import { getDiForUnitTesting } from "../../main/getDiForUnitTesting";
|
||||||
import getConfigurationFileModelInjectable from "../get-configuration-file-model/get-configuration-file-model.injectable";
|
import getConfigurationFileModelInjectable from "../get-configuration-file-model/get-configuration-file-model.injectable";
|
||||||
import appVersionInjectable from "../get-configuration-file-model/app-version/app-version.injectable";
|
import appVersionInjectable from "../vars/app-version.injectable";
|
||||||
import type { DiContainer } from "@ogre-tools/injectable";
|
import type { DiContainer } from "@ogre-tools/injectable";
|
||||||
import hotbarStoreInjectable from "../hotbars/store.injectable";
|
import hotbarStoreInjectable from "../hotbars/store.injectable";
|
||||||
import type { HotbarStore } from "../hotbars/store";
|
import type { HotbarStore } from "../hotbars/store";
|
||||||
@ -113,7 +113,6 @@ describe("HotbarStore", () => {
|
|||||||
]));
|
]));
|
||||||
|
|
||||||
di.permitSideEffects(getConfigurationFileModelInjectable);
|
di.permitSideEffects(getConfigurationFileModelInjectable);
|
||||||
di.permitSideEffects(appVersionInjectable);
|
|
||||||
di.permitSideEffects(hotbarStoreInjectable);
|
di.permitSideEffects(hotbarStoreInjectable);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@ -34,7 +34,7 @@ import { defaultThemeId } from "../vars";
|
|||||||
import writeFileInjectable from "../fs/write-file.injectable";
|
import writeFileInjectable from "../fs/write-file.injectable";
|
||||||
import { getDiForUnitTesting } from "../../main/getDiForUnitTesting";
|
import { getDiForUnitTesting } from "../../main/getDiForUnitTesting";
|
||||||
import getConfigurationFileModelInjectable from "../get-configuration-file-model/get-configuration-file-model.injectable";
|
import getConfigurationFileModelInjectable from "../get-configuration-file-model/get-configuration-file-model.injectable";
|
||||||
import appVersionInjectable from "../get-configuration-file-model/app-version/app-version.injectable";
|
import appVersionInjectable from "../vars/app-version.injectable";
|
||||||
|
|
||||||
console = new Console(stdout, stderr);
|
console = new Console(stdout, stderr);
|
||||||
|
|
||||||
@ -50,8 +50,6 @@ describe("user store tests", () => {
|
|||||||
di.override(writeFileInjectable, () => () => Promise.resolve());
|
di.override(writeFileInjectable, () => () => Promise.resolve());
|
||||||
di.override(directoryForUserDataInjectable, () => "some-directory-for-user-data");
|
di.override(directoryForUserDataInjectable, () => "some-directory-for-user-data");
|
||||||
di.permitSideEffects(getConfigurationFileModelInjectable);
|
di.permitSideEffects(getConfigurationFileModelInjectable);
|
||||||
|
|
||||||
di.permitSideEffects(appVersionInjectable);
|
|
||||||
di.permitSideEffects(userStoreInjectable);
|
di.permitSideEffects(userStoreInjectable);
|
||||||
|
|
||||||
di.unoverride(userStoreInjectable);
|
di.unoverride(userStoreInjectable);
|
||||||
|
|||||||
@ -3,8 +3,7 @@
|
|||||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||||
*/
|
*/
|
||||||
import { getInjectable } from "@ogre-tools/injectable";
|
import { getInjectable } from "@ogre-tools/injectable";
|
||||||
import { SemVer } from "semver";
|
import appSemanticVersionInjectable from "../../vars/app-semantic-version.injectable";
|
||||||
import appVersionInjectable from "../../get-configuration-file-model/app-version/app-version.injectable";
|
|
||||||
import type { UpdateChannelId } from "../update-channels";
|
import type { UpdateChannelId } from "../update-channels";
|
||||||
import { updateChannels } from "../update-channels";
|
import { updateChannels } from "../update-channels";
|
||||||
|
|
||||||
@ -12,12 +11,11 @@ const defaultUpdateChannelInjectable = getInjectable({
|
|||||||
id: "default-update-channel",
|
id: "default-update-channel",
|
||||||
|
|
||||||
instantiate: (di) => {
|
instantiate: (di) => {
|
||||||
const appVersion = di.inject(appVersionInjectable);
|
const appSemanticVersion = di.inject(appSemanticVersionInjectable);
|
||||||
|
const currentReleaseChannel = appSemanticVersion.prerelease[0]?.toString();
|
||||||
|
|
||||||
const currentReleaseChannel = new SemVer(appVersion).prerelease[0]?.toString() as UpdateChannelId;
|
if (currentReleaseChannel in updateChannels) {
|
||||||
|
return updateChannels[currentReleaseChannel as UpdateChannelId];
|
||||||
if (currentReleaseChannel && updateChannels[currentReleaseChannel]) {
|
|
||||||
return updateChannels[currentReleaseChannel];
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return updateChannels.latest;
|
return updateChannels.latest;
|
||||||
|
|||||||
@ -19,7 +19,7 @@ import { kebabCase } from "lodash";
|
|||||||
import { getLegacyGlobalDiForExtensionApi } from "../extensions/as-legacy-globals-for-extension-api/legacy-global-di-for-extension-api";
|
import { getLegacyGlobalDiForExtensionApi } from "../extensions/as-legacy-globals-for-extension-api/legacy-global-di-for-extension-api";
|
||||||
import directoryForUserDataInjectable from "./app-paths/directory-for-user-data/directory-for-user-data.injectable";
|
import directoryForUserDataInjectable from "./app-paths/directory-for-user-data/directory-for-user-data.injectable";
|
||||||
import getConfigurationFileModelInjectable from "./get-configuration-file-model/get-configuration-file-model.injectable";
|
import getConfigurationFileModelInjectable from "./get-configuration-file-model/get-configuration-file-model.injectable";
|
||||||
import appVersionInjectable from "./get-configuration-file-model/app-version/app-version.injectable";
|
import appVersionInjectable from "./vars/app-version.injectable";
|
||||||
|
|
||||||
export interface BaseStoreParams<T> extends ConfOptions<T> {
|
export interface BaseStoreParams<T> extends ConfOptions<T> {
|
||||||
syncOptions?: {
|
syncOptions?: {
|
||||||
|
|||||||
@ -3,48 +3,11 @@
|
|||||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import yaml from "js-yaml";
|
|
||||||
import { formatDuration } from "../../utils";
|
|
||||||
import capitalize from "lodash/capitalize";
|
|
||||||
import { apiBase } from "../index";
|
import { apiBase } from "../index";
|
||||||
import { helmChartStore } from "../../../renderer/components/+helm-charts/helm-chart.store";
|
|
||||||
import type { ItemObject } from "../../item.store";
|
import type { ItemObject } from "../../item.store";
|
||||||
import type { JsonApiData } from "../json-api";
|
import type { JsonApiData } from "../json-api";
|
||||||
import { buildURLPositional } from "../../utils/buildUrl";
|
import { buildURLPositional } from "../../utils/buildUrl";
|
||||||
import type { KubeJsonApiData } from "../kube-json-api";
|
import type { HelmReleaseDetails } from "../../../renderer/components/+helm-releases/release-details/release-details-model/call-for-helm-release/call-for-helm-release-details/call-for-helm-release-details.injectable";
|
||||||
|
|
||||||
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 interface HelmReleaseCreatePayload {
|
|
||||||
name?: string;
|
|
||||||
repo: string;
|
|
||||||
chart: string;
|
|
||||||
namespace: string;
|
|
||||||
version: string;
|
|
||||||
values: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface HelmReleaseUpdatePayload {
|
|
||||||
repo: string;
|
|
||||||
chart: string;
|
|
||||||
version: string;
|
|
||||||
values: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface HelmReleaseUpdateDetails {
|
export interface HelmReleaseUpdateDetails {
|
||||||
log: string;
|
log: string;
|
||||||
@ -69,47 +32,7 @@ interface EndpointQuery {
|
|||||||
all?: boolean;
|
all?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
const endpoint = buildURLPositional<EndpointParams, EndpointQuery>("/v2/releases/:namespace?/:name?/:route?");
|
export const endpoint = buildURLPositional<EndpointParams, EndpointQuery>("/v2/releases/:namespace?/:name?/:route?");
|
||||||
|
|
||||||
export async function listReleases(namespace?: string): Promise<HelmRelease[]> {
|
|
||||||
const releases = await apiBase.get<HelmReleaseDto[]>(endpoint({ namespace }));
|
|
||||||
|
|
||||||
return releases.map(toHelmRelease);
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function getRelease(name: string, namespace: string): Promise<HelmReleaseDetails> {
|
|
||||||
const path = endpoint({ name, namespace });
|
|
||||||
|
|
||||||
return apiBase.get(path);
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function createRelease(payload: HelmReleaseCreatePayload): Promise<HelmReleaseUpdateDetails> {
|
|
||||||
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,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function updateRelease(name: string, namespace: string, payload: HelmReleaseUpdatePayload): Promise<HelmReleaseUpdateDetails> {
|
|
||||||
const { repo, chart: rawChart, values: rawValues, ...data } = payload;
|
|
||||||
const chart = `${repo}/${rawChart}`;
|
|
||||||
const values = yaml.load(rawValues);
|
|
||||||
|
|
||||||
return apiBase.put(endpoint({ name, namespace }), {
|
|
||||||
data: {
|
|
||||||
chart,
|
|
||||||
values,
|
|
||||||
...data,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function deleteRelease(name: string, namespace: string): Promise<JsonApiData> {
|
export async function deleteRelease(name: string, namespace: string): Promise<JsonApiData> {
|
||||||
const path = endpoint({ name, namespace });
|
const path = endpoint({ name, namespace });
|
||||||
@ -139,7 +62,7 @@ export async function rollbackRelease(name: string, namespace: string, revision:
|
|||||||
return apiBase.put(path, { data });
|
return apiBase.put(path, { data });
|
||||||
}
|
}
|
||||||
|
|
||||||
interface HelmReleaseDto {
|
export interface HelmReleaseDto {
|
||||||
appVersion: string;
|
appVersion: string;
|
||||||
name: string;
|
name: string;
|
||||||
namespace: string;
|
namespace: string;
|
||||||
@ -158,70 +81,3 @@ export interface HelmRelease extends HelmReleaseDto, ItemObject {
|
|||||||
getUpdated: (humanize?: boolean, compact?: boolean) => string | number;
|
getUpdated: (humanize?: boolean, compact?: boolean) => string | number;
|
||||||
getRepo: () => Promise<string>;
|
getRepo: () => Promise<string>;
|
||||||
}
|
}
|
||||||
|
|
||||||
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 : "";
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|||||||
13
src/common/test-utils/get-global-override.ts
Normal file
13
src/common/test-utils/get-global-override.ts
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||||
|
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||||
|
*/
|
||||||
|
import type { Injectable } from "@ogre-tools/injectable";
|
||||||
|
|
||||||
|
export const getGlobalOverride = <T extends Injectable<any, any, any>>(
|
||||||
|
injectable: T,
|
||||||
|
overridingInstantiate: T["instantiate"],
|
||||||
|
) => ({
|
||||||
|
injectable,
|
||||||
|
overridingInstantiate,
|
||||||
|
});
|
||||||
@ -5,7 +5,6 @@
|
|||||||
|
|
||||||
// App's common configuration for any process (main, renderer, build pipeline, etc.)
|
// App's common configuration for any process (main, renderer, build pipeline, etc.)
|
||||||
import path from "path";
|
import path from "path";
|
||||||
import { SemVer } from "semver";
|
|
||||||
import packageInfo from "../../package.json";
|
import packageInfo from "../../package.json";
|
||||||
import type { ThemeId } from "../renderer/themes/store";
|
import type { ThemeId } from "../renderer/themes/store";
|
||||||
import { lazyInitialized } from "./utils/lazy-initialized";
|
import { lazyInitialized } from "./utils/lazy-initialized";
|
||||||
@ -139,7 +138,6 @@ export const lensTwitterWeblinkId = "lens-twitter-link";
|
|||||||
export const lensBlogWeblinkId = "lens-blog-link";
|
export const lensBlogWeblinkId = "lens-blog-link";
|
||||||
export const kubernetesDocumentationWeblinkId = "kubernetes-documentation-link";
|
export const kubernetesDocumentationWeblinkId = "kubernetes-documentation-link";
|
||||||
|
|
||||||
export const appSemVer = new SemVer(packageInfo.version);
|
|
||||||
export const docsUrl = "https://docs.k8slens.dev/main" as string;
|
export const docsUrl = "https://docs.k8slens.dev/main" as string;
|
||||||
|
|
||||||
export const sentryDsn = packageInfo.config?.sentryDsn ?? "";
|
export const sentryDsn = packageInfo.config?.sentryDsn ?? "";
|
||||||
|
|||||||
14
src/common/vars/app-semantic-version.injectable.ts
Normal file
14
src/common/vars/app-semantic-version.injectable.ts
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
/**
|
||||||
|
* 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 { SemVer } from "semver";
|
||||||
|
import appVersionInjectable from "./app-version.injectable";
|
||||||
|
|
||||||
|
const appSemanticVersionInjectable = getInjectable({
|
||||||
|
id: "app-semantic-version",
|
||||||
|
instantiate: (di) => new SemVer(di.inject(appVersionInjectable)),
|
||||||
|
});
|
||||||
|
|
||||||
|
export default appSemanticVersionInjectable;
|
||||||
@ -3,7 +3,7 @@
|
|||||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||||
*/
|
*/
|
||||||
import { getInjectable } from "@ogre-tools/injectable";
|
import { getInjectable } from "@ogre-tools/injectable";
|
||||||
import packageJsonInjectable from "../../vars/package-json.injectable";
|
import packageJsonInjectable from "./package-json.injectable";
|
||||||
|
|
||||||
const appVersionInjectable = getInjectable({
|
const appVersionInjectable = getInjectable({
|
||||||
id: "app-version",
|
id: "app-version",
|
||||||
@ -19,6 +19,7 @@ import directoryForUserDataInjectable
|
|||||||
import mockFs from "mock-fs";
|
import mockFs from "mock-fs";
|
||||||
import { delay } from "../../renderer/utils";
|
import { delay } from "../../renderer/utils";
|
||||||
import { observable, when } from "mobx";
|
import { observable, when } from "mobx";
|
||||||
|
import appVersionInjectable from "../../common/vars/app-version.injectable";
|
||||||
|
|
||||||
jest.setTimeout(60_000);
|
jest.setTimeout(60_000);
|
||||||
|
|
||||||
@ -56,6 +57,7 @@ describe("ExtensionDiscovery", () => {
|
|||||||
|
|
||||||
di.override(directoryForUserDataInjectable, () => "some-directory-for-user-data");
|
di.override(directoryForUserDataInjectable, () => "some-directory-for-user-data");
|
||||||
di.override(installExtensionInjectable, () => () => Promise.resolve());
|
di.override(installExtensionInjectable, () => () => Promise.resolve());
|
||||||
|
di.override(appVersionInjectable, () => "5.0.0");
|
||||||
|
|
||||||
mockFs();
|
mockFs();
|
||||||
|
|
||||||
|
|||||||
@ -3,12 +3,14 @@
|
|||||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||||
*/
|
*/
|
||||||
import { getInjectable } from "@ogre-tools/injectable";
|
import { getInjectable } from "@ogre-tools/injectable";
|
||||||
import { appSemVer } from "../../../common/vars";
|
import appSemanticVersionInjectable from "../../../common/vars/app-semantic-version.injectable";
|
||||||
import { isCompatibleBundledExtension } from "./is-compatible-bundled-extension";
|
import { isCompatibleBundledExtension } from "./is-compatible-bundled-extension";
|
||||||
|
|
||||||
const isCompatibleBundledExtensionInjectable = getInjectable({
|
const isCompatibleBundledExtensionInjectable = getInjectable({
|
||||||
id: "is-compatible-bundled-extension",
|
id: "is-compatible-bundled-extension",
|
||||||
instantiate: () => isCompatibleBundledExtension({ appSemVer }),
|
instantiate: (di) => isCompatibleBundledExtension({
|
||||||
|
appSemVer: di.inject(appSemanticVersionInjectable),
|
||||||
|
}),
|
||||||
});
|
});
|
||||||
|
|
||||||
export default isCompatibleBundledExtensionInjectable;
|
export default isCompatibleBundledExtensionInjectable;
|
||||||
|
|||||||
@ -3,12 +3,14 @@
|
|||||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||||
*/
|
*/
|
||||||
import { getInjectable } from "@ogre-tools/injectable";
|
import { getInjectable } from "@ogre-tools/injectable";
|
||||||
import { appSemVer } from "../../../common/vars";
|
import appSemanticVersionInjectable from "../../../common/vars/app-semantic-version.injectable";
|
||||||
import { isCompatibleExtension } from "./is-compatible-extension";
|
import { isCompatibleExtension } from "./is-compatible-extension";
|
||||||
|
|
||||||
const isCompatibleExtensionInjectable = getInjectable({
|
const isCompatibleExtensionInjectable = getInjectable({
|
||||||
id: "is-compatible-extension",
|
id: "is-compatible-extension",
|
||||||
instantiate: () => isCompatibleExtension({ appSemVer }),
|
instantiate: (di) => isCompatibleExtension({
|
||||||
|
appSemVer: di.inject(appSemanticVersionInjectable),
|
||||||
|
}),
|
||||||
});
|
});
|
||||||
|
|
||||||
export default isCompatibleExtensionInjectable;
|
export default isCompatibleExtensionInjectable;
|
||||||
|
|||||||
@ -54,7 +54,6 @@ import { createClusterInjectionToken } from "../../common/cluster/create-cluster
|
|||||||
import path from "path";
|
import path from "path";
|
||||||
import spawnInjectable from "../child-process/spawn.injectable";
|
import spawnInjectable from "../child-process/spawn.injectable";
|
||||||
import getConfigurationFileModelInjectable from "../../common/get-configuration-file-model/get-configuration-file-model.injectable";
|
import getConfigurationFileModelInjectable from "../../common/get-configuration-file-model/get-configuration-file-model.injectable";
|
||||||
import appVersionInjectable from "../../common/get-configuration-file-model/app-version/app-version.injectable";
|
|
||||||
import directoryForUserDataInjectable from "../../common/app-paths/directory-for-user-data/directory-for-user-data.injectable";
|
import directoryForUserDataInjectable from "../../common/app-paths/directory-for-user-data/directory-for-user-data.injectable";
|
||||||
import directoryForTempInjectable from "../../common/app-paths/directory-for-temp/directory-for-temp.injectable";
|
import directoryForTempInjectable from "../../common/app-paths/directory-for-temp/directory-for-temp.injectable";
|
||||||
import normalizedPlatformInjectable from "../../common/vars/normalized-platform.injectable";
|
import normalizedPlatformInjectable from "../../common/vars/normalized-platform.injectable";
|
||||||
@ -112,7 +111,6 @@ describe("kube auth proxy tests", () => {
|
|||||||
di.override(normalizedPlatformInjectable, () => "darwin");
|
di.override(normalizedPlatformInjectable, () => "darwin");
|
||||||
|
|
||||||
di.permitSideEffects(getConfigurationFileModelInjectable);
|
di.permitSideEffects(getConfigurationFileModelInjectable);
|
||||||
di.permitSideEffects(appVersionInjectable);
|
|
||||||
|
|
||||||
mockFs(mockMinikubeConfig);
|
mockFs(mockMinikubeConfig);
|
||||||
|
|
||||||
|
|||||||
@ -5,7 +5,7 @@
|
|||||||
import { getInjectable } from "@ogre-tools/injectable";
|
import { getInjectable } from "@ogre-tools/injectable";
|
||||||
import { computed } from "mobx";
|
import { computed } from "mobx";
|
||||||
import selectedUpdateChannelInjectable from "../../../common/application-update/selected-update-channel/selected-update-channel.injectable";
|
import selectedUpdateChannelInjectable from "../../../common/application-update/selected-update-channel/selected-update-channel.injectable";
|
||||||
import appVersionInjectable from "../../../common/get-configuration-file-model/app-version/app-version.injectable";
|
import appVersionInjectable from "../../../common/vars/app-version.injectable";
|
||||||
import { SemVer } from "semver";
|
import { SemVer } from "semver";
|
||||||
|
|
||||||
const updateCanBeDowngradedInjectable = getInjectable({
|
const updateCanBeDowngradedInjectable = getInjectable({
|
||||||
|
|||||||
@ -6,7 +6,7 @@ import { getInjectable } from "@ogre-tools/injectable";
|
|||||||
import { afterApplicationIsLoadedInjectionToken } from "../start-main-application/runnable-tokens/after-application-is-loaded-injection-token";
|
import { afterApplicationIsLoadedInjectionToken } from "../start-main-application/runnable-tokens/after-application-is-loaded-injection-token";
|
||||||
import emitEventInjectable from "../../common/app-event-bus/emit-event.injectable";
|
import emitEventInjectable from "../../common/app-event-bus/emit-event.injectable";
|
||||||
import { getCurrentDateTime } from "../../common/utils/date/get-current-date-time";
|
import { getCurrentDateTime } from "../../common/utils/date/get-current-date-time";
|
||||||
import appVersionInjectable from "../../common/get-configuration-file-model/app-version/app-version.injectable";
|
import appVersionInjectable from "../../common/vars/app-version.injectable";
|
||||||
|
|
||||||
const emitCurrentVersionToAnalyticsInjectable = getInjectable({
|
const emitCurrentVersionToAnalyticsInjectable = getInjectable({
|
||||||
id: "emit-current-version-to-analytics",
|
id: "emit-current-version-to-analytics",
|
||||||
|
|||||||
@ -15,7 +15,6 @@ import { getDiForUnitTesting } from "../../getDiForUnitTesting";
|
|||||||
import { createClusterInjectionToken } from "../../../common/cluster/create-cluster-injection-token";
|
import { createClusterInjectionToken } from "../../../common/cluster/create-cluster-injection-token";
|
||||||
import directoryForKubeConfigsInjectable from "../../../common/app-paths/directory-for-kube-configs/directory-for-kube-configs.injectable";
|
import directoryForKubeConfigsInjectable from "../../../common/app-paths/directory-for-kube-configs/directory-for-kube-configs.injectable";
|
||||||
import getConfigurationFileModelInjectable from "../../../common/get-configuration-file-model/get-configuration-file-model.injectable";
|
import getConfigurationFileModelInjectable from "../../../common/get-configuration-file-model/get-configuration-file-model.injectable";
|
||||||
import appVersionInjectable from "../../../common/get-configuration-file-model/app-version/app-version.injectable";
|
|
||||||
import clusterManagerInjectable from "../../cluster-manager.injectable";
|
import clusterManagerInjectable from "../../cluster-manager.injectable";
|
||||||
import directoryForUserDataInjectable from "../../../common/app-paths/directory-for-user-data/directory-for-user-data.injectable";
|
import directoryForUserDataInjectable from "../../../common/app-paths/directory-for-user-data/directory-for-user-data.injectable";
|
||||||
import directoryForTempInjectable from "../../../common/app-paths/directory-for-temp/directory-for-temp.injectable";
|
import directoryForTempInjectable from "../../../common/app-paths/directory-for-temp/directory-for-temp.injectable";
|
||||||
@ -59,7 +58,6 @@ describe("kubeconfig-sync.source tests", () => {
|
|||||||
di.unoverride(clusterStoreInjectable);
|
di.unoverride(clusterStoreInjectable);
|
||||||
di.permitSideEffects(clusterStoreInjectable);
|
di.permitSideEffects(clusterStoreInjectable);
|
||||||
di.permitSideEffects(getConfigurationFileModelInjectable);
|
di.permitSideEffects(getConfigurationFileModelInjectable);
|
||||||
di.permitSideEffects(appVersionInjectable);
|
|
||||||
|
|
||||||
computeDiff = computeDiffFor({
|
computeDiff = computeDiffFor({
|
||||||
directoryForKubeConfigs: di.inject(directoryForKubeConfigsInjectable),
|
directoryForKubeConfigs: di.inject(directoryForKubeConfigsInjectable),
|
||||||
|
|||||||
@ -80,7 +80,7 @@ import setUpdateOnQuitInjectable from "./electron-app/features/set-update-on-qui
|
|||||||
import downloadPlatformUpdateInjectable from "./application-update/download-platform-update/download-platform-update.injectable";
|
import downloadPlatformUpdateInjectable from "./application-update/download-platform-update/download-platform-update.injectable";
|
||||||
import startCatalogSyncInjectable from "./catalog-sync-to-renderer/start-catalog-sync.injectable";
|
import startCatalogSyncInjectable from "./catalog-sync-to-renderer/start-catalog-sync.injectable";
|
||||||
import startKubeConfigSyncInjectable from "./start-main-application/runnables/kube-config-sync/start-kube-config-sync.injectable";
|
import startKubeConfigSyncInjectable from "./start-main-application/runnables/kube-config-sync/start-kube-config-sync.injectable";
|
||||||
import appVersionInjectable from "../common/get-configuration-file-model/app-version/app-version.injectable";
|
import appVersionInjectable from "../common/vars/app-version.injectable";
|
||||||
import getRandomIdInjectable from "../common/utils/get-random-id.injectable";
|
import getRandomIdInjectable from "../common/utils/get-random-id.injectable";
|
||||||
import periodicalCheckForUpdatesInjectable from "./application-update/periodical-check-for-updates/periodical-check-for-updates.injectable";
|
import periodicalCheckForUpdatesInjectable from "./application-update/periodical-check-for-updates/periodical-check-for-updates.injectable";
|
||||||
import execFileInjectable from "../common/fs/exec-file.injectable";
|
import execFileInjectable from "../common/fs/exec-file.injectable";
|
||||||
@ -124,6 +124,16 @@ export function getDiForUnitTesting(opts: { doGeneralOverrides?: boolean } = {})
|
|||||||
di.preventSideEffects();
|
di.preventSideEffects();
|
||||||
|
|
||||||
if (doGeneralOverrides) {
|
if (doGeneralOverrides) {
|
||||||
|
const globalOverrideFilePaths = getGlobalOverridePaths();
|
||||||
|
|
||||||
|
const globalOverrides = globalOverrideFilePaths.map(
|
||||||
|
(filePath) => require(filePath).default,
|
||||||
|
);
|
||||||
|
|
||||||
|
globalOverrides.forEach(globalOverride => {
|
||||||
|
di.override(globalOverride.injectable, globalOverride.overridingInstantiate);
|
||||||
|
});
|
||||||
|
|
||||||
di.override(electronInjectable, () => ({}));
|
di.override(electronInjectable, () => ({}));
|
||||||
di.override(waitUntilBundledExtensionsAreLoadedInjectable, () => async () => {});
|
di.override(waitUntilBundledExtensionsAreLoadedInjectable, () => async () => {});
|
||||||
di.override(getRandomIdInjectable, () => () => "some-irrelevant-random-id");
|
di.override(getRandomIdInjectable, () => () => "some-irrelevant-random-id");
|
||||||
@ -211,6 +221,14 @@ const getInjectableFilePaths = memoize(() => [
|
|||||||
...glob.sync("../common/**/*.injectable.{ts,tsx}", { cwd: __dirname }),
|
...glob.sync("../common/**/*.injectable.{ts,tsx}", { cwd: __dirname }),
|
||||||
]);
|
]);
|
||||||
|
|
||||||
|
const getGlobalOverridePaths = memoize(() =>
|
||||||
|
glob.sync(
|
||||||
|
"../{common,extensions,main}/**/*.global-override-for-injectable.{ts,tsx}",
|
||||||
|
|
||||||
|
{ cwd: __dirname },
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
// TODO: Reorganize code in Runnables to get rid of requirement for override
|
// TODO: Reorganize code in Runnables to get rid of requirement for override
|
||||||
const overrideRunnablesHavingSideEffects = (di: DiContainer) => {
|
const overrideRunnablesHavingSideEffects = (di: DiContainer) => {
|
||||||
[
|
[
|
||||||
|
|||||||
@ -191,7 +191,6 @@ async function getResources(name: string, namespace: string, kubeconfigPath: str
|
|||||||
];
|
];
|
||||||
const kubectlArgs = [
|
const kubectlArgs = [
|
||||||
"get",
|
"get",
|
||||||
"--namespace", namespace,
|
|
||||||
"--kubeconfig", kubeconfigPath,
|
"--kubeconfig", kubeconfigPath,
|
||||||
"-f", "-",
|
"-f", "-",
|
||||||
"--output", "json",
|
"--output", "json",
|
||||||
|
|||||||
@ -14,7 +14,6 @@ import { getDiForUnitTesting } from "../../getDiForUnitTesting";
|
|||||||
import lensProtocolRouterMainInjectable from "../lens-protocol-router-main/lens-protocol-router-main.injectable";
|
import lensProtocolRouterMainInjectable from "../lens-protocol-router-main/lens-protocol-router-main.injectable";
|
||||||
import extensionsStoreInjectable from "../../../extensions/extensions-store/extensions-store.injectable";
|
import extensionsStoreInjectable from "../../../extensions/extensions-store/extensions-store.injectable";
|
||||||
import getConfigurationFileModelInjectable from "../../../common/get-configuration-file-model/get-configuration-file-model.injectable";
|
import getConfigurationFileModelInjectable from "../../../common/get-configuration-file-model/get-configuration-file-model.injectable";
|
||||||
import appVersionInjectable from "../../../common/get-configuration-file-model/app-version/app-version.injectable";
|
|
||||||
import { LensExtension } from "../../../extensions/lens-extension";
|
import { LensExtension } from "../../../extensions/lens-extension";
|
||||||
import type { LensExtensionId } from "../../../extensions/lens-extension";
|
import type { LensExtensionId } from "../../../extensions/lens-extension";
|
||||||
import type { ObservableMap } from "mobx";
|
import type { ObservableMap } from "mobx";
|
||||||
@ -44,7 +43,6 @@ describe("protocol router tests", () => {
|
|||||||
} as unknown as ExtensionsStore));
|
} as unknown as ExtensionsStore));
|
||||||
|
|
||||||
di.permitSideEffects(getConfigurationFileModelInjectable);
|
di.permitSideEffects(getConfigurationFileModelInjectable);
|
||||||
di.permitSideEffects(appVersionInjectable);
|
|
||||||
|
|
||||||
di.override(directoryForUserDataInjectable, () => "some-directory-for-user-data");
|
di.override(directoryForUserDataInjectable, () => "some-directory-for-user-data");
|
||||||
|
|
||||||
|
|||||||
@ -22,7 +22,6 @@ import { renderFor } from "../test-utils/renderFor";
|
|||||||
import mockFs from "mock-fs";
|
import mockFs from "mock-fs";
|
||||||
import directoryForUserDataInjectable from "../../../common/app-paths/directory-for-user-data/directory-for-user-data.injectable";
|
import directoryForUserDataInjectable from "../../../common/app-paths/directory-for-user-data/directory-for-user-data.injectable";
|
||||||
import getConfigurationFileModelInjectable from "../../../common/get-configuration-file-model/get-configuration-file-model.injectable";
|
import getConfigurationFileModelInjectable from "../../../common/get-configuration-file-model/get-configuration-file-model.injectable";
|
||||||
import appVersionInjectable from "../../../common/get-configuration-file-model/app-version/app-version.injectable";
|
|
||||||
import type { AppEvent } from "../../../common/app-event-bus/event-bus";
|
import type { AppEvent } from "../../../common/app-event-bus/event-bus";
|
||||||
import appEventBusInjectable from "../../../common/app-event-bus/app-event-bus.injectable";
|
import appEventBusInjectable from "../../../common/app-event-bus/app-event-bus.injectable";
|
||||||
import { computed } from "mobx";
|
import { computed } from "mobx";
|
||||||
@ -95,7 +94,6 @@ describe("<Catalog />", () => {
|
|||||||
di.override(broadcastMessageInjectable, () => async () => {});
|
di.override(broadcastMessageInjectable, () => async () => {});
|
||||||
|
|
||||||
di.permitSideEffects(getConfigurationFileModelInjectable);
|
di.permitSideEffects(getConfigurationFileModelInjectable);
|
||||||
di.permitSideEffects(appVersionInjectable);
|
|
||||||
|
|
||||||
mockFs();
|
mockFs();
|
||||||
CatalogEntityDetailRegistry.createInstance();
|
CatalogEntityDetailRegistry.createInstance();
|
||||||
|
|||||||
@ -21,7 +21,6 @@ import extensionDiscoveryInjectable from "../../../../extensions/extension-disco
|
|||||||
import directoryForUserDataInjectable from "../../../../common/app-paths/directory-for-user-data/directory-for-user-data.injectable";
|
import directoryForUserDataInjectable from "../../../../common/app-paths/directory-for-user-data/directory-for-user-data.injectable";
|
||||||
import directoryForDownloadsInjectable from "../../../../common/app-paths/directory-for-downloads/directory-for-downloads.injectable";
|
import directoryForDownloadsInjectable from "../../../../common/app-paths/directory-for-downloads/directory-for-downloads.injectable";
|
||||||
import getConfigurationFileModelInjectable from "../../../../common/get-configuration-file-model/get-configuration-file-model.injectable";
|
import getConfigurationFileModelInjectable from "../../../../common/get-configuration-file-model/get-configuration-file-model.injectable";
|
||||||
import appVersionInjectable from "../../../../common/get-configuration-file-model/app-version/app-version.injectable";
|
|
||||||
import assert from "assert";
|
import assert from "assert";
|
||||||
import type { InstallFromInput } from "../install-from-input/install-from-input";
|
import type { InstallFromInput } from "../install-from-input/install-from-input";
|
||||||
import installFromInputInjectable from "../install-from-input/install-from-input.injectable";
|
import installFromInputInjectable from "../install-from-input/install-from-input.injectable";
|
||||||
@ -64,7 +63,6 @@ describe("Extensions", () => {
|
|||||||
di.override(directoryForDownloadsInjectable, () => "some-directory-for-downloads");
|
di.override(directoryForDownloadsInjectable, () => "some-directory-for-downloads");
|
||||||
|
|
||||||
di.permitSideEffects(getConfigurationFileModelInjectable);
|
di.permitSideEffects(getConfigurationFileModelInjectable);
|
||||||
di.permitSideEffects(appVersionInjectable);
|
|
||||||
|
|
||||||
mockFs({
|
mockFs({
|
||||||
"some-directory-for-user-data": {},
|
"some-directory-for-user-data": {},
|
||||||
|
|||||||
@ -0,0 +1,15 @@
|
|||||||
|
/**
|
||||||
|
* 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.",
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
@ -0,0 +1,24 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||||
|
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||||
|
*/
|
||||||
|
import { getInjectable } from "@ogre-tools/injectable";
|
||||||
|
import type { 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;
|
||||||
@ -2,9 +2,20 @@
|
|||||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||||
*/
|
*/
|
||||||
|
import yaml from "js-yaml";
|
||||||
import { getInjectable } from "@ogre-tools/injectable";
|
import { getInjectable } from "@ogre-tools/injectable";
|
||||||
import type { HelmReleaseCreatePayload, HelmReleaseUpdateDetails } from "../../../../common/k8s-api/endpoints/helm-releases.api";
|
import type { HelmReleaseUpdateDetails } from "../../../../common/k8s-api/endpoints/helm-releases.api";
|
||||||
import { createRelease } 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 = (
|
export type CallForCreateHelmRelease = (
|
||||||
payload: HelmReleaseCreatePayload
|
payload: HelmReleaseCreatePayload
|
||||||
@ -12,7 +23,21 @@ export type CallForCreateHelmRelease = (
|
|||||||
|
|
||||||
const callForCreateHelmReleaseInjectable = getInjectable({
|
const callForCreateHelmReleaseInjectable = getInjectable({
|
||||||
id: "call-for-create-helm-release",
|
id: "call-for-create-helm-release",
|
||||||
instantiate: (): CallForCreateHelmRelease => createRelease,
|
|
||||||
|
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,
|
causesSideEffects: true,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@ -4,19 +4,18 @@
|
|||||||
*/
|
*/
|
||||||
import { getInjectable } from "@ogre-tools/injectable";
|
import { getInjectable } from "@ogre-tools/injectable";
|
||||||
|
|
||||||
import type {
|
|
||||||
HelmReleaseCreatePayload } from "../../../../common/k8s-api/endpoints/helm-releases.api";
|
|
||||||
import releasesInjectable from "../releases.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 callForCreateHelmReleaseInjectable from "./call-for-create-helm-release.injectable";
|
||||||
|
|
||||||
const createReleaseInjectable = getInjectable({
|
const createReleaseInjectable = getInjectable({
|
||||||
id: "create-release",
|
id: "create-release",
|
||||||
|
|
||||||
instantiate: (di) => {
|
instantiate: (di): CallForCreateHelmRelease => {
|
||||||
const releases = di.inject(releasesInjectable);
|
const releases = di.inject(releasesInjectable);
|
||||||
const callForCreateRelease = di.inject(callForCreateHelmReleaseInjectable);
|
const callForCreateRelease = di.inject(callForCreateHelmReleaseInjectable);
|
||||||
|
|
||||||
return async (payload: HelmReleaseCreatePayload) => {
|
return async (payload) => {
|
||||||
const release = await callForCreateRelease(payload);
|
const release = await callForCreateRelease(payload);
|
||||||
|
|
||||||
releases.invalidate();
|
releases.invalidate();
|
||||||
|
|||||||
@ -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 } from "@ogre-tools/injectable";
|
||||||
|
import { computed } from "mobx";
|
||||||
|
import { clusterFrameChildComponentInjectionToken } from "../../../frames/cluster-frame/cluster-frame-child-component-injection-token";
|
||||||
|
import { ReleaseDetails } from "./release-details";
|
||||||
|
import targetHelmReleaseInjectable from "./target-helm-release.injectable";
|
||||||
|
|
||||||
|
const releaseDetailsClusterFrameChildComponentInjectable = getInjectable({
|
||||||
|
id: "release-details-cluster-frame-child-component",
|
||||||
|
|
||||||
|
instantiate: (di) => {
|
||||||
|
const targetRelease = di.inject(targetHelmReleaseInjectable);
|
||||||
|
|
||||||
|
return {
|
||||||
|
id: "release-details",
|
||||||
|
Component: ReleaseDetails,
|
||||||
|
shouldRender: computed(() => !!targetRelease.get()),
|
||||||
|
};
|
||||||
|
},
|
||||||
|
injectionToken: clusterFrameChildComponentInjectionToken,
|
||||||
|
});
|
||||||
|
|
||||||
|
export default releaseDetailsClusterFrameChildComponentInjectable;
|
||||||
@ -0,0 +1,210 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||||
|
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import "./release-details.scss";
|
||||||
|
|
||||||
|
import React from "react";
|
||||||
|
|
||||||
|
import { Link } from "react-router-dom";
|
||||||
|
import { Drawer, DrawerItem, DrawerTitle } from "../../drawer";
|
||||||
|
import { cssNames, stopPropagation } from "../../../utils";
|
||||||
|
import { observer } from "mobx-react";
|
||||||
|
import { withInjectables } from "@ogre-tools/injectable-react";
|
||||||
|
import type { ConfigurationInput, MinimalResourceGroup, OnlyUserSuppliedValuesAreShownToggle, ReleaseDetailsModel } from "./release-details-model/release-details-model.injectable";
|
||||||
|
import releaseDetailsModelInjectable from "./release-details-model/release-details-model.injectable";
|
||||||
|
import { Button } from "../../button";
|
||||||
|
import { kebabCase } from "lodash/fp";
|
||||||
|
import { Badge } from "../../badge";
|
||||||
|
import { SubTitle } from "../../layout/sub-title";
|
||||||
|
import { Table, TableCell, TableHead, TableRow } from "../../table";
|
||||||
|
import { ReactiveDuration } from "../../duration/reactive-duration";
|
||||||
|
import { HelmReleaseMenu } from "../release-menu";
|
||||||
|
import { Checkbox } from "../../checkbox";
|
||||||
|
import { MonacoEditor } from "../../monaco-editor";
|
||||||
|
import { Spinner } from "../../spinner";
|
||||||
|
import type { TargetHelmRelease } from "./target-helm-release.injectable";
|
||||||
|
|
||||||
|
interface ReleaseDetailsContentProps {
|
||||||
|
targetRelease: TargetHelmRelease;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface Dependencies {
|
||||||
|
model: ReleaseDetailsModel;
|
||||||
|
}
|
||||||
|
|
||||||
|
const NonInjectedReleaseDetailsContent = observer(({ model }: Dependencies & ReleaseDetailsContentProps) => {
|
||||||
|
const isLoading = model.isLoading.get();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Drawer
|
||||||
|
className={cssNames("ReleaseDetails", model.activeTheme)}
|
||||||
|
usePortal={true}
|
||||||
|
open={true}
|
||||||
|
title={isLoading ? "" : model.release.getName()}
|
||||||
|
onClose={model.close}
|
||||||
|
testIdForClose="close-helm-release-detail"
|
||||||
|
toolbar={
|
||||||
|
!isLoading && (
|
||||||
|
<HelmReleaseMenu
|
||||||
|
release={model.release}
|
||||||
|
toolbar
|
||||||
|
hideDetails={model.close}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
data-testid={`helm-release-details-for-${model.id}`}
|
||||||
|
>
|
||||||
|
{isLoading ? (
|
||||||
|
<Spinner center data-testid="helm-release-detail-content-spinner" />
|
||||||
|
) : (
|
||||||
|
<div>
|
||||||
|
<DrawerItem name="Chart" className="chart">
|
||||||
|
<div className="flex gaps align-center">
|
||||||
|
<span>{model.release.chart}</span>
|
||||||
|
|
||||||
|
<Button
|
||||||
|
primary
|
||||||
|
label="Upgrade"
|
||||||
|
className="box right upgrade"
|
||||||
|
onClick={model.startUpgradeProcess}
|
||||||
|
data-testid="helm-release-upgrade-button"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</DrawerItem>
|
||||||
|
|
||||||
|
<DrawerItem name="Updated">
|
||||||
|
{model.release.getUpdated()}
|
||||||
|
{` ago (${model.release.updated})`}
|
||||||
|
</DrawerItem>
|
||||||
|
|
||||||
|
<DrawerItem name="Namespace">{model.release.getNs()}</DrawerItem>
|
||||||
|
|
||||||
|
<DrawerItem name="Version" onClick={stopPropagation}>
|
||||||
|
<div className="version flex gaps align-center">
|
||||||
|
<span>{model.release.getVersion()}</span>
|
||||||
|
</div>
|
||||||
|
</DrawerItem>
|
||||||
|
|
||||||
|
<DrawerItem
|
||||||
|
name="Status"
|
||||||
|
className="status"
|
||||||
|
labelsOnly>
|
||||||
|
<Badge
|
||||||
|
label={model.release.getStatus()}
|
||||||
|
className={kebabCase(model.release.getStatus())}
|
||||||
|
/>
|
||||||
|
</DrawerItem>
|
||||||
|
|
||||||
|
<ReleaseValues
|
||||||
|
configuration={model.configuration}
|
||||||
|
onlyUserSuppliedValuesAreShown={
|
||||||
|
model.onlyUserSuppliedValuesAreShown
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<DrawerTitle>Notes</DrawerTitle>
|
||||||
|
|
||||||
|
{model.notes && <div className="notes">{model.notes}</div>}
|
||||||
|
|
||||||
|
<DrawerTitle>Resources</DrawerTitle>
|
||||||
|
|
||||||
|
{model.groupedResources.length > 0 && (
|
||||||
|
<div className="resources">
|
||||||
|
{model.groupedResources.map((group) => (
|
||||||
|
<ResourceGroup key={group.kind} group={group} />
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</Drawer>
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
export const ReleaseDetailsContent = withInjectables<Dependencies, ReleaseDetailsContentProps>(NonInjectedReleaseDetailsContent, {
|
||||||
|
getProps: (di, props) => ({
|
||||||
|
model: di.inject(releaseDetailsModelInjectable, props.targetRelease),
|
||||||
|
...props,
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
|
||||||
|
const ResourceGroup = ({
|
||||||
|
group: { kind, isNamespaced, resources },
|
||||||
|
}: {
|
||||||
|
group: MinimalResourceGroup;
|
||||||
|
}) => (
|
||||||
|
<>
|
||||||
|
<SubTitle title={kind} />
|
||||||
|
|
||||||
|
<Table scrollable={false}>
|
||||||
|
<TableHead sticky={false}>
|
||||||
|
<TableCell className="name">Name</TableCell>
|
||||||
|
|
||||||
|
{isNamespaced && <TableCell className="namespace">Namespace</TableCell>}
|
||||||
|
|
||||||
|
<TableCell className="age">Age</TableCell>
|
||||||
|
</TableHead>
|
||||||
|
|
||||||
|
{resources.map(
|
||||||
|
({ creationTimestamp, detailsUrl, name, namespace, uid }) => (
|
||||||
|
<TableRow key={uid}>
|
||||||
|
<TableCell className="name">
|
||||||
|
{detailsUrl ? <Link to={detailsUrl}>{name}</Link> : name}
|
||||||
|
</TableCell>
|
||||||
|
|
||||||
|
{isNamespaced && (
|
||||||
|
<TableCell className="namespace">{namespace}</TableCell>
|
||||||
|
)}
|
||||||
|
|
||||||
|
<TableCell className="age">
|
||||||
|
<ReactiveDuration timestamp={creationTimestamp} />
|
||||||
|
</TableCell>
|
||||||
|
</TableRow>
|
||||||
|
),
|
||||||
|
)}
|
||||||
|
</Table>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
|
||||||
|
interface ReleaseValuesProps {
|
||||||
|
configuration: ConfigurationInput;
|
||||||
|
onlyUserSuppliedValuesAreShown: OnlyUserSuppliedValuesAreShownToggle;
|
||||||
|
}
|
||||||
|
|
||||||
|
const ReleaseValues = observer(({ configuration, onlyUserSuppliedValuesAreShown }: ReleaseValuesProps) => {
|
||||||
|
const configurationIsLoading = configuration.isLoading.get();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="values">
|
||||||
|
<DrawerTitle>Values</DrawerTitle>
|
||||||
|
|
||||||
|
<div className="flex column gaps">
|
||||||
|
<Checkbox
|
||||||
|
label="User-supplied values only"
|
||||||
|
value={onlyUserSuppliedValuesAreShown.value.get()}
|
||||||
|
onChange={onlyUserSuppliedValuesAreShown.toggle}
|
||||||
|
disabled={configurationIsLoading}
|
||||||
|
data-testid="user-supplied-values-only-checkbox"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<MonacoEditor
|
||||||
|
id="helm-release-configuration"
|
||||||
|
style={{ minHeight: 300 }}
|
||||||
|
value={configuration.nonSavedValue.get()}
|
||||||
|
onChange={configuration.onChange}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<Button
|
||||||
|
primary
|
||||||
|
label="Save"
|
||||||
|
waiting={configuration.isSaving.get()}
|
||||||
|
disabled={configurationIsLoading}
|
||||||
|
onClick={configuration.save}
|
||||||
|
data-testid="helm-release-configuration-save-button"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
});
|
||||||
@ -0,0 +1,15 @@
|
|||||||
|
/**
|
||||||
|
* 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.",
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
@ -0,0 +1,29 @@
|
|||||||
|
/**
|
||||||
|
* 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;
|
||||||
@ -0,0 +1,15 @@
|
|||||||
|
/**
|
||||||
|
* 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.",
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
@ -0,0 +1,45 @@
|
|||||||
|
/**
|
||||||
|
* 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;
|
||||||
@ -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 } 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";
|
||||||
|
|
||||||
|
export interface DetailedHelmRelease {
|
||||||
|
release: HelmReleaseDto;
|
||||||
|
details: HelmReleaseDetails;
|
||||||
|
}
|
||||||
|
|
||||||
|
export type CallForHelmRelease = (
|
||||||
|
name: string,
|
||||||
|
namespace: string
|
||||||
|
) => Promise<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 undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
return { release, details };
|
||||||
|
};
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
export default callForHelmReleaseInjectable;
|
||||||
@ -0,0 +1,45 @@
|
|||||||
|
/**
|
||||||
|
* 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 apiManagerInjectable from "../../../../../common/k8s-api/api-manager/manager.injectable";
|
||||||
|
import getDetailsUrlInjectable from "../../../kube-detail-params/get-details-url.injectable";
|
||||||
|
|
||||||
|
export type GetResourceDetailsUrl = (
|
||||||
|
kind: string,
|
||||||
|
apiVersion: string,
|
||||||
|
namespace: string | undefined,
|
||||||
|
name: string
|
||||||
|
) => string;
|
||||||
|
|
||||||
|
const getResourceDetailsUrlInjectable = getInjectable({
|
||||||
|
id: "get-resource-details-url",
|
||||||
|
|
||||||
|
instantiate: (di): GetResourceDetailsUrl => {
|
||||||
|
const apiManager = di.inject(apiManagerInjectable);
|
||||||
|
const getDetailsUrl = di.inject(getDetailsUrlInjectable);
|
||||||
|
|
||||||
|
const getKubeApi = (kind: string, apiVersion: string) =>
|
||||||
|
apiManager.getApi(
|
||||||
|
(api) => api.kind === kind && api.apiVersionWithGroup == apiVersion,
|
||||||
|
);
|
||||||
|
|
||||||
|
return (kind, apiVersion, namespace, name) => {
|
||||||
|
const kubeApi = getKubeApi(kind, apiVersion);
|
||||||
|
|
||||||
|
if (!kubeApi) {
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
|
||||||
|
const resourceUrl = kubeApi.getUrl({
|
||||||
|
name,
|
||||||
|
namespace,
|
||||||
|
});
|
||||||
|
|
||||||
|
return getDetailsUrl(resourceUrl);
|
||||||
|
};
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
export default getResourceDetailsUrlInjectable;
|
||||||
@ -0,0 +1,308 @@
|
|||||||
|
/**
|
||||||
|
* 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 type { 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 { ThemeStore } from "../../../../themes/store";
|
||||||
|
import themeStoreInjectable from "../../../../themes/store.injectable";
|
||||||
|
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 { 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 updateReleaseInjectable from "../../update-release/update-release.injectable";
|
||||||
|
import type { ShowCheckedErrorNotification } from "../../../notifications/show-checked-error.injectable";
|
||||||
|
import showCheckedErrorNotificationInjectable from "../../../notifications/show-checked-error.injectable";
|
||||||
|
import type { ShowNotification } from "../../../notifications";
|
||||||
|
import showSuccessNotificationInjectable from "../../../notifications/show-success-notification.injectable";
|
||||||
|
import React from "react";
|
||||||
|
import createUpgradeChartTabInjectable from "../../../dock/upgrade-chart/create-upgrade-chart-tab.injectable";
|
||||||
|
import type { HelmRelease } from "../../../../../common/k8s-api/endpoints/helm-releases.api";
|
||||||
|
import type { NavigateToHelmReleases } from "../../../../../common/front-end-routing/routes/cluster/helm/releases/navigate-to-helm-releases.injectable";
|
||||||
|
import navigateToHelmReleasesInjectable from "../../../../../common/front-end-routing/routes/cluster/helm/releases/navigate-to-helm-releases.injectable";
|
||||||
|
import assert from "assert";
|
||||||
|
import withOrphanPromiseInjectable from "../../../../../common/utils/with-orphan-promise/with-orphan-promise.injectable";
|
||||||
|
|
||||||
|
const releaseDetailsModelInjectable = getInjectable({
|
||||||
|
id: "release-details-model",
|
||||||
|
|
||||||
|
instantiate: (di, targetRelease: TargetHelmRelease) => {
|
||||||
|
const callForHelmRelease = di.inject(callForHelmReleaseInjectable);
|
||||||
|
const callForHelmReleaseConfiguration = di.inject(callForHelmReleaseConfigurationInjectable);
|
||||||
|
const themeStore = di.inject(themeStoreInjectable);
|
||||||
|
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 withOrphanPromise = di.inject(withOrphanPromiseInjectable);
|
||||||
|
|
||||||
|
const model = new ReleaseDetailsModel({
|
||||||
|
callForHelmRelease,
|
||||||
|
targetRelease,
|
||||||
|
themeStore,
|
||||||
|
callForHelmReleaseConfiguration,
|
||||||
|
getResourceDetailsUrl,
|
||||||
|
updateRelease,
|
||||||
|
showCheckedErrorNotification,
|
||||||
|
showSuccessNotification,
|
||||||
|
createUpgradeChartTab,
|
||||||
|
navigateToHelmReleases,
|
||||||
|
});
|
||||||
|
|
||||||
|
const load = withOrphanPromise(model.load);
|
||||||
|
|
||||||
|
// TODO: Reorganize Drawer to allow setting of header-bar in children to make "getPlaceholder" from injectable usable.
|
||||||
|
load();
|
||||||
|
|
||||||
|
return model;
|
||||||
|
},
|
||||||
|
|
||||||
|
lifecycle: lifecycleEnum.keyedSingleton({
|
||||||
|
getInstanceKey: (di, release: TargetHelmRelease) =>
|
||||||
|
`${release.namespace}/${release.name}`,
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
|
||||||
|
export default releaseDetailsModelInjectable;
|
||||||
|
|
||||||
|
export interface OnlyUserSuppliedValuesAreShownToggle {
|
||||||
|
value: IObservableValue<boolean>;
|
||||||
|
toggle: () => Promise<void>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ConfigurationInput {
|
||||||
|
nonSavedValue: IObservableValue<string>;
|
||||||
|
isLoading: IObservableValue<boolean>;
|
||||||
|
isSaving: IObservableValue<boolean>;
|
||||||
|
onChange: (value: string) => void;
|
||||||
|
save: () => Promise<void>;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface Dependencies {
|
||||||
|
callForHelmRelease: CallForHelmRelease;
|
||||||
|
targetRelease: TargetHelmRelease;
|
||||||
|
themeStore: ThemeStore;
|
||||||
|
callForHelmReleaseConfiguration: CallForHelmReleaseConfiguration;
|
||||||
|
getResourceDetailsUrl: GetResourceDetailsUrl;
|
||||||
|
updateRelease: CallForHelmReleaseUpdate;
|
||||||
|
showCheckedErrorNotification: ShowCheckedErrorNotification;
|
||||||
|
showSuccessNotification: ShowNotification;
|
||||||
|
createUpgradeChartTab: (release: HelmRelease) => string;
|
||||||
|
navigateToHelmReleases: NavigateToHelmReleases;
|
||||||
|
}
|
||||||
|
|
||||||
|
export class ReleaseDetailsModel {
|
||||||
|
id = `${this.dependencies.targetRelease.namespace}/${this.dependencies.targetRelease.name}`;
|
||||||
|
|
||||||
|
constructor(private dependencies: Dependencies) {}
|
||||||
|
|
||||||
|
private detailedRelease = observable.box<DetailedHelmRelease | undefined>();
|
||||||
|
|
||||||
|
readonly isLoading = observable.box(false);
|
||||||
|
|
||||||
|
readonly configuration: ConfigurationInput = {
|
||||||
|
nonSavedValue: observable.box(""),
|
||||||
|
isLoading: observable.box(false),
|
||||||
|
isSaving: observable.box(false),
|
||||||
|
|
||||||
|
onChange: action((value: string) => {
|
||||||
|
this.configuration.nonSavedValue.set(value);
|
||||||
|
}),
|
||||||
|
|
||||||
|
save: async () => {
|
||||||
|
runInAction(() => {
|
||||||
|
this.configuration.isSaving.set(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
const name = this.release.getName();
|
||||||
|
const namespace = this.release.getNs();
|
||||||
|
|
||||||
|
const data = {
|
||||||
|
chart: this.release.getChart(),
|
||||||
|
repo: await this.release.getRepo(),
|
||||||
|
version: this.release.getVersion(),
|
||||||
|
values: this.configuration.nonSavedValue.get(),
|
||||||
|
};
|
||||||
|
|
||||||
|
const result = await this.dependencies.updateRelease(name, namespace, data);
|
||||||
|
|
||||||
|
runInAction(() => {
|
||||||
|
this.configuration.isSaving.set(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!result.updateWasSuccessful) {
|
||||||
|
this.dependencies.showCheckedErrorNotification(
|
||||||
|
result.error,
|
||||||
|
"Unknown error occured while updating release",
|
||||||
|
);
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.dependencies.showSuccessNotification(
|
||||||
|
<p>
|
||||||
|
Release
|
||||||
|
{" "}
|
||||||
|
<b>{name}</b>
|
||||||
|
{" successfully updated!"}
|
||||||
|
</p>,
|
||||||
|
);
|
||||||
|
|
||||||
|
await this.loadConfiguration();
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
readonly onlyUserSuppliedValuesAreShown: OnlyUserSuppliedValuesAreShownToggle = {
|
||||||
|
value: observable.box(false),
|
||||||
|
|
||||||
|
toggle: action(async () => {
|
||||||
|
const value = this.onlyUserSuppliedValuesAreShown.value;
|
||||||
|
|
||||||
|
value.set(!value.get());
|
||||||
|
|
||||||
|
await this.loadConfiguration();
|
||||||
|
}),
|
||||||
|
};
|
||||||
|
|
||||||
|
load = async () => {
|
||||||
|
runInAction(() => {
|
||||||
|
this.isLoading.set(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
const { name, namespace } = this.dependencies.targetRelease;
|
||||||
|
|
||||||
|
const detailedRelease = await this.dependencies.callForHelmRelease(
|
||||||
|
name,
|
||||||
|
namespace,
|
||||||
|
);
|
||||||
|
|
||||||
|
runInAction(() => {
|
||||||
|
this.detailedRelease.set(detailedRelease);
|
||||||
|
});
|
||||||
|
|
||||||
|
await this.loadConfiguration();
|
||||||
|
|
||||||
|
runInAction(() => {
|
||||||
|
this.isLoading.set(false);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
private loadConfiguration = async () => {
|
||||||
|
runInAction(() => {
|
||||||
|
this.configuration.isLoading.set(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
const { name, namespace } = this.release;
|
||||||
|
|
||||||
|
const configuration =
|
||||||
|
await this.dependencies.callForHelmReleaseConfiguration(
|
||||||
|
name,
|
||||||
|
namespace,
|
||||||
|
!this.onlyUserSuppliedValuesAreShown.value.get(),
|
||||||
|
);
|
||||||
|
|
||||||
|
runInAction(() => {
|
||||||
|
this.configuration.isLoading.set(false);
|
||||||
|
this.configuration.nonSavedValue.set(configuration);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
@computed get release() {
|
||||||
|
const detailedRelease = this.detailedRelease.get();
|
||||||
|
|
||||||
|
assert(detailedRelease, "Tried to access release before load");
|
||||||
|
|
||||||
|
return toHelmRelease(detailedRelease.release);
|
||||||
|
}
|
||||||
|
|
||||||
|
@computed private get details() {
|
||||||
|
const detailedRelease = this.detailedRelease.get();
|
||||||
|
|
||||||
|
assert(detailedRelease, "Tried to access details before load");
|
||||||
|
|
||||||
|
return detailedRelease.details;
|
||||||
|
}
|
||||||
|
|
||||||
|
@computed get notes() {
|
||||||
|
return this.details.info.notes;
|
||||||
|
}
|
||||||
|
|
||||||
|
@computed get groupedResources(): MinimalResourceGroup[] {
|
||||||
|
return pipeline(
|
||||||
|
this.details.resources,
|
||||||
|
groupBy((resource) => resource.kind),
|
||||||
|
(grouped) => Object.entries(grouped),
|
||||||
|
|
||||||
|
map(([kind, resources]) => ({
|
||||||
|
kind,
|
||||||
|
|
||||||
|
resources: resources.map(
|
||||||
|
toMinimalResourceFor(this.dependencies.getResourceDetailsUrl, kind),
|
||||||
|
),
|
||||||
|
|
||||||
|
isNamespaced: resources.some(
|
||||||
|
(resource) => !!resource.metadata.namespace,
|
||||||
|
),
|
||||||
|
})),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@computed get activeTheme() {
|
||||||
|
return this.dependencies.themeStore.activeTheme.type;
|
||||||
|
}
|
||||||
|
|
||||||
|
close = () => {
|
||||||
|
this.dependencies.navigateToHelmReleases();
|
||||||
|
};
|
||||||
|
|
||||||
|
startUpgradeProcess = () => {
|
||||||
|
this.dependencies.createUpgradeChartTab(this.release);
|
||||||
|
|
||||||
|
this.close();
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface MinimalResourceGroup {
|
||||||
|
kind: string;
|
||||||
|
isNamespaced: boolean;
|
||||||
|
resources: MinimalResource[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface MinimalResource {
|
||||||
|
uid: string | undefined;
|
||||||
|
name: string;
|
||||||
|
namespace: string | undefined;
|
||||||
|
detailsUrl: string | undefined;
|
||||||
|
creationTimestamp: string | undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
const toMinimalResourceFor =
|
||||||
|
(getResourceDetailsUrl: GetResourceDetailsUrl, kind: string) =>
|
||||||
|
(resource: KubeJsonApiData): MinimalResource => {
|
||||||
|
const { creationTimestamp, name, namespace, uid } = resource.metadata;
|
||||||
|
|
||||||
|
return {
|
||||||
|
uid,
|
||||||
|
name,
|
||||||
|
namespace,
|
||||||
|
creationTimestamp,
|
||||||
|
|
||||||
|
detailsUrl: getResourceDetailsUrl(
|
||||||
|
kind,
|
||||||
|
resource.apiVersion,
|
||||||
|
namespace,
|
||||||
|
name,
|
||||||
|
),
|
||||||
|
};
|
||||||
|
};
|
||||||
@ -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 { getRelease } from "../../../../common/k8s-api/endpoints/helm-releases.api";
|
|
||||||
import { asyncComputed } from "@ogre-tools/injectable-react";
|
|
||||||
import releaseInjectable from "./release.injectable";
|
|
||||||
import { waitUntilDefined } from "../../../utils";
|
|
||||||
|
|
||||||
const releaseDetailsInjectable = getInjectable({
|
|
||||||
id: "release-details",
|
|
||||||
|
|
||||||
instantiate: (di) => {
|
|
||||||
const releaseComputed = di.inject(releaseInjectable);
|
|
||||||
|
|
||||||
return asyncComputed(async () => {
|
|
||||||
const release = await waitUntilDefined(releaseComputed);
|
|
||||||
|
|
||||||
return getRelease(release.name, release.namespace);
|
|
||||||
});},
|
|
||||||
});
|
|
||||||
|
|
||||||
export default releaseDetailsInjectable;
|
|
||||||
@ -5,296 +5,35 @@
|
|||||||
|
|
||||||
import "./release-details.scss";
|
import "./release-details.scss";
|
||||||
|
|
||||||
import React, { Component } from "react";
|
import React from "react";
|
||||||
import groupBy from "lodash/groupBy";
|
|
||||||
import type { IComputedValue } from "mobx";
|
|
||||||
import { computed, makeObservable, observable } from "mobx";
|
|
||||||
import { Link } from "react-router-dom";
|
|
||||||
import kebabCase from "lodash/kebabCase";
|
|
||||||
import type { HelmRelease, HelmReleaseDetails, HelmReleaseUpdateDetails, HelmReleaseUpdatePayload } 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, observer } from "mobx-react";
|
|
||||||
import { Spinner } from "../../spinner";
|
|
||||||
import { Table, TableCell, TableHead, TableRow } from "../../table";
|
|
||||||
import { Button } from "../../button";
|
|
||||||
import { Notifications } from "../../notifications";
|
|
||||||
import type { ThemeStore } from "../../../themes/store";
|
|
||||||
import type { ApiManager } from "../../../../common/k8s-api/api-manager";
|
|
||||||
import { SubTitle } from "../../layout/sub-title";
|
|
||||||
import { Checkbox } from "../../checkbox";
|
|
||||||
import { MonacoEditor } from "../../monaco-editor";
|
|
||||||
import type { IAsyncComputed } from "@ogre-tools/injectable-react";
|
|
||||||
import { withInjectables } from "@ogre-tools/injectable-react";
|
|
||||||
import createUpgradeChartTabInjectable from "../../dock/upgrade-chart/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";
|
|
||||||
import { KubeObjectAge } from "../../kube-object/age";
|
|
||||||
import type { KubeJsonApiData } from "../../../../common/k8s-api/kube-json-api";
|
|
||||||
import { entries } from "../../../../common/utils/objects";
|
|
||||||
import themeStoreInjectable from "../../../themes/store.injectable";
|
|
||||||
import type { GetDetailsUrl } from "../../kube-detail-params/get-details-url.injectable";
|
|
||||||
import apiManagerInjectable from "../../../../common/k8s-api/api-manager/manager.injectable";
|
|
||||||
import getDetailsUrlInjectable from "../../kube-detail-params/get-details-url.injectable";
|
|
||||||
|
|
||||||
export interface ReleaseDetailsProps {
|
import { observer } from "mobx-react";
|
||||||
hideDetails(): void;
|
import { withInjectables } from "@ogre-tools/injectable-react";
|
||||||
}
|
|
||||||
|
import type { IComputedValue } from "mobx";
|
||||||
|
import { ReleaseDetailsContent } from "./release-details-content";
|
||||||
|
import type { TargetHelmRelease } from "./target-helm-release.injectable";
|
||||||
|
import targetHelmReleaseInjectable from "./target-helm-release.injectable";
|
||||||
|
|
||||||
interface Dependencies {
|
interface Dependencies {
|
||||||
release: IComputedValue<HelmRelease | null | undefined>;
|
targetRelease: IComputedValue<
|
||||||
releaseDetails: IAsyncComputed<HelmReleaseDetails>;
|
TargetHelmRelease | undefined
|
||||||
releaseValues: IAsyncComputed<string>;
|
>;
|
||||||
updateRelease: (name: string, namespace: string, payload: HelmReleaseUpdatePayload) => Promise<HelmReleaseUpdateDetails>;
|
|
||||||
createUpgradeChartTab: (release: HelmRelease) => void;
|
|
||||||
userSuppliedValuesAreShown: { toggle: () => void; value: boolean };
|
|
||||||
themeStore: ThemeStore;
|
|
||||||
apiManager: ApiManager;
|
|
||||||
getDetailsUrl: GetDetailsUrl;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@observer
|
const NonInjectedReleaseDetails = observer(
|
||||||
class NonInjectedReleaseDetails extends Component<ReleaseDetailsProps & Dependencies> {
|
({ targetRelease }: Dependencies) => {
|
||||||
@observable saving = false;
|
const release = targetRelease.get();
|
||||||
|
|
||||||
private nonSavedValues = "";
|
return release ? <ReleaseDetailsContent targetRelease={release} /> : null;
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
constructor(props: ReleaseDetailsProps & Dependencies) {
|
export const ReleaseDetails = withInjectables<Dependencies>(
|
||||||
super(props);
|
NonInjectedReleaseDetails,
|
||||||
makeObservable(this);
|
{
|
||||||
}
|
getProps: (di) => ({
|
||||||
|
targetRelease: di.inject(targetHelmReleaseInjectable),
|
||||||
@computed get details() {
|
}),
|
||||||
return this.props.releaseDetails.value.get();
|
},
|
||||||
}
|
);
|
||||||
|
|
||||||
updateValues = async (release: HelmRelease) => {
|
|
||||||
const name = release.getName();
|
|
||||||
const namespace = release.getNs();
|
|
||||||
const data = {
|
|
||||||
chart: release.getChart(),
|
|
||||||
repo: await release.getRepo(),
|
|
||||||
version: release.getVersion(),
|
|
||||||
values: this.nonSavedValues,
|
|
||||||
};
|
|
||||||
|
|
||||||
this.saving = true;
|
|
||||||
|
|
||||||
try {
|
|
||||||
await this.props.updateRelease(name, namespace, data);
|
|
||||||
Notifications.ok(
|
|
||||||
<p>
|
|
||||||
Release
|
|
||||||
<b>{name}</b>
|
|
||||||
{" successfully updated!"}
|
|
||||||
</p>,
|
|
||||||
);
|
|
||||||
|
|
||||||
this.props.releaseValues.invalidate();
|
|
||||||
} catch (err) {
|
|
||||||
Notifications.checkedError(err, "Unknown error occured while updating release");
|
|
||||||
}
|
|
||||||
this.saving = false;
|
|
||||||
};
|
|
||||||
|
|
||||||
upgradeVersion = (release: HelmRelease) => {
|
|
||||||
const { hideDetails, createUpgradeChartTab } = this.props;
|
|
||||||
|
|
||||||
createUpgradeChartTab(release);
|
|
||||||
hideDetails();
|
|
||||||
};
|
|
||||||
|
|
||||||
renderValues(release: HelmRelease) {
|
|
||||||
return (
|
|
||||||
<Observer>
|
|
||||||
{() => {
|
|
||||||
const { saving } = this;
|
|
||||||
|
|
||||||
const releaseValuesArePending =
|
|
||||||
this.props.releaseValues.pending.get();
|
|
||||||
|
|
||||||
this.nonSavedValues = this.props.releaseValues.value.get();
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className="values">
|
|
||||||
<DrawerTitle>Values</DrawerTitle>
|
|
||||||
<div className="flex column gaps">
|
|
||||||
<Checkbox
|
|
||||||
label="User-supplied values only"
|
|
||||||
value={this.props.userSuppliedValuesAreShown.value}
|
|
||||||
onChange={this.props.userSuppliedValuesAreShown.toggle}
|
|
||||||
disabled={releaseValuesArePending}
|
|
||||||
/>
|
|
||||||
<MonacoEditor
|
|
||||||
style={{ minHeight: 300 }}
|
|
||||||
value={this.nonSavedValues}
|
|
||||||
onChange={(text) => (this.nonSavedValues = text)}
|
|
||||||
/>
|
|
||||||
<Button
|
|
||||||
primary
|
|
||||||
label="Save"
|
|
||||||
waiting={saving}
|
|
||||||
disabled={releaseValuesArePending}
|
|
||||||
onClick={() => this.updateValues(release)}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}}
|
|
||||||
</Observer>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
renderNotes() {
|
|
||||||
if (!this.details.info?.notes) return null;
|
|
||||||
const { notes } = this.details.info;
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className="notes">
|
|
||||||
{notes}
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
renderResources(resources: KubeJsonApiData[]) {
|
|
||||||
const { apiManager, getDetailsUrl } = this.props;
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className="resources">
|
|
||||||
{
|
|
||||||
entries(groupBy(resources, item => item.kind))
|
|
||||||
.map(([kind, items]) => (
|
|
||||||
<React.Fragment key={kind}>
|
|
||||||
<SubTitle title={kind} />
|
|
||||||
<Table scrollable={false}>
|
|
||||||
<TableHead sticky={false}>
|
|
||||||
<TableCell className="name">Name</TableCell>
|
|
||||||
{items[0].metadata.namespace && <TableCell className="namespace">Namespace</TableCell>}
|
|
||||||
<TableCell className="age">Age</TableCell>
|
|
||||||
</TableHead>
|
|
||||||
{items.map(item => {
|
|
||||||
const { name, namespace, uid } = item.metadata;
|
|
||||||
const api = apiManager.getApi(api => api.kind === kind && api.apiVersionWithGroup == item.apiVersion);
|
|
||||||
const detailsUrl = api ? getDetailsUrl(api.getUrl({ name, namespace })) : "";
|
|
||||||
|
|
||||||
return (
|
|
||||||
<TableRow key={uid}>
|
|
||||||
<TableCell className="name">
|
|
||||||
{detailsUrl ? <Link to={detailsUrl}>{name}</Link> : name}
|
|
||||||
</TableCell>
|
|
||||||
{namespace && (
|
|
||||||
<TableCell className="namespace">
|
|
||||||
{namespace}
|
|
||||||
</TableCell>
|
|
||||||
)}
|
|
||||||
<TableCell className="age">
|
|
||||||
<KubeObjectAge key="age" object={item} />
|
|
||||||
</TableCell>
|
|
||||||
</TableRow>
|
|
||||||
);
|
|
||||||
})}
|
|
||||||
</Table>
|
|
||||||
</React.Fragment>
|
|
||||||
))
|
|
||||||
}
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
renderContent(release: HelmRelease) {
|
|
||||||
if (!this.details) {
|
|
||||||
return <Spinner center/>;
|
|
||||||
}
|
|
||||||
|
|
||||||
const { resources } = this.details;
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div>
|
|
||||||
<DrawerItem name="Chart" className="chart">
|
|
||||||
<div className="flex gaps align-center">
|
|
||||||
<span>{release.getChart()}</span>
|
|
||||||
<Button
|
|
||||||
primary
|
|
||||||
label="Upgrade"
|
|
||||||
className="box right upgrade"
|
|
||||||
onClick={() => this.upgradeVersion(release)}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</DrawerItem>
|
|
||||||
<DrawerItem name="Updated">
|
|
||||||
{release.getUpdated()}
|
|
||||||
{` ago (${release.updated})`}
|
|
||||||
</DrawerItem>
|
|
||||||
<DrawerItem name="Namespace">
|
|
||||||
{release.getNs()}
|
|
||||||
</DrawerItem>
|
|
||||||
<DrawerItem name="Version" onClick={stopPropagation}>
|
|
||||||
<div className="version flex gaps align-center">
|
|
||||||
<span>
|
|
||||||
{release.getVersion()}
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
</DrawerItem>
|
|
||||||
<DrawerItem
|
|
||||||
name="Status"
|
|
||||||
className="status"
|
|
||||||
labelsOnly
|
|
||||||
>
|
|
||||||
<Badge
|
|
||||||
label={release.getStatus()}
|
|
||||||
className={kebabCase(release.getStatus())}
|
|
||||||
/>
|
|
||||||
</DrawerItem>
|
|
||||||
{this.renderValues(release)}
|
|
||||||
<DrawerTitle>Notes</DrawerTitle>
|
|
||||||
{this.renderNotes()}
|
|
||||||
<DrawerTitle>Resources</DrawerTitle>
|
|
||||||
{resources && this.renderResources(resources)}
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
render() {
|
|
||||||
const { hideDetails, themeStore } = this.props;
|
|
||||||
const release = this.props.release.get();
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Drawer
|
|
||||||
className={cssNames("ReleaseDetails", themeStore.activeTheme.type)}
|
|
||||||
usePortal={true}
|
|
||||||
open={Boolean(release)}
|
|
||||||
title={release ? `Release: ${release.getName()}` : ""}
|
|
||||||
onClose={hideDetails}
|
|
||||||
toolbar={release && (
|
|
||||||
<HelmReleaseMenu
|
|
||||||
release={release}
|
|
||||||
toolbar
|
|
||||||
hideDetails={hideDetails}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
>
|
|
||||||
{release && this.renderContent(release)}
|
|
||||||
</Drawer>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export const ReleaseDetails = withInjectables<Dependencies, ReleaseDetailsProps>(NonInjectedReleaseDetails, {
|
|
||||||
getProps: (di, props) => ({
|
|
||||||
...props,
|
|
||||||
release: di.inject(releaseInjectable),
|
|
||||||
releaseDetails: di.inject(releaseDetailsInjectable),
|
|
||||||
releaseValues: di.inject(releaseValuesInjectable),
|
|
||||||
userSuppliedValuesAreShown: di.inject(userSuppliedValuesAreShownInjectable),
|
|
||||||
updateRelease: di.inject(updateReleaseInjectable),
|
|
||||||
createUpgradeChartTab: di.inject(createUpgradeChartTabInjectable),
|
|
||||||
themeStore: di.inject(themeStoreInjectable),
|
|
||||||
apiManager: di.inject(apiManagerInjectable),
|
|
||||||
getDetailsUrl: di.inject(getDetailsUrlInjectable),
|
|
||||||
}),
|
|
||||||
});
|
|
||||||
|
|||||||
@ -1,36 +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 { 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({
|
|
||||||
id: "release-values",
|
|
||||||
|
|
||||||
instantiate: (di) =>
|
|
||||||
asyncComputed(async () => {
|
|
||||||
const release = di.inject(releaseInjectable).get();
|
|
||||||
|
|
||||||
// TODO: Figure out way to get rid of defensive code
|
|
||||||
if (!release) {
|
|
||||||
return "";
|
|
||||||
}
|
|
||||||
|
|
||||||
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 "";
|
|
||||||
}
|
|
||||||
}),
|
|
||||||
});
|
|
||||||
|
|
||||||
export default releaseValuesInjectable;
|
|
||||||
@ -3,29 +3,24 @@
|
|||||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||||
*/
|
*/
|
||||||
import { getInjectable } from "@ogre-tools/injectable";
|
import { getInjectable } from "@ogre-tools/injectable";
|
||||||
import { matches } from "lodash/fp";
|
|
||||||
import releasesInjectable from "../releases.injectable";
|
|
||||||
import { computed } from "mobx";
|
import { computed } from "mobx";
|
||||||
import helmReleasesRouteParametersInjectable from "../helm-releases-route-parameters.injectable";
|
import helmReleasesRouteParametersInjectable from "../helm-releases-route-parameters.injectable";
|
||||||
|
|
||||||
const releaseInjectable = getInjectable({
|
export interface TargetHelmRelease { name: string; namespace: string }
|
||||||
id: "release",
|
|
||||||
|
const targetHelmReleaseInjectable = getInjectable({
|
||||||
|
id: "target-helm-release",
|
||||||
|
|
||||||
instantiate: (di) => {
|
instantiate: (di) => {
|
||||||
const releases = di.inject(releasesInjectable);
|
|
||||||
const routeParameters = di.inject(helmReleasesRouteParametersInjectable);
|
const routeParameters = di.inject(helmReleasesRouteParametersInjectable);
|
||||||
|
|
||||||
return computed(() => {
|
return computed((): TargetHelmRelease | undefined => {
|
||||||
const name = routeParameters.name.get();
|
const name = routeParameters.name.get();
|
||||||
const namespace = routeParameters.namespace.get();
|
const namespace = routeParameters.namespace.get();
|
||||||
|
|
||||||
if (!name || !namespace) {
|
return name && namespace ? { name, namespace } : undefined;
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
return releases.value.get().find(matches({ name, namespace }));
|
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
export default releaseInjectable;
|
export default targetHelmReleaseInjectable;
|
||||||
@ -1,27 +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 { observable } from "mobx";
|
|
||||||
|
|
||||||
const userSuppliedValuesAreShownInjectable = getInjectable({
|
|
||||||
id: "user-supplied-values-are-shown",
|
|
||||||
|
|
||||||
instantiate: () => {
|
|
||||||
const state = observable.box(false);
|
|
||||||
|
|
||||||
return {
|
|
||||||
get value() {
|
|
||||||
return state.get();
|
|
||||||
},
|
|
||||||
|
|
||||||
toggle: () => {
|
|
||||||
state.set(!state.get());
|
|
||||||
},
|
|
||||||
};
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
export default userSuppliedValuesAreShownInjectable;
|
|
||||||
|
|
||||||
@ -5,9 +5,13 @@
|
|||||||
import { getInjectable } from "@ogre-tools/injectable";
|
import { getInjectable } from "@ogre-tools/injectable";
|
||||||
import { asyncComputed } from "@ogre-tools/injectable-react";
|
import { asyncComputed } from "@ogre-tools/injectable-react";
|
||||||
import namespaceStoreInjectable from "../+namespaces/store.injectable";
|
import namespaceStoreInjectable from "../+namespaces/store.injectable";
|
||||||
import { listReleases } from "../../../common/k8s-api/endpoints/helm-releases.api";
|
|
||||||
import clusterFrameContextInjectable from "../../cluster-frame-context/cluster-frame-context.injectable";
|
import clusterFrameContextInjectable from "../../cluster-frame-context/cluster-frame-context.injectable";
|
||||||
import releaseSecretsInjectable from "./release-secrets.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";
|
||||||
|
|
||||||
const releasesInjectable = getInjectable({
|
const releasesInjectable = getInjectable({
|
||||||
id: "releases",
|
id: "releases",
|
||||||
@ -16,6 +20,7 @@ const releasesInjectable = getInjectable({
|
|||||||
const clusterContext = di.inject(clusterFrameContextInjectable);
|
const clusterContext = di.inject(clusterFrameContextInjectable);
|
||||||
const namespaceStore = di.inject(namespaceStoreInjectable);
|
const namespaceStore = di.inject(namespaceStoreInjectable);
|
||||||
const releaseSecrets = di.inject(releaseSecretsInjectable);
|
const releaseSecrets = di.inject(releaseSecretsInjectable);
|
||||||
|
const callForHelmReleases = di.inject(callForHelmReleasesInjectable);
|
||||||
|
|
||||||
return asyncComputed(async () => {
|
return asyncComputed(async () => {
|
||||||
const contextNamespaces = namespaceStore.contextNamespaces || [];
|
const contextNamespaces = namespaceStore.contextNamespaces || [];
|
||||||
@ -29,15 +34,83 @@ const releasesInjectable = getInjectable({
|
|||||||
contextNamespaces.includes(namespace),
|
contextNamespaces.includes(namespace),
|
||||||
);
|
);
|
||||||
|
|
||||||
const releaseArrays = await (isLoadingAll ? listReleases() : Promise.all(
|
const releaseArrays = await (isLoadingAll ? callForHelmReleases() : Promise.all(
|
||||||
contextNamespaces.map((namespace) =>
|
contextNamespaces.map((namespace) =>
|
||||||
listReleases(namespace),
|
callForHelmReleases(namespace),
|
||||||
),
|
),
|
||||||
));
|
));
|
||||||
|
|
||||||
return releaseArrays.flat();
|
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;
|
export default releasesInjectable;
|
||||||
|
|||||||
@ -16,7 +16,6 @@ import { NamespaceSelectFilter } from "../+namespaces/namespace-select-filter";
|
|||||||
import { kebabCase } from "lodash/fp";
|
import { kebabCase } from "lodash/fp";
|
||||||
import { HelmReleaseMenu } from "./release-menu";
|
import { HelmReleaseMenu } from "./release-menu";
|
||||||
import { ReleaseRollbackDialog } from "./dialog/dialog";
|
import { ReleaseRollbackDialog } from "./dialog/dialog";
|
||||||
import { ReleaseDetails } from "./release-details/release-details";
|
|
||||||
import removableReleasesInjectable from "./removable-releases.injectable";
|
import removableReleasesInjectable from "./removable-releases.injectable";
|
||||||
import type { RemovableHelmRelease } from "./removable-releases";
|
import type { RemovableHelmRelease } from "./removable-releases";
|
||||||
import type { IComputedValue } from "mobx";
|
import type { IComputedValue } from "mobx";
|
||||||
@ -145,6 +144,7 @@ class NonInjectedHelmReleases extends Component<Dependencies> {
|
|||||||
isConfigurable
|
isConfigurable
|
||||||
tableId="helm_releases"
|
tableId="helm_releases"
|
||||||
className="HelmReleases"
|
className="HelmReleases"
|
||||||
|
customizeTableRowProps={(item) => ({ testId: `helm-release-row-for-${item.getId()}` })}
|
||||||
sortingCallbacks={{
|
sortingCallbacks={{
|
||||||
[columnId.name]: release => release.getName(),
|
[columnId.name]: release => release.getName(),
|
||||||
[columnId.namespace]: release => release.getNs(),
|
[columnId.namespace]: release => release.getNs(),
|
||||||
@ -204,10 +204,7 @@ class NonInjectedHelmReleases extends Component<Dependencies> {
|
|||||||
message: this.renderRemoveDialogMessage(selectedItems),
|
message: this.renderRemoveDialogMessage(selectedItems),
|
||||||
})}
|
})}
|
||||||
onDetails={this.onDetails}
|
onDetails={this.onDetails}
|
||||||
/>
|
spinnerTestId="helm-releases-spinner"
|
||||||
|
|
||||||
<ReleaseDetails
|
|
||||||
hideDetails={this.hideDetails}
|
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<ReleaseRollbackDialog/>
|
<ReleaseRollbackDialog/>
|
||||||
|
|||||||
@ -0,0 +1,15 @@
|
|||||||
|
/**
|
||||||
|
* 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.",
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
@ -0,0 +1,50 @@
|
|||||||
|
/**
|
||||||
|
* 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;
|
||||||
@ -3,26 +3,23 @@
|
|||||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||||
*/
|
*/
|
||||||
import { getInjectable } from "@ogre-tools/injectable";
|
import { getInjectable } from "@ogre-tools/injectable";
|
||||||
|
|
||||||
import type {
|
|
||||||
HelmReleaseUpdatePayload } from "../../../../common/k8s-api/endpoints/helm-releases.api";
|
|
||||||
import {
|
|
||||||
updateRelease,
|
|
||||||
} from "../../../../common/k8s-api/endpoints/helm-releases.api";
|
|
||||||
import releasesInjectable from "../releases.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";
|
||||||
|
|
||||||
const updateReleaseInjectable = getInjectable({
|
const updateReleaseInjectable = getInjectable({
|
||||||
id: "update-release",
|
id: "update-release",
|
||||||
|
|
||||||
instantiate: (di) => {
|
instantiate: (di): CallForHelmReleaseUpdate => {
|
||||||
const releases = di.inject(releasesInjectable);
|
const releases = di.inject(releasesInjectable);
|
||||||
|
const callForHelmReleaseUpdate = di.inject(callForHelmReleaseUpdateInjectable);
|
||||||
|
|
||||||
return async (
|
return async (
|
||||||
name: string,
|
name,
|
||||||
namespace: string,
|
namespace,
|
||||||
payload: HelmReleaseUpdatePayload,
|
payload,
|
||||||
) => {
|
) => {
|
||||||
const result = await updateRelease(name, namespace, payload);
|
const result = await callForHelmReleaseUpdate(name, namespace, payload);
|
||||||
|
|
||||||
releases.invalidate();
|
releases.invalidate();
|
||||||
|
|
||||||
|
|||||||
@ -68,6 +68,11 @@ class NonInjectedClusterView extends React.Component<Dependencies> {
|
|||||||
bindEvents() {
|
bindEvents() {
|
||||||
disposeOnUnmount(this, [
|
disposeOnUnmount(this, [
|
||||||
reaction(() => this.clusterId, async (clusterId) => {
|
reaction(() => this.clusterId, async (clusterId) => {
|
||||||
|
// TODO: replace with better handling
|
||||||
|
if (clusterId && !this.props.entityRegistry.getById(clusterId)) {
|
||||||
|
return this.props.navigateToCatalog(); // redirect to catalog when the clusterId does not correspond to an entity
|
||||||
|
}
|
||||||
|
|
||||||
this.props.clusterFrames.setVisibleCluster(clusterId);
|
this.props.clusterFrames.setVisibleCluster(clusterId);
|
||||||
this.props.clusterFrames.initView(clusterId);
|
this.props.clusterFrames.initView(clusterId);
|
||||||
requestClusterActivation(clusterId, false); // activate and fetch cluster's state from main
|
requestClusterActivation(clusterId, false); // activate and fetch cluster's state from main
|
||||||
|
|||||||
@ -17,7 +17,6 @@ import type { DiRender } from "../../test-utils/renderFor";
|
|||||||
import { renderFor } from "../../test-utils/renderFor";
|
import { renderFor } from "../../test-utils/renderFor";
|
||||||
import directoryForUserDataInjectable from "../../../../common/app-paths/directory-for-user-data/directory-for-user-data.injectable";
|
import directoryForUserDataInjectable from "../../../../common/app-paths/directory-for-user-data/directory-for-user-data.injectable";
|
||||||
import getConfigurationFileModelInjectable from "../../../../common/get-configuration-file-model/get-configuration-file-model.injectable";
|
import getConfigurationFileModelInjectable from "../../../../common/get-configuration-file-model/get-configuration-file-model.injectable";
|
||||||
import appVersionInjectable from "../../../../common/get-configuration-file-model/app-version/app-version.injectable";
|
|
||||||
import assert from "assert";
|
import assert from "assert";
|
||||||
import hostedClusterIdInjectable from "../../../cluster-frame-context/hosted-cluster-id.injectable";
|
import hostedClusterIdInjectable from "../../../cluster-frame-context/hosted-cluster-id.injectable";
|
||||||
|
|
||||||
@ -84,7 +83,6 @@ describe("<DockTabs />", () => {
|
|||||||
);
|
);
|
||||||
|
|
||||||
di.permitSideEffects(getConfigurationFileModelInjectable);
|
di.permitSideEffects(getConfigurationFileModelInjectable);
|
||||||
di.permitSideEffects(appVersionInjectable);
|
|
||||||
|
|
||||||
dockStore = di.inject(dockStoreInjectable);
|
dockStore = di.inject(dockStoreInjectable);
|
||||||
|
|
||||||
|
|||||||
@ -20,7 +20,6 @@ import type { TabId } from "../../dock/store";
|
|||||||
import userEvent from "@testing-library/user-event";
|
import userEvent from "@testing-library/user-event";
|
||||||
import { SearchStore } from "../../../../search-store/search-store";
|
import { SearchStore } from "../../../../search-store/search-store";
|
||||||
import getConfigurationFileModelInjectable from "../../../../../common/get-configuration-file-model/get-configuration-file-model.injectable";
|
import getConfigurationFileModelInjectable from "../../../../../common/get-configuration-file-model/get-configuration-file-model.injectable";
|
||||||
import appVersionInjectable from "../../../../../common/get-configuration-file-model/app-version/app-version.injectable";
|
|
||||||
import assert from "assert";
|
import assert from "assert";
|
||||||
|
|
||||||
jest.mock("electron", () => ({
|
jest.mock("electron", () => ({
|
||||||
@ -133,7 +132,6 @@ describe("<LogResourceSelector />", () => {
|
|||||||
di.override(callForLogsInjectable, () => () => Promise.resolve("some-logs"));
|
di.override(callForLogsInjectable, () => () => Promise.resolve("some-logs"));
|
||||||
|
|
||||||
di.permitSideEffects(getConfigurationFileModelInjectable);
|
di.permitSideEffects(getConfigurationFileModelInjectable);
|
||||||
di.permitSideEffects(appVersionInjectable);
|
|
||||||
|
|
||||||
render = renderFor(di);
|
render = renderFor(di);
|
||||||
|
|
||||||
|
|||||||
@ -10,13 +10,15 @@ import type { DockStore, DockTabCreateSpecific, TabId } from "../dock/store";
|
|||||||
import { TabKind } from "../dock/store";
|
import { TabKind } from "../dock/store";
|
||||||
import type { UpgradeChartTabStore } from "./store";
|
import type { UpgradeChartTabStore } from "./store";
|
||||||
import { runInAction } from "mobx";
|
import { runInAction } from "mobx";
|
||||||
|
import getRandomUpgradeChartTabIdInjectable from "./get-random-upgrade-chart-tab-id.injectable";
|
||||||
|
|
||||||
interface Dependencies {
|
interface Dependencies {
|
||||||
upgradeChartStore: UpgradeChartTabStore;
|
upgradeChartStore: UpgradeChartTabStore;
|
||||||
dockStore: DockStore;
|
dockStore: DockStore;
|
||||||
|
getRandomId: () => string;
|
||||||
}
|
}
|
||||||
|
|
||||||
const createUpgradeChartTab = ({ upgradeChartStore, dockStore }: Dependencies) => (release: HelmRelease, tabParams: DockTabCreateSpecific = {}): TabId => {
|
const createUpgradeChartTab = ({ upgradeChartStore, dockStore, getRandomId }: Dependencies) => (release: HelmRelease, tabParams: DockTabCreateSpecific = {}): TabId => {
|
||||||
const tabId = upgradeChartStore.getTabIdByRelease(release.getName());
|
const tabId = upgradeChartStore.getTabIdByRelease(release.getName());
|
||||||
|
|
||||||
if (tabId) {
|
if (tabId) {
|
||||||
@ -29,6 +31,7 @@ const createUpgradeChartTab = ({ upgradeChartStore, dockStore }: Dependencies) =
|
|||||||
return runInAction(() => {
|
return runInAction(() => {
|
||||||
const tab = dockStore.createTab(
|
const tab = dockStore.createTab(
|
||||||
{
|
{
|
||||||
|
id: getRandomId(),
|
||||||
title: `Helm Upgrade: ${release.getName()}`,
|
title: `Helm Upgrade: ${release.getName()}`,
|
||||||
...tabParams,
|
...tabParams,
|
||||||
kind: TabKind.UPGRADE_CHART,
|
kind: TabKind.UPGRADE_CHART,
|
||||||
@ -51,6 +54,7 @@ const createUpgradeChartTabInjectable = getInjectable({
|
|||||||
instantiate: (di) => createUpgradeChartTab({
|
instantiate: (di) => createUpgradeChartTab({
|
||||||
upgradeChartStore: di.inject(upgradeChartTabStoreInjectable),
|
upgradeChartStore: di.inject(upgradeChartTabStoreInjectable),
|
||||||
dockStore: di.inject(dockStoreInjectable),
|
dockStore: di.inject(dockStoreInjectable),
|
||||||
|
getRandomId: di.inject(getRandomUpgradeChartTabIdInjectable),
|
||||||
}),
|
}),
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@ -0,0 +1,13 @@
|
|||||||
|
/**
|
||||||
|
* 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 getRandomIdInjectable from "../../../../common/utils/get-random-id.injectable";
|
||||||
|
|
||||||
|
const getRandomUpgradeChartTabIdInjectable = getInjectable({
|
||||||
|
id: "get-random-upgrade-chart-tab-id",
|
||||||
|
instantiate: (di) => di.inject(getRandomIdInjectable),
|
||||||
|
});
|
||||||
|
|
||||||
|
export default getRandomUpgradeChartTabIdInjectable;
|
||||||
@ -16,7 +16,7 @@ import { Spinner } from "../../spinner";
|
|||||||
import { Badge } from "../../badge";
|
import { Badge } from "../../badge";
|
||||||
import { EditorPanel } from "../editor-panel";
|
import { EditorPanel } from "../editor-panel";
|
||||||
import { helmChartStore, type ChartVersion } from "../../+helm-charts/helm-chart.store";
|
import { helmChartStore, type ChartVersion } from "../../+helm-charts/helm-chart.store";
|
||||||
import type { HelmRelease, HelmReleaseUpdateDetails, HelmReleaseUpdatePayload } from "../../../../common/k8s-api/endpoints/helm-releases.api";
|
import type { HelmRelease } from "../../../../common/k8s-api/endpoints/helm-releases.api";
|
||||||
import type { SelectOption } from "../../select";
|
import type { SelectOption } from "../../select";
|
||||||
import { Select } from "../../select";
|
import { Select } from "../../select";
|
||||||
import type { IAsyncComputed } from "@ogre-tools/injectable-react";
|
import type { IAsyncComputed } from "@ogre-tools/injectable-react";
|
||||||
@ -24,6 +24,8 @@ import { withInjectables } from "@ogre-tools/injectable-react";
|
|||||||
import upgradeChartTabStoreInjectable from "./store.injectable";
|
import upgradeChartTabStoreInjectable from "./store.injectable";
|
||||||
import updateReleaseInjectable from "../../+helm-releases/update-release/update-release.injectable";
|
import updateReleaseInjectable from "../../+helm-releases/update-release/update-release.injectable";
|
||||||
import releasesInjectable from "../../+helm-releases/releases.injectable";
|
import releasesInjectable from "../../+helm-releases/releases.injectable";
|
||||||
|
import type { CallForHelmReleaseUpdate } from "../../+helm-releases/update-release/call-for-helm-release-update/call-for-helm-release-update.injectable";
|
||||||
|
import { first } from "lodash/fp";
|
||||||
|
|
||||||
export interface UpgradeChartProps {
|
export interface UpgradeChartProps {
|
||||||
className?: string;
|
className?: string;
|
||||||
@ -33,7 +35,7 @@ export interface UpgradeChartProps {
|
|||||||
interface Dependencies {
|
interface Dependencies {
|
||||||
releases: IAsyncComputed<HelmRelease[]>;
|
releases: IAsyncComputed<HelmRelease[]>;
|
||||||
upgradeChartTabStore: UpgradeChartTabStore;
|
upgradeChartTabStore: UpgradeChartTabStore;
|
||||||
updateRelease: (name: string, namespace: string, payload: HelmReleaseUpdatePayload) => Promise<HelmReleaseUpdateDetails>;
|
updateRelease: CallForHelmReleaseUpdate;
|
||||||
}
|
}
|
||||||
|
|
||||||
@observer
|
@observer
|
||||||
@ -96,7 +98,7 @@ export class NonInjectedUpgradeChart extends React.Component<UpgradeChartProps &
|
|||||||
const versions = await helmChartStore.getVersions(release.getChart());
|
const versions = await helmChartStore.getVersions(release.getChart());
|
||||||
|
|
||||||
this.versions.replace(versions);
|
this.versions.replace(versions);
|
||||||
this.version = this.versions[0];
|
this.version = first(this.versions);
|
||||||
}
|
}
|
||||||
|
|
||||||
onChange = action((value: string) => {
|
onChange = action((value: string) => {
|
||||||
|
|||||||
@ -39,6 +39,8 @@ export interface DrawerProps {
|
|||||||
onClose?: () => void;
|
onClose?: () => void;
|
||||||
toolbar?: React.ReactNode;
|
toolbar?: React.ReactNode;
|
||||||
children?: SingleOrMany<React.ReactNode>;
|
children?: SingleOrMany<React.ReactNode>;
|
||||||
|
"data-testid"?: string;
|
||||||
|
testIdForClose?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
const defaultProps = {
|
const defaultProps = {
|
||||||
@ -179,7 +181,7 @@ class NonInjectedDrawer extends React.Component<DrawerProps & Dependencies & typ
|
|||||||
};
|
};
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const { className, contentClass, animation, open, position, title, children, toolbar, size, usePortal } = this.props;
|
const { className, contentClass, animation, open, position, title, children, toolbar, size, usePortal, "data-testid": testId, testIdForClose } = this.props;
|
||||||
const { isCopied, width } = this.state;
|
const { isCopied, width } = this.state;
|
||||||
const copyTooltip = isCopied ? "Copied!" : "Copy";
|
const copyTooltip = isCopied ? "Copied!" : "Copy";
|
||||||
const copyIcon = isCopied ? "done" : "content_copy";
|
const copyIcon = isCopied ? "done" : "content_copy";
|
||||||
@ -193,6 +195,7 @@ class NonInjectedDrawer extends React.Component<DrawerProps & Dependencies & typ
|
|||||||
className={cssNames("Drawer", className, position)}
|
className={cssNames("Drawer", className, position)}
|
||||||
style={{ "--size": drawerSize } as React.CSSProperties}
|
style={{ "--size": drawerSize } as React.CSSProperties}
|
||||||
ref={e => this.contentElem = e}
|
ref={e => this.contentElem = e}
|
||||||
|
data-testid={testId}
|
||||||
>
|
>
|
||||||
<div className="drawer-wrapper flex column">
|
<div className="drawer-wrapper flex column">
|
||||||
<div className="drawer-title flex align-center">
|
<div className="drawer-title flex align-center">
|
||||||
@ -211,6 +214,7 @@ class NonInjectedDrawer extends React.Component<DrawerProps & Dependencies & typ
|
|||||||
material="close"
|
material="close"
|
||||||
tooltip="Close"
|
tooltip="Close"
|
||||||
onClick={this.close}
|
onClick={this.close}
|
||||||
|
data-testid={testIdForClose}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
|
|||||||
@ -16,7 +16,6 @@ import { ConfirmDialog } from "../../confirm-dialog";
|
|||||||
import mockFs from "mock-fs";
|
import mockFs from "mock-fs";
|
||||||
import directoryForUserDataInjectable from "../../../../common/app-paths/directory-for-user-data/directory-for-user-data.injectable";
|
import directoryForUserDataInjectable from "../../../../common/app-paths/directory-for-user-data/directory-for-user-data.injectable";
|
||||||
import getConfigurationFileModelInjectable from "../../../../common/get-configuration-file-model/get-configuration-file-model.injectable";
|
import getConfigurationFileModelInjectable from "../../../../common/get-configuration-file-model/get-configuration-file-model.injectable";
|
||||||
import appVersionInjectable from "../../../../common/get-configuration-file-model/app-version/app-version.injectable";
|
|
||||||
import type { HotbarStore } from "../../../../common/hotbars/store";
|
import type { HotbarStore } from "../../../../common/hotbars/store";
|
||||||
import storesAndApisCanBeCreatedInjectable from "../../../stores-apis-can-be-created.injectable";
|
import storesAndApisCanBeCreatedInjectable from "../../../stores-apis-can-be-created.injectable";
|
||||||
|
|
||||||
@ -49,7 +48,6 @@ describe("<HotbarRemoveCommand />", () => {
|
|||||||
|
|
||||||
di.permitSideEffects(hotbarStoreInjectable);
|
di.permitSideEffects(hotbarStoreInjectable);
|
||||||
di.permitSideEffects(getConfigurationFileModelInjectable);
|
di.permitSideEffects(getConfigurationFileModelInjectable);
|
||||||
di.permitSideEffects(appVersionInjectable);
|
|
||||||
|
|
||||||
render = renderFor(di);
|
render = renderFor(di);
|
||||||
});
|
});
|
||||||
|
|||||||
@ -60,6 +60,8 @@ export interface ItemListLayoutContentProps<Item extends ItemObject, PreLoadStor
|
|||||||
// other
|
// other
|
||||||
customizeRemoveDialog?: (selectedItems: Item[]) => Partial<ConfirmDialogParams>;
|
customizeRemoveDialog?: (selectedItems: Item[]) => Partial<ConfirmDialogParams>;
|
||||||
|
|
||||||
|
spinnerTestId?: string;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Message to display when a store failed to load
|
* Message to display when a store failed to load
|
||||||
*
|
*
|
||||||
@ -221,7 +223,7 @@ class NonInjectedItemListLayoutContent<
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (!this.props.getIsReady()) {
|
if (!this.props.getIsReady()) {
|
||||||
return <Spinner center />;
|
return <Spinner center data-testid={this.props.spinnerTestId} />;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.props.getFilters().length > 0) {
|
if (this.props.getFilters().length > 0) {
|
||||||
|
|||||||
@ -121,6 +121,8 @@ export type ItemListLayoutProps<Item extends ItemObject, PreLoadStores extends b
|
|||||||
customizeRemoveDialog?: (selectedItems: Item[]) => Partial<ConfirmDialogParams>;
|
customizeRemoveDialog?: (selectedItems: Item[]) => Partial<ConfirmDialogParams>;
|
||||||
renderFooter?: (parent: NonInjectedItemListLayout<Item, PreLoadStores>) => React.ReactNode;
|
renderFooter?: (parent: NonInjectedItemListLayout<Item, PreLoadStores>) => React.ReactNode;
|
||||||
|
|
||||||
|
spinnerTestId?: string;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Message to display when a store failed to load
|
* Message to display when a store failed to load
|
||||||
*
|
*
|
||||||
@ -321,6 +323,7 @@ class NonInjectedItemListLayout<I extends ItemObject, PreLoadStores extends bool
|
|||||||
onDetails={this.props.onDetails}
|
onDetails={this.props.onDetails}
|
||||||
customizeRemoveDialog={this.props.customizeRemoveDialog}
|
customizeRemoveDialog={this.props.customizeRemoveDialog}
|
||||||
failedToLoadMessage={this.props.failedToLoadMessage}
|
failedToLoadMessage={this.props.failedToLoadMessage}
|
||||||
|
spinnerTestId={this.props.spinnerTestId}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
{this.props.renderFooter?.(this)}
|
{this.props.renderFooter?.(this)}
|
||||||
|
|||||||
@ -16,7 +16,6 @@ import rendererExtensionsInjectable from "../../../extensions/renderer-extension
|
|||||||
import { computed } from "mobx";
|
import { computed } from "mobx";
|
||||||
import type { LensRendererExtension } from "../../../extensions/lens-renderer-extension";
|
import type { LensRendererExtension } from "../../../extensions/lens-renderer-extension";
|
||||||
import getConfigurationFileModelInjectable from "../../../common/get-configuration-file-model/get-configuration-file-model.injectable";
|
import getConfigurationFileModelInjectable from "../../../common/get-configuration-file-model/get-configuration-file-model.injectable";
|
||||||
import appVersionInjectable from "../../../common/get-configuration-file-model/app-version/app-version.injectable";
|
|
||||||
|
|
||||||
jest.mock("electron", () => ({
|
jest.mock("electron", () => ({
|
||||||
ipcRenderer: {
|
ipcRenderer: {
|
||||||
@ -37,9 +36,8 @@ describe("<Select />", () => {
|
|||||||
|
|
||||||
di.override(directoryForUserDataInjectable, () => "some-directory-for-user-data");
|
di.override(directoryForUserDataInjectable, () => "some-directory-for-user-data");
|
||||||
di.override(rendererExtensionsInjectable, () => computed(() => [] as LensRendererExtension[]));
|
di.override(rendererExtensionsInjectable, () => computed(() => [] as LensRendererExtension[]));
|
||||||
|
|
||||||
di.permitSideEffects(getConfigurationFileModelInjectable);
|
di.permitSideEffects(getConfigurationFileModelInjectable);
|
||||||
di.permitSideEffects(appVersionInjectable);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
afterEach(() => {
|
afterEach(() => {
|
||||||
|
|||||||
@ -41,7 +41,7 @@ import type { IpcRenderer } from "electron";
|
|||||||
import setupOnApiErrorListenersInjectable from "./api/setup-on-api-errors.injectable";
|
import setupOnApiErrorListenersInjectable from "./api/setup-on-api-errors.injectable";
|
||||||
import { observable, computed } from "mobx";
|
import { observable, computed } from "mobx";
|
||||||
import defaultShellInjectable from "./components/+preferences/default-shell.injectable";
|
import defaultShellInjectable from "./components/+preferences/default-shell.injectable";
|
||||||
import appVersionInjectable from "../common/get-configuration-file-model/app-version/app-version.injectable";
|
import appVersionInjectable from "../common/vars/app-version.injectable";
|
||||||
import provideInitialValuesForSyncBoxesInjectable from "./utils/sync-box/provide-initial-values-for-sync-boxes.injectable";
|
import provideInitialValuesForSyncBoxesInjectable from "./utils/sync-box/provide-initial-values-for-sync-boxes.injectable";
|
||||||
import requestAnimationFrameInjectable from "./components/animate/request-animation-frame.injectable";
|
import requestAnimationFrameInjectable from "./components/animate/request-animation-frame.injectable";
|
||||||
import getRandomIdInjectable from "../common/utils/get-random-id.injectable";
|
import getRandomIdInjectable from "../common/utils/get-random-id.injectable";
|
||||||
@ -96,6 +96,16 @@ export const getDiForUnitTesting = (opts: { doGeneralOverrides?: boolean } = {})
|
|||||||
di.preventSideEffects();
|
di.preventSideEffects();
|
||||||
|
|
||||||
if (doGeneralOverrides) {
|
if (doGeneralOverrides) {
|
||||||
|
const globalOverrideFilePaths = getGlobalOverridePaths();
|
||||||
|
|
||||||
|
const globalOverrides = globalOverrideFilePaths.map(
|
||||||
|
(filePath) => require(filePath).default,
|
||||||
|
);
|
||||||
|
|
||||||
|
globalOverrides.forEach(globalOverride => {
|
||||||
|
di.override(globalOverride.injectable, globalOverride.overridingInstantiate);
|
||||||
|
});
|
||||||
|
|
||||||
di.override(getRandomIdInjectable, () => () => "some-irrelevant-random-id");
|
di.override(getRandomIdInjectable, () => () => "some-irrelevant-random-id");
|
||||||
di.override(platformInjectable, () => "darwin");
|
di.override(platformInjectable, () => "darwin");
|
||||||
di.override(startTopbarStateSyncInjectable, () => ({
|
di.override(startTopbarStateSyncInjectable, () => ({
|
||||||
@ -228,6 +238,14 @@ const getInjectableFilePaths = memoize(() => [
|
|||||||
...glob.sync("../extensions/**/*.injectable.{ts,tsx}", { cwd: __dirname }),
|
...glob.sync("../extensions/**/*.injectable.{ts,tsx}", { cwd: __dirname }),
|
||||||
]);
|
]);
|
||||||
|
|
||||||
|
const getGlobalOverridePaths = memoize(() =>
|
||||||
|
glob.sync(
|
||||||
|
"../{common,extensions,renderer}/**/*.global-override-for-injectable.{ts,tsx}",
|
||||||
|
|
||||||
|
{ cwd: __dirname },
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
const overrideFunctionalInjectables = (di: DiContainer, injectables: Injectable<any, any, any>[]) => {
|
const overrideFunctionalInjectables = (di: DiContainer, injectables: Injectable<any, any, any>[]) => {
|
||||||
injectables.forEach(injectable => {
|
injectables.forEach(injectable => {
|
||||||
di.override(injectable, () => () => {
|
di.override(injectable, () => () => {
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user