From 097eb16bb88212fed1e095866c95a3ad49b577cf Mon Sep 17 00:00:00 2001 From: RachelElysia Date: Mon, 16 Dec 2024 12:31:29 -0500 Subject: [PATCH 01/10] Fleet UI: Allow select target search for labels and teams --- .../components/LiveQuery/SelectTargets.tsx | 158 ++++++++++++++++-- .../InputFieldWithIcon/InputFieldWithIcon.jsx | 17 ++ .../fields/InputFieldWithIcon/_styles.scss | 11 ++ .../forms/fields/SearchField/SearchField.tsx | 3 + .../TargetDetails/TargetDetails.tsx | 1 - .../pages/policies/PolicyPage/_styles.scss | 9 + .../queries/live/LiveQueryPage/_styles.scss | 9 + 7 files changed, 193 insertions(+), 15 deletions(-) diff --git a/frontend/components/LiveQuery/SelectTargets.tsx b/frontend/components/LiveQuery/SelectTargets.tsx index 566a8e35da18..db3c001193c8 100644 --- a/frontend/components/LiveQuery/SelectTargets.tsx +++ b/frontend/components/LiveQuery/SelectTargets.tsx @@ -1,4 +1,4 @@ -import React, { useContext, useEffect, useState } from "react"; +import React, { useContext, useEffect, useState, useRef } from "react"; import { Row } from "react-table"; import { useQuery } from "react-query"; import { useDebouncedCallback } from "use-debounce"; @@ -35,6 +35,8 @@ import Button from "components/buttons/Button"; import Spinner from "components/Spinner"; import TooltipWrapper from "components/TooltipWrapper"; import Icon from "components/Icon"; +import SearchField from "components/forms/fields/SearchField"; +import RevealButton from "components/buttons/RevealButton"; import { generateTableHeaders } from "./TargetsInput/TargetsInputHostsTableConfig"; interface ITargetPillSelectorProps { @@ -80,6 +82,7 @@ interface ITargetsQueryKey { const DEBOUNCE_DELAY = 500; const STALE_TIME = 60000; +const SECTION_CHARACTER_LIMIT = 100; const isLabel = (entity: ISelectTargetsEntity) => "label_type" in entity; const isBuiltInLabel = ( @@ -107,6 +110,20 @@ const parseLabels = (list?: ILabelSummary[]) => { return { allHosts, platforms, other }; }; +/** Returns the index at which the sum of the names in the list exceed the maximum character length */ +const getTruncatedLength = ( + list: ISelectLabel[] | ISelectTeam[], + maxLength: number +): number => { + let totalLength = 0; + let index = 0; + while (index < list.length && totalLength < maxLength) { + totalLength += list[index].name.length; + index += 1; + } + return index; +}; + const TargetPillSelector = ({ entity, isSelected, @@ -152,10 +169,19 @@ const SelectTargets = ({ isLivePolicy, isObserverCanRunQuery, }: ISelectTargetsProps): JSX.Element => { + const isMountedRef = useRef(false); const { isPremiumTier, isOnGlobalTeam, currentUser } = useContext(AppContext); const [labels, setLabels] = useState(null); - const [searchText, setSearchText] = useState(""); + const [searchTextHosts, setSearchTextHosts] = useState(""); + const [searchTextTeams, setSearchTextTeams] = useState( + undefined + ); + const [searchTextLabels, setSearchTextLabels] = useState( + undefined + ); + const [isTeamListExpanded, setIsTeamListExpanded] = useState(false); + const [isLabelsListExpanded, setIsLabelsListExpanded] = useState(false); const [debouncedSearchText, setDebouncedSearchText] = useState(""); const [isDebouncing, setIsDebouncing] = useState(false); @@ -253,6 +279,50 @@ const SelectTargets = ({ } ); + // Ensure that the team or label list is expanded on the first load only if a hidden entity is already selected + const shouldExpandList = ( + list: ISelectLabel[] | ISelectTeam[], + truncatedList: ISelectLabel[] | ISelectTeam[] + ) => { + return list.some( + (entity) => + !truncatedList.some( + (truncatedEntity) => truncatedEntity.id === entity.id + ) + ); + }; + + const expandListsOnInitialLoad = () => { + if (!isMountedRef.current && teams && labels) { + const truncatedLabels = + labels?.other?.slice( + 0, + getTruncatedLength(labels?.other, SECTION_CHARACTER_LIMIT) + ) || []; + const truncatedTeams = + teams?.slice(0, getTruncatedLength(teams, SECTION_CHARACTER_LIMIT)) || + []; + + if (shouldExpandList(targetedLabels, truncatedLabels)) { + setIsLabelsListExpanded(true); + } + + if (shouldExpandList(targetedTeams, truncatedTeams)) { + setIsTeamListExpanded(true); + } + + isMountedRef.current = true; + } + }; + + useEffect(expandListsOnInitialLoad, [ + targetedTeams, + targetedLabels, + labels, + teams, + isMountedRef, + ]); + useEffect(() => { const selected = [...targetedHosts, ...targetedLabels, ...targetedTeams]; setSelectedTargets(selected); @@ -264,8 +334,8 @@ const SelectTargets = ({ useEffect(() => { setIsDebouncing(true); - debounceSearch(searchText); - }, [searchText]); + debounceSearch(searchTextHosts); + }, [searchTextHosts]); const handleClickCancel = () => { goToQueryEditor(); @@ -313,7 +383,7 @@ const SelectTargets = ({ const handleRowSelect = (row: Row) => { setTargetedHosts((prevHosts) => prevHosts.concat(row.original)); - setSearchText(""); + setSearchTextHosts(""); // If "all hosts" is already selected when using host target picker, deselect "all hosts" if (targetedLabels.some((t) => isAllHosts(t))) { @@ -333,15 +403,60 @@ const SelectTargets = ({ goToRunQuery(); }; - const renderTargetEntityList = ( + const renderTargetEntitySection = ( header: string, entityList: ISelectLabel[] | ISelectTeam[] ): JSX.Element => { + const isSearchEnabled = header === "Teams" || header === "Labels"; + const arrFixed = entityList as Array; + const filteredEntities = arrFixed.filter( + (entity: ISelectLabel | ISelectTeam) => { + if (isSearchEnabled) { + const searchTerm: string = + (header === "Teams" ? searchTextTeams : searchTextLabels) || ""; + return searchTerm + ? entity.name.toLowerCase().includes(searchTerm.toLowerCase()) + : true; + } + return true; + } + ); + + const isListExpanded = + header === "Teams" ? isTeamListExpanded : isLabelsListExpanded; + const truncatedEntities = filteredEntities.slice( + 0, + getTruncatedLength(filteredEntities, SECTION_CHARACTER_LIMIT) + ); + const hiddenEntityCount = + filteredEntities.length - truncatedEntities.length; + + const toggleExpansion = () => { + header === "Teams" + ? setIsTeamListExpanded(!isTeamListExpanded) + : setIsLabelsListExpanded(!isLabelsListExpanded); + }; + + const entitiesToDisplay = isListExpanded + ? filteredEntities + : truncatedEntities; + return ( <> {header &&

{header}

} + {isSearchEnabled && ( + { + header === "Teams" + ? setSearchTextTeams(searchString || undefined) + : setSearchTextLabels(searchString || undefined); + }} + clearButton + /> + )}
- {entityList?.map((entity: ISelectLabel | ISelectTeam) => { + {entitiesToDisplay?.map((entity: ISelectLabel | ISelectTeam) => { const targetList = isLabel(entity) ? targetedLabels : targetedTeams; return ( + {hiddenEntityCount > 0 && ( +
+ +
+ )} ); }; @@ -456,34 +582,38 @@ const SelectTargets = ({ ); }; + if (isLoadingLabels || isLoadingTeams) { + return ; + } + return (

Select targets

{!!labels?.allHosts.length && - renderTargetEntityList("", labels.allHosts)} + renderTargetEntitySection("", labels.allHosts)} {!!labels?.platforms?.length && - renderTargetEntityList("Platforms", labels.platforms)} + renderTargetEntitySection("Platforms", labels.platforms)} {!!teams?.length && (isOnGlobalTeam - ? renderTargetEntityList("Teams", [ + ? renderTargetEntitySection("Teams", [ { id: 0, name: "No team" }, ...teams, ]) - : renderTargetEntityList("Teams", filterTeamObserverTeams()))} + : renderTargetEntitySection("Teams", filterTeamObserverTeams()))} {!!labels?.other?.length && - renderTargetEntityList("Labels", labels.other)} + renderTargetEntitySection("Labels", labels.other)}
diff --git a/frontend/components/forms/fields/InputFieldWithIcon/InputFieldWithIcon.jsx b/frontend/components/forms/fields/InputFieldWithIcon/InputFieldWithIcon.jsx index 3b416ee3fda8..8cd3b3f526e4 100644 --- a/frontend/components/forms/fields/InputFieldWithIcon/InputFieldWithIcon.jsx +++ b/frontend/components/forms/fields/InputFieldWithIcon/InputFieldWithIcon.jsx @@ -5,6 +5,7 @@ import classnames from "classnames"; import Icon from "components/Icon/Icon"; import FleetIcon from "components/icons/FleetIcon"; import TooltipWrapper from "components/TooltipWrapper"; +import Button from "components/buttons/Button"; import InputField from "../InputField"; const baseClass = "input-icon-field"; @@ -20,6 +21,7 @@ class InputFieldWithIcon extends InputField { name: PropTypes.string, onChange: PropTypes.func, onClick: PropTypes.func, + clearButton: PropTypes.func, placeholder: PropTypes.string, tabIndex: PropTypes.number, type: PropTypes.string, @@ -86,6 +88,8 @@ class InputFieldWithIcon extends InputField { inputOptions, ignore1Password, onClick, + onChange, + clearButton, } = this.props; const { onInputChange, renderHelpText } = this; @@ -111,6 +115,10 @@ class InputFieldWithIcon extends InputField { { [`${baseClass}__icon--active`]: value } ); + const handleClear = () => { + onChange(""); + }; + return (
{this.props.label && this.renderHeading()} @@ -134,6 +142,15 @@ class InputFieldWithIcon extends InputField { /> {iconSvg && } {iconName && } + {clearButton && !!value && ( + + )}
{renderHelpText()}
diff --git a/frontend/components/forms/fields/InputFieldWithIcon/_styles.scss b/frontend/components/forms/fields/InputFieldWithIcon/_styles.scss index 13ff7bbb2a23..4ee9826bc662 100644 --- a/frontend/components/forms/fields/InputFieldWithIcon/_styles.scss +++ b/frontend/components/forms/fields/InputFieldWithIcon/_styles.scss @@ -150,4 +150,15 @@ input[type="search"]::-webkit-search-results-decoration { display: none; } + + &__clear-button { + position: absolute; + right: 12px; + top: 0; + height: 40px; + width: 16px; + flex-wrap: wrap; + align-content: center; + z-index: 1; + } } diff --git a/frontend/components/forms/fields/SearchField/SearchField.tsx b/frontend/components/forms/fields/SearchField/SearchField.tsx index d487d8932412..13461a064395 100644 --- a/frontend/components/forms/fields/SearchField/SearchField.tsx +++ b/frontend/components/forms/fields/SearchField/SearchField.tsx @@ -9,6 +9,7 @@ export interface ISearchFieldProps { defaultValue?: string; onChange: (value: string) => void; onClick?: (e: React.MouseEvent) => void; + clearButton?: boolean; icon?: IconNames; } @@ -16,6 +17,7 @@ const SearchField = ({ placeholder, defaultValue = "", onChange, + clearButton, onClick, icon = "search", }: ISearchFieldProps): JSX.Element => { @@ -37,6 +39,7 @@ const SearchField = ({ value={searchQueryInput} onChange={onInputChange} onClick={onClick} + clearButton={clearButton} iconPosition="start" iconSvg={icon} /> diff --git a/frontend/components/forms/fields/SelectTargetsDropdown/TargetDetails/TargetDetails.tsx b/frontend/components/forms/fields/SelectTargetsDropdown/TargetDetails/TargetDetails.tsx index d3780901c695..ee9d49618494 100644 --- a/frontend/components/forms/fields/SelectTargetsDropdown/TargetDetails/TargetDetails.tsx +++ b/frontend/components/forms/fields/SelectTargetsDropdown/TargetDetails/TargetDetails.tsx @@ -147,7 +147,6 @@ const TargetDetails = ({ query, } = labelTarget; const labelBaseClass = "label-target"; - console.log("ERROR 1: labelTarget", labelTarget); return (
)}
diff --git a/frontend/pages/policies/PolicyPage/_styles.scss b/frontend/pages/policies/PolicyPage/_styles.scss index a4ab8d9a43a9..21236bf8bbcf 100644 --- a/frontend/pages/policies/PolicyPage/_styles.scss +++ b/frontend/pages/policies/PolicyPage/_styles.scss @@ -70,9 +70,7 @@ } &__empty-entity-search { - color: #000; - text-align: center; - + margin-top: $pad-small; font-size: $x-small; line-height: 21px; /* 150% */ } diff --git a/frontend/pages/queries/live/LiveQueryPage/_styles.scss b/frontend/pages/queries/live/LiveQueryPage/_styles.scss index ba0a897f12d9..b3340cc34caf 100644 --- a/frontend/pages/queries/live/LiveQueryPage/_styles.scss +++ b/frontend/pages/queries/live/LiveQueryPage/_styles.scss @@ -38,9 +38,7 @@ } &__empty-entity-search { - color: #000; - text-align: center; - + margin-top: $pad-small; font-size: $x-small; line-height: 21px; /* 150% */ } From a0db0635d47065df08943d7fff86f37e27906815 Mon Sep 17 00:00:00 2001 From: RachelElysia Date: Mon, 16 Dec 2024 15:54:21 -0500 Subject: [PATCH 04/10] Center align no matching hosts --- frontend/components/LiveQuery/TargetsInput/_styles.scss | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/frontend/components/LiveQuery/TargetsInput/_styles.scss b/frontend/components/LiveQuery/TargetsInput/_styles.scss index 782ab932bec2..2124f1ce8f00 100644 --- a/frontend/components/LiveQuery/TargetsInput/_styles.scss +++ b/frontend/components/LiveQuery/TargetsInput/_styles.scss @@ -47,6 +47,12 @@ box-shadow: 0px 4px 10px rgba(52, 59, 96, 0.15); box-sizing: border-box; + &__inner { + display: flex; + flex-direction: column; + align-items: center; + } + h4 { margin: 0; margin-bottom: 16px; From 705f833146910080c9cff79493cb3e8ffde0d0e7 Mon Sep 17 00:00:00 2001 From: RachelElysia Date: Mon, 16 Dec 2024 15:55:10 -0500 Subject: [PATCH 05/10] Character limit 600 not 100 --- frontend/components/LiveQuery/SelectTargets.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frontend/components/LiveQuery/SelectTargets.tsx b/frontend/components/LiveQuery/SelectTargets.tsx index b284714ba8bf..fb2593db9776 100644 --- a/frontend/components/LiveQuery/SelectTargets.tsx +++ b/frontend/components/LiveQuery/SelectTargets.tsx @@ -83,7 +83,7 @@ interface ITargetsQueryKey { const DEBOUNCE_DELAY = 500; const STALE_TIME = 60000; -const SECTION_CHARACTER_LIMIT = 100; +const SECTION_CHARACTER_LIMIT = 600; const isLabel = (entity: ISelectTargetsEntity) => "label_type" in entity; const isBuiltInLabel = ( From 0be404704824121ba7f6ae0366b0abdb14854853 Mon Sep 17 00:00:00 2001 From: RachelElysia Date: Mon, 16 Dec 2024 16:13:33 -0500 Subject: [PATCH 06/10] Add tests to untested inputfieldwithicon comonent --- .../InputFieldWithIcon.tests.tsx | 129 ++++++++++++++++++ 1 file changed, 129 insertions(+) create mode 100644 frontend/components/forms/fields/InputFieldWithIcon/InputFieldWithIcon.tests.tsx diff --git a/frontend/components/forms/fields/InputFieldWithIcon/InputFieldWithIcon.tests.tsx b/frontend/components/forms/fields/InputFieldWithIcon/InputFieldWithIcon.tests.tsx new file mode 100644 index 000000000000..3a57042745a7 --- /dev/null +++ b/frontend/components/forms/fields/InputFieldWithIcon/InputFieldWithIcon.tests.tsx @@ -0,0 +1,129 @@ +import React from "react"; +import { render, screen, fireEvent } from "@testing-library/react"; +import userEvent from "@testing-library/user-event"; +// @ts-ignore +import InputFieldWithIcon from "./InputFieldWithIcon"; + +describe("InputFieldWithIcon Component", () => { + const mockOnChange = jest.fn(); + + beforeEach(() => { + jest.clearAllMocks(); + }); + + test("renders with label and placeholder", () => { + render( + + ); + + expect(screen.getByText(/test input/i)).toBeInTheDocument(); + expect(screen.getByPlaceholderText(/enter text/i)).toBeInTheDocument(); + }); + + test("calls onChange when input value changes", async () => { + render( + + ); + + // Change the input value + await userEvent.type( + screen.getByPlaceholderText(/enter text/i), + "New Value" + ); + + expect(mockOnChange).toHaveBeenCalledTimes(9); // 'New Value' has 9 characters + }); + + test("renders help text when provided", () => { + render( + + ); + + expect(screen.getByText(/this is a help text/i)).toBeInTheDocument(); + }); + + test("renders error message when provided", () => { + render( + + ); + + expect(screen.getByText(/this is an error message/i)).toBeInTheDocument(); + }); + + test("renders clear button when clearButton is true and input has value", () => { + render( + + ); + + expect(screen.getByRole("button")).toBeInTheDocument(); + }); + + test("clears input value when clear button is clicked", async () => { + render( + + ); + + // Click the clear button + await userEvent.click(screen.getByRole("button")); + + expect(mockOnChange).toHaveBeenCalledTimes(1); + expect(mockOnChange).toHaveBeenCalledWith(""); + }); + + test("renders tooltip when provided", async () => { + render( + + ); + + await fireEvent.mouseEnter(screen.getByText(/test input/i)); + const tooltip = screen.getByText("This is a tooltip."); + expect(tooltip).toBeInTheDocument(); + }); +}); From 2e081eab579274eaa30f1d0931f816175062a5bb Mon Sep 17 00:00:00 2001 From: RachelElysia Date: Mon, 16 Dec 2024 16:14:32 -0500 Subject: [PATCH 07/10] Add changelog --- changes/22448-searchable-query-targets | 1 + 1 file changed, 1 insertion(+) create mode 100644 changes/22448-searchable-query-targets diff --git a/changes/22448-searchable-query-targets b/changes/22448-searchable-query-targets new file mode 100644 index 000000000000..5cbb33f42d34 --- /dev/null +++ b/changes/22448-searchable-query-targets @@ -0,0 +1 @@ +- Fleet UI: Add searchable query targets and cleaner UI I for uses with many teams or labels From 85ed15d4c07908536bd9c9a7b4bd966273be8e0d Mon Sep 17 00:00:00 2001 From: RachelElysia Date: Mon, 16 Dec 2024 16:19:13 -0500 Subject: [PATCH 08/10] Remove unused import --- frontend/components/LiveQuery/SelectTargets.tsx | 1 - 1 file changed, 1 deletion(-) diff --git a/frontend/components/LiveQuery/SelectTargets.tsx b/frontend/components/LiveQuery/SelectTargets.tsx index fb2593db9776..00940bdaaa43 100644 --- a/frontend/components/LiveQuery/SelectTargets.tsx +++ b/frontend/components/LiveQuery/SelectTargets.tsx @@ -38,7 +38,6 @@ import Icon from "components/Icon"; import SearchField from "components/forms/fields/SearchField"; import RevealButton from "components/buttons/RevealButton"; import { generateTableHeaders } from "./TargetsInput/TargetsInputHostsTableConfig"; -import { isEmpty } from "lodash"; interface ITargetPillSelectorProps { entity: ISelectLabel | ISelectTeam; From 07615728d8cb499999d9ca8b0379f2d1aae5454c Mon Sep 17 00:00:00 2001 From: RachelElysia Date: Mon, 23 Dec 2024 14:11:20 -0500 Subject: [PATCH 09/10] Clean code from review suggestions --- .../components/LiveQuery/SelectTargets.tsx | 85 +++++++++---------- .../TargetDetails/TargetDetails.tsx | 26 +----- .../icons/PlatformIcon/PlatformIcon.tsx | 1 - 3 files changed, 43 insertions(+), 69 deletions(-) diff --git a/frontend/components/LiveQuery/SelectTargets.tsx b/frontend/components/LiveQuery/SelectTargets.tsx index 00940bdaaa43..2f57f51c320b 100644 --- a/frontend/components/LiveQuery/SelectTargets.tsx +++ b/frontend/components/LiveQuery/SelectTargets.tsx @@ -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, @@ -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 => { @@ -175,10 +176,10 @@ const SelectTargets = ({ const [labels, setLabels] = useState(null); const [searchTextHosts, setSearchTextHosts] = useState(""); const [searchTextTeams, setSearchTextTeams] = useState( - undefined + "" ); const [searchTextLabels, setSearchTextLabels] = useState( - undefined + "" ); const [isTeamListExpanded, setIsTeamListExpanded] = useState(false); const [isLabelsListExpanded, setIsLabelsListExpanded] = useState(false); @@ -281,15 +282,14 @@ const SelectTargets = ({ // Ensure that the team or label list is expanded on the first load only if a hidden entity is already selected 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 = () => { @@ -297,11 +297,13 @@ const SelectTargets = ({ 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); @@ -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; - 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); }; @@ -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 !== "") { @@ -458,17 +459,15 @@ const SelectTargets = ({ return ( <> - {header &&

{header}

} + {entityType &&

{capitalize(entityType)}

} {isSearchEnabled && ( <> { - header === "Teams" - ? setSearchTextTeams(searchString || undefined) - : setSearchTextLabels(searchString || undefined); + entityType === "teams" + ? setSearchTextTeams(searchString) + : setSearchTextLabels(searchString); }} clearButton /> @@ -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)}
{ - 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 ( - - {" "} - ({percentOnline}% ONLINE) - - ); - } - - return false; - }; - const renderHost = (hostTarget: ISelectHost) => { const { display_text: displayText, @@ -143,9 +120,9 @@ const TargetDetails = ({ description, display_text: displayText, label_type: labelType, - // online, query, } = labelTarget; + const labelBaseClass = "label-target"; return (
@@ -165,7 +142,6 @@ const TargetDetails = ({ {count}HOSTS - {/* {onlineHosts(labelBaseClass, count, online)} */}

diff --git a/frontend/components/icons/PlatformIcon/PlatformIcon.tsx b/frontend/components/icons/PlatformIcon/PlatformIcon.tsx index 4077ea4cf807..36f1c3497fce 100644 --- a/frontend/components/icons/PlatformIcon/PlatformIcon.tsx +++ b/frontend/components/icons/PlatformIcon/PlatformIcon.tsx @@ -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"; From af79feccd2428c1856bcb3701a892649c37ca9e6 Mon Sep 17 00:00:00 2001 From: RachelElysia Date: Mon, 23 Dec 2024 15:49:49 -0500 Subject: [PATCH 10/10] Cleanuppp --- frontend/components/LiveQuery/SelectTargets.tsx | 8 ++------ .../SelectTargetsDropdown/TargetDetails/TargetDetails.tsx | 1 - 2 files changed, 2 insertions(+), 7 deletions(-) diff --git a/frontend/components/LiveQuery/SelectTargets.tsx b/frontend/components/LiveQuery/SelectTargets.tsx index 2f57f51c320b..0e7b68053ddc 100644 --- a/frontend/components/LiveQuery/SelectTargets.tsx +++ b/frontend/components/LiveQuery/SelectTargets.tsx @@ -175,12 +175,8 @@ const SelectTargets = ({ const [labels, setLabels] = useState(null); const [searchTextHosts, setSearchTextHosts] = useState(""); - const [searchTextTeams, setSearchTextTeams] = useState( - "" - ); - const [searchTextLabels, setSearchTextLabels] = useState( - "" - ); + const [searchTextTeams, setSearchTextTeams] = useState(""); + const [searchTextLabels, setSearchTextLabels] = useState(""); const [isTeamListExpanded, setIsTeamListExpanded] = useState(false); const [isLabelsListExpanded, setIsLabelsListExpanded] = useState(false); const [debouncedSearchText, setDebouncedSearchText] = useState(""); diff --git a/frontend/components/forms/fields/SelectTargetsDropdown/TargetDetails/TargetDetails.tsx b/frontend/components/forms/fields/SelectTargetsDropdown/TargetDetails/TargetDetails.tsx index 5da0db637b09..1786c571a296 100644 --- a/frontend/components/forms/fields/SelectTargetsDropdown/TargetDetails/TargetDetails.tsx +++ b/frontend/components/forms/fields/SelectTargetsDropdown/TargetDetails/TargetDetails.tsx @@ -119,7 +119,6 @@ const TargetDetails = ({ count, description, display_text: displayText, - label_type: labelType, query, } = labelTarget;