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 2 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
132 changes: 132 additions & 0 deletions client/src/app/components/SimpleSelectCheckbox.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,132 @@
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 [selectedItems, setSelectedItems] = React.useState<string[]>([]);
const [selectOptions, setSelectOptions] = React.useState<SelectOptionProps[]>(
[{ value: "show-all", label: "Show All", children: "Show All" }, ...options]
);

React.useEffect(() => {
setSelectedItems(value || []);
}, [value]);

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
) => {
const value = selectionValue as string;
if (value === "show-all") {
if (selectedItems.length === options.length) {
setSelectedItems([]);
onChange([]);
} else {
const allItemValues = options.map((option) => option.value as string);
setSelectedItems(allItemValues);
onChange(allItemValues);
}
} else {
if (selectedItems.includes(value)) {
const newSelections = selectedItems.filter((item) => item !== value);
setSelectedItems(newSelections);
onChange(newSelections);
} else {
const newSelections = [...selectedItems, value];
setSelectedItems(newSelections);
onChange(newSelections);
}
}
};

return (
<Select
role="menu"
id={id}
isOpen={isOpen}
selected={selectedItems}
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>
{selectedItems.length > 0 && (
<Badge isRead>{selectedItems.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={() => onSelect(undefined, option.value)}
isSelected={
option.value === "show-all"
? selectedItems.length === options.length
: selectedItems.includes(option.value as string)
}
{...option}
>
{option.children || option.value}
</SelectOption>
))}
</SelectList>
</Select>
);
};
19 changes: 15 additions & 4 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 @@ -86,6 +86,7 @@ export const TargetCard: React.FC<TargetCardProps> = ({

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

Expand All @@ -109,17 +110,27 @@ export const TargetCard: React.FC<TargetCardProps> = ({

return (
<Card
id={`target-card-${target.name.replace(/\s/g, "-")}`}
onClick={handleCardClick}
isSelectable={!!cardSelected}
isSelectable
isSelected={isCardSelected}
className="pf-v5-l-stack pf-v5-l-stack__item pf-m-fill"
>
<CardHeader
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
81 changes: 56 additions & 25 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 [provider, setProvider] = 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}
<SimpleSelectCheckbox
placeholderText="Filter by language..."
width={300}
value={provider}
toggleAriaLabel="Action select dropdown toggle"
toggleId="action-select-toggle"
hideClearButton
id="action-select"
options={[
{
value: "Java",
children: "Java",
},
{
value: "Go",
children: "Go",
},
]}
options={languageOptions?.map((language): SelectOptionProps => {
return {
children: <div>{language}</div>,

value: language,
};
})}
onChange={(selection) => {
setProvider(selection as string);
setProvider(selection as string[]);
}}
toggleId="action-select-toggle"
/>
{values.selectedTargets.length === 0 &&
values.customRulesFiles.length === 0 &&
Expand All @@ -172,8 +201,10 @@ export const SetTargets: React.FC = () => {
const matchingTarget = targets.find((target) => target.id === id);

const isSelected = selectedTargets?.includes(id);

if (matchingTarget && matchingTarget.provider === provider) {
if (
matchingTarget &&
provider?.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