add ability to create manual labels (#18303)
relates to #17031 Adds functionality to create manual labels in fleet. - [x] Changes file added for user-visible changes in `changes/`, `orbit/changes/` or `ee/fleetd-chrome/changes`. See [Changes files](https://fleetdm.com/docs/contributing/committing-changes#changes-files) for more information. - [x] Added/updated tests - [x] M0anual QA for all new/changed functionality --------- Co-authored-by: Martin Angers <martin.n.angers@gmail.com>
This commit is contained in:
co-authored by
Martin Angers
parent
de94299b65
commit
2fc4e520b8
@@ -30,6 +30,7 @@ import Button from "components/buttons/Button";
|
||||
import Spinner from "components/Spinner";
|
||||
import TooltipWrapper from "components/TooltipWrapper";
|
||||
import Icon from "components/Icon";
|
||||
import { generateTableHeaders } from "./TargetsInput/TargetsInputHostsTableConfig";
|
||||
|
||||
interface ITargetPillSelectorProps {
|
||||
entity: ISelectLabel | ISelectTeam;
|
||||
@@ -305,9 +306,8 @@ const SelectTargets = ({
|
||||
: setTargetedTeams(newTargets as ITeam[]);
|
||||
};
|
||||
|
||||
const handleRowSelect = (row: Row) => {
|
||||
const selectedHost = row.original as IHost;
|
||||
setTargetedHosts((prevHosts) => prevHosts.concat(selectedHost));
|
||||
const handleRowSelect = (row: Row<IHost>) => {
|
||||
setTargetedHosts((prevHosts) => prevHosts.concat(row.original));
|
||||
setSearchText("");
|
||||
|
||||
// If "all hosts" is already selected when using host target picker, deselect "all hosts"
|
||||
@@ -434,6 +434,9 @@ const SelectTargets = ({
|
||||
);
|
||||
}
|
||||
|
||||
const resultsTableConfig = generateTableHeaders();
|
||||
const selectedHostsTableConfig = generateTableHeaders(handleRowRemove);
|
||||
|
||||
return (
|
||||
<div className={`${baseClass}__wrapper`}>
|
||||
<h1>Select targets</h1>
|
||||
@@ -451,6 +454,9 @@ const SelectTargets = ({
|
||||
renderTargetEntityList("Labels", labels.other)}
|
||||
</div>
|
||||
<TargetsInput
|
||||
autofocus
|
||||
searchResultsTableConfig={resultsTableConfig}
|
||||
selectedHostsTableConifg={selectedHostsTableConfig}
|
||||
tabIndex={inputTabIndex || 0}
|
||||
searchText={searchText}
|
||||
searchResults={searchResults || []}
|
||||
@@ -459,7 +465,7 @@ const SelectTargets = ({
|
||||
hasFetchError={!!errorSearchResults}
|
||||
setSearchText={setSearchText}
|
||||
handleRowSelect={handleRowSelect}
|
||||
handleRowRemove={handleRowRemove}
|
||||
disablePagination
|
||||
/>
|
||||
<div className={`${baseClass}__targets-button-wrap`}>
|
||||
<Button
|
||||
|
||||
@@ -9,22 +9,31 @@ import DataError from "components/DataError";
|
||||
// @ts-ignore
|
||||
import InputFieldWithIcon from "components/forms/fields/InputFieldWithIcon/InputFieldWithIcon";
|
||||
import TableContainer from "components/TableContainer";
|
||||
import { generateTableHeaders } from "./TargetsInputHostsTableConfig";
|
||||
import { ITargestInputHostTableConfig } from "./TargetsInputHostsTableConfig";
|
||||
|
||||
interface ITargetsInputProps {
|
||||
tabIndex: number;
|
||||
tabIndex?: number;
|
||||
searchText: string;
|
||||
searchResults: IHost[];
|
||||
isTargetsLoading: boolean;
|
||||
hasFetchError: boolean;
|
||||
targetedHosts: IHost[];
|
||||
searchResultsTableConfig: ITargestInputHostTableConfig[];
|
||||
selectedHostsTableConifg: ITargestInputHostTableConfig[];
|
||||
/** disabled pagination for the results table. The pagination is currently
|
||||
* client side pagination. Defaults to `false` */
|
||||
disablePagination?: boolean;
|
||||
label?: string;
|
||||
placeholder?: string;
|
||||
autofocus?: boolean;
|
||||
setSearchText: (value: string) => void;
|
||||
handleRowSelect: (value: Row) => void;
|
||||
handleRowRemove: (value: Row<IHost>) => void;
|
||||
handleRowSelect: (value: Row<IHost>) => void;
|
||||
}
|
||||
|
||||
const baseClass = "targets-input";
|
||||
|
||||
const DEFAULT_LABEL = "Target specific hosts";
|
||||
|
||||
const TargetsInput = ({
|
||||
tabIndex,
|
||||
searchText,
|
||||
@@ -32,12 +41,15 @@ const TargetsInput = ({
|
||||
isTargetsLoading,
|
||||
hasFetchError,
|
||||
targetedHosts,
|
||||
searchResultsTableConfig,
|
||||
selectedHostsTableConifg,
|
||||
disablePagination = false,
|
||||
label = DEFAULT_LABEL,
|
||||
placeholder = HOSTS_SEARCH_BOX_PLACEHOLDER,
|
||||
autofocus = false,
|
||||
handleRowSelect,
|
||||
handleRowRemove,
|
||||
setSearchText,
|
||||
}: ITargetsInputProps): JSX.Element => {
|
||||
const resultsDropdownTableHeaders = generateTableHeaders();
|
||||
const selectedTableHeaders = generateTableHeaders(handleRowRemove);
|
||||
const dropdownHosts =
|
||||
searchResults && pullAllBy(searchResults, targetedHosts, "display_name");
|
||||
const isActiveSearch =
|
||||
@@ -48,20 +60,20 @@ const TargetsInput = ({
|
||||
<div>
|
||||
<div className={baseClass}>
|
||||
<InputFieldWithIcon
|
||||
autofocus
|
||||
autofocus={autofocus}
|
||||
type="search"
|
||||
iconSvg="search"
|
||||
value={searchText}
|
||||
tabIndex={tabIndex}
|
||||
iconPosition="start"
|
||||
label="Target specific hosts"
|
||||
placeholder={HOSTS_SEARCH_BOX_PLACEHOLDER}
|
||||
label={label}
|
||||
placeholder={placeholder}
|
||||
onChange={setSearchText}
|
||||
/>
|
||||
{isActiveSearch && (
|
||||
<div className={`${baseClass}__hosts-search-dropdown`}>
|
||||
<TableContainer
|
||||
columnConfigs={resultsDropdownTableHeaders}
|
||||
<TableContainer<Row<IHost>>
|
||||
columnConfigs={searchResultsTableConfig}
|
||||
data={dropdownHosts}
|
||||
isLoading={isTargetsLoading}
|
||||
resultsTitle=""
|
||||
@@ -81,7 +93,7 @@ const TargetsInput = ({
|
||||
disableCount
|
||||
disablePagination
|
||||
disableMultiRowSelect
|
||||
onSelectSingleRow={handleRowSelect}
|
||||
onClickRow={handleRowSelect}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
@@ -92,14 +104,15 @@ const TargetsInput = ({
|
||||
)}
|
||||
<div className={`${baseClass}__hosts-selected-table`}>
|
||||
<TableContainer
|
||||
columnConfigs={selectedTableHeaders}
|
||||
columnConfigs={selectedHostsTableConifg}
|
||||
data={targetedHosts}
|
||||
isLoading={false}
|
||||
resultsTitle=""
|
||||
showMarkAllPages={false}
|
||||
isAllPagesSelected={false}
|
||||
disableCount
|
||||
disablePagination
|
||||
disablePagination={disablePagination}
|
||||
isClientSidePagination={!disablePagination}
|
||||
emptyComponent={() => <></>}
|
||||
/>
|
||||
</div>
|
||||
|
||||
@@ -11,14 +11,14 @@ import LiveQueryIssueCell from "components/TableContainer/DataTable/LiveQueryIss
|
||||
import StatusIndicator from "components/StatusIndicator";
|
||||
import Icon from "components/Icon/Icon";
|
||||
|
||||
type ITargestInputhostTableConfig = Column<IHost>;
|
||||
export type ITargestInputHostTableConfig = Column<IHost>;
|
||||
type ITableStringCellProps = IStringCellProps<IHost>;
|
||||
|
||||
// NOTE: cellProps come from react-table
|
||||
// more info here https://react-table.tanstack.com/docs/api/useTable#cell-properties
|
||||
export const generateTableHeaders = (
|
||||
handleRowRemove?: (value: Row<IHost>) => void
|
||||
): ITargestInputhostTableConfig[] => {
|
||||
): ITargestInputHostTableConfig[] => {
|
||||
const deleteHeader = handleRowRemove
|
||||
? [
|
||||
{
|
||||
|
||||
@@ -93,5 +93,15 @@
|
||||
width: 100%;
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
.delete__cell {
|
||||
text-align: center;
|
||||
}
|
||||
}
|
||||
|
||||
// override the default styles for the spinner.
|
||||
// TODO: set better default styles for the spinner
|
||||
.loading-spinner.centered {
|
||||
margin: auto;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user