Skip to content

Commit f63fbb0

Browse files
committed
Show add community modules form
1 parent bfee381 commit f63fbb0

File tree

2 files changed

+271
-1
lines changed

2 files changed

+271
-1
lines changed

public/i18n/en.yaml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -645,6 +645,8 @@ extensibility:
645645
table:
646646
error: The table widget configuration is incorrect. Path value must be an array type.
647647
modules:
648+
no-community-modules: No community modules available
649+
no-community-modules-installed: No community modules installed
648650
no-modules: No modules available
649651
no-modules-installed: No modules installed
650652
all-modules-added: You have added all the modules
Lines changed: 269 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,271 @@
1+
import { useCallback, useContext, useEffect, useState } from 'react';
2+
import { MessageStrip } from '@ui5/webcomponents-react';
3+
import { useTranslation } from 'react-i18next';
4+
import { ResourceForm } from 'shared/ResourceForm';
5+
import { Spinner } from 'shared/components/Spinner/Spinner';
6+
import ModulesCard from 'components/KymaModules/components/ModulesCard';
7+
// import { cloneDeep } from 'lodash';
8+
import { useModulesReleaseQuery } from './kymaModulesQueries';
9+
import { CommunityModuleContext } from './providers/CommunityModuleProvider';
10+
11+
import './KymaModulesAddModule.scss';
12+
import { ModuleTemplatesContext } from './providers/ModuleTemplatesProvider';
13+
114
export default function CommunityModulesAddModule(props) {
2-
return <div>Community modules</div>;
15+
const { t } = useTranslation();
16+
17+
const {
18+
installedCommunityModules,
19+
communityModulesLoading,
20+
setOpenedModuleIndex: setOpenedCommunityModuleIndex,
21+
} = useContext(CommunityModuleContext);
22+
const { communityModuleTemplates: moduleTemplates } = useContext(
23+
ModuleTemplatesContext,
24+
);
25+
26+
const [selectedModules, setSelectedModules] = useState([]);
27+
28+
const { data: moduleReleaseMetas } = useModulesReleaseQuery({});
29+
30+
const [columnsCount, setColumnsCount] = useState(2);
31+
const [cardsContainerRef, setCardsContainerRef] = useState(null);
32+
33+
const calculateColumns = useCallback(() => {
34+
if (cardsContainerRef?.clientWidth) {
35+
const containerWidth = cardsContainerRef?.clientWidth;
36+
const cardWidth = 350;
37+
const gap = 16;
38+
const colNumber = Math.max(
39+
1,
40+
Math.floor((containerWidth + gap) / (cardWidth + gap)),
41+
);
42+
return colNumber;
43+
}
44+
return 2;
45+
}, [cardsContainerRef]);
46+
47+
useEffect(() => {
48+
const resizeObserver = new ResizeObserver(() => {
49+
setColumnsCount(calculateColumns());
50+
});
51+
52+
if (cardsContainerRef) {
53+
resizeObserver.observe(cardsContainerRef);
54+
}
55+
56+
return () => {
57+
if (cardsContainerRef) {
58+
resizeObserver.unobserve(cardsContainerRef);
59+
}
60+
};
61+
}, [cardsContainerRef, calculateColumns]);
62+
63+
if (communityModulesLoading) {
64+
return (
65+
<div style={{ height: 'calc(100vh - 14rem)' }}>
66+
<Spinner />
67+
</div>
68+
);
69+
}
70+
71+
const modulesAddData = moduleTemplates?.items.reduce((acc, module) => {
72+
const name = module.metadata.labels['operator.kyma-project.io/module-name'];
73+
const existingModule = acc.find(item => {
74+
return item.metadata.name === name;
75+
});
76+
const isAlreadyInstalled = installedCommunityModules?.spec?.modules?.find(
77+
installedModule => installedModule.name === name,
78+
);
79+
80+
const moduleMetaRelase = moduleReleaseMetas?.items.find(
81+
item => item.spec.moduleName === name,
82+
);
83+
84+
if (module.spec.channel) {
85+
if (!existingModule && !isAlreadyInstalled) {
86+
acc.push({
87+
name: name,
88+
channels: [
89+
{
90+
channel: module.spec.channel,
91+
version: module.spec.descriptor.component.version,
92+
isBeta:
93+
module.metadata.labels['operator.kyma-project.io/beta'] ===
94+
'true',
95+
},
96+
],
97+
docsUrl:
98+
module.metadata.annotations['operator.kyma-project.io/doc-url'],
99+
icon: {
100+
link: module.spec?.info?.icons[0]?.link,
101+
name: module.spec?.info?.icons[0]?.name,
102+
},
103+
isMetaRelease: false,
104+
});
105+
} else if (existingModule) {
106+
existingModule.channels?.push({
107+
channel: module.spec.channel,
108+
version: module.spec.descriptor.component.version,
109+
isBeta:
110+
module.metadata.labels['operator.kyma-project.io/beta'] === 'true',
111+
isMetaRelease: false,
112+
});
113+
}
114+
} else {
115+
if (!existingModule && !isAlreadyInstalled) {
116+
moduleMetaRelase?.spec.channels.forEach(channel => {
117+
if (!acc.find(item => item.name === name)) {
118+
acc.push({
119+
name: name,
120+
channels: [
121+
{
122+
channel: channel.channel,
123+
version: channel.version,
124+
isBeta: moduleMetaRelase.spec.beta ?? false,
125+
isMetaRelease: true,
126+
},
127+
],
128+
docsUrl: module.spec.info.documentation,
129+
icon: {
130+
link: module.spec?.info?.icons[0]?.link,
131+
name: module.spec?.info?.icons[0]?.name,
132+
},
133+
});
134+
} else {
135+
acc
136+
.find(item => item.name === name)
137+
.channels.push({
138+
channel: channel.channel,
139+
version: channel.version,
140+
isBeta: moduleMetaRelase.spec.beta ?? false,
141+
isMetaRelease: true,
142+
});
143+
}
144+
});
145+
}
146+
}
147+
148+
return acc ?? [];
149+
}, []);
150+
151+
const isChecked = name => {
152+
return !!selectedModules?.find(module => module.name === name);
153+
};
154+
155+
const setCheckbox = (module, checked, index) => {
156+
const newSelectedModules = [...selectedModules];
157+
if (checked) {
158+
newSelectedModules.push({
159+
name: module.name,
160+
});
161+
} else {
162+
newSelectedModules.splice(index, 1);
163+
}
164+
setSelectedModules(newSelectedModules);
165+
};
166+
167+
const checkIfSelectedModuleIsBeta = moduleName => {
168+
return selectedModules.some(({ name, channel }) => {
169+
if (moduleName && name !== moduleName) {
170+
return false;
171+
}
172+
const moduleData = modulesAddData?.find(module => module.name === name);
173+
174+
return moduleData
175+
? moduleData.channels.some(
176+
({ channel: ch, isBeta }) => ch === channel && isBeta,
177+
)
178+
: false;
179+
});
180+
};
181+
182+
const checkIfStatusModuleIsBeta = moduleName => {
183+
return modulesAddData
184+
?.find(mod => mod.name === moduleName)
185+
?.channels.some(({ channel: ch, isBeta }) => ch && isBeta);
186+
};
187+
188+
const renderCards = () => {
189+
const columns = Array.from({ length: columnsCount }, () => []);
190+
191+
modulesAddData?.forEach((module, i) => {
192+
const index = selectedModules?.findIndex(kymaResourceModule => {
193+
return kymaResourceModule.name === module?.name;
194+
});
195+
196+
const card = (
197+
<ModulesCard
198+
module={module}
199+
index={index}
200+
key={module.name}
201+
isChecked={isChecked}
202+
setCheckbox={setCheckbox}
203+
selectedModules={selectedModules}
204+
setSelectedModules={setSelectedModules}
205+
checkIfStatusModuleIsBeta={checkIfStatusModuleIsBeta}
206+
/>
207+
);
208+
columns[i % columnsCount].push(card);
209+
});
210+
211+
return (
212+
<div
213+
className="gridbox-addModule sap-margin-top-small"
214+
ref={setCardsContainerRef}
215+
>
216+
{columns.map((column, columnIndex) => (
217+
<div
218+
className={`gridbox-addModule-column column-${columnIndex}`}
219+
key={columnIndex}
220+
>
221+
{column}
222+
</div>
223+
))}
224+
</div>
225+
);
226+
};
227+
228+
return (
229+
<ResourceForm
230+
{...props}
231+
disableDefaultFields
232+
formElementRef={props.formElementRef}
233+
onChange={props.onChange}
234+
layoutNumber="startColumn"
235+
resetLayout
236+
afterCreatedCustomMessage={t('kyma-modules.module-added')}
237+
formWithoutPanel
238+
className="add-modules-form"
239+
// onSubmit={ handleSubmit}
240+
onSubmit={newData => {
241+
console.log('handling Install Community module', selectedModules);
242+
}}
243+
>
244+
<>
245+
{modulesAddData?.length !== 0 ? (
246+
<>
247+
{checkIfSelectedModuleIsBeta() ? (
248+
<MessageStrip
249+
key={'beta'}
250+
design="Critical"
251+
hideCloseButton
252+
className="sap-margin-top-small"
253+
>
254+
{t('kyma-modules.beta-alert')}
255+
</MessageStrip>
256+
) : null}
257+
{renderCards()}
258+
</>
259+
) : (
260+
<MessageStrip
261+
design="Critical"
262+
hideCloseButton
263+
className="sap-margin-top-small"
264+
>
265+
{t('extensibility.widgets.modules.no-community-modules')}
266+
</MessageStrip>
267+
)}
268+
</>
269+
</ResourceForm>
270+
);
3271
}

0 commit comments

Comments
 (0)