Skip to content

Commit 3fc41ac

Browse files
committed
In devfile search view, add an option to filter by debug support and deploy support redhat-developer#3086
Fixes: redhat-developer#3086 Signed-off-by: Victor Rubezhny <[email protected]>
1 parent 8c22ddb commit 3fc41ac

File tree

4 files changed

+350
-13
lines changed

4 files changed

+350
-13
lines changed

src/webview/common-ext/createComponentHelpers.ts

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -169,3 +169,36 @@ export function getDevfileRegistries(): DevfileRegistry[] {
169169
devfileRegistries.sort((a, b) => (a.name < b.name ? -1 : 1));
170170
return devfileRegistries;
171171
}
172+
173+
/**
174+
* Returns a list of possible the devfile capabilities.
175+
*
176+
* Currently the capabilities are predefined and include:
177+
* - Debug Support
178+
* - Deploy Support
179+
*
180+
* @returns a list of the devfile capabilities
181+
*/
182+
export function getDevfileCapabilities(): string[] {
183+
return [ 'Debug Support', 'Deploy Support'];
184+
}
185+
186+
/**
187+
* Returns a list of the devfile tags found in devfiles registries.
188+
*
189+
* @returns a list of the devfile tags
190+
*/
191+
export function getDevfileTags(url?:string ): string[] {
192+
const devfileRegistries = getDevfileRegistries();
193+
194+
const devfileTags: string[] = [
195+
...new Set(
196+
devfileRegistries
197+
.filter((devfileRegistry) => url ? devfileRegistry.url === url : true)
198+
.flatMap((_devfileRegistry) => _devfileRegistry.devfiles)
199+
.flatMap((_devfile) => _devfile.tags))
200+
]
201+
.sort((a, b) => a.localeCompare(b));
202+
203+
return devfileTags;
204+
}

src/webview/common/devfileSearch.tsx

Lines changed: 231 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -165,6 +165,103 @@ function RegistriesPicker(props: {
165165
);
166166
}
167167

168+
function RegistryCapabilitiesPicker(props: {
169+
capabilityEnabled: { capabilityName: string; enabled: boolean }[];
170+
setCapabilityEnabled: React.Dispatch<
171+
React.SetStateAction<{ capabilityName: string; enabled: boolean }[]>
172+
>;
173+
}) {
174+
function onCheckboxClick(clickedCapability: string, checked: boolean) {
175+
const updatedList = [...props.capabilityEnabled] //
176+
.filter((entry) => entry.capabilityName !== clickedCapability);
177+
updatedList.push({
178+
capabilityName: clickedCapability,
179+
enabled: checked,
180+
});
181+
const filteredUpdatedList = updatedList
182+
.sort((capA, capB) => {
183+
return capA.capabilityName.localeCompare(capB.capabilityName)
184+
});
185+
props.setCapabilityEnabled([...filteredUpdatedList]);
186+
}
187+
188+
return (
189+
<Stack direction="column" spacing={1} marginY={2}>
190+
<Typography variant="body2" marginBottom={1}>
191+
Filter by Capabilities
192+
</Typography>
193+
<FormGroup>
194+
{props.capabilityEnabled.map((_cap) => {
195+
return (
196+
<FormControlLabel
197+
control={
198+
<Checkbox
199+
checked={_cap.enabled}
200+
onChange={(_e, checked) =>
201+
onCheckboxClick(_cap.capabilityName, checked)
202+
}
203+
/>
204+
}
205+
label={_cap.capabilityName}
206+
key={_cap.capabilityName}
207+
/>
208+
);
209+
})}
210+
</FormGroup>
211+
</Stack>
212+
);
213+
}
214+
215+
function RegistryTagsPicker(props: {
216+
tagEnabled: { tagName: string; enabled: boolean }[];
217+
setTagEnabled: React.Dispatch<
218+
React.SetStateAction<{ tagName: string; enabled: boolean }[]>
219+
>;
220+
}) {
221+
function onCheckboxClick(clickedTag: string, checked: boolean) {
222+
const prevVal = props.tagEnabled.find(
223+
(entry) => entry.tagName === clickedTag,
224+
);
225+
const updatedList = [...props.tagEnabled] //
226+
.filter((entry) => entry.tagName !== clickedTag);
227+
updatedList.push({
228+
tagName: clickedTag,
229+
enabled: checked,
230+
});
231+
const filteredUpdatedList = updatedList
232+
.sort((tagA, tagB) => {
233+
return tagA.tagName.localeCompare(tagB.tagName)
234+
});
235+
props.setTagEnabled([...filteredUpdatedList]);
236+
}
237+
238+
return (
239+
<Stack direction="column" spacing={1} marginY={2}>
240+
<Typography variant="body2" marginBottom={1}>
241+
Filter by Tags
242+
</Typography>
243+
<FormGroup>
244+
{props.tagEnabled.map((tag) => {
245+
return (
246+
<FormControlLabel
247+
control={
248+
<Checkbox
249+
checked={tag.enabled}
250+
onChange={(_e, checked) =>
251+
onCheckboxClick(tag.tagName, checked)
252+
}
253+
/>
254+
}
255+
label={tag.tagName}
256+
key={tag.tagName}
257+
/>
258+
);
259+
})}
260+
</FormGroup>
261+
</Stack>
262+
);
263+
}
264+
168265
const SelectTemplateProject = React.forwardRef(
169266
(
170267
props: {
@@ -389,6 +486,22 @@ export type DevfileSearchProps = {
389486
goBack?: () => void;
390487
};
391488

489+
/**
490+
* Calculates if specified devfile is to be included into search results
491+
* based on devfile tags and tags filter. A devfile is to be included if:
492+
* - it contains any of selected tags
493+
* - always if there is no any selected tags
494+
*
495+
* @param tags1
496+
* @param tags2
497+
* @returns
498+
*/
499+
function isToBeIncluded(devfileTags: string[], selectedTags: string[]): boolean {
500+
return selectedTags.length == 0 || devfileTags.filter((_devfileTag) => {
501+
return selectedTags.find((_selectedTags) => _devfileTag === _selectedTags) !== undefined;
502+
}).length > 0;
503+
}
504+
392505
export function DevfileSearch(props: DevfileSearchProps) {
393506
const ITEMS_PER_PAGE = 6;
394507
const QUARKUS_REGEX = /[Qq]uarkus/;
@@ -399,13 +512,30 @@ export function DevfileSearch(props: DevfileSearchProps) {
399512
const [registryEnabled, setRegistryEnabled] = React.useState<
400513
{ registryName: string; registryUrl: string; enabled: boolean }[]
401514
>([]);
515+
const [devfileCapabilities, setDevfileCapabilities] = React.useState<string[]>([]);
516+
const [capabilityEnabled, setCapabilityEnabled] = React.useState<
517+
{ capabilityName: string; enabled: boolean }[]
518+
>([]);
519+
const [devfileTags, setDevfileTags] = React.useState<string[]>([]);
520+
const [tagEnabled, setTagEnabled] = React.useState<
521+
{ tagName: string; enabled: boolean }[]
522+
>([]);
402523
const [searchText, setSearchText] = React.useState('');
403524

404525
function respondToMessage(messageEvent: MessageEvent) {
405526
const message = messageEvent.data as Message;
406527
switch (message.action) {
407528
case 'devfileRegistries': {
408529
setDevfileRegistries((_devfileRegistries) => message.data);
530+
break;
531+
}
532+
case 'devfileCapabilities': {
533+
setDevfileCapabilities((_devfileCapabilities) => message.data);
534+
break;
535+
}
536+
case 'devfileTags': {
537+
setDevfileTags((_devfileTags) => message.data);
538+
break;
409539
}
410540
}
411541
}
@@ -422,6 +552,28 @@ export function DevfileSearch(props: DevfileSearchProps) {
422552
setRegistryEnabled((_) => enabledArray);
423553
}, [devfileRegistries]);
424554

555+
React.useEffect(() => {
556+
const enabledArray = [];
557+
for (let capability of devfileCapabilities) {
558+
enabledArray.push({
559+
capabilityName: capability,
560+
enabled: false, // All values set to false means that no filter is to be applied
561+
});
562+
}
563+
setCapabilityEnabled((_) => enabledArray);
564+
}, [devfileCapabilities]);
565+
566+
React.useEffect(() => {
567+
const enabledArray = [];
568+
for (let tag of devfileTags) {
569+
enabledArray.push({
570+
tagName: tag,
571+
enabled: false, // All values set to false means that no filter is to be applied
572+
});
573+
}
574+
setTagEnabled((_) => enabledArray);
575+
}, [devfileTags]);
576+
425577
React.useEffect(() => {
426578
props.setSelectedDevfile(selectedDevfile);
427579
}, [selectedDevfile]);
@@ -437,21 +589,54 @@ export function DevfileSearch(props: DevfileSearchProps) {
437589
window.vscodeApi.postMessage({ action: 'getDevfileRegistries' });
438590
}, []);
439591

592+
React.useEffect(() => {
593+
window.vscodeApi.postMessage({ action: 'getDevfileCapabilities' });
594+
}, []);
595+
596+
React.useEffect(() => {
597+
window.vscodeApi.postMessage({ action: 'getDevfileTags' });
598+
}, []);
599+
440600
React.useEffect(() => {
441601
setCurrentPage((_) => 1);
442-
}, [registryEnabled, searchText]);
602+
}, [registryEnabled, capabilityEnabled, tagEnabled, searchText]);
443603

444604
if (!devfileRegistries) {
445605
return <LoadScreen title="Retrieving list of Devfiles" />;
446606
}
447607

608+
if(!devfileCapabilities) {
609+
return <LoadScreen title="Retrieving list of Devfile Capabilities" />;
610+
}
611+
612+
if(!devfileTags) {
613+
return <LoadScreen title="Retrieving list of Devfile Tags" />;
614+
}
615+
448616
const activeRegistries = registryEnabled //
449617
.filter((entry) => entry.enabled) //
450618
.map((entry) => entry.registryName);
451619

620+
const debugSupport = capabilityEnabled //
621+
.filter((_cap) => _cap.capabilityName === 'Debug Support') //
622+
.filter((_cap) => _cap.enabled) //
623+
.length > 0;
624+
625+
const deploySupport = capabilityEnabled //
626+
.filter((_cap) => _cap.capabilityName === 'Deploy Support') //
627+
.filter((_cap) => _cap.enabled) //
628+
.length > 0;
629+
630+
const activeTags = tagEnabled
631+
.filter((_tag) => _tag.enabled) //
632+
.map((_tag) => _tag.tagName);
633+
452634
const devfiles: Devfile[] = devfileRegistries //
453635
.filter((devfileRegistry) => activeRegistries.includes(devfileRegistry.name)) //
454636
.flatMap((devfileRegistry) => devfileRegistry.devfiles) //
637+
.filter((devfile) => debugSupport ? devfile.supportsDebug : true) //
638+
.filter((devfile) => deploySupport ? devfile.supportsDeploy : true) //
639+
.filter((devfile) => isToBeIncluded(devfile.tags, activeTags)) //
455640
.filter((devfile) => {
456641
const searchTerms = searchText.split(/\s+/);
457642
return every(
@@ -485,17 +670,51 @@ export function DevfileSearch(props: DevfileSearchProps) {
485670
<>
486671
<Stack direction="column" height="100%" spacing={3}>
487672
<Typography variant="h5">{props.titleText}</Typography>
488-
<Stack direction="row" flexGrow="1" spacing={2}>
489-
{devfileRegistries.length > 1 && (
490-
<>
491-
<RegistriesPicker
492-
registryEnabled={registryEnabled}
493-
setRegistryEnabled={setRegistryEnabled}
494-
/>
495-
<Divider orientation="vertical" />
496-
</>
497-
)}
498-
<Stack direction="column" sx={{ flexGrow: '1', height: '100%' }} spacing={3}>
673+
<Stack direction="row" spacing={2}>
674+
<Stack direction="column" sx={{ height: 'calc(100vh - 200px - 5em)', overflow: 'scroll', minWidth:'20%' }} spacing={0}>
675+
{devfileRegistries.length > 1 && (
676+
<>
677+
<Stack direction="row" sx={{width: '100%' }} spacing={0}>
678+
<RegistriesPicker
679+
registryEnabled={registryEnabled}
680+
setRegistryEnabled={setRegistryEnabled}
681+
/>
682+
</Stack>
683+
<Stack direction="column" sx={{ width: '100%' }} spacing={0} marginBottom={1}>
684+
<Divider orientation="horizontal" />
685+
</Stack>
686+
</>
687+
)}
688+
{devfileCapabilities.length > 1 && (
689+
<>
690+
<Stack direction="row" sx={{ width: '100%' }} spacing={0}>
691+
<RegistryCapabilitiesPicker
692+
capabilityEnabled={capabilityEnabled}
693+
setCapabilityEnabled={setCapabilityEnabled}
694+
/>
695+
</Stack>
696+
<Stack direction="column" sx={{ flexGrow: '1', width: '100%' }} spacing={0} marginBottom={1}>
697+
<Divider orientation="horizontal" />
698+
</Stack>
699+
</>
700+
)}
701+
{/* disabled */ false && devfileTags.length > 1 && (
702+
<>
703+
<Stack direction="row" sx={{ width: '100%' }} spacing={0}>
704+
<RegistryTagsPicker
705+
tagEnabled={tagEnabled}
706+
setTagEnabled={setTagEnabled}
707+
/>
708+
</Stack>
709+
</>
710+
)}
711+
<Stack direction="column" sx={{ flexGrow: '1', height: '100%', width: '100%' }} spacing={0}>
712+
</Stack>
713+
</Stack>
714+
<Stack direction="column" spacing={3}>
715+
<Divider orientation="vertical" sx={{ height: 'calc(100vh - 200px - 5em)'}} />
716+
</Stack>
717+
<Stack direction="column" sx={{ flexGrow: '1' }} spacing={3}>
499718
<SearchBar
500719
searchText={searchText}
501720
setSearchText={setSearchText}

0 commit comments

Comments
 (0)