Skip to content

Commit 05bf438

Browse files
authored
Fix editor update flow (#1310)
* fix: editor update flow Signed-off-by: Oleksii Orel <oorel@redhat.com>
1 parent f2e8000 commit 05bf438

File tree

7 files changed

+126
-84
lines changed

7 files changed

+126
-84
lines changed

packages/dashboard-frontend/src/services/workspace-client/devworkspace/__tests__/__mocks__/devWorkspaceSpecTemplates.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
*/
1212

1313
import devfileApi from '@/services/devfileApi';
14+
import { EDITOR_DEVFILE_API_QUERY } from '@/store/DevfileRegistries/const';
1415

1516
const getVSCodeDevWorkspaceTemplate = (cpuLimit = '1500m'): devfileApi.DevWorkspaceTemplate => {
1617
return {
@@ -19,7 +20,7 @@ const getVSCodeDevWorkspaceTemplate = (cpuLimit = '1500m'): devfileApi.DevWorksp
1920
metadata: {
2021
annotations: {
2122
'che.eclipse.org/components-update-policy': 'managed',
22-
'che.eclipse.org/plugin-registry-url': 'che-incubator/che-code/latest',
23+
'che.eclipse.org/plugin-registry-url': `${EDITOR_DEVFILE_API_QUERY}che-incubator/che-code/latest`,
2324
},
2425
creationTimestamp: new Date('2024-05-30T12:51:45Z'),
2526
generation: 1,

packages/dashboard-frontend/src/services/workspace-client/devworkspace/__tests__/__mocks__/editorDefinitions.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@
1212

1313
import { V230Devfile } from '@devfile/api';
1414

15-
const getVSCodeEditorDefinition = (): V230Devfile => {
15+
const getVSCodeEditorDefinition = (cpuLimit = '500m'): V230Devfile => {
1616
return {
1717
attributes: {
1818
version: null,
@@ -36,7 +36,7 @@ const getVSCodeEditorDefinition = (): V230Devfile => {
3636
{
3737
container: {
3838
command: ['/entrypoint-init-container.sh'],
39-
cpuLimit: '500m',
39+
cpuLimit,
4040
cpuRequest: '30m',
4141
image: 'quay.io/che-incubator/che-code:latest',
4242
memoryLimit: '256Mi',

packages/dashboard-frontend/src/services/workspace-client/devworkspace/__tests__/devWorkspaceClient.editorUpdate.spec.ts

Lines changed: 41 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
*/
1212

1313
import mockAxios from 'axios';
14+
import { dump } from 'js-yaml';
1415

1516
import { container } from '@/inversify.config';
1617
import { dashboardBackendPrefix } from '@/services/backend-client/const';
@@ -23,11 +24,7 @@ import {
2324
DevWorkspaceClient,
2425
REGISTRY_URL,
2526
} from '@/services/workspace-client/devworkspace/devWorkspaceClient';
26-
27-
const mockFetchData = jest.fn();
28-
jest.mock('@/services/registry/fetchData', () => ({
29-
fetchData: (...args: unknown[]) => mockFetchData(...args),
30-
}));
27+
import { EDITOR_DEVFILE_API_QUERY } from '@/store/DevfileRegistries/const';
3128

3229
describe('DevWorkspace client editor update', () => {
3330
const namespace = 'admin-che';
@@ -134,53 +131,49 @@ describe('DevWorkspace client editor update', () => {
134131

135132
describe('DevWorkspaceTemplate with plugin registry URL', () => {
136133
it('should return patch for an editor if it has been updated', async () => {
137-
const template = getVSCodeDevWorkspaceTemplate('1000m');
134+
const editor = getVSCodeEditorDefinition('1000m') as devfileApi.Devfile;
135+
const template = getVSCodeDevWorkspaceTemplate('500m');
138136
template.metadata.annotations = {
139137
'che.eclipse.org/components-update-policy': 'managed',
140138
'che.eclipse.org/plugin-registry-url':
141-
'https://192.168.64.24.nip.io/plugin-registry/v3/plugins/che-incubator/che-code/latest/devfile.yaml',
139+
'https://192.168.64.24.nip.io/che-incubator/che-code/devfile.yaml',
142140
};
143141

144-
const mockPatch = mockAxios.get as jest.Mock;
145-
mockPatch.mockResolvedValueOnce(new Promise(resolve => resolve({ data: template })));
142+
const mockGet = mockAxios.get as jest.Mock;
143+
mockGet.mockResolvedValueOnce(new Promise(resolve => resolve({ data: template })));
146144

147-
// if cpuLimit changed from '1000m' to '500m'
148-
const newTemplate = getVSCodeDevWorkspaceTemplate('500m');
149-
newTemplate.metadata.annotations = {
150-
'che.eclipse.org/components-update-policy': 'managed',
151-
'che.eclipse.org/plugin-registry-url':
152-
'https://192.168.64.24.nip.io/plugin-registry/v3/plugins/che-incubator/che-code/latest/devfile.yaml',
153-
};
145+
const mockPost = mockAxios.post as jest.Mock;
146+
mockPost.mockResolvedValueOnce(new Promise(resolve => resolve({ data: dump(editor) })));
154147

155-
const editors: devfileApi.Devfile[] = [getVSCodeEditorDefinition() as devfileApi.Devfile];
156-
const editorName = newTemplate.metadata.name;
148+
const editorName = template.metadata.name;
157149

158150
const patch = await client.checkForTemplatesUpdate(
159151
editorName,
160152
namespace,
161-
editors,
153+
[editor],
162154
pluginRegistryUrl,
163155
pluginRegistryInternalUrl,
164156
undefined,
165157
);
166158

167-
expect(mockPatch.mock.calls).toEqual([
159+
expect(mockGet.mock.calls).toEqual([
168160
[`${dashboardBackendPrefix}/namespace/${namespace}/devworkspacetemplates/${editorName}`],
169161
]);
170162

171-
expect(patch).toEqual([
172-
{
173-
op: 'replace',
174-
path: '/metadata/annotations',
175-
value: {
176-
[COMPONENT_UPDATE_POLICY]: 'managed',
177-
[REGISTRY_URL]: 'che-incubator/che-code/latest',
163+
expect(mockPost.mock.calls).toEqual([
164+
[
165+
`${dashboardBackendPrefix}/data/resolver`,
166+
{
167+
url: 'https://192.168.64.24.nip.io/che-incubator/che-code/devfile.yaml',
178168
},
179-
},
169+
],
170+
]);
171+
172+
expect(patch).toEqual([
180173
{
181174
op: 'replace',
182175
path: '/spec',
183-
value: newTemplate.spec,
176+
value: getVSCodeDevWorkspaceTemplate('1000m').spec,
184177
},
185178
]);
186179
});
@@ -196,16 +189,13 @@ describe('DevWorkspace client editor update', () => {
196189
const mockPatch = mockAxios.get as jest.Mock;
197190
mockPatch.mockResolvedValueOnce(new Promise(resolve => resolve({ data: template })));
198191

199-
// if cpuLimit changed from '1000m' to '500m'
200-
const newTemplate = getVSCodeDevWorkspaceTemplate('500m');
201-
newTemplate.metadata.annotations = {
202-
'che.eclipse.org/components-update-policy': 'managed',
203-
'che.eclipse.org/plugin-registry-url':
204-
'https://192.168.64.24.nip.io/custom-registry/v3/plugins/che-incubator/che-code/latest/devfile.yaml',
205-
};
192+
const editors: devfileApi.Devfile[] = [
193+
getVSCodeEditorDefinition('1000m') as devfileApi.Devfile,
194+
];
195+
const editorName = template.metadata.name;
206196

207-
const editors: devfileApi.Devfile[] = [getVSCodeEditorDefinition() as devfileApi.Devfile];
208-
const editorName = newTemplate.metadata.name;
197+
const mockPost = mockAxios.post as jest.Mock;
198+
mockPost.mockResolvedValueOnce(new Promise(resolve => resolve({ data: editors[0] })));
209199

210200
const patch = await client.checkForTemplatesUpdate(
211201
editorName,
@@ -216,8 +206,13 @@ describe('DevWorkspace client editor update', () => {
216206
undefined,
217207
);
218208

219-
expect(mockPatch.mock.calls).toEqual([
220-
[`${dashboardBackendPrefix}/namespace/${namespace}/devworkspacetemplates/${editorName}`],
209+
expect(mockPost.mock.calls).toEqual([
210+
[
211+
`${dashboardBackendPrefix}/data/resolver`,
212+
{
213+
url: 'https://192.168.64.24.nip.io/custom-registry/v3/plugins/che-incubator/che-code/latest/devfile.yaml',
214+
},
215+
],
221216
]);
222217

223218
expect(patch).toEqual([]);
@@ -230,10 +225,6 @@ describe('DevWorkspace client editor update', () => {
230225
'che.eclipse.org/plugin-registry-url':
231226
'https://192.168.64.24.nip.io/plugin-registry/v3/plugins/che-incubator/che-code/latest/devfile.yaml',
232227
};
233-
234-
const mockPatch = mockAxios.get as jest.Mock;
235-
mockPatch.mockResolvedValueOnce(new Promise(resolve => resolve({ data: template })));
236-
237228
// if cpuLimit changed from '1000m' to '500m'
238229
const newTemplate = getVSCodeDevWorkspaceTemplate('500m');
239230
newTemplate.metadata.annotations = {
@@ -242,6 +233,9 @@ describe('DevWorkspace client editor update', () => {
242233
'https://192.168.64.24.nip.io/plugin-registry/v3/plugins/che-incubator/che-code/latest/devfile.yaml',
243234
};
244235

236+
const mockGet = mockAxios.get as jest.Mock;
237+
mockGet.mockResolvedValueOnce(new Promise(resolve => resolve({ data: template })));
238+
245239
const editors: devfileApi.Devfile[] = [getVSCodeEditorDefinition() as devfileApi.Devfile];
246240
const editorName = newTemplate.metadata.name;
247241

@@ -254,7 +248,7 @@ describe('DevWorkspace client editor update', () => {
254248
undefined,
255249
);
256250

257-
expect(mockPatch.mock.calls).toEqual([
251+
expect(mockGet.mock.calls).toEqual([
258252
[`${dashboardBackendPrefix}/namespace/${namespace}/devworkspacetemplates/${editorName}`],
259253
]);
260254

@@ -264,7 +258,7 @@ describe('DevWorkspace client editor update', () => {
264258
path: '/metadata/annotations',
265259
value: {
266260
[COMPONENT_UPDATE_POLICY]: 'managed',
267-
[REGISTRY_URL]: 'che-incubator/che-code/latest',
261+
[REGISTRY_URL]: `${EDITOR_DEVFILE_API_QUERY}che-incubator/che-code/latest`,
268262
},
269263
},
270264
]);
@@ -311,7 +305,7 @@ describe('DevWorkspace client editor update', () => {
311305
path: '/metadata/annotations',
312306
value: {
313307
[COMPONENT_UPDATE_POLICY]: 'managed',
314-
[REGISTRY_URL]: 'che-incubator/custom-editor/latest',
308+
[REGISTRY_URL]: `${EDITOR_DEVFILE_API_QUERY}che-incubator/custom-editor/latest`,
315309
},
316310
},
317311
]);

packages/dashboard-frontend/src/services/workspace-client/devworkspace/devWorkspaceClient.ts

Lines changed: 56 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ import {
1818
} from '@devfile/api';
1919
import { api } from '@eclipse-che/common';
2020
import { inject, injectable } from 'inversify';
21+
import { load } from 'js-yaml';
2122
import cloneDeep from 'lodash/cloneDeep';
2223
import isEqual from 'lodash/isEqual';
2324

@@ -36,13 +37,15 @@ import {
3637
import { delay } from '@/services/helpers/delay';
3738
import { isWebTerminal } from '@/services/helpers/devworkspace';
3839
import { DevWorkspaceStatus } from '@/services/helpers/types';
40+
import { fetchData } from '@/services/registry/fetchData';
3941
import { WorkspaceAdapter } from '@/services/workspace-adapter';
4042
import {
4143
devWorkspaceApiGroup,
4244
devWorkspaceSingularSubresource,
4345
devWorkspaceVersion,
4446
} from '@/services/workspace-client/devworkspace/converters';
4547
import { DevWorkspaceDefaultPluginsHandler } from '@/services/workspace-client/devworkspace/DevWorkspaceDefaultPluginsHandler';
48+
import { EDITOR_DEVFILE_API_QUERY } from '@/store/DevfileRegistries/const';
4649
import { WorkspacesDefaultPlugins } from '@/store/Plugins/devWorkspacePlugins';
4750

4851
export const COMPONENT_UPDATE_POLICY = 'che.eclipse.org/components-update-policy';
@@ -668,68 +671,91 @@ export class DevWorkspaceClient {
668671
title: string;
669672
},
670673
): Promise<api.IPatch[]> {
671-
const managedTemplate = await DwtApi.getTemplateByName(namespace, editorName);
672-
673674
const patch: api.IPatch[] = [];
675+
const managedTemplate = await DwtApi.getTemplateByName(namespace, editorName);
676+
const _editorIdOrPath = managedTemplate.metadata?.annotations?.[REGISTRY_URL];
677+
const updatePolicy = managedTemplate.metadata?.annotations?.[COMPONENT_UPDATE_POLICY];
674678

675-
let editorReference = managedTemplate.metadata?.annotations?.[REGISTRY_URL];
676-
677-
if (
678-
!editorReference ||
679-
managedTemplate.metadata?.annotations?.[COMPONENT_UPDATE_POLICY] !== 'managed'
680-
) {
679+
if (!_editorIdOrPath || updatePolicy !== 'managed') {
681680
console.log('Template is not managed');
682681
return patch;
683682
}
684683

685-
if (/^(https?:\/\/)/.test(editorReference)) {
684+
let url: string | undefined;
685+
if (/^(https?:\/\/)/.test(_editorIdOrPath)) {
686+
url = _editorIdOrPath;
686687
// Define a regular expression pattern to match URLs containing 'plugin-registry/v3/plugins'
687688
// and ending with 'devfile.yaml'. The part between 'v3/plugins/' and '/devfile.yaml' is captured.
688689
const pluginRegistryURLPattern = /plugin-registry\/v3\/plugins\/(.+?)\/devfile\.yaml$/;
689-
const match = editorReference.match(pluginRegistryURLPattern);
690+
const match = url.match(pluginRegistryURLPattern);
690691

691692
if (match) {
692-
editorReference = match[1];
693693
const annotations = {
694694
[COMPONENT_UPDATE_POLICY]: 'managed',
695-
[REGISTRY_URL]: editorReference,
695+
[REGISTRY_URL]: `${EDITOR_DEVFILE_API_QUERY}${match[1]}`,
696696
};
697697
// Create a patch to update the annotations by replacing plugin registry URL with the editor reference
698698
patch.push({
699699
op: 'replace',
700700
path: '/metadata/annotations',
701701
value: annotations,
702702
});
703-
} else {
704-
console.log('Template is not managed');
705-
return patch;
703+
}
704+
} else {
705+
url = `${EDITOR_DEVFILE_API_QUERY}${_editorIdOrPath}`;
706+
const annotations = {
707+
[COMPONENT_UPDATE_POLICY]: 'managed',
708+
[REGISTRY_URL]: url,
709+
};
710+
patch.push({
711+
op: 'replace',
712+
path: '/metadata/annotations',
713+
value: annotations,
714+
});
715+
}
716+
717+
let editor: devfileApi.Devfile | undefined = undefined;
718+
// Found the target editors
719+
if (url.startsWith(EDITOR_DEVFILE_API_QUERY)) {
720+
const editorReference = url.replace(EDITOR_DEVFILE_API_QUERY, '');
721+
722+
const _editor = editors.find(e => {
723+
return (
724+
e.metadata?.attributes?.publisher +
725+
'/' +
726+
e.metadata?.name +
727+
'/' +
728+
e.metadata?.attributes?.version ===
729+
editorReference
730+
);
731+
});
732+
if (_editor !== undefined) {
733+
editor = cloneDeep(_editor);
734+
}
735+
} else {
736+
const editorContent = await fetchData<string | devfileApi.Devfile>(url);
737+
if (typeof editorContent === 'string') {
738+
editor = load(editorContent) as devfileApi.Devfile;
739+
} else if (typeof editorContent === 'object') {
740+
editor = editorContent;
706741
}
707742
}
708743

709-
const originalEditor: devfileApi.Devfile | undefined = editors.find(editor => {
710-
return (
711-
editor.metadata?.attributes?.publisher +
712-
'/' +
713-
editor.metadata?.name +
714-
'/' +
715-
editor.metadata?.attributes?.version ===
716-
editorReference
717-
);
718-
});
719-
if (!originalEditor) {
744+
if (editor === undefined) {
745+
console.warn('Failed to get editor');
720746
return patch;
721747
}
722748

723749
const spec: Partial<V1alpha2DevWorkspaceTemplateSpec> = {};
724-
for (const key in originalEditor) {
750+
for (const key in editor) {
725751
if (key !== 'schemaVersion' && key !== 'metadata') {
726752
if (key === 'components') {
727-
originalEditor.components?.forEach(component => {
753+
editor.components?.forEach(component => {
728754
if (component.container && !component.container.sourceMapping) {
729755
component.container.sourceMapping = '/projects';
730756
}
731757
});
732-
spec.components = originalEditor.components;
758+
spec.components = editor.components;
733759
this.addEnvVarsToContainers(
734760
spec.components,
735761
pluginRegistryUrl,
@@ -738,7 +764,7 @@ export class DevWorkspaceClient {
738764
clusterConsole,
739765
);
740766
} else {
741-
spec[key] = originalEditor[key];
767+
spec[key] = editor[key];
742768
}
743769
}
744770
}

packages/dashboard-frontend/src/store/DevfileRegistries/__tests__/getEditor.spec.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ import { dump } from 'js-yaml';
1616
import devfileApi from '@/services/devfileApi';
1717
import { RootState } from '@/store';
1818
import { actionCreators } from '@/store/DevfileRegistries/actions';
19+
import { EDITOR_DEVFILE_API_QUERY } from '@/store/DevfileRegistries/const';
1920
import { getEditor } from '@/store/DevfileRegistries/getEditor';
2021
import { State } from '@/store/DevfileRegistries/reducer';
2122

@@ -140,7 +141,7 @@ describe('getEditor', () => {
140141

141142
expect(result).toEqual({
142143
content: 'dumped devfile content',
143-
editorYamlUrl: 'che-incubator/che-idea/next',
144+
editorYamlUrl: `${EDITOR_DEVFILE_API_QUERY}che-incubator/che-idea/next`,
144145
});
145146
expect(dispatch).not.toHaveBeenCalled();
146147
});

0 commit comments

Comments
 (0)