mirror of
https://github.com/lensapp/lens.git
synced 2025-05-20 05:10:56 +00:00
Merge branch 'ui-components/resizing-anchor-error-boundary' of https://github.com/lensapp/lens into ui-components/resizing-anchor-error-boundary
This commit is contained in:
commit
f4432a4659
2
.github/workflows/cron-test.yaml
vendored
2
.github/workflows/cron-test.yaml
vendored
@ -51,7 +51,7 @@ jobs:
|
|||||||
command: npm ci
|
command: npm ci
|
||||||
|
|
||||||
- name: Build library parts
|
- name: Build library parts
|
||||||
run: run npm build -- --ignore open-lens
|
run: npm run build -- --ignore open-lens
|
||||||
|
|
||||||
- run: npm run test:unit
|
- run: npm run test:unit
|
||||||
name: Run tests
|
name: Run tests
|
||||||
|
|||||||
@ -114,7 +114,7 @@ const tests: KubeApiParseTestData[] = [
|
|||||||
}],
|
}],
|
||||||
];
|
];
|
||||||
|
|
||||||
const throwtests = [
|
const invalidTests = [
|
||||||
undefined,
|
undefined,
|
||||||
"",
|
"",
|
||||||
"ajklsmh",
|
"ajklsmh",
|
||||||
@ -125,7 +125,7 @@ describe("parseApi unit tests", () => {
|
|||||||
expect(parseKubeApi(url)).toStrictEqual(expected);
|
expect(parseKubeApi(url)).toStrictEqual(expected);
|
||||||
});
|
});
|
||||||
|
|
||||||
it.each(throwtests)("testing %j should throw", (url) => {
|
it.each(invalidTests)("testing %j should throw", (url) => {
|
||||||
expect(() => parseKubeApi(url as never)).toThrowError("invalid apiPath");
|
expect(parseKubeApi(url as never)).toBe(undefined);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@ -74,9 +74,13 @@ export class ApiManager {
|
|||||||
return iter.find(this.apis.values(), pathOrCallback);
|
return iter.find(this.apis.values(), pathOrCallback);
|
||||||
}
|
}
|
||||||
|
|
||||||
const { apiBase } = parseKubeApi(pathOrCallback);
|
const parsedApi = parseKubeApi(pathOrCallback);
|
||||||
|
|
||||||
return this.apis.get(apiBase);
|
if (!parsedApi) {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
return this.apis.get(parsedApi.apiBase);
|
||||||
}
|
}
|
||||||
|
|
||||||
getApiByKind(kind: string, apiVersion: string) {
|
getApiByKind(kind: string, apiVersion: string) {
|
||||||
@ -141,9 +145,10 @@ export class ApiManager {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const { apiBase } = typeof apiOrBase === "string"
|
const { apiBase } = typeof apiOrBase === "string"
|
||||||
? parseKubeApi(apiOrBase)
|
? parseKubeApi(apiOrBase) ?? {}
|
||||||
: apiOrBase;
|
: apiOrBase;
|
||||||
const api = this.getApi(apiBase);
|
|
||||||
|
const api = apiBase && this.getApi(apiBase);
|
||||||
|
|
||||||
if (!api) {
|
if (!api) {
|
||||||
return undefined;
|
return undefined;
|
||||||
|
|||||||
@ -26,6 +26,30 @@ export interface JsonApiError {
|
|||||||
errors?: { id: string; title: string; status?: number }[];
|
errors?: { id: string; title: string; status?: number }[];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface KubeJsonApiErrorCause {
|
||||||
|
reason: string;
|
||||||
|
message: string;
|
||||||
|
field: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface KubeJsonApiErrorDetails {
|
||||||
|
name: string;
|
||||||
|
group: string;
|
||||||
|
kind: string;
|
||||||
|
causes: KubeJsonApiErrorCause[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface KubeJsonApiError {
|
||||||
|
kind: "Status";
|
||||||
|
apiVersion: "v1";
|
||||||
|
metadata: object;
|
||||||
|
status: string;
|
||||||
|
message: string;
|
||||||
|
reason: string;
|
||||||
|
details: KubeJsonApiErrorDetails;
|
||||||
|
code: number;
|
||||||
|
}
|
||||||
|
|
||||||
export interface JsonApiParams<D> {
|
export interface JsonApiParams<D> {
|
||||||
data?: PartialDeep<D>; // request body
|
data?: PartialDeep<D>; // request body
|
||||||
}
|
}
|
||||||
@ -246,7 +270,7 @@ export class JsonApi<Data = JsonApiData, Params extends JsonApiParams<Data> = Js
|
|||||||
export class JsonApiErrorParsed {
|
export class JsonApiErrorParsed {
|
||||||
isUsedForNotification = false;
|
isUsedForNotification = false;
|
||||||
|
|
||||||
constructor(private error: JsonApiError | DOMException, private messages: string[]) {
|
constructor(private error: JsonApiError | DOMException | KubeJsonApiError, private messages: string[]) {
|
||||||
}
|
}
|
||||||
|
|
||||||
get isAborted() {
|
get isAborted() {
|
||||||
|
|||||||
@ -22,16 +22,16 @@ export interface IKubeApiParsed extends IKubeApiLinkRef {
|
|||||||
apiVersionWithGroup: string;
|
apiVersionWithGroup: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function parseKubeApi(path: string): IKubeApiParsed {
|
export function parseKubeApi(path: string): IKubeApiParsed | undefined {
|
||||||
const apiPath = new URL(path, "https://localhost").pathname;
|
const apiPath = new URL(path, "https://localhost").pathname;
|
||||||
const [, prefix, ...parts] = apiPath.split("/");
|
const [, prefix, ...parts] = apiPath.split("/");
|
||||||
const apiPrefix = `/${prefix}`;
|
const apiPrefix = `/${prefix}`;
|
||||||
const [left, right, namespaced] = array.split(parts, "namespaces");
|
const [left, right, namespaced] = array.split(parts, "namespaces");
|
||||||
let apiGroup!: string;
|
let apiGroup: string;
|
||||||
let apiVersion!: string;
|
let apiVersion: string | undefined;
|
||||||
let namespace!: string;
|
let namespace: string | undefined;
|
||||||
let resource!: string;
|
let resource: string;
|
||||||
let name!: string;
|
let name: string | undefined;
|
||||||
|
|
||||||
if (namespaced) {
|
if (namespaced) {
|
||||||
switch (right.length) {
|
switch (right.length) {
|
||||||
@ -46,26 +46,21 @@ export function parseKubeApi(path: string): IKubeApiParsed {
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
let rest: string[];
|
||||||
apiVersion = left.pop()!;
|
|
||||||
apiGroup = left.join("/");
|
[apiVersion, ...rest] = left;
|
||||||
|
apiGroup = rest.join("/");
|
||||||
} else {
|
} else {
|
||||||
switch (left.length) {
|
if (left.length === 0) {
|
||||||
case 0:
|
return undefined;
|
||||||
throw new Error(`invalid apiPath: ${apiPath}`);
|
}
|
||||||
case 4:
|
|
||||||
[apiGroup, apiVersion, resource, name] = left;
|
if (left.length === 1 || left.length === 2) {
|
||||||
break;
|
[apiVersion, resource] = left;
|
||||||
case 2:
|
apiGroup = "";
|
||||||
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
} else if (left.length === 4) {
|
||||||
resource = left.pop()!;
|
[apiGroup, apiVersion, resource, name] = left;
|
||||||
// fallthrough
|
} else {
|
||||||
case 1:
|
|
||||||
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
|
||||||
apiVersion = left.pop()!;
|
|
||||||
apiGroup = "";
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
/**
|
/**
|
||||||
* Given that
|
* Given that
|
||||||
* - `apiVersion` is `GROUP/VERSION` and
|
* - `apiVersion` is `GROUP/VERSION` and
|
||||||
@ -82,15 +77,14 @@ export function parseKubeApi(path: string): IKubeApiParsed {
|
|||||||
* 3. otherwise assume apiVersion <- left[0]
|
* 3. otherwise assume apiVersion <- left[0]
|
||||||
* 4. always resource, name <- left[(0 or 1)+1..]
|
* 4. always resource, name <- left[(0 or 1)+1..]
|
||||||
*/
|
*/
|
||||||
if (left[0].includes(".") || left[1].match(/^v[0-9]/)) {
|
if (left[0].includes(".") || left[1].match(/^v[0-9]/)) {
|
||||||
[apiGroup, apiVersion] = left;
|
[apiGroup, apiVersion] = left;
|
||||||
resource = left.slice(2).join("/");
|
resource = left.slice(2).join("/");
|
||||||
} else {
|
} else {
|
||||||
apiGroup = "";
|
apiGroup = "";
|
||||||
apiVersion = left[0];
|
apiVersion = left[0];
|
||||||
[resource, name] = left.slice(1);
|
[resource, name] = left.slice(1);
|
||||||
}
|
}
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -98,7 +92,7 @@ export function parseKubeApi(path: string): IKubeApiParsed {
|
|||||||
const apiBase = [apiPrefix, apiGroup, apiVersion, resource].filter(v => v).join("/");
|
const apiBase = [apiPrefix, apiGroup, apiVersion, resource].filter(v => v).join("/");
|
||||||
|
|
||||||
if (!apiBase) {
|
if (!apiBase) {
|
||||||
throw new Error(`invalid apiPath: ${apiPath}`);
|
return undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
@ -110,7 +104,7 @@ export function parseKubeApi(path: string): IKubeApiParsed {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function isIKubeApiParsed(refOrParsed: IKubeApiLinkRef | IKubeApiParsed): refOrParsed is IKubeApiParsed {
|
function isIKubeApiParsed(refOrParsed: IKubeApiLinkRef | IKubeApiParsed): refOrParsed is IKubeApiParsed {
|
||||||
return "apiGroup" in refOrParsed;
|
return "apiGroup" in refOrParsed && !!refOrParsed.apiGroup;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function createKubeApiURL(linkRef: IKubeApiLinkRef): string;
|
export function createKubeApiURL(linkRef: IKubeApiLinkRef): string;
|
||||||
|
|||||||
@ -264,12 +264,16 @@ export class KubeApi<
|
|||||||
allowedUsableVersions,
|
allowedUsableVersions,
|
||||||
} = opts;
|
} = opts;
|
||||||
|
|
||||||
assert(fullApiPathname, "apiBase MUST be provied either via KubeApiOptions.apiBase or KubeApiOptions.objectConstructor.apiBase");
|
assert(fullApiPathname, "apiBase MUST be provided either via KubeApiOptions.apiBase or KubeApiOptions.objectConstructor.apiBase");
|
||||||
assert(request, "request MUST be provided if not in a cluster page frame context");
|
assert(request, "request MUST be provided if not in a cluster page frame context");
|
||||||
|
|
||||||
const { apiBase, apiPrefix, apiGroup, apiVersion, resource } = parseKubeApi(fullApiPathname);
|
const parsedApi = parseKubeApi(fullApiPathname);
|
||||||
|
|
||||||
assert(kind, "kind MUST be provied either via KubeApiOptions.kind or KubeApiOptions.objectConstructor.kind");
|
assert(parsedApi, "apiBase MUST be a valid kube api pathname");
|
||||||
|
|
||||||
|
const { apiBase, apiPrefix, apiGroup, apiVersion, resource } = parsedApi;
|
||||||
|
|
||||||
|
assert(kind, "kind MUST be provided either via KubeApiOptions.kind or KubeApiOptions.objectConstructor.kind");
|
||||||
assert(apiPrefix, "apiBase MUST be parsable as a kubeApi selfLink style string");
|
assert(apiPrefix, "apiBase MUST be parsable as a kubeApi selfLink style string");
|
||||||
|
|
||||||
this.doCheckPreferredVersion = doCheckPreferredVersion;
|
this.doCheckPreferredVersion = doCheckPreferredVersion;
|
||||||
@ -308,8 +312,14 @@ export class KubeApi<
|
|||||||
const apiBases = new Set(rawApiBases);
|
const apiBases = new Set(rawApiBases);
|
||||||
|
|
||||||
for (const apiUrl of apiBases) {
|
for (const apiUrl of apiBases) {
|
||||||
|
const parsedApi = parseKubeApi(apiUrl);
|
||||||
|
|
||||||
|
if (!parsedApi) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const { apiPrefix, apiGroup, resource } = parseKubeApi(apiUrl);
|
const { apiPrefix, apiGroup, resource } = parsedApi;
|
||||||
const list = await this.request.get(`${apiPrefix}/${apiGroup}`) as KubeApiResourceVersionList;
|
const list = await this.request.get(`${apiPrefix}/${apiGroup}`) as KubeApiResourceVersionList;
|
||||||
const resourceVersions = getOrderedVersions(list, this.allowedUsableVersions?.[apiGroup]);
|
const resourceVersions = getOrderedVersions(list, this.allowedUsableVersions?.[apiGroup]);
|
||||||
|
|
||||||
@ -324,8 +334,8 @@ export class KubeApi<
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch {
|
||||||
// Exception is ignored as we can try the next url
|
// ignore exception to try next url
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -7,18 +7,18 @@ import { parseKubeApi } from "../kube-api-parse";
|
|||||||
import { kubeApiInjectionToken } from "./kube-api-injection-token";
|
import { kubeApiInjectionToken } from "./kube-api-injection-token";
|
||||||
import type { KubeApi } from "../kube-api";
|
import type { KubeApi } from "../kube-api";
|
||||||
|
|
||||||
|
export type GetKubeApiFromPath = (apiPath: string) => KubeApi | undefined;
|
||||||
|
|
||||||
const getKubeApiFromPathInjectable = getInjectable({
|
const getKubeApiFromPathInjectable = getInjectable({
|
||||||
id: "get-kube-api-from-path",
|
id: "get-kube-api-from-path",
|
||||||
|
|
||||||
instantiate: (di) => {
|
instantiate: (di): GetKubeApiFromPath => {
|
||||||
const kubeApis = di.injectMany(kubeApiInjectionToken);
|
const kubeApis = di.injectMany(kubeApiInjectionToken);
|
||||||
|
|
||||||
return (apiPath: string) => {
|
return (apiPath: string) => {
|
||||||
const parsed = parseKubeApi(apiPath);
|
const parsed = parseKubeApi(apiPath);
|
||||||
|
|
||||||
const kubeApi = kubeApis.find((api) => api.apiBase === parsed.apiBase);
|
return kubeApis.find((api) => api.apiBase === parsed?.apiBase);
|
||||||
|
|
||||||
return (kubeApi as KubeApi) || undefined;
|
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|||||||
@ -324,7 +324,11 @@ export class KubeObjectStore<
|
|||||||
|
|
||||||
@action
|
@action
|
||||||
async loadFromPath(resourcePath: string) {
|
async loadFromPath(resourcePath: string) {
|
||||||
const { namespace, name } = parseKubeApi(resourcePath);
|
const parsedApi = parseKubeApi(resourcePath);
|
||||||
|
|
||||||
|
assert(parsedApi, "resourcePath must be a valid kube api");
|
||||||
|
|
||||||
|
const { namespace, name } = parsedApi;
|
||||||
|
|
||||||
assert(name, "name must be part of resourcePath");
|
assert(name, "name must be part of resourcePath");
|
||||||
|
|
||||||
|
|||||||
@ -26,15 +26,11 @@ import type { ItemObject } from "@k8slens/list-layout";
|
|||||||
import type { Patch } from "rfc6902";
|
import type { Patch } from "rfc6902";
|
||||||
import assert from "assert";
|
import assert from "assert";
|
||||||
import type { JsonObject } from "type-fest";
|
import type { JsonObject } from "type-fest";
|
||||||
import requestKubeObjectPatchInjectable
|
import requestKubeObjectPatchInjectable from "./endpoints/resource-applier.api/request-patch.injectable";
|
||||||
from "./endpoints/resource-applier.api/request-patch.injectable";
|
|
||||||
import { apiKubeInjectionToken } from "./api-kube";
|
import { apiKubeInjectionToken } from "./api-kube";
|
||||||
import requestKubeObjectCreationInjectable
|
import requestKubeObjectCreationInjectable from "./endpoints/resource-applier.api/request-update.injectable";
|
||||||
from "./endpoints/resource-applier.api/request-update.injectable";
|
|
||||||
import { dump } from "js-yaml";
|
import { dump } from "js-yaml";
|
||||||
import {
|
import { getLegacyGlobalDiForExtensionApi } from "../../extensions/as-legacy-globals-for-extension-api/legacy-global-di-for-extension-api";
|
||||||
getLegacyGlobalDiForExtensionApi,
|
|
||||||
} from "../../extensions/as-legacy-globals-for-extension-api/legacy-global-di-for-extension-api";
|
|
||||||
import autoBind from "auto-bind";
|
import autoBind from "auto-bind";
|
||||||
|
|
||||||
export type KubeJsonApiDataFor<K> = K extends KubeObject<infer Metadata, infer Status, infer Spec>
|
export type KubeJsonApiDataFor<K> = K extends KubeObject<infer Metadata, infer Status, infer Spec>
|
||||||
@ -552,14 +548,30 @@ export class KubeObject<
|
|||||||
}
|
}
|
||||||
|
|
||||||
constructor(data: KubeJsonApiData<Metadata, Status, Spec>) {
|
constructor(data: KubeJsonApiData<Metadata, Status, Spec>) {
|
||||||
if (typeof data !== "object") {
|
if (!isObject(data)) {
|
||||||
throw new TypeError(`Cannot create a KubeObject from ${typeof data}`);
|
throw new TypeError(`Cannot create a KubeObject from ${typeof data}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!data.metadata || typeof data.metadata !== "object") {
|
if (!isObject(data.metadata)) {
|
||||||
throw new KubeCreationError(`Cannot create a KubeObject from an object without metadata`, data);
|
throw new KubeCreationError(`Cannot create a KubeObject from an object without metadata`, data);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!isString(data.metadata.name)) {
|
||||||
|
throw new KubeCreationError(`Cannot create a KubeObject from an object without metadata.name being a string`, data);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!isString(data.metadata.uid)) {
|
||||||
|
throw new KubeCreationError(`Cannot create a KubeObject from an object without metadata.uid being a string`, data);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!isString(data.metadata.resourceVersion)) {
|
||||||
|
throw new KubeCreationError(`Cannot create a KubeObject from an object without metadata.resourceVersion being a string`, data);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!isString(data.metadata.selfLink)) {
|
||||||
|
throw new KubeCreationError(`Cannot create a KubeObject from an object without metadata.selfLink being a string`, data);
|
||||||
|
}
|
||||||
|
|
||||||
Object.assign(this, data);
|
Object.assign(this, data);
|
||||||
autoBind(this);
|
autoBind(this);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -2,6 +2,9 @@
|
|||||||
* 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 { JsonApiErrorParsed } from "../k8s-api/json-api";
|
||||||
|
|
||||||
export const getErrorMessage = (error: unknown): string => {
|
export const getErrorMessage = (error: unknown): string => {
|
||||||
if (typeof error === "string") {
|
if (typeof error === "string") {
|
||||||
return error;
|
return error;
|
||||||
@ -11,5 +14,9 @@ export const getErrorMessage = (error: unknown): string => {
|
|||||||
return error.message;
|
return error.message;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (error instanceof JsonApiErrorParsed) {
|
||||||
|
return error.toString();
|
||||||
|
}
|
||||||
|
|
||||||
return JSON.stringify(error);
|
return JSON.stringify(error);
|
||||||
};
|
};
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@ -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 getRandomIdForEditResourceTabInjectable from "../../../renderer/components/dock/edit-resource/get-random-id-for-edit-resource-tab.injectable";
|
||||||
import type { AsyncFnMock } from "@async-fn/jest";
|
import type { AsyncFnMock } from "@async-fn/jest";
|
||||||
import asyncFn from "@async-fn/jest";
|
import asyncFn from "@async-fn/jest";
|
||||||
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";
|
import dockStoreInjectable from "../../../renderer/components/dock/dock/store.injectable";
|
||||||
import { Namespace } from "../../../common/k8s-api/endpoints";
|
import { Namespace } from "../../../common/k8s-api/endpoints";
|
||||||
import showSuccessNotificationInjectable from "../../../renderer/components/notifications/show-success-notification.injectable";
|
import showSuccessNotificationInjectable from "../../../renderer/components/notifications/show-success-notification.injectable";
|
||||||
@ -21,15 +19,22 @@ import showErrorNotificationInjectable from "../../../renderer/components/notifi
|
|||||||
import readJsonFileInjectable from "../../../common/fs/read-json-file.injectable";
|
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 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 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 type { ApiKubePatch } from "../../../renderer/k8s/api-kube-patch.injectable";
|
||||||
import callForResourceInjectable from "../../../renderer/components/dock/edit-resource/edit-resource-model/call-for-resource/call-for-resource.injectable";
|
import type { ApiKubeGet } from "../../../renderer/k8s/api-kube-get.injectable";
|
||||||
|
import apiKubePatchInjectable from "../../../renderer/k8s/api-kube-patch.injectable";
|
||||||
|
import apiKubeGetInjectable from "../../../renderer/k8s/api-kube-get.injectable";
|
||||||
|
import type { KubeJsonApiData } from "../../../common/k8s-api/kube-json-api";
|
||||||
|
import type { BaseKubeJsonApiObjectMetadata, KubeObjectScope } from "../../../common/k8s-api/kube-object";
|
||||||
|
import { JsonApiErrorParsed } from "../../../common/k8s-api/json-api";
|
||||||
|
import type { ShowNotification } from "../../../renderer/components/notifications";
|
||||||
|
import React from "react";
|
||||||
|
|
||||||
describe("cluster/namespaces - edit namespace from new tab", () => {
|
describe("cluster/namespaces - edit namespace from new tab", () => {
|
||||||
let builder: ApplicationBuilder;
|
let builder: ApplicationBuilder;
|
||||||
let callForResourceMock: AsyncFnMock<CallForResource>;
|
let apiKubePatchMock: AsyncFnMock<ApiKubePatch>;
|
||||||
let callForPatchResourceMock: AsyncFnMock<CallForPatchResource>;
|
let apiKubeGetMock: AsyncFnMock<ApiKubeGet>;
|
||||||
let showSuccessNotificationMock: jest.Mock;
|
let showSuccessNotificationMock: jest.MockedFunction<ShowNotification>;
|
||||||
let showErrorNotificationMock: jest.Mock;
|
let showErrorNotificationMock: jest.MockedFunction<ShowNotification>;
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
builder = getApplicationBuilder();
|
builder = getApplicationBuilder();
|
||||||
@ -57,11 +62,11 @@ describe("cluster/namespaces - edit namespace from new tab", () => {
|
|||||||
.mockReturnValueOnce("some-second-tab-id"),
|
.mockReturnValueOnce("some-second-tab-id"),
|
||||||
);
|
);
|
||||||
|
|
||||||
callForResourceMock = asyncFn();
|
apiKubePatchMock = asyncFn();
|
||||||
windowDi.override(callForResourceInjectable, () => callForResourceMock);
|
windowDi.override(apiKubePatchInjectable, () => apiKubePatchMock);
|
||||||
|
|
||||||
callForPatchResourceMock = asyncFn();
|
apiKubeGetMock = asyncFn();
|
||||||
windowDi.override(callForPatchResourceInjectable, () => callForPatchResourceMock);
|
windowDi.override(apiKubeGetInjectable, () => apiKubeGetMock);
|
||||||
});
|
});
|
||||||
|
|
||||||
builder.afterWindowStart(() => {
|
builder.afterWindowStart(() => {
|
||||||
@ -156,16 +161,16 @@ describe("cluster/namespaces - edit namespace from new tab", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it("calls for namespace", () => {
|
it("calls for namespace", () => {
|
||||||
expect(callForResourceMock).toHaveBeenCalledWith(
|
expect(apiKubeGetMock).toHaveBeenCalledWith(
|
||||||
"/apis/some-api-version/namespaces/some-uid",
|
"/apis/some-api-version/namespaces/some-uid",
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
describe("when call for namespace resolves with namespace", () => {
|
describe("when call for namespace resolves with namespace", () => {
|
||||||
let someNamespace: Namespace;
|
let someNamespaceData: KubeJsonApiData<BaseKubeJsonApiObjectMetadata<KubeObjectScope.Cluster>, unknown, unknown>;
|
||||||
|
|
||||||
beforeEach(async () => {
|
beforeEach(async () => {
|
||||||
someNamespace = new Namespace({
|
someNamespaceData = ({
|
||||||
apiVersion: "some-api-version",
|
apiVersion: "some-api-version",
|
||||||
kind: "Namespace",
|
kind: "Namespace",
|
||||||
|
|
||||||
@ -173,16 +178,12 @@ describe("cluster/namespaces - edit namespace from new tab", () => {
|
|||||||
uid: "some-uid",
|
uid: "some-uid",
|
||||||
name: "some-name",
|
name: "some-name",
|
||||||
resourceVersion: "some-resource-version",
|
resourceVersion: "some-resource-version",
|
||||||
selfLink: "/apis/some-api-version/namespaces/some-uid",
|
|
||||||
somePropertyToBeRemoved: "some-value",
|
somePropertyToBeRemoved: "some-value",
|
||||||
somePropertyToBeChanged: "some-old-value",
|
somePropertyToBeChanged: "some-old-value",
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
await callForResourceMock.resolve({
|
await apiKubeGetMock.resolve(someNamespaceData);
|
||||||
callWasSuccessful: true,
|
|
||||||
response: someNamespace,
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it("renders", () => {
|
it("renders", () => {
|
||||||
@ -206,9 +207,9 @@ metadata:
|
|||||||
uid: some-uid
|
uid: some-uid
|
||||||
name: some-name
|
name: some-name
|
||||||
resourceVersion: some-resource-version
|
resourceVersion: some-resource-version
|
||||||
selfLink: /apis/some-api-version/namespaces/some-uid
|
|
||||||
somePropertyToBeRemoved: some-value
|
somePropertyToBeRemoved: some-value
|
||||||
somePropertyToBeChanged: some-old-value
|
somePropertyToBeChanged: some-old-value
|
||||||
|
selfLink: /apis/some-api-version/namespaces/some-uid
|
||||||
`);
|
`);
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -226,15 +227,22 @@ metadata:
|
|||||||
});
|
});
|
||||||
|
|
||||||
it("calls for save with just the adding version label", () => {
|
it("calls for save with just the adding version label", () => {
|
||||||
expect(callForPatchResourceMock).toHaveBeenCalledWith(
|
expect(apiKubePatchMock).toHaveBeenCalledWith(
|
||||||
someNamespace,
|
"/apis/some-api-version/namespaces/some-uid",
|
||||||
[{
|
{
|
||||||
op: "add",
|
data: [{
|
||||||
path: "/metadata/labels",
|
op: "add",
|
||||||
value: {
|
path: "/metadata/labels",
|
||||||
"k8slens-edit-resource-version": "some-api-version",
|
value: {
|
||||||
|
"k8slens-edit-resource-version": "some-api-version",
|
||||||
|
},
|
||||||
|
}],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
headers: {
|
||||||
|
"content-type": "application/json-patch+json",
|
||||||
},
|
},
|
||||||
}],
|
},
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -262,9 +270,11 @@ metadata:
|
|||||||
|
|
||||||
describe("when saving resolves with success", () => {
|
describe("when saving resolves with success", () => {
|
||||||
beforeEach(async () => {
|
beforeEach(async () => {
|
||||||
await callForPatchResourceMock.resolve({
|
await apiKubePatchMock.resolve({
|
||||||
callWasSuccessful: true,
|
kind: "Namespace",
|
||||||
response: { name: "some-name", kind: "Namespace" },
|
metadata: {
|
||||||
|
name: "some-name",
|
||||||
|
},
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -309,12 +319,9 @@ metadata:
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe("when saving resolves with failure", () => {
|
describe("when saving fails", () => {
|
||||||
beforeEach(async () => {
|
beforeEach(async () => {
|
||||||
await callForPatchResourceMock.resolve({
|
await apiKubePatchMock.reject(new Error("some-error"));
|
||||||
callWasSuccessful: false,
|
|
||||||
error: "some-error",
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it("renders", () => {
|
it("renders", () => {
|
||||||
@ -380,9 +387,11 @@ metadata:
|
|||||||
|
|
||||||
describe("when saving resolves with success", () => {
|
describe("when saving resolves with success", () => {
|
||||||
beforeEach(async () => {
|
beforeEach(async () => {
|
||||||
await callForPatchResourceMock.resolve({
|
await apiKubePatchMock.resolve({
|
||||||
callWasSuccessful: true,
|
kind: "Namespace",
|
||||||
response: { name: "some-name", kind: "Namespace" },
|
metadata: {
|
||||||
|
name: "some-name",
|
||||||
|
},
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -397,12 +406,9 @@ metadata:
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe("when saving resolves with failure", () => {
|
describe("when saving failings", () => {
|
||||||
beforeEach(async () => {
|
beforeEach(async () => {
|
||||||
await callForPatchResourceMock.resolve({
|
await apiKubePatchMock.reject(new Error("some-error"));
|
||||||
callWasSuccessful: false,
|
|
||||||
error: "Some error",
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it("renders", () => {
|
it("renders", () => {
|
||||||
@ -415,6 +421,57 @@ metadata:
|
|||||||
).toBeInTheDocument();
|
).toBeInTheDocument();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe("when saving failings with a JsonApiError", () => {
|
||||||
|
beforeEach(async () => {
|
||||||
|
await apiKubePatchMock.reject(new JsonApiErrorParsed(
|
||||||
|
{
|
||||||
|
kind: "Status",
|
||||||
|
apiVersion: "v1",
|
||||||
|
metadata: {},
|
||||||
|
status: "Failure",
|
||||||
|
message: "PodDisruptionBudget.policy \"frontend-pdb\" is invalid: spec.minAvailable: Invalid value: -10: must be greater than or equal to 0",
|
||||||
|
reason: "Invalid",
|
||||||
|
details: {
|
||||||
|
name: "frontend-pdb",
|
||||||
|
group: "policy",
|
||||||
|
kind: "PodDisruptionBudget",
|
||||||
|
causes: [
|
||||||
|
{
|
||||||
|
reason: "FieldValueInvalid",
|
||||||
|
message: "Invalid value: -10: must be greater than or equal to 0",
|
||||||
|
field: "spec.minAvailable",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
code: 422,
|
||||||
|
},
|
||||||
|
[
|
||||||
|
"PodDisruptionBudget.policy \"frontend-pdb\" is invalid: spec.minAvailable: Invalid value: -10: must be greater than or equal to 0",
|
||||||
|
],
|
||||||
|
));
|
||||||
|
});
|
||||||
|
|
||||||
|
it("renders", () => {
|
||||||
|
expect(rendered.baseElement).toMatchSnapshot();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("does not close the dock tab", () => {
|
||||||
|
expect(
|
||||||
|
rendered.getByTestId("dock-tab-for-some-first-tab-id"),
|
||||||
|
).toBeInTheDocument();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("shows an error notification with a condensed message", () => {
|
||||||
|
expect(showErrorNotificationMock).toBeCalledWith(
|
||||||
|
<p>
|
||||||
|
{"Failed to save resource:"}
|
||||||
|
{" "}
|
||||||
|
{'PodDisruptionBudget.policy "frontend-pdb" is invalid: spec.minAvailable: Invalid value: -10: must be greater than or equal to 0'}
|
||||||
|
</p>,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe("when selecting to cancel", () => {
|
describe("when selecting to cancel", () => {
|
||||||
@ -451,9 +508,9 @@ metadata:
|
|||||||
uid: some-uid
|
uid: some-uid
|
||||||
name: some-name
|
name: some-name
|
||||||
resourceVersion: some-resource-version
|
resourceVersion: some-resource-version
|
||||||
selfLink: /apis/some-api-version/namespaces/some-uid
|
|
||||||
somePropertyToBeChanged: some-changed-value
|
somePropertyToBeChanged: some-changed-value
|
||||||
someAddedProperty: some-new-value
|
someAddedProperty: some-new-value
|
||||||
|
selfLink: /apis/some-api-version/namespaces/some-uid
|
||||||
`,
|
`,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
@ -474,9 +531,9 @@ metadata:
|
|||||||
uid: some-uid
|
uid: some-uid
|
||||||
name: some-name
|
name: some-name
|
||||||
resourceVersion: some-resource-version
|
resourceVersion: some-resource-version
|
||||||
selfLink: /apis/some-api-version/namespaces/some-uid
|
|
||||||
somePropertyToBeChanged: some-changed-value
|
somePropertyToBeChanged: some-changed-value
|
||||||
someAddedProperty: some-new-value
|
someAddedProperty: some-new-value
|
||||||
|
selfLink: /apis/some-api-version/namespaces/some-uid
|
||||||
`);
|
`);
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -499,9 +556,9 @@ metadata:
|
|||||||
uid: some-uid
|
uid: some-uid
|
||||||
name: some-name
|
name: some-name
|
||||||
resourceVersion: some-resource-version
|
resourceVersion: some-resource-version
|
||||||
selfLink: /apis/some-api-version/namespaces/some-uid
|
|
||||||
somePropertyToBeRemoved: some-value
|
somePropertyToBeRemoved: some-value
|
||||||
somePropertyToBeChanged: some-old-value
|
somePropertyToBeChanged: some-old-value
|
||||||
|
selfLink: /apis/some-api-version/namespaces/some-uid
|
||||||
`,
|
`,
|
||||||
draft: `apiVersion: some-api-version
|
draft: `apiVersion: some-api-version
|
||||||
kind: Namespace
|
kind: Namespace
|
||||||
@ -509,9 +566,9 @@ metadata:
|
|||||||
uid: some-uid
|
uid: some-uid
|
||||||
name: some-name
|
name: some-name
|
||||||
resourceVersion: some-resource-version
|
resourceVersion: some-resource-version
|
||||||
selfLink: /apis/some-api-version/namespaces/some-uid
|
|
||||||
somePropertyToBeChanged: some-changed-value
|
somePropertyToBeChanged: some-changed-value
|
||||||
someAddedProperty: some-new-value
|
someAddedProperty: some-new-value
|
||||||
|
selfLink: /apis/some-api-version/namespaces/some-uid
|
||||||
`,
|
`,
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@ -526,41 +583,46 @@ metadata:
|
|||||||
});
|
});
|
||||||
|
|
||||||
it("calls for save with changed configuration", () => {
|
it("calls for save with changed configuration", () => {
|
||||||
expect(callForPatchResourceMock).toHaveBeenCalledWith(
|
expect(apiKubePatchMock).toHaveBeenCalledWith(
|
||||||
someNamespace,
|
"/apis/some-api-version/namespaces/some-uid",
|
||||||
[
|
{
|
||||||
{
|
data: [
|
||||||
op: "remove",
|
{
|
||||||
path: "/metadata/somePropertyToBeRemoved",
|
op: "remove",
|
||||||
},
|
path: "/metadata/somePropertyToBeRemoved",
|
||||||
{
|
|
||||||
op: "add",
|
|
||||||
path: "/metadata/someAddedProperty",
|
|
||||||
value: "some-new-value",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
op: "add",
|
|
||||||
path: "/metadata/labels",
|
|
||||||
value: {
|
|
||||||
"k8slens-edit-resource-version": "some-api-version",
|
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
op: "add",
|
||||||
|
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",
|
||||||
|
value: "some-changed-value",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
headers: {
|
||||||
|
"content-type": "application/json-patch+json",
|
||||||
},
|
},
|
||||||
{
|
},
|
||||||
op: "replace",
|
|
||||||
path: "/metadata/somePropertyToBeChanged",
|
|
||||||
value: "some-changed-value",
|
|
||||||
},
|
|
||||||
],
|
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("given save resolves and another change in configuration, when saving, calls for save with changed configuration", async () => {
|
it("given save resolves and another change in configuration, when saving, calls for save with changed configuration", async () => {
|
||||||
await callForPatchResourceMock.resolve({
|
await apiKubePatchMock.resolve({
|
||||||
callWasSuccessful: true,
|
kind: "Namespace",
|
||||||
|
metadata: {
|
||||||
response: {
|
|
||||||
name: "some-name",
|
name: "some-name",
|
||||||
kind: "Namespace",
|
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -585,7 +647,7 @@ metadata:
|
|||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
callForPatchResourceMock.mockClear();
|
apiKubePatchMock.mockClear();
|
||||||
|
|
||||||
const saveButton = rendered.getByTestId(
|
const saveButton = rendered.getByTestId(
|
||||||
"save-edit-resource-from-tab-for-some-first-tab-id",
|
"save-edit-resource-from-tab-for-some-first-tab-id",
|
||||||
@ -593,15 +655,22 @@ metadata:
|
|||||||
|
|
||||||
fireEvent.click(saveButton);
|
fireEvent.click(saveButton);
|
||||||
|
|
||||||
expect(callForPatchResourceMock).toHaveBeenCalledWith(
|
expect(apiKubePatchMock).toHaveBeenCalledWith(
|
||||||
someNamespace,
|
"/apis/some-api-version/namespaces/some-uid",
|
||||||
[
|
{
|
||||||
{
|
data: [
|
||||||
op: "add",
|
{
|
||||||
path: "/metadata/someOtherAddedProperty",
|
op: "add",
|
||||||
value: "some-other-new-value",
|
path: "/metadata/someOtherAddedProperty",
|
||||||
|
value: "some-other-new-value",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
headers: {
|
||||||
|
"content-type": "application/json-patch+json",
|
||||||
},
|
},
|
||||||
],
|
},
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@ -692,7 +761,7 @@ metadata:
|
|||||||
|
|
||||||
describe("given clicking the context menu for second namespace, when clicking to edit namespace", () => {
|
describe("given clicking the context menu for second namespace, when clicking to edit namespace", () => {
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
callForResourceMock.mockClear();
|
apiKubeGetMock.mockClear();
|
||||||
|
|
||||||
// TODO: Make implementation match the description
|
// TODO: Make implementation match the description
|
||||||
const namespaceStub = new Namespace(someOtherNamespaceDataStub);
|
const namespaceStub = new Namespace(someOtherNamespaceDataStub);
|
||||||
@ -725,7 +794,7 @@ metadata:
|
|||||||
});
|
});
|
||||||
|
|
||||||
it("calls for second namespace", () => {
|
it("calls for second namespace", () => {
|
||||||
expect(callForResourceMock).toHaveBeenCalledWith(
|
expect(apiKubeGetMock).toHaveBeenCalledWith(
|
||||||
"/apis/some-api-version/namespaces/some-other-uid",
|
"/apis/some-api-version/namespaces/some-other-uid",
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
@ -747,10 +816,7 @@ metadata:
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
await callForResourceMock.resolve({
|
await apiKubeGetMock.resolve(someOtherNamespace);
|
||||||
callWasSuccessful: true,
|
|
||||||
response: someOtherNamespace,
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it("renders", () => {
|
it("renders", () => {
|
||||||
@ -773,7 +839,7 @@ metadata:
|
|||||||
});
|
});
|
||||||
|
|
||||||
it("when selecting to save, calls for save of second namespace with just the add edit version label", () => {
|
it("when selecting to save, calls for save of second namespace with just the add edit version label", () => {
|
||||||
callForPatchResourceMock.mockClear();
|
apiKubePatchMock.mockClear();
|
||||||
|
|
||||||
const saveButton = rendered.getByTestId(
|
const saveButton = rendered.getByTestId(
|
||||||
"save-edit-resource-from-tab-for-some-second-tab-id",
|
"save-edit-resource-from-tab-for-some-second-tab-id",
|
||||||
@ -781,21 +847,28 @@ metadata:
|
|||||||
|
|
||||||
fireEvent.click(saveButton);
|
fireEvent.click(saveButton);
|
||||||
|
|
||||||
expect(callForPatchResourceMock).toHaveBeenCalledWith(
|
expect(apiKubePatchMock).toHaveBeenCalledWith(
|
||||||
someOtherNamespace,
|
"/apis/some-api-version/namespaces/some-other-uid",
|
||||||
[{
|
{
|
||||||
op: "add",
|
data: [{
|
||||||
path: "/metadata/labels",
|
op: "add",
|
||||||
value: {
|
path: "/metadata/labels",
|
||||||
"k8slens-edit-resource-version": "some-api-version",
|
value: {
|
||||||
|
"k8slens-edit-resource-version": "some-api-version",
|
||||||
|
},
|
||||||
|
}],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
headers: {
|
||||||
|
"content-type": "application/json-patch+json",
|
||||||
},
|
},
|
||||||
}],
|
},
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
describe("when clicking dock tab for the first namespace", () => {
|
describe("when clicking dock tab for the first namespace", () => {
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
callForResourceMock.mockClear();
|
apiKubeGetMock.mockClear();
|
||||||
|
|
||||||
const tab = rendered.getByTestId("dock-tab-for-some-first-tab-id");
|
const tab = rendered.getByTestId("dock-tab-for-some-first-tab-id");
|
||||||
|
|
||||||
@ -825,7 +898,7 @@ metadata:
|
|||||||
});
|
});
|
||||||
|
|
||||||
it("does not call for namespace", () => {
|
it("does not call for namespace", () => {
|
||||||
expect(callForResourceMock).not.toHaveBeenCalledWith("/apis/some-api-version/namespaces/some-uid");
|
expect(apiKubeGetMock).not.toHaveBeenCalledWith("/apis/some-api-version/namespaces/some-uid");
|
||||||
});
|
});
|
||||||
|
|
||||||
it("has configuration in the editor", () => {
|
it("has configuration in the editor", () => {
|
||||||
@ -839,14 +912,14 @@ metadata:
|
|||||||
uid: some-uid
|
uid: some-uid
|
||||||
name: some-name
|
name: some-name
|
||||||
resourceVersion: some-resource-version
|
resourceVersion: some-resource-version
|
||||||
selfLink: /apis/some-api-version/namespaces/some-uid
|
|
||||||
somePropertyToBeRemoved: some-value
|
somePropertyToBeRemoved: some-value
|
||||||
somePropertyToBeChanged: some-old-value
|
somePropertyToBeChanged: some-old-value
|
||||||
|
selfLink: /apis/some-api-version/namespaces/some-uid
|
||||||
`);
|
`);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("when selecting to save, calls for save of first namespace with just the new edit version label", () => {
|
it("when selecting to save, calls for save of first namespace with just the new edit version label", () => {
|
||||||
callForPatchResourceMock.mockClear();
|
apiKubePatchMock.mockClear();
|
||||||
|
|
||||||
const saveButton = rendered.getByTestId(
|
const saveButton = rendered.getByTestId(
|
||||||
"save-edit-resource-from-tab-for-some-first-tab-id",
|
"save-edit-resource-from-tab-for-some-first-tab-id",
|
||||||
@ -854,15 +927,22 @@ metadata:
|
|||||||
|
|
||||||
fireEvent.click(saveButton);
|
fireEvent.click(saveButton);
|
||||||
|
|
||||||
expect(callForPatchResourceMock).toHaveBeenCalledWith(
|
expect(apiKubePatchMock).toHaveBeenCalledWith(
|
||||||
someNamespace,
|
"/apis/some-api-version/namespaces/some-uid",
|
||||||
[ {
|
{
|
||||||
op: "add",
|
data: [{
|
||||||
path: "/metadata/labels",
|
op: "add",
|
||||||
value: {
|
path: "/metadata/labels",
|
||||||
"k8slens-edit-resource-version": "some-api-version",
|
value: {
|
||||||
|
"k8slens-edit-resource-version": "some-api-version",
|
||||||
|
},
|
||||||
|
}],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
headers: {
|
||||||
|
"content-type": "application/json-patch+json",
|
||||||
},
|
},
|
||||||
}],
|
},
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@ -870,41 +950,9 @@ metadata:
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe("when call for namespace resolves without namespace", () => {
|
|
||||||
beforeEach(async () => {
|
|
||||||
await callForResourceMock.resolve({
|
|
||||||
callWasSuccessful: true,
|
|
||||||
response: undefined,
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it("renders", () => {
|
|
||||||
expect(rendered.baseElement).toMatchSnapshot();
|
|
||||||
});
|
|
||||||
|
|
||||||
it("still shows the dock tab for editing namespace", () => {
|
|
||||||
expect(
|
|
||||||
rendered.getByTestId("dock-tab-for-some-first-tab-id"),
|
|
||||||
).toBeInTheDocument();
|
|
||||||
});
|
|
||||||
|
|
||||||
it("shows error message", () => {
|
|
||||||
expect(
|
|
||||||
rendered.getByTestId("dock-tab-content-for-some-first-tab-id"),
|
|
||||||
).toHaveTextContent("Resource not found");
|
|
||||||
});
|
|
||||||
|
|
||||||
it("does not show error notification", () => {
|
|
||||||
expect(showErrorNotificationMock).not.toHaveBeenCalled();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe("when call for namespace resolves with failure", () => {
|
describe("when call for namespace resolves with failure", () => {
|
||||||
beforeEach(async () => {
|
beforeEach(async () => {
|
||||||
await callForResourceMock.resolve({
|
await apiKubeGetMock.reject(new Error("some-error-missing-namespace"));
|
||||||
callWasSuccessful: false,
|
|
||||||
error: "some-error",
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it("renders", () => {
|
it("renders", () => {
|
||||||
|
|||||||
@ -8,8 +8,8 @@ import type { ApplicationBuilder } from "../../../renderer/components/test-utils
|
|||||||
import { getApplicationBuilder } from "../../../renderer/components/test-utils/get-application-builder";
|
import { getApplicationBuilder } from "../../../renderer/components/test-utils/get-application-builder";
|
||||||
import type { AsyncFnMock } from "@async-fn/jest";
|
import type { AsyncFnMock } from "@async-fn/jest";
|
||||||
import asyncFn 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 type { RequestKubeResource } from "../../../renderer/components/dock/edit-resource/edit-resource-model/request-kube-resource.injectable";
|
||||||
import callForResourceInjectable from "../../../renderer/components/dock/edit-resource/edit-resource-model/call-for-resource/call-for-resource.injectable";
|
import requestKubeResourceInjectable from "../../../renderer/components/dock/edit-resource/edit-resource-model/request-kube-resource.injectable";
|
||||||
import directoryForLensLocalStorageInjectable from "../../../common/directory-for-lens-local-storage/directory-for-lens-local-storage.injectable";
|
import directoryForLensLocalStorageInjectable from "../../../common/directory-for-lens-local-storage/directory-for-lens-local-storage.injectable";
|
||||||
import writeJsonFileInjectable from "../../../common/fs/write-json-file.injectable";
|
import writeJsonFileInjectable from "../../../common/fs/write-json-file.injectable";
|
||||||
import { TabKind } from "../../../renderer/components/dock/dock/store";
|
import { TabKind } from "../../../renderer/components/dock/dock/store";
|
||||||
@ -17,14 +17,14 @@ import { Namespace } from "../../../common/k8s-api/endpoints";
|
|||||||
|
|
||||||
describe("cluster/namespaces - edit namespaces from previously opened tab", () => {
|
describe("cluster/namespaces - edit namespaces from previously opened tab", () => {
|
||||||
let builder: ApplicationBuilder;
|
let builder: ApplicationBuilder;
|
||||||
let callForNamespaceMock: AsyncFnMock<CallForResource>;
|
let requestKubeResourceMock: AsyncFnMock<RequestKubeResource>;
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
builder = getApplicationBuilder();
|
builder = getApplicationBuilder();
|
||||||
|
|
||||||
builder.setEnvironmentToClusterFrame();
|
builder.setEnvironmentToClusterFrame();
|
||||||
|
|
||||||
callForNamespaceMock = asyncFn();
|
requestKubeResourceMock = asyncFn();
|
||||||
|
|
||||||
builder.beforeWindowStart(({ windowDi }) => {
|
builder.beforeWindowStart(({ windowDi }) => {
|
||||||
windowDi.override(
|
windowDi.override(
|
||||||
@ -32,7 +32,7 @@ describe("cluster/namespaces - edit namespaces from previously opened tab", () =
|
|||||||
() => "/some-directory-for-lens-local-storage",
|
() => "/some-directory-for-lens-local-storage",
|
||||||
);
|
);
|
||||||
|
|
||||||
windowDi.override(callForResourceInjectable, () => callForNamespaceMock);
|
windowDi.override(requestKubeResourceInjectable, () => requestKubeResourceMock);
|
||||||
});
|
});
|
||||||
|
|
||||||
builder.afterWindowStart(() => {
|
builder.afterWindowStart(() => {
|
||||||
@ -97,7 +97,7 @@ describe("cluster/namespaces - edit namespaces from previously opened tab", () =
|
|||||||
});
|
});
|
||||||
|
|
||||||
it("calls for namespace", () => {
|
it("calls for namespace", () => {
|
||||||
expect(callForNamespaceMock).toHaveBeenCalledWith(
|
expect(requestKubeResourceMock).toHaveBeenCalledWith(
|
||||||
"/apis/some-api-version/namespaces/some-uid",
|
"/apis/some-api-version/namespaces/some-uid",
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
@ -122,7 +122,7 @@ describe("cluster/namespaces - edit namespaces from previously opened tab", () =
|
|||||||
|
|
||||||
// TODO: Figure out why act is needed here. In CI it works without it.
|
// TODO: Figure out why act is needed here. In CI it works without it.
|
||||||
await act(async () => {
|
await act(async () => {
|
||||||
await callForNamespaceMock.resolve({
|
await requestKubeResourceMock.resolve({
|
||||||
callWasSuccessful: true,
|
callWasSuccessful: true,
|
||||||
response: someNamespace,
|
response: someNamespace,
|
||||||
});
|
});
|
||||||
|
|||||||
@ -4,6 +4,7 @@
|
|||||||
*/
|
*/
|
||||||
import { getInjectable, createInstantiationTargetDecorator, instantiationDecoratorToken } from "@ogre-tools/injectable";
|
import { getInjectable, createInstantiationTargetDecorator, instantiationDecoratorToken } from "@ogre-tools/injectable";
|
||||||
import { pick } from "lodash";
|
import { pick } from "lodash";
|
||||||
|
import { inspect } from "util";
|
||||||
import { parseKubeApi } from "../../../common/k8s-api/kube-api-parse";
|
import { parseKubeApi } from "../../../common/k8s-api/kube-api-parse";
|
||||||
import showDetailsInjectable from "../../../renderer/components/kube-detail-params/show-details.injectable";
|
import showDetailsInjectable from "../../../renderer/components/kube-detail-params/show-details.injectable";
|
||||||
import emitTelemetryInjectable from "./emit-telemetry.injectable";
|
import emitTelemetryInjectable from "./emit-telemetry.injectable";
|
||||||
@ -21,13 +22,15 @@ const telemetryDecoratorForShowDetailsInjectable = getInjectable({
|
|||||||
? {
|
? {
|
||||||
action: "open",
|
action: "open",
|
||||||
...(() => {
|
...(() => {
|
||||||
try {
|
const parsedApi = parseKubeApi(args[0]);
|
||||||
return {
|
|
||||||
resource: pick(parseKubeApi(args[0]), "apiPrefix", "apiVersion", "apiGroup", "namespace", "resource", "name"),
|
if (!parsedApi) {
|
||||||
};
|
return { error: `invalid apiPath: ${inspect(args[0])}` };
|
||||||
} catch (error) {
|
|
||||||
return { error: `${error}` };
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
resource: pick(parsedApi, "apiPrefix", "apiVersion", "apiGroup", "namespace", "resource", "name"),
|
||||||
|
};
|
||||||
})(),
|
})(),
|
||||||
}
|
}
|
||||||
: {
|
: {
|
||||||
|
|||||||
@ -21,7 +21,7 @@ describe("emit telemetry with params for calls to showDetails", () => {
|
|||||||
showDetails = di.inject(showDetailsInjectable);
|
showDetails = di.inject(showDetailsInjectable);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("when showDetails is called with no selflink (ie closing) should emit telemetry with param indicating closing the drawer", () => {
|
it("when showDetails is called with no selfLink (ie closing) should emit telemetry with param indicating closing the drawer", () => {
|
||||||
showDetails(undefined);
|
showDetails(undefined);
|
||||||
|
|
||||||
expect(emitAppEventMock).toBeCalledWith({
|
expect(emitAppEventMock).toBeCalledWith({
|
||||||
@ -34,7 +34,7 @@ describe("emit telemetry with params for calls to showDetails", () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it("when showDetails is called with empty selflink (ie closing) should emit telemetry with param indicating closing the drawer", () => {
|
it("when showDetails is called with empty selfLink (ie closing) should emit telemetry with param indicating closing the drawer", () => {
|
||||||
showDetails("");
|
showDetails("");
|
||||||
|
|
||||||
expect(emitAppEventMock).toBeCalledWith({
|
expect(emitAppEventMock).toBeCalledWith({
|
||||||
@ -47,7 +47,7 @@ describe("emit telemetry with params for calls to showDetails", () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it("when showDetails is called with valid selflink should emit telemetry with param indicating opening the drawer with that resource", () => {
|
it("when showDetails is called with valid selfLink should emit telemetry with param indicating opening the drawer with that resource", () => {
|
||||||
showDetails("/api/v1/namespaces/default/pods/some-name");
|
showDetails("/api/v1/namespaces/default/pods/some-name");
|
||||||
|
|
||||||
expect(emitAppEventMock).toBeCalledWith({
|
expect(emitAppEventMock).toBeCalledWith({
|
||||||
@ -68,7 +68,7 @@ describe("emit telemetry with params for calls to showDetails", () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it("when showDetails is called with invalid selflink should emit telemetry with param indicating opening the drawer but also show error", () => {
|
it("when showDetails is called with invalid selfLink should emit telemetry with param indicating opening the drawer but also show error", () => {
|
||||||
showDetails("some-non-self-link-value");
|
showDetails("some-non-self-link-value");
|
||||||
|
|
||||||
expect(emitAppEventMock).toBeCalledWith({
|
expect(emitAppEventMock).toBeCalledWith({
|
||||||
@ -77,7 +77,7 @@ describe("emit telemetry with params for calls to showDetails", () => {
|
|||||||
name: "show-details",
|
name: "show-details",
|
||||||
params: {
|
params: {
|
||||||
action: "open",
|
action: "open",
|
||||||
error: "Error: invalid apiPath: /some-non-self-link-value",
|
error: "invalid apiPath: 'some-non-self-link-value'",
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@ -22,8 +22,16 @@ describe("NetworkPolicyDetails", () => {
|
|||||||
|
|
||||||
it("should render w/o errors", () => {
|
it("should render w/o errors", () => {
|
||||||
const policy = new NetworkPolicy({
|
const policy = new NetworkPolicy({
|
||||||
metadata: {} as never,
|
metadata: {
|
||||||
spec: {} as never,
|
name: "some-network-policy-name",
|
||||||
|
namespace: "some-namespace",
|
||||||
|
resourceVersion: "1",
|
||||||
|
selfLink: "/apis/networking.k8s.io/v1/namespace/some-namespace/some-network-policy-name",
|
||||||
|
uid: "1",
|
||||||
|
},
|
||||||
|
spec: {
|
||||||
|
podSelector: {},
|
||||||
|
},
|
||||||
apiVersion: "networking.k8s.io/v1",
|
apiVersion: "networking.k8s.io/v1",
|
||||||
kind: "NetworkPolicy",
|
kind: "NetworkPolicy",
|
||||||
});
|
});
|
||||||
@ -34,7 +42,13 @@ describe("NetworkPolicyDetails", () => {
|
|||||||
|
|
||||||
it("should render egress nodeSelector", async () => {
|
it("should render egress nodeSelector", async () => {
|
||||||
const policy = new NetworkPolicy({
|
const policy = new NetworkPolicy({
|
||||||
metadata: {} as never,
|
metadata: {
|
||||||
|
name: "some-network-policy-name",
|
||||||
|
namespace: "some-namespace",
|
||||||
|
resourceVersion: "1",
|
||||||
|
selfLink: "/apis/networking.k8s.io/v1/namespace/some-namespace/some-network-policy-name",
|
||||||
|
uid: "1",
|
||||||
|
},
|
||||||
spec: {
|
spec: {
|
||||||
egress: [{
|
egress: [{
|
||||||
to: [{
|
to: [{
|
||||||
@ -58,7 +72,13 @@ describe("NetworkPolicyDetails", () => {
|
|||||||
|
|
||||||
it("should not crash if egress nodeSelector doesn't have matchLabels", async () => {
|
it("should not crash if egress nodeSelector doesn't have matchLabels", async () => {
|
||||||
const policy = new NetworkPolicy({
|
const policy = new NetworkPolicy({
|
||||||
metadata: {} as never,
|
metadata: {
|
||||||
|
name: "some-network-policy-name",
|
||||||
|
namespace: "some-namespace",
|
||||||
|
resourceVersion: "1",
|
||||||
|
selfLink: "/apis/networking.k8s.io/v1/namespace/some-namespace/some-network-policy-name",
|
||||||
|
uid: "1",
|
||||||
|
},
|
||||||
spec: {
|
spec: {
|
||||||
egress: [{
|
egress: [{
|
||||||
to: [{
|
to: [{
|
||||||
|
|||||||
@ -1,12 +0,0 @@
|
|||||||
/**
|
|
||||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
|
||||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
|
||||||
*/
|
|
||||||
import { getGlobalOverride } from "@k8slens/test-utils";
|
|
||||||
import callForPatchResourceInjectable from "./call-for-patch-resource.injectable";
|
|
||||||
|
|
||||||
export default getGlobalOverride(callForPatchResourceInjectable, () => () => {
|
|
||||||
throw new Error(
|
|
||||||
"Tried to call patching of kube resource without explicit override.",
|
|
||||||
);
|
|
||||||
});
|
|
||||||
@ -1,49 +0,0 @@
|
|||||||
/**
|
|
||||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
|
||||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
|
||||||
*/
|
|
||||||
import { getInjectable } from "@ogre-tools/injectable";
|
|
||||||
import type { AsyncResult } from "@k8slens/utilities";
|
|
||||||
import apiManagerInjectable from "../../../../../../common/k8s-api/api-manager/manager.injectable";
|
|
||||||
import type { JsonPatch } from "../../../../../../common/k8s-api/kube-object.store";
|
|
||||||
import type { KubeObject } from "../../../../../../common/k8s-api/kube-object";
|
|
||||||
import assert from "assert";
|
|
||||||
import { getErrorMessage } from "../../../../../../common/utils/get-error-message";
|
|
||||||
|
|
||||||
export type CallForPatchResource = (
|
|
||||||
item: KubeObject,
|
|
||||||
patch: JsonPatch
|
|
||||||
) => AsyncResult<{ name: string; kind: string }>;
|
|
||||||
|
|
||||||
const callForPatchResourceInjectable = getInjectable({
|
|
||||||
id: "call-for-patch-resource",
|
|
||||||
instantiate: (di): CallForPatchResource => {
|
|
||||||
const apiManager = di.inject(apiManagerInjectable);
|
|
||||||
|
|
||||||
return async (item, patch) => {
|
|
||||||
const store = apiManager.getStore(item.selfLink);
|
|
||||||
|
|
||||||
assert(store);
|
|
||||||
|
|
||||||
let kubeObject: KubeObject;
|
|
||||||
|
|
||||||
try {
|
|
||||||
kubeObject = await store.patch(item, patch);
|
|
||||||
} catch (e: any) {
|
|
||||||
return {
|
|
||||||
callWasSuccessful: false,
|
|
||||||
error: getErrorMessage(e),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
callWasSuccessful: true,
|
|
||||||
response: { name: kubeObject.getName(), kind: kubeObject.kind },
|
|
||||||
};
|
|
||||||
};
|
|
||||||
},
|
|
||||||
|
|
||||||
causesSideEffects: true,
|
|
||||||
});
|
|
||||||
|
|
||||||
export default callForPatchResourceInjectable;
|
|
||||||
@ -1,12 +0,0 @@
|
|||||||
/**
|
|
||||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
|
||||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
|
||||||
*/
|
|
||||||
import { getGlobalOverride } from "@k8slens/test-utils";
|
|
||||||
import callForResourceInjectable from "./call-for-resource.injectable";
|
|
||||||
|
|
||||||
export default getGlobalOverride(callForResourceInjectable, () => () => {
|
|
||||||
throw new Error(
|
|
||||||
"Tried to call for kube resource without explicit override.",
|
|
||||||
);
|
|
||||||
});
|
|
||||||
@ -1,41 +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 { 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 apiKubeInjectable from "../../../../../k8s/api-kube.injectable";
|
|
||||||
|
|
||||||
export type CallForResource = (selfLink: string) => AsyncResult<KubeObject | undefined>;
|
|
||||||
|
|
||||||
const callForResourceInjectable = getInjectable({
|
|
||||||
id: "call-for-resource",
|
|
||||||
|
|
||||||
instantiate: (di): CallForResource => {
|
|
||||||
const apiKube = di.inject(apiKubeInjectable);
|
|
||||||
|
|
||||||
return async (apiPath: string) => {
|
|
||||||
const parsed = parseKubeApi(apiPath);
|
|
||||||
|
|
||||||
if (!parsed.name) {
|
|
||||||
return { callWasSuccessful: false, error: "Invalid API path" };
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
return {
|
|
||||||
callWasSuccessful: true,
|
|
||||||
response: new KubeObject(await apiKube.get(apiPath)),
|
|
||||||
};
|
|
||||||
} catch (e) {
|
|
||||||
return { callWasSuccessful: false, error: getErrorMessage(e) };
|
|
||||||
}
|
|
||||||
};
|
|
||||||
},
|
|
||||||
|
|
||||||
causesSideEffects: true,
|
|
||||||
});
|
|
||||||
|
|
||||||
export default callForResourceInjectable;
|
|
||||||
@ -3,8 +3,8 @@
|
|||||||
* 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, lifecycleEnum } from "@ogre-tools/injectable";
|
import { getInjectable, lifecycleEnum } from "@ogre-tools/injectable";
|
||||||
import type { CallForResource } from "./call-for-resource/call-for-resource.injectable";
|
import type { RequestKubeResource } from "./request-kube-resource.injectable";
|
||||||
import callForResourceInjectable from "./call-for-resource/call-for-resource.injectable";
|
import requestKubeResourceInjectable from "./request-kube-resource.injectable";
|
||||||
import { waitUntilDefined } from "@k8slens/utilities";
|
import { waitUntilDefined } from "@k8slens/utilities";
|
||||||
import editResourceTabStoreInjectable from "../store.injectable";
|
import editResourceTabStoreInjectable from "../store.injectable";
|
||||||
import type { EditingResource, EditResourceTabStore } from "../store";
|
import type { EditingResource, EditResourceTabStore } from "../store";
|
||||||
@ -12,8 +12,8 @@ import { action, computed, observable, runInAction } from "mobx";
|
|||||||
import type { KubeObject, RawKubeObject } from "../../../../../common/k8s-api/kube-object";
|
import type { KubeObject, RawKubeObject } from "../../../../../common/k8s-api/kube-object";
|
||||||
import yaml from "js-yaml";
|
import yaml from "js-yaml";
|
||||||
import assert from "assert";
|
import assert from "assert";
|
||||||
import type { CallForPatchResource } from "./call-for-patch-resource/call-for-patch-resource.injectable";
|
import type { RequestPatchKubeResource } from "./request-patch-kube-resource.injectable";
|
||||||
import callForPatchResourceInjectable from "./call-for-patch-resource/call-for-patch-resource.injectable";
|
import requestPatchKubeResourceInjectable from "./request-patch-kube-resource.injectable";
|
||||||
import { createPatch } from "rfc6902";
|
import { createPatch } from "rfc6902";
|
||||||
import type { ShowNotification } from "../../../notifications";
|
import type { ShowNotification } from "../../../notifications";
|
||||||
import showSuccessNotificationInjectable from "../../../notifications/show-success-notification.injectable";
|
import showSuccessNotificationInjectable from "../../../notifications/show-success-notification.injectable";
|
||||||
@ -28,8 +28,8 @@ const editResourceModelInjectable = getInjectable({
|
|||||||
const store = di.inject(editResourceTabStoreInjectable);
|
const store = di.inject(editResourceTabStoreInjectable);
|
||||||
|
|
||||||
const model = new EditResourceModel({
|
const model = new EditResourceModel({
|
||||||
callForResource: di.inject(callForResourceInjectable),
|
requestKubeResource: di.inject(requestKubeResourceInjectable),
|
||||||
callForPatchResource: di.inject(callForPatchResourceInjectable),
|
requestPatchKubeResource: di.inject(requestPatchKubeResourceInjectable),
|
||||||
showSuccessNotification: di.inject(showSuccessNotificationInjectable),
|
showSuccessNotification: di.inject(showSuccessNotificationInjectable),
|
||||||
showErrorNotification: di.inject(showErrorNotificationInjectable),
|
showErrorNotification: di.inject(showErrorNotificationInjectable),
|
||||||
store,
|
store,
|
||||||
@ -50,8 +50,8 @@ const editResourceModelInjectable = getInjectable({
|
|||||||
export default editResourceModelInjectable;
|
export default editResourceModelInjectable;
|
||||||
|
|
||||||
interface Dependencies {
|
interface Dependencies {
|
||||||
callForResource: CallForResource;
|
requestKubeResource: RequestKubeResource;
|
||||||
callForPatchResource: CallForPatchResource;
|
requestPatchKubeResource: RequestPatchKubeResource;
|
||||||
waitForEditingResource: () => Promise<EditingResource>;
|
waitForEditingResource: () => Promise<EditingResource>;
|
||||||
showSuccessNotification: ShowNotification;
|
showSuccessNotification: ShowNotification;
|
||||||
showErrorNotification: ShowNotification;
|
showErrorNotification: ShowNotification;
|
||||||
@ -59,17 +59,21 @@ interface Dependencies {
|
|||||||
readonly tabId: string;
|
readonly tabId: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
function getEditSelfLinkFor(object: RawKubeObject): string {
|
function getEditSelfLinkFor(object: RawKubeObject): string | undefined {
|
||||||
const lensVersionLabel = object.metadata.labels?.[EditResourceLabelName];
|
const lensVersionLabel = object.metadata.labels?.[EditResourceLabelName];
|
||||||
|
|
||||||
if (lensVersionLabel) {
|
if (lensVersionLabel) {
|
||||||
const { apiVersionWithGroup, ...parsedApi } = parseKubeApi(object.metadata.selfLink);
|
const parsedKubeApi = parseKubeApi(object.metadata.selfLink);
|
||||||
|
|
||||||
parsedApi.apiVersion = lensVersionLabel;
|
if (!parsedKubeApi) {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
const { apiVersionWithGroup, ...parsedApi } = parsedKubeApi;
|
||||||
|
|
||||||
return createKubeApiURL({
|
return createKubeApiURL({
|
||||||
...parsedApi,
|
...parsedApi,
|
||||||
apiVersion: `${parsedApi.apiGroup}/${parsedApi.apiVersion}`,
|
apiVersion: lensVersionLabel,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -130,7 +134,7 @@ export class EditResourceModel {
|
|||||||
load = async (): Promise<void> => {
|
load = async (): Promise<void> => {
|
||||||
await this.dependencies.waitForEditingResource();
|
await this.dependencies.waitForEditingResource();
|
||||||
|
|
||||||
let result = await this.dependencies.callForResource(this.selfLink);
|
let result = await this.dependencies.requestKubeResource(this.selfLink);
|
||||||
|
|
||||||
if (!result.callWasSuccessful) {
|
if (!result.callWasSuccessful) {
|
||||||
return void this.dependencies.showErrorNotification(`Loading resource failed: ${result.error}`);
|
return void this.dependencies.showErrorNotification(`Loading resource failed: ${result.error}`);
|
||||||
@ -139,9 +143,13 @@ export class EditResourceModel {
|
|||||||
if (result?.response?.metadata.labels?.[EditResourceLabelName]) {
|
if (result?.response?.metadata.labels?.[EditResourceLabelName]) {
|
||||||
const parsed = parseKubeApi(this.selfLink);
|
const parsed = parseKubeApi(this.selfLink);
|
||||||
|
|
||||||
|
if (!parsed) {
|
||||||
|
return void this.dependencies.showErrorNotification(`Object's selfLink is invalid: "${this.selfLink}"`);
|
||||||
|
}
|
||||||
|
|
||||||
parsed.apiVersion = result.response.metadata.labels[EditResourceLabelName];
|
parsed.apiVersion = result.response.metadata.labels[EditResourceLabelName];
|
||||||
|
|
||||||
result = await this.dependencies.callForResource(createKubeApiURL(parsed));
|
result = await this.dependencies.requestKubeResource(createKubeApiURL(parsed));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!result.callWasSuccessful) {
|
if (!result.callWasSuccessful) {
|
||||||
@ -186,7 +194,18 @@ export class EditResourceModel {
|
|||||||
|
|
||||||
const patches = createPatch(firstVersion, currentVersion);
|
const patches = createPatch(firstVersion, currentVersion);
|
||||||
const selfLink = getEditSelfLinkFor(currentVersion);
|
const selfLink = getEditSelfLinkFor(currentVersion);
|
||||||
const result = await this.dependencies.callForPatchResource(this.resource, patches);
|
|
||||||
|
if (!selfLink) {
|
||||||
|
this.dependencies.showErrorNotification((
|
||||||
|
<p>
|
||||||
|
{`Cannot save resource, unknown selfLink: "${currentVersion.metadata.selfLink}"`}
|
||||||
|
</p>
|
||||||
|
));
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
const result = await this.dependencies.requestPatchKubeResource(selfLink, patches);
|
||||||
|
|
||||||
if (!result.callWasSuccessful) {
|
if (!result.callWasSuccessful) {
|
||||||
this.dependencies.showErrorNotification((
|
this.dependencies.showErrorNotification((
|
||||||
|
|||||||
@ -0,0 +1,46 @@
|
|||||||
|
/**
|
||||||
|
* 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 { KubeObjectMetadata, KubeObjectScope } from "../../../../../common/k8s-api/kube-object";
|
||||||
|
import { KubeObject } from "../../../../../common/k8s-api/kube-object";
|
||||||
|
import type { AsyncResult } from "@k8slens/utilities";
|
||||||
|
import { getErrorMessage } from "../../../../../common/utils/get-error-message";
|
||||||
|
import type { Writable } from "type-fest";
|
||||||
|
import type { KubeJsonApiData } from "../../../../../common/k8s-api/kube-json-api";
|
||||||
|
import { parseKubeApi } from "../../../../../common/k8s-api/kube-api-parse";
|
||||||
|
import apiKubeGetInjectable from "../../../../k8s/api-kube-get.injectable";
|
||||||
|
|
||||||
|
export type RequestKubeResource = (selfLink: string) => AsyncResult<KubeObject | undefined>;
|
||||||
|
|
||||||
|
const requestKubeResourceInjectable = getInjectable({
|
||||||
|
id: "request-kube-resource",
|
||||||
|
|
||||||
|
instantiate: (di): RequestKubeResource => {
|
||||||
|
const apiKubeGet = di.inject(apiKubeGetInjectable);
|
||||||
|
|
||||||
|
return async (selfLink) => {
|
||||||
|
const parsed = parseKubeApi(selfLink);
|
||||||
|
|
||||||
|
if (!parsed?.name) {
|
||||||
|
return { callWasSuccessful: false, error: "Invalid API path" };
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const rawData = await apiKubeGet(selfLink) as KubeJsonApiData<KubeObjectMetadata<KubeObjectScope>, unknown, unknown>;
|
||||||
|
|
||||||
|
(rawData.metadata as Writable<typeof rawData.metadata>).selfLink = selfLink;
|
||||||
|
|
||||||
|
return {
|
||||||
|
callWasSuccessful: true,
|
||||||
|
response: new KubeObject(rawData),
|
||||||
|
};
|
||||||
|
} catch (e) {
|
||||||
|
return { callWasSuccessful: false, error: getErrorMessage(e) };
|
||||||
|
}
|
||||||
|
};
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
export default requestKubeResourceInjectable;
|
||||||
@ -0,0 +1,41 @@
|
|||||||
|
/**
|
||||||
|
* 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 { AsyncResult } from "@k8slens/utilities";
|
||||||
|
import type { JsonPatch } from "../../../../../common/k8s-api/kube-object.store";
|
||||||
|
import { getErrorMessage } from "../../../../../common/utils/get-error-message";
|
||||||
|
import { patchTypeHeaders } from "../../../../../common/k8s-api/kube-api";
|
||||||
|
import apiKubePatchInjectable from "../../../../k8s/api-kube-patch.injectable";
|
||||||
|
|
||||||
|
export type RequestPatchKubeResource = (selfLink: string, patch: JsonPatch) => AsyncResult<{ name: string; kind: string }>;
|
||||||
|
|
||||||
|
const requestPatchKubeResourceInjectable = getInjectable({
|
||||||
|
id: "request-patch-kube-resource",
|
||||||
|
instantiate: (di): RequestPatchKubeResource => {
|
||||||
|
const apiKubePatch = di.inject(apiKubePatchInjectable);
|
||||||
|
|
||||||
|
return async (selfLink, patch) => {
|
||||||
|
try {
|
||||||
|
const { metadata, kind } = await apiKubePatch(selfLink, { data: patch }, {
|
||||||
|
headers: {
|
||||||
|
"content-type": patchTypeHeaders.json,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
return {
|
||||||
|
callWasSuccessful: true,
|
||||||
|
response: { name: metadata.name, kind },
|
||||||
|
};
|
||||||
|
} catch (e) {
|
||||||
|
return {
|
||||||
|
callWasSuccessful: false,
|
||||||
|
error: getErrorMessage(e),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
};
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
export default requestPatchKubeResourceInjectable;
|
||||||
@ -16,7 +16,6 @@ const showErrorNotificationInjectable = getInjectable({
|
|||||||
return (message, customOpts = {}) =>
|
return (message, customOpts = {}) =>
|
||||||
notificationsStore.add({
|
notificationsStore.add({
|
||||||
status: NotificationStatus.ERROR,
|
status: NotificationStatus.ERROR,
|
||||||
timeout: 5000,
|
|
||||||
message,
|
message,
|
||||||
...customOpts,
|
...customOpts,
|
||||||
});
|
});
|
||||||
|
|||||||
@ -16,7 +16,6 @@ const showInfoNotificationInjectable = getInjectable({
|
|||||||
return (message, customOpts = {}) =>
|
return (message, customOpts = {}) =>
|
||||||
notificationsStore.add({
|
notificationsStore.add({
|
||||||
status: NotificationStatus.INFO,
|
status: NotificationStatus.INFO,
|
||||||
timeout: 5000,
|
|
||||||
message,
|
message,
|
||||||
...customOpts,
|
...customOpts,
|
||||||
});
|
});
|
||||||
|
|||||||
20
packages/core/src/renderer/k8s/api-kube-get.injectable.ts
Normal file
20
packages/core/src/renderer/k8s/api-kube-get.injectable.ts
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
/**
|
||||||
|
* 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 { KubeJsonApi } from "../../common/k8s-api/kube-json-api";
|
||||||
|
import apiKubeInjectable from "./api-kube.injectable";
|
||||||
|
|
||||||
|
export type ApiKubeGet = KubeJsonApi["get"];
|
||||||
|
|
||||||
|
const apiKubeGetInjectable = getInjectable({
|
||||||
|
id: "api-kube-get",
|
||||||
|
instantiate: (di): ApiKubeGet => {
|
||||||
|
const apiKube = di.inject(apiKubeInjectable);
|
||||||
|
|
||||||
|
return (...params) => apiKube.get(...params);
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
export default apiKubeGetInjectable;
|
||||||
20
packages/core/src/renderer/k8s/api-kube-patch.injectable.ts
Normal file
20
packages/core/src/renderer/k8s/api-kube-patch.injectable.ts
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
/**
|
||||||
|
* 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 { KubeJsonApi } from "../../common/k8s-api/kube-json-api";
|
||||||
|
import apiKubeInjectable from "./api-kube.injectable";
|
||||||
|
|
||||||
|
export type ApiKubePatch = KubeJsonApi["patch"];
|
||||||
|
|
||||||
|
const apiKubePatchInjectable = getInjectable({
|
||||||
|
id: "api-kube-patch",
|
||||||
|
instantiate: (di): ApiKubePatch => {
|
||||||
|
const apiKube = di.inject(apiKubeInjectable);
|
||||||
|
|
||||||
|
return (...params) => apiKube.patch(...params);
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
export default apiKubePatchInjectable;
|
||||||
Loading…
Reference in New Issue
Block a user