mirror of
https://github.com/lensapp/lens.git
synced 2025-05-20 05:10:56 +00:00
Persist apiVersion when editing resources in monaco (#4406)
* Persist apiVersion when editing resources in monaco - Use a new custom k8slens prefixed label - Means that users aren't surprised when they use lens to update a resource to a new apiVersionWithGroup - Doesn't touch the versions in the stores Signed-off-by: Sebastian Malton <sebastian@malton.name> * Fix lint Signed-off-by: Sebastian Malton <sebastian@malton.name> * fix: Fix lint issues Signed-off-by: Sebastian Malton <sebastian@malton.name> * chore: make lint not bail on failure Signed-off-by: Sebastian Malton <sebastian@malton.name> * chore: Run lint:fix on all files Signed-off-by: Sebastian Malton <sebastian@malton.name> --------- Signed-off-by: Sebastian Malton <sebastian@malton.name>
This commit is contained in:
1 parent
058494bc73
commit
807f98ed1b
57 files changed
+295
-400
No files matched your search
@@ -109,7 +109,25 @@ export function parseKubeApi(path: string): IKubeApiParsed {
|
||||
};
|
||||
}
|
||||
|
||||
export function createKubeApiURL({ apiPrefix = "/apis", resource, apiVersion, name, namespace }: IKubeApiLinkRef): string {
|
||||
function isIKubeApiParsed(refOrParsed: IKubeApiLinkRef | IKubeApiParsed): refOrParsed is IKubeApiParsed {
|
||||
return "apiGroup" in refOrParsed;
|
||||
}
|
||||
|
||||
export function createKubeApiURL(linkRef: IKubeApiLinkRef): string;
|
||||
export function createKubeApiURL(linkParsed: IKubeApiParsed): string;
|
||||
|
||||
export function createKubeApiURL(ref: IKubeApiLinkRef | IKubeApiParsed): string {
|
||||
if (isIKubeApiParsed(ref)) {
|
||||
return createKubeApiURL({
|
||||
apiPrefix: ref.apiPrefix,
|
||||
resource: ref.resource,
|
||||
name: ref.name,
|
||||
namespace: ref.namespace,
|
||||
apiVersion: `${ref.apiGroup}/${ref.apiVersion}`,
|
||||
});
|
||||
}
|
||||
|
||||
const { apiPrefix = "/apis", resource, apiVersion, name, namespace } = ref;
|
||||
const parts = [apiPrefix, apiVersion];
|
||||
|
||||
if (namespace) {
|
||||
|
||||
@@ -193,7 +193,7 @@ export interface KubeApiWatchOptions<Object extends KubeObject, Data extends Kub
|
||||
|
||||
export type KubeApiPatchType = "merge" | "json" | "strategic";
|
||||
|
||||
const patchTypeHeaders: Record<KubeApiPatchType, string> = {
|
||||
export const patchTypeHeaders: Record<KubeApiPatchType, string> = {
|
||||
"merge": "application/merge-patch+json",
|
||||
"json": "application/json-patch+json",
|
||||
"strategic": "application/strategic-merge-patch+json",
|
||||
|
||||
@@ -437,6 +437,19 @@ const resourceApplierAnnotationsForFiltering = [
|
||||
|
||||
const filterOutResourceApplierAnnotations = (label: string) => !resourceApplierAnnotationsForFiltering.some(key => label.startsWith(key));
|
||||
|
||||
export interface RawKubeObject<
|
||||
Metadata extends KubeObjectMetadata = KubeObjectMetadata,
|
||||
Status = Record<string, unknown>,
|
||||
Spec = Record<string, unknown>,
|
||||
> {
|
||||
apiVersion: string;
|
||||
kind: string;
|
||||
metadata: Metadata;
|
||||
status?: Status;
|
||||
spec?: Spec;
|
||||
managedFields?: any;
|
||||
}
|
||||
|
||||
export class KubeObject<
|
||||
Metadata extends KubeObjectMetadata<KubeObjectScope> = KubeObjectMetadata<KubeObjectScope>,
|
||||
Status = unknown,
|
||||
@@ -538,23 +551,6 @@ export class KubeObject<
|
||||
return Object.entries(labels).map(([name, value]) => `${name}=${value}`);
|
||||
}
|
||||
|
||||
/**
|
||||
* These must be RFC6902 compliant paths
|
||||
*/
|
||||
private static readonly nonEditablePathPrefixes = [
|
||||
"/metadata/managedFields",
|
||||
"/status",
|
||||
];
|
||||
private static readonly nonEditablePaths = new Set([
|
||||
"/apiVersion",
|
||||
"/kind",
|
||||
"/metadata/name",
|
||||
"/metadata/selfLink",
|
||||
"/metadata/resourceVersion",
|
||||
"/metadata/uid",
|
||||
...KubeObject.nonEditablePathPrefixes,
|
||||
]);
|
||||
|
||||
constructor(data: KubeJsonApiData<Metadata, Status, Spec>) {
|
||||
if (typeof data !== "object") {
|
||||
throw new TypeError(`Cannot create a KubeObject from ${typeof data}`);
|
||||
@@ -684,18 +680,6 @@ export class KubeObject<
|
||||
* @deprecated use KubeApi.patch instead
|
||||
*/
|
||||
async patch(patch: Patch): Promise<KubeJsonApiData | null> {
|
||||
for (const op of patch) {
|
||||
if (KubeObject.nonEditablePaths.has(op.path)) {
|
||||
throw new Error(`Failed to update ${this.kind}: JSON pointer ${op.path} has been modified`);
|
||||
}
|
||||
|
||||
for (const pathPrefix of KubeObject.nonEditablePathPrefixes) {
|
||||
if (op.path.startsWith(`${pathPrefix}/`)) {
|
||||
throw new Error(`Failed to update ${this.kind}: Child JSON pointer of ${op.path} has been modified`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const di = getLegacyGlobalDiForExtensionApi();
|
||||
const requestKubeObjectPatch = di.inject(requestKubeObjectPatchInjectable);
|
||||
const result = await requestKubeObjectPatch(this.getName(), this.kind, this.getNs(), patch);
|
||||
|
||||
+2
@@ -7469,6 +7469,8 @@ metadata:
|
||||
selfLink: /apis/some-api-version/namespaces/some-uid
|
||||
somePropertyToBeRemoved: some-value
|
||||
somePropertyToBeChanged: some-old-value
|
||||
labels:
|
||||
k8slens-edit-resource-version: some-api-version
|
||||
|
||||
</textarea>
|
||||
</div>
|
||||
|
||||
@@ -12,8 +12,6 @@ import createEditResourceTabInjectable from "../../../renderer/components/dock/e
|
||||
import getRandomIdForEditResourceTabInjectable from "../../../renderer/components/dock/edit-resource/get-random-id-for-edit-resource-tab.injectable";
|
||||
import type { AsyncFnMock } from "@async-fn/jest";
|
||||
import asyncFn from "@async-fn/jest";
|
||||
import type { CallForResource } from "../../../renderer/components/dock/edit-resource/edit-resource-model/call-for-resource/call-for-resource.injectable";
|
||||
import callForResourceInjectable from "../../../renderer/components/dock/edit-resource/edit-resource-model/call-for-resource/call-for-resource.injectable";
|
||||
import type { CallForPatchResource } from "../../../renderer/components/dock/edit-resource/edit-resource-model/call-for-patch-resource/call-for-patch-resource.injectable";
|
||||
import callForPatchResourceInjectable from "../../../renderer/components/dock/edit-resource/edit-resource-model/call-for-patch-resource/call-for-patch-resource.injectable";
|
||||
import dockStoreInjectable from "../../../renderer/components/dock/dock/store.injectable";
|
||||
@@ -23,6 +21,8 @@ import showErrorNotificationInjectable from "../../../renderer/components/notifi
|
||||
import readJsonFileInjectable from "../../../common/fs/read-json-file.injectable";
|
||||
import directoryForLensLocalStorageInjectable from "../../../common/directory-for-lens-local-storage/directory-for-lens-local-storage.injectable";
|
||||
import hostedClusterIdInjectable from "../../../renderer/cluster-frame-context/hosted-cluster-id.injectable";
|
||||
import type { CallForResource } from "../../../renderer/components/dock/edit-resource/edit-resource-model/call-for-resource/call-for-resource.injectable";
|
||||
import callForResourceInjectable from "../../../renderer/components/dock/edit-resource/edit-resource-model/call-for-resource/call-for-resource.injectable";
|
||||
|
||||
describe("cluster/namespaces - edit namespace from new tab", () => {
|
||||
let builder: ApplicationBuilder;
|
||||
@@ -225,10 +225,16 @@ metadata:
|
||||
expect(rendered.baseElement).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it("calls for save with empty values", () => {
|
||||
it("calls for save with just the adding version label", () => {
|
||||
expect(callForPatchResourceMock).toHaveBeenCalledWith(
|
||||
someNamespace,
|
||||
[],
|
||||
[{
|
||||
op: "add",
|
||||
path: "/metadata/labels",
|
||||
value: {
|
||||
"k8slens-edit-resource-version": "some-api-version",
|
||||
},
|
||||
}],
|
||||
);
|
||||
});
|
||||
|
||||
@@ -532,6 +538,13 @@ metadata:
|
||||
path: "/metadata/someAddedProperty",
|
||||
value: "some-new-value",
|
||||
},
|
||||
{
|
||||
op: "add",
|
||||
path: "/metadata/labels",
|
||||
value: {
|
||||
"k8slens-edit-resource-version": "some-api-version",
|
||||
},
|
||||
},
|
||||
{
|
||||
op: "replace",
|
||||
path: "/metadata/somePropertyToBeChanged",
|
||||
@@ -759,7 +772,7 @@ metadata:
|
||||
`);
|
||||
});
|
||||
|
||||
it("when selecting to save, calls for save of second namespace", () => {
|
||||
it("when selecting to save, calls for save of second namespace with just the add edit version label", () => {
|
||||
callForPatchResourceMock.mockClear();
|
||||
|
||||
const saveButton = rendered.getByTestId(
|
||||
@@ -770,7 +783,13 @@ metadata:
|
||||
|
||||
expect(callForPatchResourceMock).toHaveBeenCalledWith(
|
||||
someOtherNamespace,
|
||||
[],
|
||||
[{
|
||||
op: "add",
|
||||
path: "/metadata/labels",
|
||||
value: {
|
||||
"k8slens-edit-resource-version": "some-api-version",
|
||||
},
|
||||
}],
|
||||
);
|
||||
});
|
||||
|
||||
@@ -826,7 +845,7 @@ metadata:
|
||||
`);
|
||||
});
|
||||
|
||||
it("when selecting to save, calls for save of first namespace", () => {
|
||||
it("when selecting to save, calls for save of first namespace with just the new edit version label", () => {
|
||||
callForPatchResourceMock.mockClear();
|
||||
|
||||
const saveButton = rendered.getByTestId(
|
||||
@@ -837,7 +856,13 @@ metadata:
|
||||
|
||||
expect(callForPatchResourceMock).toHaveBeenCalledWith(
|
||||
someNamespace,
|
||||
[],
|
||||
[ {
|
||||
op: "add",
|
||||
path: "/metadata/labels",
|
||||
value: {
|
||||
"k8slens-edit-resource-version": "some-api-version",
|
||||
},
|
||||
}],
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
File renamed without changes.
+1
@@ -16,6 +16,7 @@
|
||||
.TableCell {
|
||||
word-break: break-word;
|
||||
|
||||
|
||||
&:first-child {
|
||||
margin-left: $margin * 2;
|
||||
}
|
||||
+14
-14
@@ -13,7 +13,7 @@ import { getDiForUnitTesting } from "../../getDiForUnitTesting";
|
||||
import storesAndApisCanBeCreatedInjectable from "../../stores-apis-can-be-created.injectable";
|
||||
import type { DiRender } from "../test-utils/renderFor";
|
||||
import { renderFor } from "../test-utils/renderFor";
|
||||
import { HpaDetails } from "./hpa-details";
|
||||
import { HorizontalPodAutoscalerDetails } from "./details";
|
||||
|
||||
jest.mock("react-router-dom", () => ({
|
||||
Link: ({ children }: { children: React.ReactNode }) => children,
|
||||
@@ -62,7 +62,7 @@ describe("<HpaDetails/>", () => {
|
||||
const hpa = new HorizontalPodAutoscaler(hpaV2);
|
||||
|
||||
result = render(
|
||||
<HpaDetails object={hpa} />,
|
||||
<HorizontalPodAutoscalerDetails object={hpa} />,
|
||||
);
|
||||
|
||||
expect(result.baseElement).toMatchSnapshot();
|
||||
@@ -72,7 +72,7 @@ describe("<HpaDetails/>", () => {
|
||||
const hpa = new HorizontalPodAutoscaler(hpaV2);
|
||||
|
||||
result = render(
|
||||
<HpaDetails object={hpa} />,
|
||||
<HorizontalPodAutoscalerDetails object={hpa} />,
|
||||
);
|
||||
|
||||
expect(result.queryByTestId("hpa-metrics")).toBeNull();
|
||||
@@ -101,7 +101,7 @@ describe("<HpaDetails/>", () => {
|
||||
});
|
||||
|
||||
result = render(
|
||||
<HpaDetails object={hpa} />,
|
||||
<HorizontalPodAutoscalerDetails object={hpa} />,
|
||||
);
|
||||
|
||||
expect(result.getByText("CPU Utilization percentage")).toBeInTheDocument();
|
||||
@@ -131,7 +131,7 @@ describe("<HpaDetails/>", () => {
|
||||
);
|
||||
|
||||
result = render(
|
||||
<HpaDetails object={hpa} />,
|
||||
<HorizontalPodAutoscalerDetails object={hpa} />,
|
||||
);
|
||||
|
||||
expect(result.getByText("Resource cpu on Pods")).toBeInTheDocument();
|
||||
@@ -160,7 +160,7 @@ describe("<HpaDetails/>", () => {
|
||||
);
|
||||
|
||||
result = render(
|
||||
<HpaDetails object={hpa} />,
|
||||
<HorizontalPodAutoscalerDetails object={hpa} />,
|
||||
);
|
||||
|
||||
expect(result.getByText("Resource cpu on Pods")).toBeInTheDocument();
|
||||
@@ -191,7 +191,7 @@ describe("<HpaDetails/>", () => {
|
||||
);
|
||||
|
||||
result = render(
|
||||
<HpaDetails object={hpa} />,
|
||||
<HorizontalPodAutoscalerDetails object={hpa} />,
|
||||
);
|
||||
|
||||
expect(result.getByText("packets-per-second on Pods")).toBeInTheDocument();
|
||||
@@ -216,7 +216,7 @@ describe("<HpaDetails/>", () => {
|
||||
);
|
||||
|
||||
result = render(
|
||||
<HpaDetails object={hpa} />,
|
||||
<HorizontalPodAutoscalerDetails object={hpa} />,
|
||||
);
|
||||
|
||||
expect(result.getByText("packets-per-second on Pods")).toBeInTheDocument();
|
||||
@@ -252,7 +252,7 @@ describe("<HpaDetails/>", () => {
|
||||
);
|
||||
|
||||
result = render(
|
||||
<HpaDetails object={hpa} />,
|
||||
<HorizontalPodAutoscalerDetails object={hpa} />,
|
||||
);
|
||||
|
||||
expect(result.getByText(/requests-per-second/)).toHaveTextContent("requests-per-second onService/nginx");
|
||||
@@ -277,7 +277,7 @@ describe("<HpaDetails/>", () => {
|
||||
);
|
||||
|
||||
result = render(
|
||||
<HpaDetails object={hpa} />,
|
||||
<HorizontalPodAutoscalerDetails object={hpa} />,
|
||||
);
|
||||
|
||||
expect(result.getByText("requests-per-second")).toBeInTheDocument();
|
||||
@@ -311,7 +311,7 @@ describe("<HpaDetails/>", () => {
|
||||
);
|
||||
|
||||
result = render(
|
||||
<HpaDetails object={hpa} />,
|
||||
<HorizontalPodAutoscalerDetails object={hpa} />,
|
||||
);
|
||||
|
||||
expect(result.getByText("queue_messages_ready on {\"matchLabels\":{\"queue\":\"worker_tasks\"}}")).toBeInTheDocument();
|
||||
@@ -339,7 +339,7 @@ describe("<HpaDetails/>", () => {
|
||||
);
|
||||
|
||||
result = render(
|
||||
<HpaDetails object={hpa} />,
|
||||
<HorizontalPodAutoscalerDetails object={hpa} />,
|
||||
);
|
||||
|
||||
expect(result.getByText("queue_messages_ready on {\"matchLabels\":{\"queue\":\"worker_tasks\"}}")).toBeInTheDocument();
|
||||
@@ -368,7 +368,7 @@ describe("<HpaDetails/>", () => {
|
||||
);
|
||||
|
||||
result = render(
|
||||
<HpaDetails object={hpa} />,
|
||||
<HorizontalPodAutoscalerDetails object={hpa} />,
|
||||
);
|
||||
|
||||
expect(result.baseElement).toMatchSnapshot();
|
||||
@@ -398,7 +398,7 @@ describe("<HpaDetails/>", () => {
|
||||
);
|
||||
|
||||
result = render(
|
||||
<HpaDetails object={hpa} />,
|
||||
<HorizontalPodAutoscalerDetails object={hpa} />,
|
||||
);
|
||||
|
||||
expect(result.baseElement).toMatchSnapshot();
|
||||
+5
-5
@@ -3,7 +3,7 @@
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
|
||||
import "./hpa-details.scss";
|
||||
import "./details.scss";
|
||||
|
||||
import React from "react";
|
||||
import { observer } from "mobx-react";
|
||||
@@ -22,8 +22,8 @@ import { withInjectables } from "@ogre-tools/injectable-react";
|
||||
import apiManagerInjectable from "../../../common/k8s-api/api-manager/manager.injectable";
|
||||
import getDetailsUrlInjectable from "../kube-detail-params/get-details-url.injectable";
|
||||
import loggerInjectable from "../../../common/logger.injectable";
|
||||
import getHorizontalPodAutoscalerMetrics from "./get-hpa-metrics.injectable";
|
||||
import { getMetricName } from "./get-hpa-metric-name";
|
||||
import getHorizontalPodAutoscalerMetrics from "./get-metrics.injectable";
|
||||
import { getMetricName } from "./get-metric-name";
|
||||
|
||||
export interface HpaDetailsProps extends KubeObjectDetailsProps<HorizontalPodAutoscaler> {
|
||||
}
|
||||
@@ -36,7 +36,7 @@ interface Dependencies {
|
||||
}
|
||||
|
||||
@observer
|
||||
class NonInjectedHpaDetails extends React.Component<HpaDetailsProps & Dependencies> {
|
||||
class NonInjectedHorizontalPodAutoscalerDetails extends React.Component<HpaDetailsProps & Dependencies> {
|
||||
private renderTargetLink(target: HorizontalPodAutoscalerMetricTarget | undefined) {
|
||||
if (!target) {
|
||||
return null;
|
||||
@@ -177,7 +177,7 @@ class NonInjectedHpaDetails extends React.Component<HpaDetailsProps & Dependenci
|
||||
}
|
||||
}
|
||||
|
||||
export const HpaDetails = withInjectables<Dependencies, HpaDetailsProps>(NonInjectedHpaDetails, {
|
||||
export const HorizontalPodAutoscalerDetails = withInjectables<Dependencies, HpaDetailsProps>(NonInjectedHorizontalPodAutoscalerDetails, {
|
||||
getProps: (di, props) => ({
|
||||
...props,
|
||||
apiManager: di.inject(apiManagerInjectable),
|
||||
File renamed without changes.
+3
-3
@@ -5,9 +5,9 @@
|
||||
import { getInjectable } from "@ogre-tools/injectable";
|
||||
import type { HorizontalPodAutoscaler, HorizontalPodAutoscalerMetricSpec, HorizontalPodAutoscalerMetricStatus } from "../../../common/k8s-api/endpoints";
|
||||
import { HpaMetricType } from "../../../common/k8s-api/endpoints";
|
||||
import { getMetricName } from "./get-hpa-metric-name";
|
||||
import { HorizontalPodAutoscalerV1MetricParser } from "./hpa-v1-metric-parser";
|
||||
import { HorizontalPodAutoscalerV2MetricParser } from "./hpa-v2-metric-parser";
|
||||
import { getMetricName } from "./get-metric-name";
|
||||
import { HorizontalPodAutoscalerV1MetricParser } from "./metric-parser-v1";
|
||||
import { HorizontalPodAutoscalerV2MetricParser } from "./metric-parser-v2";
|
||||
|
||||
type Parser = HorizontalPodAutoscalerV1MetricParser | HorizontalPodAutoscalerV2MetricParser;
|
||||
|
||||
@@ -3,5 +3,5 @@
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
|
||||
export * from "./hpa";
|
||||
export * from "./hpa-details";
|
||||
export * from "./list-view";
|
||||
export * from "./details";
|
||||
File renamed without changes.
+2
-2
@@ -3,7 +3,7 @@
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
|
||||
import "./hpa.scss";
|
||||
import "./list-view.scss";
|
||||
|
||||
import React from "react";
|
||||
import { observer } from "mobx-react";
|
||||
@@ -17,7 +17,7 @@ import { KubeObjectAge } from "../kube-object/age";
|
||||
import type { HorizontalPodAutoscalerStore } from "./store";
|
||||
import { withInjectables } from "@ogre-tools/injectable-react";
|
||||
import horizontalPodAutoscalerStoreInjectable from "./store.injectable";
|
||||
import getHorizontalPodAutoscalerMetrics from "./get-hpa-metrics.injectable";
|
||||
import getHorizontalPodAutoscalerMetrics from "./get-metrics.injectable";
|
||||
import { NamespaceSelectBadge } from "../+namespaces/namespace-select-badge";
|
||||
|
||||
enum columnId {
|
||||
File renamed without changes.
File renamed without changes.
+6
-6
@@ -3,7 +3,7 @@
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
import type { DiContainer } from "@ogre-tools/injectable";
|
||||
import getHorizontalPodAutoscalerMetrics from "./get-hpa-metrics.injectable";
|
||||
import getHorizontalPodAutoscalerMetrics from "./get-metrics.injectable";
|
||||
import { getDiForUnitTesting } from "../../getDiForUnitTesting";
|
||||
import { HorizontalPodAutoscaler, HpaMetricType } from "../../../common/k8s-api/endpoints";
|
||||
|
||||
@@ -658,10 +658,10 @@ describe("getHorizontalPodAutoscalerMetrics", () => {
|
||||
],
|
||||
},
|
||||
});
|
||||
|
||||
|
||||
expect(getMetrics(hpa)[0]).toEqual("10% / 50%");
|
||||
});
|
||||
|
||||
|
||||
it("should return correct resource metrics with current value", () => {
|
||||
const hpa = new HorizontalPodAutoscaler({
|
||||
...hpaV2Beta1,
|
||||
@@ -691,7 +691,7 @@ describe("getHorizontalPodAutoscalerMetrics", () => {
|
||||
],
|
||||
},
|
||||
});
|
||||
|
||||
|
||||
expect(getMetrics(hpa)[0]).toEqual("500m / 100m");
|
||||
});
|
||||
|
||||
@@ -787,7 +787,7 @@ describe("getHorizontalPodAutoscalerMetrics", () => {
|
||||
type: HpaMetricType.Pods,
|
||||
pods: {
|
||||
metricName: "packets-per-second",
|
||||
|
||||
|
||||
targetAverageValue: "1k",
|
||||
},
|
||||
},
|
||||
@@ -1038,7 +1038,7 @@ describe("getHorizontalPodAutoscalerMetrics", () => {
|
||||
],
|
||||
},
|
||||
});
|
||||
|
||||
|
||||
expect(getMetrics(hpa)[0]).toEqual("unknown / 50%");
|
||||
});
|
||||
});
|
||||
+1
-1
@@ -5,7 +5,7 @@
|
||||
import { getInjectable } from "@ogre-tools/injectable";
|
||||
import { routeSpecificComponentInjectionToken } from "../../routes/route-specific-component-injection-token";
|
||||
import horizontalPodAutoscalersRouteInjectable from "../../../common/front-end-routing/routes/cluster/config/horizontal-pod-autoscalers/horizontal-pod-autoscalers-route.injectable";
|
||||
import { HorizontalPodAutoscalers } from "./hpa";
|
||||
import { HorizontalPodAutoscalers } from "./list-view";
|
||||
|
||||
const horizontalPodAutoscalersRouteComponentInjectable = getInjectable({
|
||||
id: "horizontal-pod-autoscalers-route-component",
|
||||
File renamed without changes.
+9
-18
@@ -3,44 +3,35 @@
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
import { getInjectable } from "@ogre-tools/injectable";
|
||||
import type { KubeObject } from "../../../../../../common/k8s-api/kube-object";
|
||||
import { KubeObject } from "../../../../../../common/k8s-api/kube-object";
|
||||
import { parseKubeApi } from "../../../../../../common/k8s-api/kube-api-parse";
|
||||
import type { AsyncResult } from "@k8slens/utilities";
|
||||
import { getErrorMessage } from "../../../../../../common/utils/get-error-message";
|
||||
import apiManagerInjectable from "../../../../../../common/k8s-api/api-manager/manager.injectable";
|
||||
import { waitUntilDefined } from "@k8slens/utilities";
|
||||
import apiKubeInjectable from "../../../../../k8s/api-kube.injectable";
|
||||
|
||||
export type CallForResource = (
|
||||
selfLink: string
|
||||
) => AsyncResult<KubeObject | undefined>;
|
||||
export type CallForResource = (selfLink: string) => AsyncResult<KubeObject | undefined>;
|
||||
|
||||
const callForResourceInjectable = getInjectable({
|
||||
id: "call-for-resource",
|
||||
|
||||
instantiate: (di): CallForResource => {
|
||||
const apiManager = di.inject(apiManagerInjectable);
|
||||
const apiKube = di.inject(apiKubeInjectable);
|
||||
|
||||
return async (apiPath: string) => {
|
||||
const api = await waitUntilDefined(() => apiManager.getApi(apiPath));
|
||||
|
||||
const parsed = parseKubeApi(apiPath);
|
||||
|
||||
if (!api || !parsed.name) {
|
||||
if (!parsed.name) {
|
||||
return { callWasSuccessful: false, error: "Invalid API path" };
|
||||
}
|
||||
|
||||
let resource: KubeObject | null;
|
||||
|
||||
try {
|
||||
resource = await api.get({
|
||||
name: parsed.name,
|
||||
namespace: parsed.namespace,
|
||||
});
|
||||
return {
|
||||
callWasSuccessful: true,
|
||||
response: new KubeObject(await apiKube.get(apiPath)),
|
||||
};
|
||||
} catch (e) {
|
||||
return { callWasSuccessful: false, error: getErrorMessage(e) };
|
||||
}
|
||||
|
||||
return { callWasSuccessful: true, response: resource || undefined };
|
||||
};
|
||||
},
|
||||
|
||||
|
||||
+79
-37
@@ -7,9 +7,9 @@ import type { CallForResource } from "./call-for-resource/call-for-resource.inje
|
||||
import callForResourceInjectable from "./call-for-resource/call-for-resource.injectable";
|
||||
import { waitUntilDefined } from "@k8slens/utilities";
|
||||
import editResourceTabStoreInjectable from "../store.injectable";
|
||||
import type { EditResourceTabStore } from "../store";
|
||||
import { action, computed, makeObservable, observable, runInAction } from "mobx";
|
||||
import type { KubeObject } from "../../../../../common/k8s-api/kube-object";
|
||||
import type { EditingResource, EditResourceTabStore } from "../store";
|
||||
import { action, computed, observable, runInAction } from "mobx";
|
||||
import type { KubeObject, RawKubeObject } from "../../../../../common/k8s-api/kube-object";
|
||||
import yaml from "js-yaml";
|
||||
import assert from "assert";
|
||||
import type { CallForPatchResource } from "./call-for-patch-resource/call-for-patch-resource.injectable";
|
||||
@@ -19,18 +19,22 @@ import type { ShowNotification } from "../../../notifications";
|
||||
import showSuccessNotificationInjectable from "../../../notifications/show-success-notification.injectable";
|
||||
import React from "react";
|
||||
import showErrorNotificationInjectable from "../../../notifications/show-error-notification.injectable";
|
||||
import { createKubeApiURL, parseKubeApi } from "../../../../../common/k8s-api/kube-api-parse";
|
||||
|
||||
const editResourceModelInjectable = getInjectable({
|
||||
id: "edit-resource-model",
|
||||
|
||||
instantiate: async (di, tabId: string) => {
|
||||
const store = di.inject(editResourceTabStoreInjectable);
|
||||
|
||||
const model = new EditResourceModel({
|
||||
callForResource: di.inject(callForResourceInjectable),
|
||||
callForPatchResource: di.inject(callForPatchResourceInjectable),
|
||||
showSuccessNotification: di.inject(showSuccessNotificationInjectable),
|
||||
showErrorNotification: di.inject(showErrorNotificationInjectable),
|
||||
store: di.inject(editResourceTabStoreInjectable),
|
||||
store,
|
||||
tabId,
|
||||
waitForEditingResource: () => waitUntilDefined(() => store.getData(tabId)),
|
||||
});
|
||||
|
||||
await model.load();
|
||||
@@ -48,19 +52,42 @@ export default editResourceModelInjectable;
|
||||
interface Dependencies {
|
||||
callForResource: CallForResource;
|
||||
callForPatchResource: CallForPatchResource;
|
||||
waitForEditingResource: () => Promise<EditingResource>;
|
||||
showSuccessNotification: ShowNotification;
|
||||
showErrorNotification: ShowNotification;
|
||||
readonly store: EditResourceTabStore;
|
||||
readonly tabId: string;
|
||||
}
|
||||
|
||||
export class EditResourceModel {
|
||||
constructor(private readonly dependencies: Dependencies) {
|
||||
makeObservable(this);
|
||||
function getEditSelfLinkFor(object: RawKubeObject): string {
|
||||
const lensVersionLabel = object.metadata.labels?.[EditResourceLabelName];
|
||||
|
||||
if (lensVersionLabel) {
|
||||
const { apiVersionWithGroup, ...parsedApi } = parseKubeApi(object.metadata.selfLink);
|
||||
|
||||
parsedApi.apiVersion = lensVersionLabel;
|
||||
|
||||
return createKubeApiURL({
|
||||
...parsedApi,
|
||||
apiVersion: `${parsedApi.apiGroup}/${parsedApi.apiVersion}`,
|
||||
});
|
||||
}
|
||||
|
||||
return object.metadata.selfLink;
|
||||
}
|
||||
|
||||
/**
|
||||
* The label name that Lens uses to receive the desired api version
|
||||
*/
|
||||
export const EditResourceLabelName = "k8slens-edit-resource-version";
|
||||
|
||||
export class EditResourceModel {
|
||||
constructor(protected readonly dependencies: Dependencies) {}
|
||||
|
||||
readonly configuration = {
|
||||
value: computed(() => this.editingResource.draft || this.editingResource.firstDraft || ""),
|
||||
value: computed(
|
||||
() => this.editingResource.draft || this.editingResource.firstDraft || "",
|
||||
),
|
||||
|
||||
onChange: action((value: string) => {
|
||||
this.editingResource.draft = value;
|
||||
@@ -100,27 +127,39 @@ export class EditResourceModel {
|
||||
return this.editingResource.resource;
|
||||
}
|
||||
|
||||
load = async () => {
|
||||
await waitUntilDefined(() => this.dependencies.store.getData(this.dependencies.tabId));
|
||||
load = async (): Promise<void> => {
|
||||
await this.dependencies.waitForEditingResource();
|
||||
|
||||
const result = await this.dependencies.callForResource(this.selfLink);
|
||||
let result = await this.dependencies.callForResource(this.selfLink);
|
||||
|
||||
if (!result.callWasSuccessful) {
|
||||
this.dependencies.showErrorNotification(
|
||||
`Loading resource failed: ${result.error}`,
|
||||
);
|
||||
return void this.dependencies.showErrorNotification(`Loading resource failed: ${result.error}`);
|
||||
}
|
||||
|
||||
if (result?.response?.metadata.labels?.[EditResourceLabelName]) {
|
||||
const parsed = parseKubeApi(this.selfLink);
|
||||
|
||||
parsed.apiVersion = result.response.metadata.labels[EditResourceLabelName];
|
||||
|
||||
result = await this.dependencies.callForResource(createKubeApiURL(parsed));
|
||||
}
|
||||
|
||||
if (!result.callWasSuccessful) {
|
||||
return void this.dependencies.showErrorNotification(`Loading resource failed: ${result.error}`);
|
||||
}
|
||||
|
||||
const resource = result.response;
|
||||
|
||||
runInAction(() => {
|
||||
this._resource = resource;
|
||||
});
|
||||
|
||||
if (!resource) {
|
||||
return;
|
||||
}
|
||||
|
||||
runInAction(() => {
|
||||
this._resource = result.response;
|
||||
|
||||
if (this._resource) {
|
||||
this.editingResource.firstDraft = yaml.dump(
|
||||
this._resource.toPlainObject(),
|
||||
);
|
||||
}
|
||||
this.editingResource.firstDraft = yaml.dump(resource.toPlainObject());
|
||||
});
|
||||
};
|
||||
|
||||
@@ -138,16 +177,16 @@ export class EditResourceModel {
|
||||
|
||||
save = async () => {
|
||||
const currentValue = this.configuration.value.get();
|
||||
const currentVersion = yaml.load(currentValue);
|
||||
const firstVersion = yaml.load(
|
||||
this.editingResource.firstDraft ?? currentValue,
|
||||
);
|
||||
const patches = createPatch(firstVersion, currentVersion);
|
||||
const currentVersion = yaml.load(currentValue) as RawKubeObject;
|
||||
const firstVersion = yaml.load(this.editingResource.firstDraft ?? currentValue);
|
||||
|
||||
const result = await this.dependencies.callForPatchResource(
|
||||
this.resource,
|
||||
patches,
|
||||
);
|
||||
// Make sure we save this label so that we can use it in the future
|
||||
currentVersion.metadata.labels ??= {};
|
||||
currentVersion.metadata.labels[EditResourceLabelName] = currentVersion.apiVersion.split("/").pop();
|
||||
|
||||
const patches = createPatch(firstVersion, currentVersion);
|
||||
const selfLink = getEditSelfLinkFor(currentVersion);
|
||||
const result = await this.dependencies.callForPatchResource(this.resource, patches);
|
||||
|
||||
if (!result.callWasSuccessful) {
|
||||
this.dependencies.showErrorNotification((
|
||||
@@ -158,23 +197,26 @@ export class EditResourceModel {
|
||||
</p>
|
||||
));
|
||||
|
||||
return;
|
||||
return null;
|
||||
}
|
||||
|
||||
const { kind, name } = result.response;
|
||||
|
||||
this.dependencies.showSuccessNotification((
|
||||
this.dependencies.showSuccessNotification(
|
||||
<p>
|
||||
{`${kind} `}
|
||||
{kind}
|
||||
{" "}
|
||||
<b>{name}</b>
|
||||
{" updated."}
|
||||
</p>
|
||||
));
|
||||
</p>,
|
||||
);
|
||||
|
||||
runInAction(() => {
|
||||
this.editingResource.firstDraft = currentValue;
|
||||
this.editingResource.firstDraft = yaml.dump(currentVersion);
|
||||
this.editingResource.resource = selfLink;
|
||||
});
|
||||
|
||||
return result.response.toString();
|
||||
// NOTE: This is required for `saveAndClose` to work correctly
|
||||
return [];
|
||||
};
|
||||
}
|
||||
@@ -56,12 +56,14 @@ const NonInjectedEditResource = observer(({
|
||||
<span>Namespace:</span>
|
||||
<Badge label={model.namespace} />
|
||||
</div>
|
||||
)} />
|
||||
)}
|
||||
/>
|
||||
<EditorPanel
|
||||
tabId={tabId}
|
||||
value={model.configuration.value.get()}
|
||||
onChange={model.configuration.onChange}
|
||||
onError={model.configuration.error.onChange} />
|
||||
onError={model.configuration.error.onChange}
|
||||
/>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
+2
-2
@@ -4,7 +4,7 @@
|
||||
*/
|
||||
import { getInjectable } from "@ogre-tools/injectable";
|
||||
import { kubeObjectDetailItemInjectionToken } from "../kube-object-detail-item-injection-token";
|
||||
import { HpaDetails } from "../../../+config-horizontal-pod-autoscalers";
|
||||
import { HorizontalPodAutoscalerDetails } from "../../../+config-horizontal-pod-autoscalers";
|
||||
import { computed } from "mobx";
|
||||
import { kubeObjectMatchesToKindAndApiVersion } from "../kube-object-matches-to-kind-and-api-version";
|
||||
import currentKubeObjectInDetailsInjectable from "../../current-kube-object-in-details.injectable";
|
||||
@@ -16,7 +16,7 @@ const horizontalPodAutoscalerDetailItemInjectable = getInjectable({
|
||||
const kubeObject = di.inject(currentKubeObjectInDetailsInjectable);
|
||||
|
||||
return {
|
||||
Component: HpaDetails,
|
||||
Component: HorizontalPodAutoscalerDetails,
|
||||
enabled: computed(() => isHorizontalPodAutoscaler(kubeObject.value.get()?.object)),
|
||||
orderNumber: 10,
|
||||
};
|
||||
|
||||
@@ -246,12 +246,10 @@ class NonInjectedMonacoEditor extends React.Component<MonacoEditorProps & Depend
|
||||
|
||||
this.dispose.push(
|
||||
reaction(() => this.model, this.onModelChange),
|
||||
reaction(() => this.theme, theme => {
|
||||
if (theme) {
|
||||
editor.setTheme(theme);
|
||||
}
|
||||
reaction(() => this.theme, editor.setTheme),
|
||||
reaction(() => this.props.value, value => this.setValue(value), {
|
||||
fireImmediately: true,
|
||||
}),
|
||||
reaction(() => this.props.value, value => this.setValue(value)),
|
||||
reaction(() => this.options, opts => this.editor.updateOptions(opts)),
|
||||
|
||||
() => onDidLayoutChangeDisposer.dispose(),
|
||||
|
||||
Reference in new issue
Block a user