From c0d7be040d470cd626bea1cefb1c57940ba97ecc Mon Sep 17 00:00:00 2001 From: Thomas Heartman Date: Fri, 26 Jul 2024 11:38:29 +0200 Subject: [PATCH] fix: make config dropdown list generic over values (#7676) This PR makes the config dropdown list generic over its values, so that you can pass stuff that isn't strings. It also updates the existing impression data button to use booleans instead. --- .../ConfigButtons/DropdownList.tsx | 30 +++++++++---------- .../ConfigButtons/MultiSelectConfigButton.tsx | 20 ++++++------- .../SingleSelectConfigButton.tsx | 16 +++++----- .../component/feature/hooks/useFeatureForm.ts | 8 ++--- .../CreateFeatureDialog.tsx | 15 +++++----- 5 files changed, 42 insertions(+), 47 deletions(-) diff --git a/frontend/src/component/common/DialogFormTemplate/ConfigButtons/DropdownList.tsx b/frontend/src/component/common/DialogFormTemplate/ConfigButtons/DropdownList.tsx index c95ad1f08f..d72d99ffdc 100644 --- a/frontend/src/component/common/DialogFormTemplate/ConfigButtons/DropdownList.tsx +++ b/frontend/src/component/common/DialogFormTemplate/ConfigButtons/DropdownList.tsx @@ -1,18 +1,16 @@ import Search from '@mui/icons-material/Search'; -import { type FC, useRef, useState } from 'react'; +import { useRef, useState } from 'react'; import { InputAdornment, List, ListItemText } from '@mui/material'; import { StyledDropdownSearch } from './shared.styles'; import { StyledCheckbox, StyledListItem } from './DropdownList.styles'; -const useSelectionManagement = ( - handleToggle: (value: string) => () => void, -) => { +function useSelectionManagement(handleToggle: (value: T) => () => void) { const listRefs = useRef>([]); const handleSelection = ( event: React.KeyboardEvent, index: number, - filteredOptions: { label: string; value: string }[], + filteredOptions: { label: string; value: T }[], ) => { // we have to be careful not to prevent other keys e.g tab if (event.key === 'ArrowDown' && index < listRefs.current.length - 1) { @@ -45,32 +43,32 @@ const useSelectionManagement = ( }; return { listRefs, handleSelection }; -}; +} -export type DropdownListProps = { - options: Array<{ label: string; value: string }>; - onChange: (value: string) => void; +export type DropdownListProps = { + options: Array<{ label: string; value: T }>; + onChange: (value: T) => void; search: { label: string; placeholder: string; }; - multiselect?: { selectedOptions: Set }; + multiselect?: { selectedOptions: Set }; }; -export const DropdownList: FC = ({ +export function DropdownList({ options, onChange, search, multiselect, -}) => { +}: DropdownListProps) { const [searchText, setSearchText] = useState(''); - const onSelection = (selected: string) => { + const onSelection = (selected: T) => { onChange(selected); }; const { listRefs, handleSelection } = useSelectionManagement( - (selected: string) => () => onSelection(selected), + (selected: T) => () => onSelection(selected), ); const filteredOptions = options?.filter((option) => @@ -109,7 +107,7 @@ export const DropdownList: FC = ({ return ( = ({ ); -}; +} diff --git a/frontend/src/component/common/DialogFormTemplate/ConfigButtons/MultiSelectConfigButton.tsx b/frontend/src/component/common/DialogFormTemplate/ConfigButtons/MultiSelectConfigButton.tsx index 66b09bfe07..7941cd9857 100644 --- a/frontend/src/component/common/DialogFormTemplate/ConfigButtons/MultiSelectConfigButton.tsx +++ b/frontend/src/component/common/DialogFormTemplate/ConfigButtons/MultiSelectConfigButton.tsx @@ -1,24 +1,24 @@ -import { type FC, useState } from 'react'; +import { useState } from 'react'; import { ConfigButton, type ConfigButtonProps } from './ConfigButton'; import { DropdownList, type DropdownListProps } from './DropdownList'; -type MultiSelectConfigButtonProps = Pick< +type MultiSelectConfigButtonProps = Pick< ConfigButtonProps, 'button' | 'onOpen' | 'onClose' | 'description' | 'tooltip' > & - Pick & { - selectedOptions: Set; - onChange: (values: Set) => void; + Pick, 'search' | 'options'> & { + selectedOptions: Set; + onChange: (values: Set) => void; }; -export const MultiSelectConfigButton: FC = ({ +export function MultiSelectConfigButton({ selectedOptions, onChange, ...rest -}) => { +}: MultiSelectConfigButtonProps) { const [anchorEl, setAnchorEl] = useState(); - const handleToggle = (value: string) => { + const handleToggle = (value: T) => { if (selectedOptions.has(value)) { selectedOptions.delete(value); } else { @@ -30,7 +30,7 @@ export const MultiSelectConfigButton: FC = ({ return ( - multiselect={{ selectedOptions, }} @@ -39,4 +39,4 @@ export const MultiSelectConfigButton: FC = ({ /> ); -}; +} diff --git a/frontend/src/component/common/DialogFormTemplate/ConfigButtons/SingleSelectConfigButton.tsx b/frontend/src/component/common/DialogFormTemplate/ConfigButtons/SingleSelectConfigButton.tsx index 9c5903e428..a57dfdcc5d 100644 --- a/frontend/src/component/common/DialogFormTemplate/ConfigButtons/SingleSelectConfigButton.tsx +++ b/frontend/src/component/common/DialogFormTemplate/ConfigButtons/SingleSelectConfigButton.tsx @@ -1,21 +1,21 @@ -import { type FC, useState } from 'react'; +import { useState } from 'react'; import { ConfigButton, type ConfigButtonProps } from './ConfigButton'; import { DropdownList, type DropdownListProps } from './DropdownList'; -type SingleSelectConfigButtonProps = Pick< +type SingleSelectConfigButtonProps = Pick< ConfigButtonProps, 'button' | 'onOpen' | 'onClose' | 'description' | 'tooltip' > & - Pick; + Pick, 'search' | 'onChange' | 'options'>; -export const SingleSelectConfigButton: FC = ({ +export function SingleSelectConfigButton({ onChange, ...props -}) => { +}: SingleSelectConfigButtonProps) { const [anchorEl, setAnchorEl] = useState(); const [recentlyClosed, setRecentlyClosed] = useState(false); - const handleChange = (value: any) => { + const handleChange = (value: T) => { onChange(value); setAnchorEl(null); props.onClose?.(); @@ -34,7 +34,7 @@ export const SingleSelectConfigButton: FC = ({ anchorEl={anchorEl} setAnchorEl={setAnchorEl} > - + {...props} onChange={handleChange} /> ); -}; +} diff --git a/frontend/src/component/feature/hooks/useFeatureForm.ts b/frontend/src/component/feature/hooks/useFeatureForm.ts index e76944bab8..ff76634026 100644 --- a/frontend/src/component/feature/hooks/useFeatureForm.ts +++ b/frontend/src/component/feature/hooks/useFeatureForm.ts @@ -17,7 +17,7 @@ const useFeatureForm = ( const { validateFeatureToggleName } = useFeatureApi(); const toggleQueryName = params.get('name'); const [type, setType] = useState(initialType); - const [tags, setTags] = useState>(new Set()); + const [tags, setTags] = useState>(new Set()); const [name, setName] = useState(toggleQueryName || initialName); const [project, setProject] = useState(projectId || initialProject); const [description, setDescription] = useState(initialDescription); @@ -50,11 +50,7 @@ const useFeatureForm = ( }, [initialImpressionData]); const getTogglePayload = () => { - const splitTags: ITag[] = Array.from(tags).map((tag) => { - const [type, value] = tag.split(':'); - return { type, value }; - }); - const tagsPayload = tags.size > 0 ? { tags: splitTags } : {}; + const tagsPayload = tags.size > 0 ? { tags: Array.from(tags) } : {}; return { type, name, diff --git a/frontend/src/component/project/Project/PaginatedProjectFeatureToggles/ProjectFeatureTogglesHeader/CreateFeatureDialog.tsx b/frontend/src/component/project/Project/PaginatedProjectFeatureToggles/ProjectFeatureTogglesHeader/CreateFeatureDialog.tsx index e361bb9a46..b354d77f68 100644 --- a/frontend/src/component/project/Project/PaginatedProjectFeatureToggles/ProjectFeatureTogglesHeader/CreateFeatureDialog.tsx +++ b/frontend/src/component/project/Project/PaginatedProjectFeatureToggles/ProjectFeatureTogglesHeader/CreateFeatureDialog.tsx @@ -35,6 +35,7 @@ import useAllTags from 'hooks/api/getters/useAllTags/useAllTags'; import Label from '@mui/icons-material/Label'; import { ProjectIcon } from 'component/common/ProjectIcon/ProjectIcon'; import { MultiSelectConfigButton } from 'component/common/DialogFormTemplate/ConfigButtons/MultiSelectConfigButton'; +import type { ITag } from 'interfaces/tags'; interface ICreateFeatureDialogProps { open: boolean; @@ -272,7 +273,7 @@ export const CreateFeatureDialog = ({ /> } /> - tooltip={{ header: 'Select tags', }} @@ -280,7 +281,7 @@ export const CreateFeatureDialog = ({ selectedOptions={tags} options={allTags.map((tag) => ({ label: `${tag.type}:${tag.value}`, - value: `${tag.type}:${tag.value}`, + value: tag, }))} onChange={setTags} button={{ @@ -334,7 +335,7 @@ export const CreateFeatureDialog = ({ onClose={clearDocumentationOverride} /> - tooltip={{ header: 'Enable or disable impression data', }} @@ -342,11 +343,11 @@ export const CreateFeatureDialog = ({ configButtonData.impressionData.text } options={[ - { label: 'On', value: 'true' }, - { label: 'Off', value: 'false' }, + { label: 'On', value: true }, + { label: 'Off', value: false }, ]} - onChange={(value: string) => { - setImpressionData(value === 'true'); + onChange={(value: boolean) => { + setImpressionData(value); }} button={{ label: `Impression data ${impressionData ? 'on' : 'off'}`,