mirror of
https://github.com/lensapp/lens.git
synced 2025-05-20 05:10:56 +00:00
Use JSON patch for editing components (#3674)
This commit is contained in:
1 parent
8b1de233f2
commit
4705a66632
18 files changed
+242
-106
No files matched your search
@@ -72,6 +72,8 @@ export class ConfigMapDetails extends React.Component<Props> {
|
||||
<>ConfigMap <b>{configMap.getName()}</b> successfully updated.</>
|
||||
</p>
|
||||
);
|
||||
} catch (error) {
|
||||
Notifications.error(`Failed to save config map: ${error}`);
|
||||
} finally {
|
||||
this.isSaving = false;
|
||||
}
|
||||
|
||||
@@ -112,6 +112,7 @@ const dummyDeployment: Deployment = {
|
||||
toPlainObject: jest.fn(),
|
||||
update: jest.fn(),
|
||||
delete: jest.fn(),
|
||||
patch: jest.fn(),
|
||||
};
|
||||
|
||||
describe("<DeploymentScaleDialog />", () => {
|
||||
|
||||
@@ -107,6 +107,7 @@ const dummyReplicaSet: ReplicaSet = {
|
||||
toPlainObject: jest.fn(),
|
||||
update: jest.fn(),
|
||||
delete: jest.fn(),
|
||||
patch: jest.fn(),
|
||||
};
|
||||
|
||||
describe("<ReplicaSetScaleDialog />", () => {
|
||||
|
||||
@@ -117,6 +117,7 @@ const dummyStatefulSet: StatefulSet = {
|
||||
toPlainObject: jest.fn(),
|
||||
update: jest.fn(),
|
||||
delete: jest.fn(),
|
||||
patch: jest.fn(),
|
||||
};
|
||||
|
||||
describe("<StatefulSetScaleDialog />", () => {
|
||||
|
||||
@@ -33,10 +33,10 @@ import { createResourceStore } from "./create-resource.store";
|
||||
import type { DockTab } from "./dock.store";
|
||||
import { EditorPanel } from "./editor-panel";
|
||||
import { InfoPanel } from "./info-panel";
|
||||
import { resourceApplierApi } from "../../../common/k8s-api/endpoints/resource-applier.api";
|
||||
import type { JsonApiErrorParsed } from "../../../common/k8s-api/json-api";
|
||||
import * as resourceApplierApi from "../../../common/k8s-api/endpoints/resource-applier.api";
|
||||
import { Notifications } from "../notifications";
|
||||
import { monacoModelsManager } from "./monaco-model-manager";
|
||||
import logger from "../../../common/logger";
|
||||
|
||||
interface Props {
|
||||
className?: string;
|
||||
@@ -95,7 +95,7 @@ export class CreateResource extends React.Component<Props> {
|
||||
});
|
||||
};
|
||||
|
||||
create = async () => {
|
||||
create = async (): Promise<undefined> => {
|
||||
if (this.error || !this.data.trim()) {
|
||||
// do not save when field is empty or there is an error
|
||||
return null;
|
||||
@@ -103,31 +103,31 @@ export class CreateResource extends React.Component<Props> {
|
||||
|
||||
// skip empty documents if "---" pasted at the beginning or end
|
||||
const resources = jsYaml.safeLoadAll(this.data).filter(Boolean);
|
||||
const createdResources: string[] = [];
|
||||
const errors: string[] = [];
|
||||
|
||||
await Promise.all(
|
||||
resources.map(data => {
|
||||
return resourceApplierApi.update(data)
|
||||
.then(item => createdResources.push(item.metadata.name))
|
||||
.catch((err: JsonApiErrorParsed) => errors.push(err.toString()));
|
||||
})
|
||||
);
|
||||
|
||||
if (errors.length) {
|
||||
errors.forEach(error => Notifications.error(error));
|
||||
if (!createdResources.length) throw errors[0];
|
||||
if (resources.length === 0) {
|
||||
return void logger.info("Nothing to create");
|
||||
}
|
||||
const successMessage = (
|
||||
<p>
|
||||
{createdResources.length === 1 ? "Resource" : "Resources"}{" "}
|
||||
<b>{createdResources.join(", ")}</b> successfully created
|
||||
</p>
|
||||
);
|
||||
|
||||
Notifications.ok(successMessage);
|
||||
const createdResources: string[] = [];
|
||||
|
||||
return successMessage;
|
||||
for (const result of await Promise.allSettled(resources.map(resourceApplierApi.update))) {
|
||||
if (result.status === "fulfilled") {
|
||||
createdResources.push(result.value.metadata.name);
|
||||
} else {
|
||||
Notifications.error(result.reason.toString());
|
||||
}
|
||||
}
|
||||
|
||||
if (createdResources.length > 0) {
|
||||
Notifications.ok((
|
||||
<p>
|
||||
{createdResources.length === 1 ? "Resource" : "Resources"}{" "}
|
||||
<b>{createdResources.join(", ")}</b> successfully created
|
||||
</p>
|
||||
));
|
||||
}
|
||||
|
||||
return undefined;
|
||||
};
|
||||
|
||||
renderControls(){
|
||||
|
||||
@@ -31,6 +31,7 @@ import {monacoModelsManager} from "./monaco-model-manager";
|
||||
export interface EditingResource {
|
||||
resource: string; // resource path, e.g. /api/v1/namespaces/default
|
||||
draft?: string; // edited draft in yaml
|
||||
firstDraft?: string;
|
||||
}
|
||||
|
||||
export class EditResourceStore extends DockTabStore<EditingResource> {
|
||||
@@ -106,6 +107,10 @@ export class EditResourceStore extends DockTabStore<EditingResource> {
|
||||
return dockStore.getTabById(tabId);
|
||||
}
|
||||
|
||||
clearInitialDraft(tabId: TabId): void {
|
||||
delete this.getData(tabId)?.firstDraft;
|
||||
}
|
||||
|
||||
reset() {
|
||||
super.reset();
|
||||
Array.from(this.watchers).forEach(([tabId, dispose]) => {
|
||||
|
||||
@@ -24,7 +24,7 @@ import "./edit-resource.scss";
|
||||
import React from "react";
|
||||
import { action, computed, makeObservable, observable } from "mobx";
|
||||
import { observer } from "mobx-react";
|
||||
import jsYaml from "js-yaml";
|
||||
import yaml from "js-yaml";
|
||||
import type { DockTab } from "./dock.store";
|
||||
import { cssNames } from "../../utils";
|
||||
import { editResourceStore } from "./edit-resource.store";
|
||||
@@ -33,6 +33,7 @@ import { Badge } from "../badge";
|
||||
import { EditorPanel } from "./editor-panel";
|
||||
import { Spinner } from "../spinner";
|
||||
import type { KubeObject } from "../../../common/k8s-api/kube-object";
|
||||
import { createPatch } from "rfc6902";
|
||||
|
||||
interface Props {
|
||||
className?: string;
|
||||
@@ -71,16 +72,17 @@ export class EditResource extends React.Component<Props> {
|
||||
return draft;
|
||||
}
|
||||
|
||||
return jsYaml.safeDump(this.resource.toPlainObject()); // dump resource first time
|
||||
return yaml.safeDump(this.resource.toPlainObject()); // dump resource first time
|
||||
}
|
||||
|
||||
@action
|
||||
saveDraft(draft: string | object) {
|
||||
if (typeof draft === "object") {
|
||||
draft = draft ? jsYaml.safeDump(draft) : undefined;
|
||||
draft = draft ? yaml.safeDump(draft) : undefined;
|
||||
}
|
||||
|
||||
editResourceStore.setData(this.tabId, {
|
||||
firstDraft: draft, // this must be before the next line
|
||||
...editResourceStore.getData(this.tabId),
|
||||
draft,
|
||||
});
|
||||
@@ -95,16 +97,18 @@ export class EditResource extends React.Component<Props> {
|
||||
if (this.error) {
|
||||
return null;
|
||||
}
|
||||
const store = editResourceStore.getStore(this.tabId);
|
||||
const updatedResource: KubeObject = await store.update(this.resource, jsYaml.safeLoad(this.draft));
|
||||
|
||||
this.saveDraft(updatedResource.toPlainObject()); // update with new resourceVersion to avoid further errors on save
|
||||
const resourceType = updatedResource.kind;
|
||||
const resourceName = updatedResource.getName();
|
||||
const store = editResourceStore.getStore(this.tabId);
|
||||
const currentVersion = yaml.safeLoad(this.draft);
|
||||
const firstVersion = yaml.safeLoad(editResourceStore.getData(this.tabId).firstDraft ?? this.draft);
|
||||
const patches = createPatch(firstVersion, currentVersion);
|
||||
const updatedResource = await store.patch(this.resource, patches);
|
||||
|
||||
editResourceStore.clearInitialDraft(this.tabId);
|
||||
|
||||
return (
|
||||
<p>
|
||||
{resourceType} <b>{resourceName}</b> updated.
|
||||
{updatedResource.kind} <b>{updatedResource.getName()}</b> updated.
|
||||
</p>
|
||||
);
|
||||
};
|
||||
@@ -126,9 +130,9 @@ export class EditResource extends React.Component<Props> {
|
||||
submittingMessage="Applying.."
|
||||
controls={(
|
||||
<div className="resource-info flex gaps align-center">
|
||||
<span>Kind:</span> <Badge label={resource.kind}/>
|
||||
<span>Kind:</span><Badge label={resource.kind}/>
|
||||
<span>Name:</span><Badge label={resource.getName()}/>
|
||||
<span>Namespace:</span> <Badge label={resource.getNs() || "global"}/>
|
||||
<span>Namespace:</span><Badge label={resource.getNs() || "global"}/>
|
||||
</div>
|
||||
)}
|
||||
/>
|
||||
|
||||
@@ -91,10 +91,7 @@ export class EditorPanel extends React.Component<Props> {
|
||||
|
||||
onChange = (value: string) => {
|
||||
this.validate(value);
|
||||
|
||||
if (this.props.onChange) {
|
||||
this.props.onChange(value, this.yamlError);
|
||||
}
|
||||
this.props.onChange?.(value, this.yamlError);
|
||||
};
|
||||
|
||||
render() {
|
||||
|
||||
@@ -44,15 +44,11 @@ export class KubeObjectMenu<T extends KubeObject> extends React.Component<KubeOb
|
||||
}
|
||||
|
||||
get isEditable() {
|
||||
const { editable } = this.props;
|
||||
|
||||
return editable !== undefined ? editable : !!(this.store && this.store.update);
|
||||
return this.props.editable ?? Boolean(this.store?.patch);
|
||||
}
|
||||
|
||||
get isRemovable() {
|
||||
const { removable } = this.props;
|
||||
|
||||
return removable !== undefined ? removable : !!(this.store && this.store.remove);
|
||||
return this.props.removable ?? Boolean(this.store?.remove);
|
||||
}
|
||||
|
||||
@boundMethod
|
||||
|
||||
Reference in new issue
Block a user