Skip to content

Commit f6923f8

Browse files
committed
frontend: EditButton: Use JSON patch to prevent resource version conflicts
Signed-off-by: jaehanbyun <awbrg789@naver.com>
1 parent 8ca3822 commit f6923f8

File tree

3 files changed

+48
-2
lines changed

3 files changed

+48
-2
lines changed

frontend/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,7 @@
5757
"eslint-plugin-simple-import-sort": "^12.1.1",
5858
"eslint-plugin-unused-imports": "^4.1.3",
5959
"fake-indexeddb": "^6.0.0",
60+
"fast-json-patch": "^3.1.1",
6061
"fuse.js": "^7.0.0",
6162
"humanize-duration": "^3.27.2",
6263
"i18next": "^23.15.1",

frontend/src/components/common/Resource/EditButton.tsx

Lines changed: 46 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,10 +15,13 @@
1515
*/
1616

1717
import { Icon } from '@iconify/react';
18+
import { compare } from 'fast-json-patch';
1819
import React from 'react';
1920
import { useTranslation } from 'react-i18next';
2021
import { useDispatch } from 'react-redux';
2122
import { useLocation } from 'react-router-dom';
23+
import { patch } from '../../../lib/k8s/api/v1/clusterRequests';
24+
import { KubeObjectEndpoint } from '../../../lib/k8s/api/v2/KubeObjectEndpoint';
2225
import { KubeObject } from '../../../lib/k8s/KubeObject';
2326
import { KubeObjectInterface } from '../../../lib/k8s/KubeObject';
2427
import { CallbackActionOptions, clusterAction } from '../../../redux/clusterActionSlice';
@@ -51,6 +54,10 @@ export default function EditButton(props: EditButtonProps) {
5154
const dispatchHeadlampEditEvent = useEventCallback(HeadlampEventType.EDIT_RESOURCE);
5255
const activityId = 'edit-' + item.metadata.uid;
5356

57+
// Store the original resource snapshot (firstDraft) for JSON Patch comparison
58+
// This is set when the editor is opened
59+
const originalResourceRef = React.useRef<any>(null);
60+
5461
function makeErrorMessage(err: any) {
5562
const status: number = err.status;
5663
switch (status) {
@@ -63,7 +70,41 @@ export default function EditButton(props: EditButtonProps) {
6370

6471
async function updateFunc(newItem: KubeObjectInterface) {
6572
try {
66-
await item.update(newItem);
73+
if (!originalResourceRef.current) {
74+
throw new Error('Original resource snapshot not found');
75+
}
76+
77+
// Calculate JSON Patch: diff between original (when editor opened) and new (user edited)
78+
const patches = compare(originalResourceRef.current, newItem);
79+
80+
if (patches.length === 0) {
81+
// No changes detected
82+
Activity.close(activityId);
83+
return;
84+
}
85+
86+
// Build the API URL
87+
const apiInfo = item._class().apiEndpoint.apiInfo[0];
88+
const endpoint: KubeObjectEndpoint = {
89+
group: apiInfo.group,
90+
version: apiInfo.version,
91+
resource: apiInfo.resource,
92+
};
93+
94+
const namespace = item.getNamespace();
95+
const name = item.getName();
96+
const urlParts = [KubeObjectEndpoint.toUrl(endpoint, namespace), name];
97+
const url = urlParts.filter(Boolean).join('/');
98+
99+
// Use the patch function with JSON Patch content type
100+
// Override the default 'application/merge-patch+json' to 'application/json-patch+json'
101+
await patch(url, patches, true, {
102+
cluster: item._clusterName,
103+
headers: {
104+
'Content-Type': 'application/json-patch+json',
105+
},
106+
});
107+
67108
Activity.close(activityId);
68109
} catch (err) {
69110
Activity.update(activityId, { minimized: false });
@@ -128,6 +169,9 @@ export default function EditButton(props: EditButtonProps) {
128169
if (afterConfirm) {
129170
afterConfirm();
130171
}
172+
const editableObject = item.getEditableObject();
173+
originalResourceRef.current = editableObject;
174+
131175
Activity.launch({
132176
id: activityId,
133177
title: t('translation|Edit') + ': ' + item.metadata.name,
@@ -136,7 +180,7 @@ export default function EditButton(props: EditButtonProps) {
136180
content: (
137181
<EditorDialog
138182
noDialog
139-
item={item.getEditableObject()}
183+
item={editableObject}
140184
open
141185
onClose={() => Activity.close(activityId)}
142186
onSave={handleSave}

plugins/headlamp-plugin/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,7 @@
7070
"eslint-plugin-react-hooks": "^4.6.2",
7171
"eslint-plugin-simple-import-sort": "^12.1.1",
7272
"eslint-plugin-unused-imports": "^4.1.3",
73+
"fast-json-patch": "^3.1.1",
7374
"fs-extra": "^11.2.0",
7475
"fuse.js": "^7.0.0",
7576
"humanize-duration": "^3.27.2",

0 commit comments

Comments
 (0)