From 80ee8ec845c8bf4d70535766592e617923768488 Mon Sep 17 00:00:00 2001 From: Ian Bolton Date: Mon, 22 Jul 2024 18:01:04 -0400 Subject: [PATCH] :bug: Address targets falling out of sync after label selection changes (#2019) Resolves: https://issues.redhat.com/browse/MTA-3180 Currently, the set-options screen allows edit of target labels which can be set manually on the Advanced options step or by way of selecting a target card on the target card step. This PR brings the form labels and selectedTarget form values in sync by updating the selected targets to reflect any form label changes in the set options page. This fixes the mta-3180 scenario by causing validation to reflect the now accurate state of the selectedTargets form value. Signed-off-by: Ian Bolton --- .../analysis-wizard/set-options.tsx | 40 ++++++---- .../analysis-wizard/set-targets.tsx | 49 ++---------- .../applications/analysis-wizard/utils.ts | 79 ++++++++++++++++++- 3 files changed, 110 insertions(+), 58 deletions(-) diff --git a/client/src/app/pages/applications/analysis-wizard/set-options.tsx b/client/src/app/pages/applications/analysis-wizard/set-options.tsx index 8efc561b35..f5597388d1 100644 --- a/client/src/app/pages/applications/analysis-wizard/set-options.tsx +++ b/client/src/app/pages/applications/analysis-wizard/set-options.tsx @@ -29,6 +29,11 @@ import { DEFAULT_SELECT_MAX_HEIGHT } from "@app/Constants"; import { useFetchTargets } from "@app/queries/targets"; import defaultSources from "./sources"; import { QuestionCircleIcon } from "@patternfly/react-icons"; +import { + findLabelBySelector, + isLabelInFormLabels, + updateSelectedTargetsBasedOnLabels, +} from "./utils"; export const SetOptions: React.FC = () => { const { t } = useTranslation(); @@ -41,6 +46,7 @@ export const SetOptions: React.FC = () => { excludedRulesTags, autoTaggingEnabled, advancedAnalysisEnabled, + selectedTargets, } = watch(); const [isSelectTargetsOpen, setSelectTargetsOpen] = React.useState(false); @@ -119,27 +125,33 @@ export const SetOptions: React.FC = () => { isOpen={isSelectTargetsOpen} onSelect={(_, selection) => { const selectionWithLabelSelector = `konveyor.io/target=${selection}`; - const matchingLabel = - defaultTargetsAndTargetsLabels.find( - (label) => label.label === selectionWithLabelSelector - ) || ""; - - const formLabelLabels = formLabels.map( - (formLabel) => formLabel.label + const matchingLabel = findLabelBySelector( + defaultTargetsAndTargetsLabels, + selectionWithLabelSelector ); + let updatedFormLabels = []; if ( matchingLabel && - !formLabelLabels.includes(matchingLabel.label) + !isLabelInFormLabels(formLabels, matchingLabel.label) ) { - onChange([...formLabels, matchingLabel]); + updatedFormLabels = [...formLabels, matchingLabel]; + onChange(updatedFormLabels); } else { - onChange( - formLabels.filter( - (formLabel) => - formLabel.label !== selectionWithLabelSelector - ) + updatedFormLabels = formLabels.filter( + (formLabel) => + formLabel.label !== selectionWithLabelSelector ); + onChange(updatedFormLabels); } + + const updatedSelectedTargets = + updateSelectedTargetsBasedOnLabels( + updatedFormLabels, + selectedTargets, + targets + ); + setValue("selectedTargets", updatedSelectedTargets); + onBlur(); setSelectTargetsOpen(!isSelectTargetsOpen); }} diff --git a/client/src/app/pages/applications/analysis-wizard/set-targets.tsx b/client/src/app/pages/applications/analysis-wizard/set-targets.tsx index 9d0f0577bd..d2d191d2d2 100644 --- a/client/src/app/pages/applications/analysis-wizard/set-targets.tsx +++ b/client/src/app/pages/applications/analysis-wizard/set-targets.tsx @@ -19,6 +19,7 @@ import { useFetchTargets } from "@app/queries/targets"; import { Application, TagCategory, Target } from "@app/api/models"; import { useFetchTagCategories } from "@app/queries/tags"; import { SimpleSelectCheckbox } from "@app/components/SimpleSelectCheckbox"; +import { getUpdatedFormLabels, updateSelectedTargets } from "./utils"; interface SetTargetsProps { applications: Application[]; } @@ -101,61 +102,23 @@ export const SetTargets: React.FC = ({ applications }) => { selectedLabelName: string, target: Target ) => { - const updatedSelectedTargets = getUpdatedSelectedTargets( - isSelecting, - target + const updatedSelectedTargets = updateSelectedTargets( + target.id, + selectedTargets ); const updatedFormLabels = getUpdatedFormLabels( isSelecting, selectedLabelName, - target + target, + formLabels ); setValue("formLabels", updatedFormLabels); setValue("selectedTargets", updatedSelectedTargets); }; - const getUpdatedSelectedTargets = (isSelecting: boolean, target: Target) => { - const { selectedTargets } = values; - if (isSelecting) { - return [...selectedTargets, target.id]; - } - return selectedTargets.filter((id) => id !== target.id); - }; - - const getUpdatedFormLabels = ( - isSelecting: boolean, - selectedLabelName: string, - target: Target - ) => { - const { formLabels } = values; - if (target.custom) { - const customTargetLabelNames = target.labels?.map((label) => label.name); - const otherSelectedLabels = formLabels?.filter( - (formLabel) => !customTargetLabelNames?.includes(formLabel.name) - ); - return isSelecting && target.labels - ? [...otherSelectedLabels, ...target.labels] - : otherSelectedLabels; - } else { - const otherSelectedLabels = formLabels?.filter( - (formLabel) => formLabel.name !== selectedLabelName - ); - if (isSelecting) { - const matchingLabel = target.labels?.find( - (label) => label.name === selectedLabelName - ); - return matchingLabel - ? [...otherSelectedLabels, matchingLabel] - : otherSelectedLabels; - } - return otherSelectedLabels; - } - }; - const allProviders = targets.flatMap((target) => target.provider); - const languageOptions = Array.from(new Set(allProviders)); return ( diff --git a/client/src/app/pages/applications/analysis-wizard/utils.ts b/client/src/app/pages/applications/analysis-wizard/utils.ts index 9075478cad..07494fc4c4 100644 --- a/client/src/app/pages/applications/analysis-wizard/utils.ts +++ b/client/src/app/pages/applications/analysis-wizard/utils.ts @@ -1,5 +1,5 @@ import * as React from "react"; -import { Application } from "@app/api/models"; +import { Application, Target, TargetLabel } from "@app/api/models"; import { AnalysisMode, ANALYSIS_MODES } from "./schema"; export const isApplicationBinaryEnabled = ( @@ -60,3 +60,80 @@ export const useAnalyzableApplicationsByMode = ( ), [applications] ); + +export const updateSelectedTargets = ( + targetId: number, + selectedTargetIDs: number[] +) => { + const isSelected = selectedTargetIDs.includes(targetId); + return isSelected + ? selectedTargetIDs.filter((id) => id !== targetId) + : [...selectedTargetIDs, targetId]; +}; + +export const getUpdatedFormLabels = ( + isSelecting: boolean, + selectedLabelName: string, + target: Target, + formLabels: TargetLabel[] +) => { + if (target.custom) { + const customTargetLabelNames = target.labels?.map((label) => label.name); + const otherSelectedLabels = formLabels?.filter( + (formLabel) => !customTargetLabelNames?.includes(formLabel.name) + ); + return isSelecting && target.labels + ? [...otherSelectedLabels, ...target.labels] + : otherSelectedLabels; + } else { + const otherSelectedLabels = formLabels?.filter( + (formLabel) => formLabel.name !== selectedLabelName + ); + if (isSelecting) { + const matchingLabel = target.labels?.find( + (label) => label.name === selectedLabelName + ); + return matchingLabel + ? [...otherSelectedLabels, matchingLabel] + : otherSelectedLabels; + } + return otherSelectedLabels; + } +}; +export const findLabelBySelector = (labels: TargetLabel[], selector: string) => + labels.find((label) => label.label === selector) || ""; + +export const isLabelInFormLabels = (formLabels: TargetLabel[], label: string) => + formLabels.some((formLabel) => formLabel.label === label); + +export const labelToTargetId = (labelName: string, targets: Target[]) => { + const target = targets.find( + (t) => t.labels?.some((l) => l.name === labelName) + ); + return target ? target.id : null; +}; + +export const updateSelectedTargetsBasedOnLabels = ( + currentFormLabels: TargetLabel[], + selectedTargets: number[], + targets: Target[] +) => { + const newSelectedTargets = currentFormLabels.reduce( + (acc: number[], formLabel) => { + const targetId = labelToTargetId(formLabel.name, targets); + if (targetId && !acc.includes(targetId)) { + acc.push(targetId); + } + return acc; + }, + [] + ); + + const filteredSelectedTargets = selectedTargets.filter((targetId) => + currentFormLabels.some( + (formLabel) => labelToTargetId(formLabel.name, targets) === targetId + ) + ); + + return [...new Set([...newSelectedTargets, ...filteredSelectedTargets])]; +};