Fleet UI: Fix dropdown rendering (#40418)
This commit is contained in:
@@ -0,0 +1 @@
|
||||
- Fleet UI: Fix dropdown rendering center of a row and from pushing down save button below open dropdown options
|
||||
@@ -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>
|
||||
|
||||
+35
-26
@@ -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;
|
||||
|
||||
+37
-20
@@ -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;
|
||||
|
||||
Reference in New Issue
Block a user