Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

✨ [AnalysisWizard] Language discovery changes #1951

Merged
merged 4 commits into from
Jun 20, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
121 changes: 121 additions & 0 deletions client/src/app/components/SimpleSelectCheckbox.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
import React from "react";
import {
Select,
SelectOption,
SelectList,
MenuToggle,
Badge,
SelectOptionProps,
MenuToggleElement,
} from "@patternfly/react-core";
import spacing from "@patternfly/react-styles/css/utilities/Spacing/spacing";

export interface ISimpleSelectBasicProps {
onChange: (selection: string | string[]) => void;
options: SelectOptionProps[];
value?: string[];
placeholderText?: string;
id?: string;
toggleId?: string;
toggleAriaLabel?: string;
selectMultiple?: boolean;
width?: number;
noResultsFoundText?: string;
hideClearButton?: false;
}

export const SimpleSelectCheckbox: React.FC<ISimpleSelectBasicProps> = ({
onChange,
options,
value,
placeholderText = "Select...",
id,
toggleId,
toggleAriaLabel,
width,
}) => {
const [isOpen, setIsOpen] = React.useState(false);
const [selectOptions, setSelectOptions] = React.useState<SelectOptionProps[]>(
[{ value: "show-all", label: "Show All", children: "Show All" }, ...options]
);

React.useEffect(() => {
const updatedOptions = [
{ value: "show-all", label: "Show All", children: "Show All" },
...options,
];
setSelectOptions(updatedOptions);
}, [options]);

const onToggleClick = () => {
setIsOpen(!isOpen);
};

const onSelect = (
_event: React.MouseEvent<Element, MouseEvent> | undefined,
selectionValue: string | number | undefined
) => {
if (!value || !selectionValue) {
return;
}
let newValue: string[] = [];
if (selectionValue === "show-all") {
newValue =
value.length === options.length ? [] : options.map((opt) => opt.value);
} else {
if (value.includes(selectionValue as string)) {
newValue = value.filter((item) => item !== selectionValue);
} else {
newValue = [...value, selectionValue as string];
}
}
onChange(newValue);
};

return (
<Select
role="menu"
id={id}
isOpen={isOpen}
selected={value}
onSelect={onSelect}
onOpenChange={setIsOpen}
toggle={(toggleref: React.Ref<MenuToggleElement>) => (
<MenuToggle
aria-label={toggleAriaLabel}
id={toggleId}
ref={toggleref}
onClick={onToggleClick}
style={{ width: width && width + "px" }}
isExpanded={isOpen}
>
<span className={spacing.mrSm}>{placeholderText}</span>
{value && value.length > 0 && <Badge isRead>{value.length}</Badge>}
</MenuToggle>
)}
aria-label={toggleAriaLabel}
>
<SelectList>
{selectOptions.map((option, index) => (
<SelectOption
id={`checkbox-for-${option.value}`}
hasCheckbox
key={option.value}
isFocused={index === 0}
onClick={(e) => {
onSelect(e, option.value);
}}
isSelected={
option.value === "show-all"
? value?.length === options.length
: value?.includes(option.value as string)
}
{...option}
>
{option.children || option.value}
</SelectOption>
))}
</SelectList>
</Select>
);
};
30 changes: 21 additions & 9 deletions client/src/app/components/target-card/target-card.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ import {
SelectVariant,
SelectOptionObject,
} from "@patternfly/react-core/deprecated";
import { GripVerticalIcon } from "@patternfly/react-icons";
import { GripVerticalIcon, InfoCircleIcon } from "@patternfly/react-icons";
import spacing from "@patternfly/react-styles/css/utilities/Spacing/spacing";
import { useTranslation } from "react-i18next";

Expand Down Expand Up @@ -85,14 +85,16 @@ export const TargetCard: React.FC<TargetCardProps> = ({
);

const handleCardClick = (event: React.MouseEvent) => {
// Stop 'select' event propagation
const eventTarget: any = event.target;
if (eventTarget.type === "button") return;
const eventTarget = event.target as HTMLElement;

if (eventTarget.tagName === "BUTTON" || eventTarget.tagName === "LABEL") {
event.preventDefault();
}

setCardSelected(!isCardSelected);
onCardClick &&
selectedLabelName &&
if (onCardClick && selectedLabelName) {
onCardClick(!isCardSelected, selectedLabelName, target);
}
};

const handleLabelSelection = (
Expand All @@ -106,20 +108,30 @@ export const TargetCard: React.FC<TargetCardProps> = ({
onSelectedCardTargetChange(selection as string);
}
};

return (
<Card
id={`target-card-${target.name.replace(/\s/g, "-")}`}
onClick={handleCardClick}
isSelectable={!!cardSelected}
isSelected={isCardSelected}
className="pf-v5-l-stack pf-v5-l-stack__item pf-m-fill"
>
<CardHeader
checked={isCardSelected}
selectableActions={{
selectableActionId: "" + target.id,
selectableActionId: "target-name-" + target.name,
selectableActionAriaLabelledby: `${target.name}-selectable-action-label`,
isChecked: isCardSelected,
}}
/>
>
<Label
id={`${target.provider}-selectable-action-label`}
variant="outline"
icon={<InfoCircleIcon />}
>
{target.provider}
</Label>
</CardHeader>
<CardBody>
<Flex>
<FlexItem>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -359,7 +359,7 @@ export const AnalysisWizard: React.FC<IAnalysisWizard> = ({
isDisabled={!isStepEnabled(StepId.SetTargets)}
footer={{ isNextDisabled: !isStepEnabled(StepId.SetTargets + 1) }}
>
<SetTargets />
<SetTargets applications={applications} />
</WizardStep>,
<WizardStep
key={StepId.Scope}
Expand Down
83 changes: 57 additions & 26 deletions client/src/app/pages/applications/analysis-wizard/set-targets.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import {
GalleryItem,
Form,
Alert,
SelectOptionProps,
} from "@patternfly/react-core";
import { useTranslation } from "react-i18next";
import { useFormContext } from "react-hook-form";
Expand All @@ -15,24 +16,53 @@ import { TargetCard } from "@app/components/target-card/target-card";
import { AnalysisWizardFormValues } from "./schema";
import { useSetting } from "@app/queries/settings";
import { useFetchTargets } from "@app/queries/targets";
import { Target } from "@app/api/models";
import { SimpleSelectTypeahead } from "@app/components/SimpleSelectTypeahead";

export const SetTargets: React.FC = () => {
import { Application, TagCategory, Target } from "@app/api/models";
import { useFetchTagCategories } from "@app/queries/tags";
import { SimpleSelectCheckbox } from "@app/components/SimpleSelectCheckbox";
interface SetTargetsProps {
applications: Application[];
}

export const SetTargets: React.FC<SetTargetsProps> = ({ applications }) => {
const { t } = useTranslation();

const { targets } = useFetchTargets();

const [provider, setProvider] = useState("Java");

const targetOrderSetting = useSetting("ui.target.order");

const { watch, setValue, getValues } =
useFormContext<AnalysisWizardFormValues>();

const values = getValues();
const formLabels = watch("formLabels");
const selectedTargets = watch("selectedTargets");

const { tagCategories, isFetching, fetchError } = useFetchTagCategories();

const findCategoryForTag = (tagId: number) => {
return tagCategories.find(
(category: TagCategory) =>
category.tags?.some((categoryTag) => categoryTag.id === tagId)
);
};

const initialProviders = Array.from(
new Set(
applications
.flatMap((app) => app.tags || [])
.map((tag) => {
return {
category: findCategoryForTag(tag.id),
tag,
};
})
.filter((tagWithCat) => tagWithCat?.category?.name === "Language")
.map((tagWithCat) => tagWithCat.tag.name)
)
).filter(Boolean);

const [providers, setProviders] = useState(initialProviders);

const handleOnSelectedCardTargetChange = (selectedLabelName: string) => {
const otherSelectedLabels = formLabels?.filter((formLabel) => {
return formLabel.name !== selectedLabelName;
Expand Down Expand Up @@ -124,6 +154,10 @@ export const SetTargets: React.FC = () => {
}
};

const allProviders = targets.flatMap((target) => target.provider);

const languageOptions = Array.from(new Set(allProviders));

return (
<Form
onSubmit={(event) => {
Expand All @@ -136,26 +170,21 @@ export const SetTargets: React.FC = () => {
</Title>
<Text>{t("wizard.label.setTargets")}</Text>
</TextContent>
<SimpleSelectTypeahead
width={200}
value={provider}
toggleAriaLabel="Action select dropdown toggle"
toggleId="action-select-toggle"
hideClearButton
id="action-select"
options={[
{
value: "Java",
children: "Java",
},
{
value: "Go",
children: "Go",
},
]}
<SimpleSelectCheckbox
placeholderText="Filter by language..."
width={300}
value={providers}
options={languageOptions?.map((language): SelectOptionProps => {
return {
children: <div>{language}</div>,

value: language,
};
})}
onChange={(selection) => {
setProvider(selection as string);
setProviders(selection as string[]);
}}
toggleId="action-select-toggle"
/>
{values.selectedTargets.length === 0 &&
values.customRulesFiles.length === 0 &&
Expand All @@ -170,10 +199,12 @@ export const SetTargets: React.FC = () => {
{targetOrderSetting.isSuccess
? targetOrderSetting.data.map((id, index) => {
const matchingTarget = targets.find((target) => target.id === id);

const isSelected = selectedTargets?.includes(id);

if (matchingTarget && matchingTarget.provider === provider) {
if (
matchingTarget &&
providers?.some((p) => matchingTarget?.provider?.includes(p))
) {
return (
<GalleryItem key={index}>
<TargetCard
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -235,7 +235,10 @@ export const MigrationTargets: React.FC = () => {
const matchingTarget = targets.find(
(target) => target.id === id
);
if (matchingTarget && matchingTarget.provider === provider) {
if (
matchingTarget &&
matchingTarget.provider?.includes(provider)
) {
return (
<SortableItem
key={id}
Expand Down
Loading