Fleet UI: Fix dropdown rendering (#40418)

This commit is contained in:
RachelElysia
2026-02-26 11:42:00 -05:00
committed by GitHub
parent c7745519e1
commit baf7f82aed
6 changed files with 92 additions and 57 deletions
+1
View File
@@ -0,0 +1 @@
- Fleet UI: Fix dropdown rendering center of a row and from pushing down save button below open dropdown options
+1
View File
@@ -0,0 +1 @@
- Fleet Desktop: Surface custom transparency links to the device user
@@ -103,7 +103,7 @@ type DropdownWrapperVariant = "table-filter" | "button";
export interface IDropdownWrapper {
options: CustomOptionType[];
value?: PropsValue<CustomOptionType> | string;
value?: PropsValue<CustomOptionType> | string; // Future: Handle number types, cascade of type checking will be needed
onChange: (newValue: SingleValue<CustomOptionType>) => void;
name: string;
className?: string;
@@ -1,8 +1,9 @@
import React from "react";
import { noop } from "lodash";
// @ts-ignore
import Dropdown from "components/forms/fields/Dropdown";
import DropdownWrapper, {
CustomOptionType,
} from "components/forms/fields/DropdownWrapper/DropdownWrapper";
import FormField from "components/forms/FormField";
const PLATFORM_STRINGS: { [key: string]: string } = {
@@ -12,7 +13,7 @@ const PLATFORM_STRINGS: { [key: string]: string } = {
centos: "CentOS Linux",
};
const platformOptions = [
const platformOptions: CustomOptionType[] = [
{ label: "All platforms", value: "" },
{ label: "macOS", value: "darwin" },
{ label: "Windows", value: "windows" },
@@ -33,25 +34,31 @@ const PlatformField = ({
isEditing = false,
onChange = noop,
}: IPlatformFieldProps) => {
const handleDropdownChange = (newValue: CustomOptionType | null) => {
// DropdownWrapper passes a SingleValue<CustomOptionType> | null
onChange(newValue?.value ?? "");
};
return (
<div className={baseClass}>
{!isEditing ? (
<div className="form-field form-field--dropdown">
<Dropdown
<DropdownWrapper
label="Platform"
name="platform"
onChange={onChange}
onChange={handleDropdownChange}
// DropdownWrapper accepts either option or string; uses string for simiplicity
value={platform}
options={platformOptions}
classname={`${baseClass}__platform-dropdown`}
wrapperClassName={`${baseClass}__form-field ${baseClass}__form-field--platform`}
className={`${baseClass}__platform-dropdown`}
wrapperClassname={`${baseClass}__form-field ${baseClass}__form-field--platform`}
isSearchable={false}
placeholder="All platforms"
/>
</div>
) : (
<FormField label="Platform" name="platform">
<>
<p>{platform ? PLATFORM_STRINGS[platform] : "All platforms"}</p>
</>
<p>{platform ? PLATFORM_STRINGS[platform] : "All platforms"}</p>
</FormField>
)}
</div>
@@ -16,8 +16,9 @@ import { getPathWithQueryParams } from "utilities/url";
import { getExtensionFromFileName } from "utilities/file/fileUtils";
import { getDisplayedSoftwareName } from "pages/SoftwarePage/helpers";
// @ts-ignore
import Dropdown from "components/forms/fields/Dropdown";
import DropdownWrapper, {
CustomOptionType,
} from "components/forms/fields/DropdownWrapper/DropdownWrapper";
import Modal from "components/Modal";
import DataError from "components/DataError";
import Spinner from "components/Spinner";
@@ -195,31 +196,34 @@ const InstallSoftwareModal = ({
// Filters and transforms software titles into dropdown options
// to include only software compatible with the policy's platform(s)
const availableSoftwareOptions = useCallback(
(policy: IFormPolicy) => {
(policy: IFormPolicy): CustomOptionType[] => {
const policyPlatforms = policy.platform.split(",");
return titlesAvailableForInstall
?.filter(
(title) => title.platform && policyPlatforms.includes(title.platform)
)
.map((title) => {
return {
label: getDisplayedSoftwareName(title.name, title.display_name),
value: title.id,
helpText: generateSoftwareOptionHelpText(title),
};
});
return (
titlesAvailableForInstall
?.filter(
(title) =>
title.platform && policyPlatforms.includes(title.platform)
)
.map((title) => {
return {
label: getDisplayedSoftwareName(title.name, title.display_name),
value: String(title.id), // string for DropdownWrapper
helpText: generateSoftwareOptionHelpText(title),
};
}) ?? []
);
},
[titlesAvailableForInstall]
);
// Cache availableSoftwareOptions for each unique platform
const memoizedAvailableSoftwareOptions = useMemo(() => {
const cache = new Map();
return (policy: IFormPolicy) => {
let options = availableSoftwareOptions(policy) || [];
const cache = new Map<string, CustomOptionType[]>();
return (policy: IFormPolicy): CustomOptionType[] => {
let options = availableSoftwareOptions(policy);
const installOptionsByPlatformMismatchSelectedInstaller =
policy.swIdToInstall &&
!options.some((opt) => opt.value === policy.swIdToInstall);
!options.some((opt) => Number(opt.value) === policy.swIdToInstall);
// More unique cache key if installOptionsByPlatformMismatchSelectedInstaller
const key = `${policy.platform}${
@@ -241,7 +245,7 @@ const InstallSoftwareModal = ({
currentSoftware.name,
currentSoftware.display_name
),
value: currentSoftware.id,
value: String(currentSoftware.id),
helpText: generateSoftwareOptionHelpText(currentSoftware),
},
...options,
@@ -251,7 +255,7 @@ const InstallSoftwareModal = ({
cache.set(key, options);
}
return cache.get(key);
return cache.get(key) ?? [];
};
}, [availableSoftwareOptions, titlesAvailableForInstall]);
@@ -311,22 +315,27 @@ const InstallSoftwareModal = ({
onClick={(e) => {
e.stopPropagation();
}}
className={`${baseClass}__dropdown-wrapper`}
>
<Dropdown
options={memoizedAvailableSoftwareOptions(formPolicy)} // Options filtered for policy's platform(s)
value={formPolicy.swIdToInstall}
onChange={({ value }: ISwDropdownField) =>
<DropdownWrapper
options={memoizedAvailableSoftwareOptions(formPolicy)}
value={
formPolicy.swIdToInstall != null
? String(formPolicy.swIdToInstall)
: ""
}
onChange={(newValue) =>
onChange(
onSelectPolicySoftware(item, {
name: formPolicy.name,
value,
value: newValue?.value ? Number(newValue.value) : 0,
})
)
}
placeholder="Select software"
className={`${baseClass}__software-dropdown`}
name={formPolicy.name}
parseTarget
isSearchable
/>
</span>
) : null;
@@ -1,5 +1,6 @@
import React, { useCallback, useRef } from "react";
import { useQuery } from "react-query";
import { SingleValue } from "react-select-5";
import { omit } from "lodash";
import paths from "router/paths";
@@ -13,8 +14,9 @@ import scriptsAPI, {
import { IScript } from "interfaces/script";
// @ts-ignore
import Dropdown from "components/forms/fields/Dropdown";
import DropdownWrapper, {
CustomOptionType,
} from "components/forms/fields/DropdownWrapper/DropdownWrapper";
import DataError from "components/DataError";
import Spinner from "components/Spinner";
import CustomLink from "components/CustomLink";
@@ -113,11 +115,6 @@ const PolicyRunScriptModal = ({
};
};
const availableScriptOptions = availableScripts?.map((script) => ({
label: script.name,
value: script.id,
}));
const renderContent = () => {
if (isAvailableScriptsError) {
return <DataError />;
@@ -175,29 +172,49 @@ const PolicyRunScriptModal = ({
renderItemRow={(item, onChange) => {
const formPolicy = {
...item,
runScriptEnabled: !!item.scriptIdToRun,
runScriptEnabled: !!item.runScriptEnabled,
};
return item.runScriptEnabled ? (
const availableScriptOptions =
availableScripts?.map((script) => ({
label: script.name,
value: String(script.id), // DropdownWrapper expects string value
})) ?? [];
const handleScriptChange = (
newValue: SingleValue<CustomOptionType>
) => {
const numericId = newValue?.value
? Number(newValue.value)
: null;
onChange(
onSelectPolicyScript(item, {
name: formPolicy.name,
value: numericId ?? 0,
})
);
};
return formPolicy.runScriptEnabled ? (
<span
onClick={(e: React.MouseEvent<HTMLButtonElement>) => {
e.stopPropagation();
}}
className={`${baseClass}__dropdown-wrapper`}
>
<Dropdown
options={availableScriptOptions} // Options filtered for policy's platform(s)
value={formPolicy.scriptIdToRun}
onChange={({ value }: IScriptDropdownField) =>
onChange(
onSelectPolicyScript(item, {
name: formPolicy.name,
value,
})
)
<DropdownWrapper
options={availableScriptOptions}
value={
formPolicy.scriptIdToRun != null
? String(formPolicy.scriptIdToRun)
: ""
}
onChange={handleScriptChange}
placeholder="Select script"
className={`${baseClass}__script-dropdown`}
name={formPolicy.name}
parseTarget
isSearchable
/>
</span>
) : null;