Skip to content

Commit 9a17217

Browse files
committed
Add language options from providers & select by app tags
Move to simple multi select menu in set targets step Drive target selection options from targets list Add kind support for tasks Address missing checkbox on deselect Add provider to target card header Signed-off-by: Ian Bolton <[email protected]>
1 parent 460fded commit 9a17217

File tree

9 files changed

+226
-37
lines changed

9 files changed

+226
-37
lines changed

client/src/app/api/models.ts

+5-3
Original file line numberDiff line numberDiff line change
@@ -310,7 +310,8 @@ export interface Task {
310310
createTime?: string;
311311
application: { id: number };
312312
name: string;
313-
addon: string;
313+
kind?: string;
314+
addon?: string;
314315
data: TaskData;
315316
error?: string;
316317
image?: string;
@@ -377,7 +378,8 @@ export interface TaskgroupTask {
377378
export interface Taskgroup {
378379
id?: number;
379380
name: string;
380-
addon: string;
381+
kind?: string;
382+
addon?: string;
381383
data: TaskData;
382384
tasks: TaskgroupTask[];
383385
}
@@ -425,7 +427,7 @@ export interface Target {
425427
labels?: TargetLabel[];
426428
image?: RulesetImage;
427429
ruleset: Ruleset;
428-
provider?: string;
430+
provider?: string[];
429431
}
430432

431433
export interface Metadata {
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,133 @@
1+
import React from "react";
2+
import {
3+
Select,
4+
SelectOption,
5+
SelectList,
6+
MenuToggle,
7+
Badge,
8+
SelectOptionProps,
9+
MenuToggleElement,
10+
} from "@patternfly/react-core";
11+
import spacing from "@patternfly/react-styles/css/utilities/Spacing/spacing";
12+
13+
export interface ISimpleSelectBasicProps {
14+
onChange: (selection: string | string[]) => void;
15+
options: SelectOptionProps[];
16+
value?: string[];
17+
placeholderText?: string;
18+
id?: string;
19+
toggleId?: string;
20+
toggleAriaLabel?: string;
21+
selectMultiple?: boolean;
22+
width?: number;
23+
noResultsFoundText?: string;
24+
hideClearButton?: false;
25+
}
26+
27+
export const SimpleSelectCheckbox: React.FC<ISimpleSelectBasicProps> = ({
28+
onChange,
29+
options,
30+
value,
31+
placeholderText = "Select...",
32+
id,
33+
toggleId,
34+
toggleAriaLabel,
35+
width,
36+
}) => {
37+
const [isOpen, setIsOpen] = React.useState(false);
38+
const [selectedItems, setSelectedItems] = React.useState<string[]>([]);
39+
const [selectOptions, setSelectOptions] = React.useState<SelectOptionProps[]>(
40+
[
41+
{ value: "select-all", label: "Select All", children: "Select All" },
42+
...options,
43+
]
44+
);
45+
46+
React.useEffect(() => {
47+
setSelectedItems(value || []);
48+
}, [value]);
49+
50+
React.useEffect(() => {
51+
const updatedOptions = [
52+
{ value: "select-all", label: "Select All", children: "Select All" },
53+
...options,
54+
];
55+
setSelectOptions(updatedOptions);
56+
}, [options]);
57+
58+
const onToggleClick = () => {
59+
setIsOpen(!isOpen);
60+
};
61+
62+
const onSelect = (
63+
_event: React.MouseEvent<Element, MouseEvent> | undefined,
64+
selectionValue: string | number | undefined
65+
) => {
66+
const value = selectionValue as string;
67+
if (value === "select-all") {
68+
if (selectedItems.length === options.length) {
69+
setSelectedItems([]);
70+
onChange([]);
71+
} else {
72+
const allItemValues = options.map((option) => option.value as string);
73+
setSelectedItems(allItemValues);
74+
onChange(allItemValues);
75+
}
76+
} else {
77+
if (selectedItems.includes(value)) {
78+
const newSelections = selectedItems.filter((item) => item !== value);
79+
setSelectedItems(newSelections);
80+
onChange(newSelections);
81+
} else {
82+
const newSelections = [...selectedItems, value];
83+
setSelectedItems(newSelections);
84+
onChange(newSelections);
85+
}
86+
}
87+
};
88+
89+
return (
90+
<Select
91+
role="menu"
92+
id={id}
93+
isOpen={isOpen}
94+
selected={selectedItems}
95+
onSelect={onSelect}
96+
onOpenChange={setIsOpen}
97+
toggle={(toggleref: React.Ref<MenuToggleElement>) => (
98+
<MenuToggle
99+
ref={toggleref}
100+
onClick={onToggleClick}
101+
style={{ width: width && width + "px" }}
102+
isExpanded={isOpen}
103+
id={toggleId}
104+
>
105+
<span className={spacing.mrSm}>{placeholderText}</span>
106+
{selectedItems.length > 0 && (
107+
<Badge isRead>{selectedItems.length}</Badge>
108+
)}
109+
</MenuToggle>
110+
)}
111+
aria-label={toggleAriaLabel}
112+
>
113+
<SelectList>
114+
{selectOptions.map((option, index) => (
115+
<SelectOption
116+
hasCheckbox
117+
key={option.value}
118+
isFocused={index === 0}
119+
onClick={() => onSelect(undefined, option.value)}
120+
isSelected={
121+
option.value === "select-all"
122+
? selectedItems.length === options.length
123+
: selectedItems.includes(option.value as string)
124+
}
125+
{...option}
126+
>
127+
{option.children || option.value}
128+
</SelectOption>
129+
))}
130+
</SelectList>
131+
</Select>
132+
);
133+
};

client/src/app/components/target-card/target-card.tsx

+13-3
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ import {
2525
SelectVariant,
2626
SelectOptionObject,
2727
} from "@patternfly/react-core/deprecated";
28-
import { GripVerticalIcon } from "@patternfly/react-icons";
28+
import { GripVerticalIcon, InfoCircleIcon } from "@patternfly/react-icons";
2929
import spacing from "@patternfly/react-styles/css/utilities/Spacing/spacing";
3030
import { useTranslation } from "react-i18next";
3131

@@ -86,6 +86,7 @@ export const TargetCard: React.FC<TargetCardProps> = ({
8686

8787
const handleCardClick = (event: React.MouseEvent) => {
8888
// Stop 'select' event propagation
89+
event.preventDefault();
8990
const eventTarget: any = event.target;
9091
if (eventTarget.type === "button") return;
9192

@@ -110,16 +111,25 @@ export const TargetCard: React.FC<TargetCardProps> = ({
110111
return (
111112
<Card
112113
onClick={handleCardClick}
113-
isSelectable={!!cardSelected}
114+
isSelectable
114115
isSelected={isCardSelected}
115116
className="pf-v5-l-stack pf-v5-l-stack__item pf-m-fill"
116117
>
117118
<CardHeader
118119
selectableActions={{
119120
selectableActionId: "" + target.id,
121+
selectableActionAriaLabelledby: `${target.name}-selectable-action-label`,
120122
isChecked: isCardSelected,
121123
}}
122-
/>
124+
>
125+
<Label
126+
id={`${target.provider}-selectable-action-label`}
127+
variant="outline"
128+
icon={<InfoCircleIcon />}
129+
>
130+
{target.provider}
131+
</Label>
132+
</CardHeader>
123133
<CardBody>
124134
<Flex>
125135
<FlexItem>

client/src/app/pages/applications/analysis-wizard/analysis-wizard.tsx

+2-2
Original file line numberDiff line numberDiff line change
@@ -72,7 +72,7 @@ const defaultTaskData: TaskData = {
7272

7373
export const defaultTaskgroup: Taskgroup = {
7474
name: `taskgroup.analyzer`,
75-
addon: "analyzer",
75+
kind: "analyzer",
7676
data: {
7777
...defaultTaskData,
7878
},
@@ -359,7 +359,7 @@ export const AnalysisWizard: React.FC<IAnalysisWizard> = ({
359359
isDisabled={!isStepEnabled(StepId.SetTargets)}
360360
footer={{ isNextDisabled: !isStepEnabled(StepId.SetTargets + 1) }}
361361
>
362-
<SetTargets />
362+
<SetTargets applications={applications} />
363363
</WizardStep>,
364364
<WizardStep
365365
key={StepId.Scope}

client/src/app/pages/applications/analysis-wizard/set-targets.tsx

+56-25
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import {
77
GalleryItem,
88
Form,
99
Alert,
10+
SelectOptionProps,
1011
} from "@patternfly/react-core";
1112
import { useTranslation } from "react-i18next";
1213
import { useFormContext } from "react-hook-form";
@@ -15,24 +16,53 @@ import { TargetCard } from "@app/components/target-card/target-card";
1516
import { AnalysisWizardFormValues } from "./schema";
1617
import { useSetting } from "@app/queries/settings";
1718
import { useFetchTargets } from "@app/queries/targets";
18-
import { Target } from "@app/api/models";
19-
import { SimpleSelectTypeahead } from "@app/components/SimpleSelectTypeahead";
20-
21-
export const SetTargets: React.FC = () => {
19+
import { Application, TagCategory, Target } from "@app/api/models";
20+
import { useFetchTagCategories } from "@app/queries/tags";
21+
import { SimpleSelectCheckbox } from "@app/components/SimpleSelectCheckbox";
22+
interface SetTargetsProps {
23+
applications: Application[];
24+
}
25+
26+
export const SetTargets: React.FC<SetTargetsProps> = ({ applications }) => {
2227
const { t } = useTranslation();
2328

2429
const { targets } = useFetchTargets();
2530

26-
const [provider, setProvider] = useState("Java");
27-
2831
const targetOrderSetting = useSetting("ui.target.order");
2932

3033
const { watch, setValue, getValues } =
3134
useFormContext<AnalysisWizardFormValues>();
35+
3236
const values = getValues();
3337
const formLabels = watch("formLabels");
3438
const selectedTargets = watch("selectedTargets");
3539

40+
const { tagCategories, isFetching, fetchError } = useFetchTagCategories();
41+
42+
const findCategoryForTag = (tagId: number) => {
43+
return tagCategories.find(
44+
(category: TagCategory) =>
45+
category.tags?.some((categoryTag) => categoryTag.id === tagId)
46+
);
47+
};
48+
49+
const initialProviders = Array.from(
50+
new Set(
51+
applications
52+
.flatMap((app) => app.tags || [])
53+
.map((tag) => {
54+
return {
55+
category: findCategoryForTag(tag.id),
56+
tag,
57+
};
58+
})
59+
.filter((tagWithCat) => tagWithCat?.category?.name === "Language")
60+
.map((tagWithCat) => tagWithCat.tag.name)
61+
)
62+
).filter((name) => name !== undefined);
63+
64+
const [provider, setProvider] = useState(initialProviders);
65+
3666
const handleOnSelectedCardTargetChange = (selectedLabelName: string) => {
3767
const otherSelectedLabels = formLabels?.filter((formLabel) => {
3868
return formLabel.name !== selectedLabelName;
@@ -124,6 +154,10 @@ export const SetTargets: React.FC = () => {
124154
}
125155
};
126156

157+
const allProviders = targets.flatMap((target) => target.provider);
158+
159+
const languageOptions = Array.from(new Set(allProviders));
160+
127161
return (
128162
<Form
129163
onSubmit={(event) => {
@@ -136,26 +170,21 @@ export const SetTargets: React.FC = () => {
136170
</Title>
137171
<Text>{t("wizard.label.setTargets")}</Text>
138172
</TextContent>
139-
<SimpleSelectTypeahead
140-
width={200}
173+
<SimpleSelectCheckbox
174+
placeholderText="Select a language..."
175+
width={300}
141176
value={provider}
142-
toggleAriaLabel="Action select dropdown toggle"
143-
toggleId="action-select-toggle"
144-
hideClearButton
145-
id="action-select"
146-
options={[
147-
{
148-
value: "Java",
149-
children: "Java",
150-
},
151-
{
152-
value: "Go",
153-
children: "Go",
154-
},
155-
]}
177+
options={languageOptions?.map((language): SelectOptionProps => {
178+
return {
179+
children: <div>{language}</div>,
180+
181+
value: language,
182+
};
183+
})}
156184
onChange={(selection) => {
157-
setProvider(selection as string);
185+
setProvider(selection as string[]);
158186
}}
187+
toggleId="language-select-toggle"
159188
/>
160189
{values.selectedTargets.length === 0 &&
161190
values.customRulesFiles.length === 0 &&
@@ -172,8 +201,10 @@ export const SetTargets: React.FC = () => {
172201
const matchingTarget = targets.find((target) => target.id === id);
173202

174203
const isSelected = selectedTargets?.includes(id);
175-
176-
if (matchingTarget && matchingTarget.provider === provider) {
204+
if (
205+
matchingTarget &&
206+
provider?.some((p) => matchingTarget?.provider?.includes(p))
207+
) {
177208
return (
178209
<GalleryItem key={index}>
179210
<TargetCard

client/src/app/pages/applications/applications-table/applications-table.tsx

+1-1
Original file line numberDiff line numberDiff line change
@@ -148,7 +148,7 @@ export const ApplicationsTable: React.FC = () => {
148148
tasks.find((task: Task) => task.application?.id === application.id);
149149

150150
const { tasks, hasActiveTasks } = useFetchTasks(
151-
{ addon: "analyzer" },
151+
{ kind: "analyzer", addon: "analyzer" },
152152
isAnalyzeModalOpen
153153
);
154154

client/src/app/pages/migration-targets/components/custom-target-form.tsx

+1-1
Original file line numberDiff line numberDiff line change
@@ -307,7 +307,7 @@ export const CustomTargetForm: React.FC<CustomTargetFormProps> = ({
307307
},
308308
}),
309309
},
310-
provider: providerType || "Java",
310+
provider: [providerType] || ["Java"],
311311
};
312312

313313
if (target) {

client/src/app/pages/migration-targets/migration-targets.tsx

+4-1
Original file line numberDiff line numberDiff line change
@@ -235,7 +235,10 @@ export const MigrationTargets: React.FC = () => {
235235
const matchingTarget = targets.find(
236236
(target) => target.id === id
237237
);
238-
if (matchingTarget && matchingTarget.provider === provider) {
238+
if (
239+
matchingTarget &&
240+
matchingTarget.provider?.includes(provider)
241+
) {
239242
return (
240243
<SortableItem
241244
key={id}

0 commit comments

Comments
 (0)