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

🐛 Analysis - Update the TaskGroup to id selected rulesets #2034

Merged
merged 5 commits into from
Aug 2, 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
1 change: 1 addition & 0 deletions client/src/app/api/models.ts
Original file line number Diff line number Diff line change
Expand Up @@ -416,6 +416,7 @@ export interface TaskData {
included: string[];
excluded: string[];
};
ruleSets?: Ref[]; // Target.ruleset.{ id, name }
};
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import { useTranslation } from "react-i18next";
import {
Application,
New,
Ref,
TaskData,
Taskgroup,
TaskgroupTask,
Expand Down Expand Up @@ -223,6 +224,16 @@ export const AnalysisWizard: React.FC<IAnalysisWizard> = ({
const matchingSourceCredential = identities.find(
(identity) => identity.name === fieldValues.associatedCredentials
);

const ruleSetRefsFromSelectedTargets: Ref[] = fieldValues.selectedTargets
.map(({ ruleset }) => ruleset)
.filter(Boolean)
.map<Ref>(({ id, name }) => ({ id: id ?? 0, name: name ?? "" }));
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can zero ID be used by some existing rule set? if yes then I would go with safer value.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nope. Ids start at 1.

// TODO: Type `Ruleset` has the id and name as optional/undefined to support
// object creation. At runtime, id and name will always be defined on
// existing objects. In future, update the Ruleset creation code to use
// New<Ruleset> or similar to avoid these issues.

return {
...currentTaskgroup,
tasks: analyzableApplications.map((app: Application) => initTask(app)),
Expand Down Expand Up @@ -286,6 +297,9 @@ export const AnalysisWizard: React.FC<IAnalysisWizard> = ({
name: matchingSourceCredential.name,
},
}),
...(ruleSetRefsFromSelectedTargets.length > 0 && {
ruleSets: ruleSetRefsFromSelectedTargets,
}),
},
},
};
Expand Down
4 changes: 2 additions & 2 deletions client/src/app/pages/applications/analysis-wizard/schema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import {
IReadFile,
FileLoadError,
TargetLabel,
Target,
} from "@app/api/models";
import { useTranslation } from "react-i18next";
import { useAnalyzableApplicationsByMode } from "./utils";
Expand Down Expand Up @@ -55,7 +56,7 @@ const useModeStepSchema = ({

export interface TargetsStepValues {
formLabels: TargetLabel[];
selectedTargets: number[];
selectedTargets: Target[];
}

const useTargetsStepSchema = (): yup.SchemaOf<TargetsStepValues> => {
Expand Down Expand Up @@ -114,7 +115,6 @@ export const customRulesFilesSchema: yup.SchemaOf<IReadFile> = yup.object({
});

const useCustomRulesStepSchema = (): yup.SchemaOf<CustomRulesStepValues> => {
const { t } = useTranslation();
return yup.object({
rulesKind: yup.string().defined(),
customRulesFiles: yup
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,11 +29,8 @@ 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";
import { updateSelectedTargetsBasedOnLabels } from "./utils";
import { toggle } from "radash";

export const SetOptions: React.FC = () => {
const { t } = useTranslation();
Expand Down Expand Up @@ -124,25 +121,14 @@ export const SetOptions: React.FC = () => {
selections={targetSelections}
isOpen={isSelectTargetsOpen}
onSelect={(_, selection) => {
const selectionWithLabelSelector = `konveyor.io/target=${selection}`;
const matchingLabel = findLabelBySelector(
defaultTargetsAndTargetsLabels,
selectionWithLabelSelector
const selectionLabel = `konveyor.io/target=${selection}`;
const matchingLabel = defaultTargetsAndTargetsLabels.find(
(label) => label.label === selectionLabel
);
let updatedFormLabels = [];
if (
matchingLabel &&
!isLabelInFormLabels(formLabels, matchingLabel.label)
) {
updatedFormLabels = [...formLabels, matchingLabel];
onChange(updatedFormLabels);
} else {
updatedFormLabels = formLabels.filter(
(formLabel) =>
formLabel.label !== selectionWithLabelSelector
);
onChange(updatedFormLabels);
}
const updatedFormLabels = !matchingLabel
? formLabels
: toggle(formLabels, matchingLabel, (tl) => tl.label);
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nice!

onChange(updatedFormLabels);

const updatedSelectedTargets =
updateSelectedTargetsBasedOnLabels(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +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";
import { getUpdatedFormLabels, toggleSelectedTargets } from "./utils";

interface SetTargetsProps {
applications: Application[];
Expand Down Expand Up @@ -103,20 +103,19 @@ export const SetTargets: React.FC<SetTargetsProps> = ({ applications }) => {
selectedLabelName: string,
target: Target
) => {
const updatedSelectedTargets = updateSelectedTargets(
target.id,
const updatedSelectedTargets = toggleSelectedTargets(
target,
selectedTargets
);
setValue("selectedTargets", updatedSelectedTargets);

const updatedFormLabels = getUpdatedFormLabels(
isSelecting,
selectedLabelName,
target,
formLabels
);

setValue("formLabels", updatedFormLabels);
setValue("selectedTargets", updatedSelectedTargets);
};

const allProviders = targets.flatMap((target) => target.provider);
Expand Down Expand Up @@ -176,7 +175,7 @@ export const SetTargets: React.FC<SetTargetsProps> = ({ applications }) => {
<TargetCard
readOnly
item={target}
cardSelected={selectedTargets?.includes(target.id)}
cardSelected={selectedTargets.some(({ id }) => id === target.id)}
onSelectedCardTargetChange={(selectedTarget) => {
handleOnSelectedCardTargetChange(selectedTarget);
}}
Expand Down
72 changes: 40 additions & 32 deletions client/src/app/pages/applications/analysis-wizard/utils.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import * as React from "react";
import { Application, Target, TargetLabel } from "@app/api/models";
import { AnalysisMode, ANALYSIS_MODES } from "./schema";
import { toggle, unique } from "radash";
import { getParsedLabel } from "@app/utils/rules-utils";

export const isApplicationBinaryEnabled = (
application: Application
Expand Down Expand Up @@ -61,14 +63,14 @@ 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];
/**
* Toggle the existence of a target within the array and return the array
*/
export const toggleSelectedTargets = (
target: Target,
selectedTargets: Target[]
): Target[] => {
return toggle(selectedTargets, target, (t) => t.id);
};

export const getUpdatedFormLabels = (
Expand Down Expand Up @@ -100,40 +102,46 @@ export const getUpdatedFormLabels = (
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);
/**
* Match a target to a set of target type labels based on if the target supports
* label choice.
*/
const matchTargetToLabels = (target: Target, labels: TargetLabel[]) => {
if (!target.labels?.length) {
return false;
}

export const labelToTargetId = (labelName: string, targets: Target[]) => {
const target = targets.find(
(t) => t.labels?.some((l) => l.name === labelName)
const targetTargetLabelCount = target.labels?.reduce(
(count, tl) =>
getParsedLabel(tl.label).labelType === "target" ? count + 1 : count,
0
);
return target ? target.id : null;

const matches = labels
.map((l) => target.labels?.find((tl) => tl.label === l.label) ?? false)
.filter(Boolean).length;

return target.choice ? matches >= 1 : matches === targetTargetLabelCount;
};

/**
* Given a set of selected labels, return a set of targets where (1) the target's labels
* properly match the select labels or (2) the target is selected but has no labels.
*/
export const updateSelectedTargetsBasedOnLabels = (
currentFormLabels: TargetLabel[],
selectedTargets: number[],
selectedTargets: Target[],
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;
},
[]
): Target[] => {
const targetsFromLabels = unique(
targets.filter((target) => matchTargetToLabels(target, currentFormLabels)),
(target) => target.id
);

const filteredSelectedTargets = selectedTargets.filter((targetId) =>
currentFormLabels.some(
(formLabel) => labelToTargetId(formLabel.name, targets) === targetId
)
const selectedTargetsWithNoLabel = selectedTargets.filter(
(target) => (target.labels?.length ?? 0) === 0
);

return [...new Set([...newSelectedTargets, ...filteredSelectedTargets])];
return [...targetsFromLabels, ...selectedTargetsWithNoLabel];
};
Loading