mirror of
https://github.com/fleetdm/fleet
synced 2026-05-23 08:58:41 +00:00
Fix targets selector bug (#2981)
This commit is contained in:
parent
bfdedd65e8
commit
c8f3934772
2 changed files with 60 additions and 32 deletions
1
changes/issue-2953-select-targets
Normal file
1
changes/issue-2953-select-targets
Normal file
|
|
@ -0,0 +1 @@
|
|||
* Fix target selector bug in case where different target types have the same numeric id
|
||||
|
|
@ -1,7 +1,7 @@
|
|||
import React, { useState } from "react";
|
||||
import { useQuery } from "react-query";
|
||||
import { Row } from "react-table";
|
||||
import { filter, forEach, isEmpty, remove, unionBy } from "lodash";
|
||||
import { filter, forEach, isEmpty, remove, unionWith } from "lodash";
|
||||
|
||||
// @ts-ignore
|
||||
import { formatSelectedTargetsForApi } from "fleet/helpers";
|
||||
|
|
@ -20,17 +20,31 @@ import CheckIcon from "../../../../../assets/images/icon-check-purple-32x32@2x.p
|
|||
import ExternalURLIcon from "../../../../../assets/images/icon-external-url-12x12@2x.png";
|
||||
import ErrorIcon from "../../../../../assets/images/icon-error-16x16@2x.png";
|
||||
|
||||
interface ISelectHost extends IHost {
|
||||
target_type?: string;
|
||||
}
|
||||
|
||||
interface ISelectLabel extends ILabel {
|
||||
target_type?: string;
|
||||
}
|
||||
|
||||
interface ISelectTeam extends ITeam {
|
||||
target_type?: string;
|
||||
}
|
||||
|
||||
type ISelectTargetsEntity = ISelectHost | ISelectLabel | ISelectTeam;
|
||||
|
||||
interface ITargetPillSelectorProps {
|
||||
entity: ILabel | ITeam;
|
||||
entity: ISelectLabel | ISelectTeam;
|
||||
isSelected: boolean;
|
||||
onClick: (
|
||||
value: ILabel | ITeam
|
||||
value: ISelectLabel | ISelectTeam
|
||||
) => React.MouseEventHandler<HTMLButtonElement>;
|
||||
}
|
||||
|
||||
interface ISelectTargetsProps {
|
||||
baseClass: string;
|
||||
selectedTargets: ITarget[];
|
||||
selectedTargets: ISelectTargetsEntity[];
|
||||
queryIdForEdit: number | null;
|
||||
goToQueryEditor: () => void;
|
||||
goToRunQuery: () => void;
|
||||
|
|
@ -43,6 +57,11 @@ interface IModifiedUseQueryTargetsResponse {
|
|||
onlineCount: number;
|
||||
}
|
||||
|
||||
const isSameSelectTargetsEntity = (
|
||||
e1: ISelectTargetsEntity,
|
||||
e2: ISelectTargetsEntity
|
||||
) => e1.id === e2.id && e1.target_type === e2.target_type;
|
||||
|
||||
const TargetPillSelector = ({
|
||||
entity,
|
||||
isSelected,
|
||||
|
|
@ -88,7 +107,9 @@ const SelectTargets = ({
|
|||
const [platformLabels, setPlatformLabels] = useState<ILabel[] | null>(null);
|
||||
const [teams, setTeams] = useState<ITeam[] | null>(null);
|
||||
const [otherLabels, setOtherLabels] = useState<ILabel[] | null>(null);
|
||||
const [selectedLabels, setSelectedLabels] = useState<any>([]);
|
||||
const [selectedLabels, setSelectedLabels] = useState<ISelectTargetsEntity[]>(
|
||||
[]
|
||||
);
|
||||
const [inputTabIndex, setInputTabIndex] = useState<number>(0);
|
||||
const [searchText, setSearchText] = useState<string>("");
|
||||
const [relatedHosts, setRelatedHosts] = useState<IHost[]>([]);
|
||||
|
|
@ -169,32 +190,36 @@ const SelectTargets = ({
|
|||
}
|
||||
);
|
||||
|
||||
const handleSelectedLabels = (entity: ILabel | ITeam) => (
|
||||
const handleSelectedLabels = (selectedLabel: ISelectTargetsEntity) => (
|
||||
e: React.MouseEvent<HTMLButtonElement>
|
||||
): void => {
|
||||
e.preventDefault();
|
||||
|
||||
let targets = selectedTargets;
|
||||
const labels = selectedLabels;
|
||||
let newTargets = null;
|
||||
const targets = selectedTargets;
|
||||
const removed = remove(labels, ({ id }) => id === entity.id);
|
||||
const removed = remove(labels, (label) =>
|
||||
isSameSelectTargetsEntity(label, selectedLabel)
|
||||
);
|
||||
|
||||
// visually show selection
|
||||
const isRemoval = removed.length > 0;
|
||||
if (isRemoval) {
|
||||
newTargets = labels;
|
||||
targets = targets.filter(
|
||||
(target) => !isSameSelectTargetsEntity(target, selectedLabel)
|
||||
);
|
||||
} else {
|
||||
labels.push(entity);
|
||||
labels.push(selectedLabel);
|
||||
|
||||
// prepare the labels data
|
||||
forEach(labels, (label) => {
|
||||
label.target_type = "label_type" in label ? "labels" : "teams";
|
||||
});
|
||||
|
||||
newTargets = unionBy(targets, labels, "id");
|
||||
targets = unionWith(targets, labels, isSameSelectTargetsEntity);
|
||||
}
|
||||
|
||||
setSelectedLabels([...labels]);
|
||||
setSelectedTargets([...newTargets]);
|
||||
setSelectedTargets([...targets]);
|
||||
};
|
||||
|
||||
const handleRowSelect = (row: Row) => {
|
||||
|
|
@ -211,31 +236,33 @@ const SelectTargets = ({
|
|||
const handleRowRemove = (row: Row) => {
|
||||
const targets = selectedTargets;
|
||||
const hostTarget = row.original as ITarget;
|
||||
remove(targets, (t) => t.id === hostTarget.id);
|
||||
remove(targets, (t) => t.id === hostTarget.id && t.target_type === "hosts");
|
||||
|
||||
setSelectedTargets([...targets]);
|
||||
};
|
||||
|
||||
const renderTargetEntityList = (
|
||||
header: string,
|
||||
entityList: ILabel[] | ITeam[]
|
||||
): JSX.Element => (
|
||||
<>
|
||||
{header && <h3>{header}</h3>}
|
||||
<div className="selector-block">
|
||||
{entityList?.map((entity: ILabel | ITeam) => (
|
||||
<TargetPillSelector
|
||||
key={entity.id}
|
||||
entity={entity}
|
||||
isSelected={selectedLabels.some(
|
||||
({ id }: ILabel | ITeam) => id === entity.id
|
||||
)}
|
||||
onClick={handleSelectedLabels}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
entityList: ISelectLabel[] | ISelectTeam[]
|
||||
): JSX.Element => {
|
||||
return (
|
||||
<>
|
||||
{header && <h3>{header}</h3>}
|
||||
<div className="selector-block">
|
||||
{entityList?.map((entity: ISelectLabel | ISelectTeam) => (
|
||||
<TargetPillSelector
|
||||
key={`target_${entity.target_type}_${entity.id}`}
|
||||
entity={entity}
|
||||
isSelected={selectedLabels.some((label: ISelectTargetsEntity) =>
|
||||
isSameSelectTargetsEntity(label, entity)
|
||||
)}
|
||||
onClick={handleSelectedLabels}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
if (isEmpty(searchText) && isTargetsLoading) {
|
||||
return (
|
||||
|
|
|
|||
Loading…
Reference in a new issue