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

Fleet UI: Allow select target search for labels and teams #24798

Merged
merged 10 commits into from
Dec 23, 2024
85 changes: 42 additions & 43 deletions frontend/components/LiveQuery/SelectTargets.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ import targetsAPI, {
} from "services/entities/targets";
import teamsAPI, { ILoadTeamsResponse } from "services/entities/teams";
import { formatSelectedTargetsForApi } from "utilities/helpers";
import { capitalize } from "lodash";
import permissions from "utilities/permissions";
import {
LABEL_DISPLAY_MAP,
Expand Down Expand Up @@ -111,7 +112,7 @@ const parseLabels = (list?: ILabelSummary[]) => {
};

/** Returns the index at which the sum of the names in the list exceed the maximum character length */
const getTruncatedLength = (
const getTruncatedEntityCount = (
list: ISelectLabel[] | ISelectTeam[],
maxLength: number
): number => {
Expand Down Expand Up @@ -175,10 +176,10 @@ const SelectTargets = ({
const [labels, setLabels] = useState<ILabelsByType | null>(null);
const [searchTextHosts, setSearchTextHosts] = useState("");
const [searchTextTeams, setSearchTextTeams] = useState<string | undefined>(
RachelElysia marked this conversation as resolved.
Show resolved Hide resolved
undefined
""
RachelElysia marked this conversation as resolved.
Show resolved Hide resolved
);
const [searchTextLabels, setSearchTextLabels] = useState<string | undefined>(
undefined
""
);
const [isTeamListExpanded, setIsTeamListExpanded] = useState(false);
const [isLabelsListExpanded, setIsLabelsListExpanded] = useState(false);
Expand Down Expand Up @@ -281,27 +282,28 @@ const SelectTargets = ({

// Ensure that the team or label list is expanded on the first load only if a hidden entity is already selected
RachelElysia marked this conversation as resolved.
Show resolved Hide resolved
const shouldExpandList = (
list: ISelectLabel[] | ISelectTeam[],
targetedList: ISelectLabel[] | ISelectTeam[],
truncatedList: ISelectLabel[] | ISelectTeam[]
) => {
return list.some(
(entity) =>
!truncatedList.some(
(truncatedEntity) => truncatedEntity.id === entity.id
)
);
// Set used to improve lookup time
const truncatedIds = new Set(truncatedList.map((entity) => entity.id));

// Check if any entity targeted is not in truncated list shown
return targetedList.some((entity) => !truncatedIds.has(entity.id));
};

const expandListsOnInitialLoad = () => {
RachelElysia marked this conversation as resolved.
Show resolved Hide resolved
if (!isMountedRef.current && teams && labels) {
const truncatedLabels =
labels?.other?.slice(
0,
getTruncatedLength(labels?.other, SECTION_CHARACTER_LIMIT)
getTruncatedEntityCount(labels?.other, SECTION_CHARACTER_LIMIT)
) || [];
const truncatedTeams =
teams?.slice(0, getTruncatedLength(teams, SECTION_CHARACTER_LIMIT)) ||
[];
teams?.slice(
0,
getTruncatedEntityCount(teams, SECTION_CHARACTER_LIMIT)
) || [];

if (shouldExpandList(targetedLabels, truncatedLabels)) {
setIsLabelsListExpanded(true);
Expand Down Expand Up @@ -404,35 +406,36 @@ const SelectTargets = ({
};

const renderTargetEntitySection = (
header: string,
entityType: string,
entityList: ISelectLabel[] | ISelectTeam[]
): JSX.Element => {
const isSearchEnabled = header === "Teams" || header === "Labels";
const searchTerm: string =
(header === "Teams" ? searchTextTeams : searchTextLabels) || "";
const isSearchEnabled = entityType === "teams" || entityType === "labels";
const searchTerm = (
(entityType === "teams" ? searchTextTeams : searchTextLabels) || ""
).toLowerCase();
const arrFixed = entityList as Array<typeof entityList[number]>;
const filteredEntities = arrFixed.filter(
(entity: ISelectLabel | ISelectTeam) => {
if (isSearchEnabled) {
return searchTerm
? entity.name.toLowerCase().includes(searchTerm.toLowerCase())
: true;
}
return true;
}
);
const filteredEntities = isSearchEnabled
? arrFixed.filter((entity: ISelectLabel | ISelectTeam) => {
if (isSearchEnabled) {
return searchTerm
? entity.name.toLowerCase().includes(searchTerm)
: true;
}
return true;
})
: arrFixed;

const isListExpanded =
header === "Teams" ? isTeamListExpanded : isLabelsListExpanded;
entityType === "teams" ? isTeamListExpanded : isLabelsListExpanded;
const truncatedEntities = filteredEntities.slice(
0,
getTruncatedLength(filteredEntities, SECTION_CHARACTER_LIMIT)
getTruncatedEntityCount(filteredEntities, SECTION_CHARACTER_LIMIT)
);
const hiddenEntityCount =
filteredEntities.length - truncatedEntities.length;

const toggleExpansion = () => {
header === "Teams"
entityType === "teams"
? setIsTeamListExpanded(!isTeamListExpanded)
: setIsLabelsListExpanded(!isLabelsListExpanded);
};
Expand All @@ -441,9 +444,7 @@ const SelectTargets = ({
? filteredEntities
: truncatedEntities;

const emptySearchString = `No matching ${
header === "Teams" ? "teams" : "labels"
}.`;
const emptySearchString = `No matching ${entityType}.`;

const renderEmptySearchString = () => {
if (entitiesToDisplay.length === 0 && searchTerm !== "") {
Expand All @@ -458,17 +459,15 @@ const SelectTargets = ({

return (
<>
{header && <h3>{header}</h3>}
{entityType && <h3>{capitalize(entityType)}</h3>}
{isSearchEnabled && (
<>
<SearchField
placeholder={
header === "Teams" ? "Search teams" : "Search labels"
}
placeholder={`Search ${entityType}`}
onChange={(searchString) => {
header === "Teams"
? setSearchTextTeams(searchString || undefined)
: setSearchTextLabels(searchString || undefined);
entityType === "teams"
? setSearchTextTeams(searchString)
: setSearchTextLabels(searchString);
}}
clearButton
/>
Expand Down Expand Up @@ -616,13 +615,13 @@ const SelectTargets = ({
renderTargetEntitySection("Platforms", labels.platforms)}
{!!teams?.length &&
(isOnGlobalTeam
? renderTargetEntitySection("Teams", [
? renderTargetEntitySection("teams", [
{ id: 0, name: "No team" },
...teams,
])
: renderTargetEntitySection("Teams", filterTeamObserverTeams()))}
: renderTargetEntitySection("teams", filterTeamObserverTeams()))}
{!!labels?.other?.length &&
renderTargetEntitySection("Labels", labels.other)}
renderTargetEntitySection("labels", labels.other)}
</div>
<TargetsInput
autofocus
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,7 @@ import AceEditor from "react-ace";
import classnames from "classnames";

import { humanHostMemory } from "utilities/helpers";
// @ts-ignore
import FleetIcon from "components/icons/FleetIcon";
// @ts-ignore
import PlatformIcon from "components/icons/PlatformIcon";
import { ISelectHost, ISelectLabel, ISelectTeam } from "interfaces/target";

Expand All @@ -25,27 +23,6 @@ const TargetDetails = ({
className = "",
handleBackToResults = noop,
}: ITargetDetailsProps): JSX.Element => {
const onlineHosts = (
labelBaseClass: string,
count: number,
online: number
) => {
const offline = count - online;
const percentCount = ((count - offline) / count) * 100;
const percentOnline = parseFloat(percentCount.toFixed(2));

if (online > 0) {
return (
<span className={`${labelBaseClass}__hosts-online`}>
{" "}
({percentOnline}% ONLINE)
</span>
);
}

return false;
};

const renderHost = (hostTarget: ISelectHost) => {
const {
display_text: displayText,
Expand Down Expand Up @@ -143,9 +120,9 @@ const TargetDetails = ({
description,
display_text: displayText,
label_type: labelType,
RachelElysia marked this conversation as resolved.
Show resolved Hide resolved
// online,
query,
} = labelTarget;

const labelBaseClass = "label-target";
return (
RachelElysia marked this conversation as resolved.
Show resolved Hide resolved
<div className={`${labelBaseClass} ${className}`}>
Expand All @@ -165,7 +142,6 @@ const TargetDetails = ({
<span className={`${labelBaseClass}__hosts-count`}>
<strong>{count}</strong>HOSTS
</span>
{/* {onlineHosts(labelBaseClass, count, online)} */}
</p>

<p className={`${labelBaseClass}__description`}>
Expand Down
1 change: 0 additions & 1 deletion frontend/components/icons/PlatformIcon/PlatformIcon.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import React from "react";
import classnames from "classnames";

// @ts-ignore
import FleetIcon from "components/icons/FleetIcon";
import platformIconClass from "utilities/platform_icon_class";

Expand Down
Loading