Skip to content

Commit c90eb20

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 c90eb20

File tree

4 files changed

+338
-11
lines changed

4 files changed

+338
-11
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: 229 additions & 10 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: true,
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: true,
572+
});
573+
}
574+
setTagEnabled((_) => enabledArray);
575+
}, [devfileTags]);
576+
425577
React.useEffect(() => {
426578
props.setSelectedDevfile(selectedDevfile);
427579
}, [selectedDevfile]);
@@ -437,6 +589,14 @@ 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);
442602
}, [registryEnabled, searchText]);
@@ -445,13 +605,38 @@ export function DevfileSearch(props: DevfileSearchProps) {
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)) //
638+
.filter((devfile) => (deploySupport === devfile.supportsDeploy)) //
639+
.filter((devfile) => isToBeIncluded(devfile.tags, activeTags)) //
455640
.filter((devfile) => {
456641
const searchTerms = searchText.split(/\s+/);
457642
return every(
@@ -486,16 +671,50 @@ export function DevfileSearch(props: DevfileSearchProps) {
486671
<Stack direction="column" height="100%" spacing={3}>
487672
<Typography variant="h5">{props.titleText}</Typography>
488673
<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}>
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={{ flexGrow: '1', width: '100%' }} spacing={0}>
678+
<RegistriesPicker
679+
registryEnabled={registryEnabled}
680+
setRegistryEnabled={setRegistryEnabled}
681+
/>
682+
</Stack>
683+
<Stack direction="column" sx={{ flexGrow: '1', width: '100%' }} spacing={0} marginBottom={1}>
684+
<Divider orientation="horizontal" />
685+
</Stack>
686+
</>
687+
)}
688+
{devfileCapabilities.length > 1 && (
689+
<>
690+
<Stack direction="row" sx={{ flexGrow: '1', 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={{ flexGrow: '1', 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" sx={{ flexGrow: '1' }} 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}

src/webview/create-component/createComponentLoader.ts

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,8 @@ import { ExtensionID } from '../../util/constants';
2020
import { DevfileConverter } from '../../util/devfileConverter';
2121
import { selectWorkspaceFolder } from '../../util/workspace';
2222
import {
23+
getDevfileCapabilities,
24+
getDevfileTags,
2325
getDevfileRegistries,
2426
isValidProjectFolder,
2527
validateComponentName
@@ -128,6 +130,26 @@ export default class CreateComponentLoader {
128130
});
129131
break;
130132
}
133+
/**
134+
* The panel requested the list of devfile capabilities. Respond with this list.
135+
*/
136+
case 'getDevfileCapabilities': {
137+
void CreateComponentLoader.panel.webview.postMessage({
138+
action: 'devfileCapabilities',
139+
data: getDevfileCapabilities(),
140+
});
141+
break;
142+
}
143+
/**
144+
* The panel requested the list of devfile tags. Respond with this list.
145+
*/
146+
case 'getDevfileTags': {
147+
void CreateComponentLoader.panel.webview.postMessage({
148+
action: 'devfileTags',
149+
data: getDevfileTags(),
150+
});
151+
break;
152+
}
131153
/**
132154
* The panel requested the list of workspace folders. Respond with this list.
133155
*/
@@ -513,3 +535,23 @@ function sendUpdatedRegistries() {
513535
});
514536
}
515537
}
538+
539+
function sendUpdatedCapabilities() {
540+
if (CreateComponentLoader.panel) {
541+
let capabilities = getDevfileCapabilities();
542+
void CreateComponentLoader.panel.webview.postMessage({
543+
action: 'devfileCapabilities',
544+
data: capabilities,
545+
});
546+
}
547+
}
548+
549+
function sendUpdatedTags() {
550+
if (CreateComponentLoader.panel) {
551+
let tags = getDevfileTags();
552+
void CreateComponentLoader.panel.webview.postMessage({
553+
action: 'devfileTags',
554+
data: tags,
555+
});
556+
}
557+
}

0 commit comments

Comments
 (0)