From ea2005b459140237629865d534f4047cd40d0048 Mon Sep 17 00:00:00 2001 From: Ian Bolton Date: Thu, 20 Jun 2024 09:27:28 -0400 Subject: [PATCH] :sparkles: [AnalysisWizard] Language discovery changes (#1951) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Resolves https://github.com/konveyor/tackle2-ui/issues/1950 UI Tests PR: 1136 Needs: https://github.com/konveyor/tackle-ui-tests/pull/1136 Includes: - Provide a "(Show All)" option to display all targets regardless of language tags. Adds a new component to handle this new menu type. - Populate the options list from the Provider field of the fetched targets. - Initially select the languages based on the tags, of tag category "Language", across the applications selected for analysis. - Update TS type for Target to reflect the provider type changing to a string[]. - Covers the hub change to the task model described here: >The application Analysis fields used to correlated to task by addon. This needs to be updated to correlate by kind == "analyzer" (when not blank) else fallback on addon == "analyzer". The fallback is needed to correlate tasks created in previous releases. - Each target card should always display a label with it's provider. This will allow cards that belong to different providers to be differentiated when more than one language is selected. Screenshot 2024-06-12 at 3 37 47 PM --------- Signed-off-by: Ian Bolton Co-authored-by: Radoslaw Szwajkowski --- .../app/components/SimpleSelectCheckbox.tsx | 121 ++++++++++++++++++ .../components/target-card/target-card.tsx | 30 +++-- .../analysis-wizard/analysis-wizard.tsx | 2 +- .../analysis-wizard/set-targets.tsx | 83 ++++++++---- .../migration-targets/migration-targets.tsx | 5 +- 5 files changed, 204 insertions(+), 37 deletions(-) create mode 100644 client/src/app/components/SimpleSelectCheckbox.tsx diff --git a/client/src/app/components/SimpleSelectCheckbox.tsx b/client/src/app/components/SimpleSelectCheckbox.tsx new file mode 100644 index 000000000..d95d90382 --- /dev/null +++ b/client/src/app/components/SimpleSelectCheckbox.tsx @@ -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 = ({ + onChange, + options, + value, + placeholderText = "Select...", + id, + toggleId, + toggleAriaLabel, + width, +}) => { + const [isOpen, setIsOpen] = React.useState(false); + const [selectOptions, setSelectOptions] = React.useState( + [{ 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 | 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 ( + + ); +}; diff --git a/client/src/app/components/target-card/target-card.tsx b/client/src/app/components/target-card/target-card.tsx index 691462353..de63ed27b 100644 --- a/client/src/app/components/target-card/target-card.tsx +++ b/client/src/app/components/target-card/target-card.tsx @@ -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"; @@ -85,14 +85,16 @@ export const TargetCard: React.FC = ({ ); 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 = ( @@ -106,20 +108,30 @@ export const TargetCard: React.FC = ({ onSelectedCardTargetChange(selection as string); } }; - return ( + > + + diff --git a/client/src/app/pages/applications/analysis-wizard/analysis-wizard.tsx b/client/src/app/pages/applications/analysis-wizard/analysis-wizard.tsx index 58096ff8d..f13cc2fb2 100644 --- a/client/src/app/pages/applications/analysis-wizard/analysis-wizard.tsx +++ b/client/src/app/pages/applications/analysis-wizard/analysis-wizard.tsx @@ -359,7 +359,7 @@ export const AnalysisWizard: React.FC = ({ isDisabled={!isStepEnabled(StepId.SetTargets)} footer={{ isNextDisabled: !isStepEnabled(StepId.SetTargets + 1) }} > - + , { +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 = ({ applications }) => { const { t } = useTranslation(); const { targets } = useFetchTargets(); - const [provider, setProvider] = useState("Java"); - const targetOrderSetting = useSetting("ui.target.order"); const { watch, setValue, getValues } = useFormContext(); + 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; @@ -124,6 +154,10 @@ export const SetTargets: React.FC = () => { } }; + const allProviders = targets.flatMap((target) => target.provider); + + const languageOptions = Array.from(new Set(allProviders)); + return (
{ @@ -136,26 +170,21 @@ export const SetTargets: React.FC = () => { {t("wizard.label.setTargets")} - { + return { + children:
{language}
, + + value: language, + }; + })} onChange={(selection) => { - setProvider(selection as string); + setProviders(selection as string[]); }} + toggleId="action-select-toggle" /> {values.selectedTargets.length === 0 && values.customRulesFiles.length === 0 && @@ -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 ( { const matchingTarget = targets.find( (target) => target.id === id ); - if (matchingTarget && matchingTarget.provider === provider) { + if ( + matchingTarget && + matchingTarget.provider?.includes(provider) + ) { return (