Skip to content

Commit 98a2ec5

Browse files
authored
feat: Community module synchronius installation (#4214)
* installation partially working * add mvp of upload dialog * remove old file * fix fetch * fix * fix deletion * use singleGet instead of fetch * improve code * first implementation * move upload to context * move things to proper place * refactor and improve code quality * improve error handling * improve moduleDuringInstalation state * fix upload * fix upload * remove console.logs and finished uploads * improve notifications * remove not needed sleep * small fixes * cleanup * fix eslint * fix lint * small fixes * fix logic * use transform function instead of direct updates * fix lint * fix code review * fix after mege
1 parent aa780c8 commit 98a2ec5

File tree

15 files changed

+601
-164
lines changed

15 files changed

+601
-164
lines changed

public/i18n/en.yaml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -953,6 +953,7 @@ modules:
953953
source-yaml-fetch-failed: 'Failed to download source yaml: {{error}}'
954954
source-yaml-invalid-url: Ensure your URL ends with ".yaml".
955955
success: '{{resourceType}} installed'
956+
upload: Start {{resourceType}} upload
956957
no-modules: No community modules available
957958
no-modules-description: Customize your cluster by adding the modules you need.
958959
no-modules-installed: No community modules installed

src/components/Modules/community/CommunityModulesAddModule.tsx

Lines changed: 130 additions & 62 deletions
Original file line numberDiff line numberDiff line change
@@ -7,29 +7,43 @@ import { ResourceForm } from 'shared/ResourceForm';
77
import { MessageStrip } from '@ui5/webcomponents-react';
88
import { Spinner } from 'shared/components/Spinner/Spinner';
99
import {
10-
fetchResourcesToApply,
1110
getAvailableCommunityModules,
1211
VersionInfo,
1312
} from 'components/Modules/community/communityModulesHelpers';
1413
import {
15-
DEFAULT_K8S_NAMESPACE,
1614
getModuleName,
1715
ModuleTemplateListType,
1816
ModuleTemplateType,
1917
} from 'components/Modules/support';
20-
import { useCallback, useContext, useEffect, useMemo, useState } from 'react';
21-
import { UnsavedMessageBox } from 'shared/components/UnsavedMessageBox/UnsavedMessageBox';
22-
import { createPortal } from 'react-dom';
18+
import React, {
19+
useCallback,
20+
useContext,
21+
useEffect,
22+
useMemo,
23+
useState,
24+
} from 'react';
2325
import { isResourceEditedAtom } from 'state/resourceEditedAtom';
2426
import { refreshExtenshionsAtom } from 'state/refreshExtenshionsAtom';
25-
import { useUploadResources } from 'resources/Namespaces/YamlUpload/useUploadResources';
26-
import { usePost } from 'shared/hooks/BackendAPI/usePost';
27+
import { PostFn, usePost } from 'shared/hooks/BackendAPI/usePost';
2728
import { CommunityModuleContext } from 'components/Modules/community/providers/CommunityModuleProvider';
2829
import CommunityModuleCard from 'components/Modules/community/components/CommunityModuleCard';
29-
30-
import { useNotification } from 'shared/contexts/NotificationContext';
30+
import {
31+
NotificationContextArgs,
32+
useNotification,
33+
} from 'shared/contexts/NotificationContext';
3134

3235
import 'components/Modules/KymaModulesAddModule.scss';
36+
import { useSingleGet } from 'shared/hooks/BackendAPI/useGet';
37+
import { CommunityModulesInstallationContext } from 'components/Modules/community/providers/CommunitModulesInstalationProvider';
38+
import { MutationFn, useUpdate } from 'shared/hooks/BackendAPI/useMutation';
39+
import { useAtomValue } from 'jotai/index';
40+
import { allNodesAtom } from 'state/navigation/allNodesAtom';
41+
import {
42+
CallbackFn,
43+
installCommunityModule,
44+
} from 'components/Modules/community/communityModulesInstallHelpers';
45+
import { UnsavedMessageBox } from 'shared/components/UnsavedMessageBox/UnsavedMessageBox';
46+
import { createPortal } from 'react-dom';
3347

3448
type VersionDisplayInfo = {
3549
moduleTemplate: {
@@ -49,14 +63,14 @@ type ModuleDisplayInfo = {
4963

5064
function onVersionChange(
5165
moduleTemplates: ModuleTemplateListType,
52-
moduleTemplatesToApply: Map<string, ModuleTemplateType>,
66+
moduleTemplatesToApply: { map: Map<string, ModuleTemplateType> },
5367
setModulesTemplatesToApply: (
54-
update: SetStateAction<Map<string, ModuleTemplateType>>,
68+
update: SetStateAction<{ map: Map<string, ModuleTemplateType> }>,
5569
) => void,
5670
setIsResourceEdited: (update: SetStateAction<any>) => void,
5771
): any {
5872
return (value: string, shouldRemove: boolean) => {
59-
const newModulesTemplatesToApply = new Map(moduleTemplatesToApply);
73+
const newModulesTemplatesToApply = new Map(moduleTemplatesToApply.map);
6074

6175
const [name, namespace] = value.split('|');
6276
const newModuleTemplateToApply = moduleTemplates.items.find(
@@ -88,7 +102,7 @@ function onVersionChange(
88102
});
89103
}
90104

91-
setModulesTemplatesToApply(newModulesTemplatesToApply);
105+
setModulesTemplatesToApply({ map: newModulesTemplatesToApply });
92106
};
93107
}
94108

@@ -113,39 +127,108 @@ function transformDataForDisplay(
113127
});
114128
}
115129

130+
async function upload(
131+
t: Function,
132+
communityModulesTemplatesToUpload: { map: Map<string, ModuleTemplateType> },
133+
setModulesTemplatesToUpload: React.Dispatch<
134+
SetStateAction<{ map: Map<string, ModuleTemplateType> }>
135+
>,
136+
clusterNodes: any,
137+
namespaceNodes: any,
138+
postRequest: PostFn,
139+
patchRequest: MutationFn,
140+
singleGet: Function,
141+
notification: NotificationContextArgs,
142+
callback: CallbackFn,
143+
) {
144+
if (communityModulesTemplatesToUpload.map.size === 0) {
145+
return;
146+
}
147+
148+
try {
149+
let errorOccurred = false;
150+
for (const module of communityModulesTemplatesToUpload.map.values()) {
151+
try {
152+
notification.notifySuccess({
153+
content: t('modules.community.messages.upload', {
154+
resourceType: getModuleName(module),
155+
}),
156+
});
157+
158+
await installCommunityModule(
159+
module,
160+
clusterNodes,
161+
namespaceNodes,
162+
postRequest,
163+
patchRequest,
164+
singleGet,
165+
callback,
166+
);
167+
} catch (e) {
168+
errorOccurred = true;
169+
notification.notifyError({
170+
content: t('modules.community.messages.install-failure', {
171+
resourceType: getModuleName(module),
172+
error: e instanceof Error && e?.message ? e.message : '',
173+
}),
174+
});
175+
}
176+
}
177+
setModulesTemplatesToUpload({ map: new Map() });
178+
if (!errorOccurred) {
179+
notification.notifySuccess({
180+
content: t('modules.community.messages.success', {
181+
resourceType: 'Community Module',
182+
}),
183+
});
184+
}
185+
} catch (e) {
186+
console.error(e);
187+
notification.notifyError({
188+
content: t('modules.community.messages.install-failure', {
189+
resourceType: 'Community Module',
190+
error: e instanceof Error && e?.message ? e.message : '',
191+
}),
192+
});
193+
}
194+
}
195+
116196
export default function CommunityModulesAddModule(props: any) {
117197
const { t } = useTranslation();
118198
const navigate = useNavigate();
119199
const { isEnabled: isCommunityModulesEnabled } =
120200
useFeature('COMMUNITY_MODULES');
121-
const notification = useNotification();
122-
const post = usePost();
123201
const setIsResourceEdited = useSetAtom(isResourceEditedAtom);
202+
124203
const [refreshExtenshionsCount, setRefreshExtenshions] = useAtom(
125204
refreshExtenshionsAtom,
126205
);
206+
const notification = useNotification();
207+
const postRequest = usePost();
208+
const patchRequest = useUpdate();
127209

128-
const [resourcesToApply, setResourcesToApply] = useState<{ value: any }[]>(
129-
[],
210+
const singleGet = useSingleGet();
211+
const clusterNodes = useAtomValue(allNodesAtom).filter(
212+
(node) => !node.namespaced,
130213
);
214+
const namespaceNodes = useAtomValue(allNodesAtom).filter(
215+
(node) => node.namespaced,
216+
);
217+
131218
const [layoutColumn, setLayoutColumn] = useAtom(columnLayoutAtom);
132219

133-
const uploadResources = useUploadResources(
134-
resourcesToApply,
135-
setResourcesToApply,
136-
() => {},
137-
DEFAULT_K8S_NAMESPACE,
138-
);
139220
const [
140221
communityModulesTemplatesToApply,
141222
setCommunityModulesTemplatesToApply,
142-
] = useState(new Map<string, ModuleTemplateType>());
223+
] = useState({ map: new Map<string, ModuleTemplateType>() });
143224

144225
const {
145226
notInstalledCommunityModuleTemplates,
146227
installedCommunityModulesLoading: notInstalledCommunityModulesLoading,
147228
} = useContext(CommunityModuleContext);
148229

230+
const { callback } = useContext(CommunityModulesInstallationContext);
231+
149232
const availableCommunityModules = useMemo(() => {
150233
if (!notInstalledCommunityModulesLoading) {
151234
return getAvailableCommunityModules(
@@ -160,14 +243,6 @@ export default function CommunityModulesAddModule(props: any) {
160243
notInstalledCommunityModulesLoading,
161244
]);
162245

163-
useEffect(() => {
164-
fetchResourcesToApply(
165-
communityModulesTemplatesToApply,
166-
setResourcesToApply,
167-
post,
168-
);
169-
}, [communityModulesTemplatesToApply]); // eslint-disable-line react-hooks/exhaustive-deps
170-
171246
const [columnsCount, setColumnsCount] = useState(2);
172247
const [cardsContainerRef, setCardsContainerRef] =
173248
useState<HTMLDivElement | null>(null);
@@ -214,7 +289,7 @@ export default function CommunityModulesAddModule(props: any) {
214289
);
215290

216291
const isChecked = (name: string) => {
217-
const sth = !!communityModulesTemplatesToApply.get(name);
292+
const sth = !!communityModulesTemplatesToApply.map.get(name);
218293
return sth;
219294
};
220295
const renderCards = () => {
@@ -232,7 +307,7 @@ export default function CommunityModulesAddModule(props: any) {
232307
setCommunityModulesTemplatesToApply,
233308
setIsResourceEdited,
234309
)}
235-
selectedModules={communityModulesTemplatesToApply}
310+
selectedModules={communityModulesTemplatesToApply.map}
236311
/>
237312
);
238313
columns[i % columnsCount].push(card);
@@ -257,33 +332,27 @@ export default function CommunityModulesAddModule(props: any) {
257332

258333
const handleSubmit = (e: any) => {
259334
e.preventDefault();
260-
try {
261-
uploadResources();
262-
263-
notification.notifySuccess({
264-
content: t('modules.community.messages.success', {
265-
resourceType: 'Community Module',
266-
}),
267-
});
268-
269-
setLayoutColumn({
270-
...layoutColumn,
271-
layout: 'OneColumn',
272-
midColumn: null,
273-
endColumn: null,
274-
showCreate: null,
275-
});
276-
navigate(window.location.pathname, { replace: true });
277-
setRefreshExtenshions(refreshExtenshionsCount + 1);
278-
} catch (e) {
279-
console.error(e);
280-
notification.notifyError({
281-
content: t('modules.community.messages.install-failure', {
282-
resourceType: 'Community Module',
283-
error: e instanceof Error && e?.message ? e.message : '',
284-
}),
285-
});
286-
}
335+
upload(
336+
t,
337+
communityModulesTemplatesToApply,
338+
setCommunityModulesTemplatesToApply,
339+
clusterNodes,
340+
namespaceNodes,
341+
postRequest,
342+
patchRequest,
343+
singleGet,
344+
notification,
345+
callback,
346+
);
347+
navigate(window.location.pathname, { replace: true });
348+
setRefreshExtenshions(refreshExtenshionsCount + 1);
349+
setLayoutColumn({
350+
...layoutColumn,
351+
layout: 'OneColumn',
352+
midColumn: null,
353+
endColumn: null,
354+
showCreate: null,
355+
});
287356
};
288357

289358
if (isCommunityModulesEnabled) {
@@ -317,7 +386,6 @@ export default function CommunityModulesAddModule(props: any) {
317386
)}
318387
</>
319388
</ResourceForm>
320-
321389
{createPortal(<UnsavedMessageBox />, document.body)}
322390
</>
323391
);

src/components/Modules/community/CommunityModulesList.tsx

Lines changed: 52 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import React, { useEffect } from 'react';
1+
import React, { useContext, useEffect, useState } from 'react';
22
import { useTranslation } from 'react-i18next';
33
import { Button } from '@ui5/webcomponents-react';
44
import pluralize from 'pluralize';
@@ -8,6 +8,7 @@ import {
88
findCrd,
99
findExtension,
1010
findModuleTemplate,
11+
getModuleName,
1112
ModuleTemplateListType,
1213
ModuleTemplateType,
1314
} from 'components/Modules/support';
@@ -29,6 +30,11 @@ import { GenericList } from 'shared/components/GenericList/GenericList';
2930
import { useNavigate } from 'react-router';
3031
import { useFetchModuleData } from 'components/Modules/hooks';
3132
import { ModulesListRows } from 'components/Modules/components/ModulesListRows';
33+
import {
34+
CommunityModulesInstallationContext,
35+
moduleInstallationState,
36+
} from 'components/Modules/community/providers/CommunitModulesInstalationProvider';
37+
import { State } from 'components/Modules/community/components/uploadStateAtom';
3238

3339
type CommunityModulesListProps = {
3440
moduleTemplates: ModuleTemplateListType;
@@ -44,6 +50,23 @@ type CommunityModulesListProps = {
4450
setSelectedEntry?: React.Dispatch<React.SetStateAction<any>>;
4551
};
4652

53+
// This function create fake module templates which is treated as installed to show progress of module upload
54+
function createFakeModuleTemplateWithStatus(
55+
moduleState: moduleInstallationState,
56+
) {
57+
return {
58+
name: getModuleName(moduleState.moduleTpl),
59+
namespace: moduleState.moduleTpl.metadata.namespace,
60+
moduleTemplateName: moduleState.moduleTpl.metadata.name,
61+
version: moduleState.moduleTpl.spec.version,
62+
fakeStatus: {
63+
type: moduleState.state,
64+
state: moduleState.state,
65+
message: moduleState.message,
66+
},
67+
};
68+
}
69+
4770
export const CommunityModulesList = ({
4871
moduleTemplates,
4972
selectedModules: installedModules,
@@ -89,6 +112,33 @@ export const CommunityModulesList = ({
89112
);
90113
const getScope = useGetScope();
91114

115+
const { modulesDuringUpload } = useContext(
116+
CommunityModulesInstallationContext,
117+
);
118+
119+
const [modulesToDisplay, setModulesToDisplay] =
120+
useState<any[]>(installedModules);
121+
122+
useEffect(() => {
123+
const modulesDuringProcessing = modulesDuringUpload.filter((m) => {
124+
return !installedModules.find((installedModule) => {
125+
return installedModule.moduleTemplateName === m.moduleTpl.metadata.name;
126+
});
127+
});
128+
129+
if (modulesDuringProcessing.length === 0) {
130+
setModulesToDisplay(installedModules);
131+
return;
132+
}
133+
134+
const moduleTemplatesDuringUpload = modulesDuringProcessing
135+
.filter((m) => m.state !== State.Finished)
136+
.map((m) => createFakeModuleTemplateWithStatus(m));
137+
setModulesToDisplay(
138+
[...installedModules].concat(moduleTemplatesDuringUpload),
139+
);
140+
}, [installedModules, modulesDuringUpload]);
141+
92142
const handleShowAddModule = () => {
93143
setLayoutColumn({
94144
startColumn: {
@@ -296,7 +346,7 @@ export const CommunityModulesList = ({
296346
customColumnLayout={customColumnLayout as any}
297347
enableColumnLayout
298348
hasDetailsView
299-
entries={installedModules as any}
349+
entries={modulesToDisplay as any}
300350
serverDataLoading={modulesLoading}
301351
headerRenderer={headerRenderer}
302352
rowRenderer={(resource) =>

0 commit comments

Comments
 (0)