Skip to content

Commit

Permalink
Merge branch 'main' into default-analysis
Browse files Browse the repository at this point in the history
  • Loading branch information
ibolton336 committed Apr 9, 2024
2 parents 636fb1c + d84d176 commit ef9c08b
Show file tree
Hide file tree
Showing 13 changed files with 467 additions and 521 deletions.
5 changes: 4 additions & 1 deletion client/public/locales/en/translation.json
Original file line number Diff line number Diff line change
Expand Up @@ -178,6 +178,7 @@
"blockedDeleteTarget": "Cannot delete {{what}} because it is associated with a target.",
"defaultBlockedDelete": "Cannot delete {{what}} because it is associated with another object.",
"cannotDeleteApplicationsAssignedToMigrationWave": "Cannot delete applications that are assigned to a migration wave.",
"cannotDeleteNonEmptyTagCategory": "Cannot delete a tag category that contains tags.",
"continueConfirmation": "Yes, continue",
"copyAssessmentAndReviewBody": "Some of the selected target applications have an in-progress or complete assessment/review. By continuing, the existing assessment(s)/review(s) will be replaced by the copied assessment/review. Do you wish to continue?",
"copyAssessmentAndReviewQuestion": "Copy assessment and review?",
Expand Down Expand Up @@ -220,7 +221,9 @@
"toTagApplication": "Either no tags exist yet or you may not have permission to view any. If you have permission, try creating a new custom tag.",
"unsavedChanges": "Are you sure you want to close the assessment? Any unsaved changes will be lost.",
"noAnswers": "Are you sure you want to close the assessment? There are no answers to save.",
"unlinkTicket": "Unlink from Jira"
"unlinkTicket": "Unlink from Jira",
"noTagsAvailable": "No tags available",
"noAssociatedTags": "This tag category has no associated tags."
},
"proposedActions": {
"refactor": "Refactor",
Expand Down
8 changes: 6 additions & 2 deletions client/src/app/components/FilterToolbar/FilterToolbar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,12 @@ export enum FilterType {

export type FilterValue = string[] | undefined | null;

export interface FilterSelectOptionProps extends SelectOptionProps {
key: string;
export interface FilterSelectOptionProps {
optionProps?: SelectOptionProps;
value: string;
label?: string;
chipLabel?: string;
groupLabel?: string;
}

export interface IBasicFilterCategory<
Expand Down
180 changes: 81 additions & 99 deletions client/src/app/components/FilterToolbar/MultiselectFilterControl.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -25,8 +25,6 @@ import { TimesIcon } from "@patternfly/react-icons";

import "./select-overrides.css";

const CHIP_BREAK_DELINEATOR = " / ";

export interface IMultiselectFilterControlProps<TItem>
extends IFilterControlProps<TItem, string> {
category: IMultiselectFilterCategory<TItem, string>;
Expand Down Expand Up @@ -63,15 +61,8 @@ export const MultiselectFilterControl = <TItem,>({
(i) => i
) as FilterSelectOptionProps[]);

const getOptionKeyFromOptionValue = (optionValue: string) =>
flatOptions.find(({ value }) => value === optionValue)?.key;

const getOptionValueFromOptionKey = (optionKey: string) =>
flatOptions.find(({ key }) => key === optionKey)?.value;

const getOptionKeyFromChip = (chipDisplayValue: string) => {
return flatOptions.find(({ value }) => value === chipDisplayValue)?.key;
};
const getOptionFromOptionValue = (optionValue: string) =>
flatOptions.find(({ value }) => value === optionValue);

const [focusedItemIndex, setFocusedItemIndex] = React.useState<number | null>(
null
Expand All @@ -83,39 +74,43 @@ export const MultiselectFilterControl = <TItem,>({

const onFilterClearAll = () => setFilterValue([]);
const onFilterClear = (chip: string | ToolbarChip) => {
const displayValue = typeof chip === "string" ? chip : chip.key;
const optionKey = getOptionKeyFromChip(displayValue);
const value = typeof chip === "string" ? chip : chip.key;

if (optionKey) {
const newValue = filterValue?.filter((val) => val !== optionKey) ?? [];
if (value) {
const newValue = filterValue?.filter((val) => val !== value) ?? [];
setFilterValue(newValue.length > 0 ? newValue : null);
}
};

/*
* Note: Chips can be a `ToolbarChip` or a plain `string`. Use a hack to split a
* selected option in 2 parts. Assuming the option is in the format "Group / Item"
* break the text and show a chip with the Item and the Group as a tooltip.
* Note: Create chips only as `ToolbarChip` (no plain string)
*/
const chips = filterValue?.map((s, index) => {
const displayValue = getOptionValueFromOptionKey(s);
const chip: string = displayValue?.toString() ?? "";
const idx = chip.indexOf(CHIP_BREAK_DELINEATOR);

if (idx > 0) {
const tooltip = chip.substring(0, idx);
const text = chip.substring(idx + CHIP_BREAK_DELINEATOR.length);
const chips = filterValue
?.map((value, index) => {
const option = getOptionFromOptionValue(value);
if (!option) {
return null;
}

const { chipLabel, label, groupLabel } = option;
const displayValue: string = chipLabel ?? label ?? value ?? "";

return {
key: chip,
node: (
<Tooltip id={`tooltip-chip-${index}`} content={<div>{tooltip}</div>}>
<div>{text}</div>
key: value,
node: groupLabel ? (
<Tooltip
id={`tooltip-chip-${index}`}
content={<div>{groupLabel}</div>}
>
<div>{displayValue}</div>
</Tooltip>
) : (
displayValue
),
} as ToolbarChip;
}
return chip;
});
};
})

.filter(Boolean);

const renderSelectOptions = (
filter: (option: FilterSelectOptionProps, groupName?: string) => boolean
Expand All @@ -125,64 +120,51 @@ export const MultiselectFilterControl = <TItem,>({
selectOptions as Record<string, FilterSelectOptionProps[]>
)
.sort(([groupA], [groupB]) => groupA.localeCompare(groupB))
.map(([group, options], index) => {
const groupFiltered =
options?.filter((o) => filter(o, group)) ?? [];
return groupFiltered.length === 0 ? undefined : (
<SelectGroup key={`group-${index}`} label={group}>
{groupFiltered.map((optionProps) => {
const optionKey = getOptionKeyFromOptionValue(
optionProps.value
);
if (!optionKey) return null;
return (
<SelectOption
{...optionProps}
key={optionProps.key}
isSelected={filterValue?.includes(optionKey)}
/>
);
})}
</SelectGroup>
);
})
.filter(Boolean)
.map(([group, options]): [string, FilterSelectOptionProps[]] => [
group,
options?.filter((o) => filter(o, group)) ?? [],
])
.filter(([, groupFiltered]) => groupFiltered?.length)
.map(([group, groupFiltered], index) => (
<SelectGroup key={`group-${index}`} label={group}>
{groupFiltered.map(({ value, label, optionProps }) => (
<SelectOption
{...optionProps}
key={value}
value={value}
isSelected={filterValue?.includes(value)}
>
{label ?? value}
</SelectOption>
))}
</SelectGroup>
))
: flatOptions
.filter((o) => filter(o))
.map((optionProps, index) => {
const optionKey = getOptionKeyFromOptionValue(optionProps.value);
if (!optionKey) return null;
return (
<SelectOption
{...optionProps}
{...(!optionProps.isDisabled && { hasCheckbox: true })}
key={optionProps.value || optionProps.children}
value={optionProps.value}
isFocused={focusedItemIndex === index}
isSelected={filterValue?.includes(optionKey)}
>
{optionProps.value}
</SelectOption>
);
});
.map(({ label, value, optionProps = {} }, index) => (
<SelectOption
{...optionProps}
{...(!optionProps.isDisabled && { hasCheckbox: true })}
key={value}
value={value}
isFocused={focusedItemIndex === index}
isSelected={filterValue?.includes(value)}
>
{label ?? value}
</SelectOption>
));

const onSelect = (value: string | undefined) => {
if (value && value !== "No results") {
const optionKey = getOptionKeyFromOptionValue(value);

if (optionKey) {
let newFilterValue: string[];

if (filterValue && filterValue.includes(optionKey)) {
newFilterValue = filterValue.filter((item) => item !== optionKey);
} else {
newFilterValue = filterValue
? [...filterValue, optionKey]
: [optionKey];
}
let newFilterValue: string[];

setFilterValue(newFilterValue);
if (filterValue && filterValue.includes(value)) {
newFilterValue = filterValue.filter((item) => item !== value);
} else {
newFilterValue = filterValue ? [...filterValue, value] : [value];
}

setFilterValue(newFilterValue);
}
textInputRef.current?.focus();
};
Expand Down Expand Up @@ -211,9 +193,9 @@ export const MultiselectFilterControl = <TItem,>({
}

setFocusedItemIndex(indexToFocus);
const focusedItem = selectOptions.filter((option) => !option.isDisabled)[
indexToFocus
];
const focusedItem = selectOptions.filter(
({ optionProps }) => !optionProps?.isDisabled
)[indexToFocus];
setActiveItem(
`select-multi-typeahead-checkbox-${focusedItem.value.replace(" ", "-")}`
);
Expand All @@ -237,28 +219,25 @@ export const MultiselectFilterControl = <TItem,>({
if (!newSelectOptions.length) {
newSelectOptions = [
{
key: "no-results",
isDisabled: true,
hasCheckbox: false,
children: `No results found for "${inputValue}"`,
value: "No results",
value: "no-results",
optionProps: {
isDisabled: true,
hasCheckbox: false,
},
label: `No results found for "${inputValue}"`,
},
];
}

if (!isFilterDropdownOpen) {
setIsFilterDropdownOpen(true);
}
}

setSelectOptions(newSelectOptions);
setFocusedItemIndex(null);
setActiveItem(null);
}, [inputValue]);
}, [inputValue, category.selectOptions]);

const onInputKeyDown = (event: React.KeyboardEvent<HTMLInputElement>) => {
const enabledMenuItems = Array.isArray(selectOptions)
? selectOptions.filter((option) => !option.isDisabled)
? selectOptions.filter(({ optionProps }) => !optionProps?.isDisabled)
: [];
const [firstMenuItem] = enabledMenuItems;
const focusedItem = focusedItemIndex
Expand Down Expand Up @@ -301,6 +280,9 @@ export const MultiselectFilterControl = <TItem,>({
value: string
) => {
setInputValue(value);
if (!isFilterDropdownOpen) {
setIsFilterDropdownOpen(true);
}
};

const toggle = (toggleRef: React.Ref<MenuToggleElement>) => (
Expand Down
41 changes: 24 additions & 17 deletions client/src/app/components/FilterToolbar/SelectFilterControl.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -33,20 +33,26 @@ export const SelectFilterControl = <TItem, TFilterCategoryKey extends string>({
>): JSX.Element | null => {
const [isFilterDropdownOpen, setIsFilterDropdownOpen] = React.useState(false);

const getOptionKeyFromOptionValue = (optionValue: string) =>
category.selectOptions.find(({ value }) => value === optionValue)?.key;
const getOptionFromOptionValue = (optionValue: string) =>
category.selectOptions.find(({ value }) => value === optionValue);

const getOptionValueFromOptionKey = (optionKey: string) =>
category.selectOptions.find(({ key }) => key === optionKey)?.value;

const chips = filterValue?.map((key) => {
const displayValue = getOptionValueFromOptionKey(key);
return displayValue ? displayValue : key;
});
const chips = filterValue
?.map((value) => {
const option = getOptionFromOptionValue(value);
if (!option) {
return null;
}
const { chipLabel, label } = option;
return {
key: value,
node: chipLabel ?? label ?? value,
};
})
.filter(Boolean);

const onFilterSelect = (value: string) => {
const optionKey = getOptionKeyFromOptionValue(value);
setFilterValue(optionKey ? [optionKey] : null);
const option = getOptionFromOptionValue(value);
setFilterValue(option ? [value] : null);
setIsFilterDropdownOpen(false);
};

Expand All @@ -59,7 +65,7 @@ export const SelectFilterControl = <TItem, TFilterCategoryKey extends string>({
let displayText = "Any";
if (filterValue && filterValue.length > 0) {
const selectedKey = filterValue[0];
const selectedDisplayValue = getOptionValueFromOptionKey(selectedKey);
const selectedDisplayValue = getOptionFromOptionValue(selectedKey)?.label;
displayText = selectedDisplayValue ? selectedDisplayValue : selectedKey;
}

Expand Down Expand Up @@ -103,15 +109,16 @@ export const SelectFilterControl = <TItem, TFilterCategoryKey extends string>({
shouldFocusToggleOnSelect
>
<SelectList>
{category.selectOptions.map((o, index) => {
const isSelected = filterValue?.includes(o.key);
{category.selectOptions.map(({ label, value, optionProps }) => {
const isSelected = filterValue?.includes(value);
return (
<SelectOption
{...o}
key={`${index}-${o.value}`}
{...optionProps}
key={value}
value={value}
isSelected={isSelected}
>
{o.value}
{label ?? value}
</SelectOption>
);
})}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { Tag, TagCategory } from "@app/api/models";
import { COLOR_HEX_VALUES_BY_NAME } from "@app/Constants";
import { LabelCustomColor } from "@app/components/LabelCustomColor";

export const getTagCategoryFallbackColor = (category?: TagCategory) => {
export const getTagCategoryFallbackColor = (category?: TagCategory | null) => {
if (!category?.id) return COLOR_HEX_VALUES_BY_NAME.gray;
const colorValues = Object.values(COLOR_HEX_VALUES_BY_NAME);
return colorValues[category?.id % colorValues.length];
Expand Down
Loading

0 comments on commit ef9c08b

Please sign in to comment.