mirror of
https://github.com/Unleash/unleash.git
synced 2025-02-23 00:22:19 +01:00
support setting mode and stickiness in new project form (#6972)
This PR is a combination of two PRs: This PR adds a functioning environment selection button to the new project creation form. Selected environments are added to the payload and to the API preview. The implementation is mostly lifted from the existing FilterItem component we have for search filters. However, our need here is less complex, so I've removed some of the things we don't need. There is still more cleanup to be done, however, but I'd like to implement the rest of the submenus first, to see what we really do need in the end. --- This PR adds support for stickiness and project mode in the new project creation form. Achieve this, it does a few things: 1. Moves `resolveStickinessOptions` from `frontend/src/component/feature/StrategyTypes/FlexibleStrategy/StickinessSelect/StickinessSelect.tsx` and into a separate hook. This component was used by the old project creation form. Because the new form has a different input, but needs the same option, moved that code into a reusable hook. 2. It adds functioning buttons for project stickiness and mode. 3. It adds labels to the search inputs for the dropdowns. Inputs *must* have labels to meet a11y requirements. However, the designs don't have labels, so we can hide them visually. Though that leads to another issue (refer to the screen shot below). 4. It updates the `SelectionButton` component to handle both single- and multiselect cases. It instead exports these two subcomponents. These are currently in one file, but I'll split them out into their separate files in a later PR. As a side effect of working with the selection buttons, it also improves how we handle keyboard interaction for these buttons. Here's what it looks like for single-select lists. Notice the missing part of the input's border around the top (where the label *would* be if we showed it). We should figure out how best to handle it. I've done like this for now, but we can sort it out later. 
This commit is contained in:
parent
44e86fc068
commit
bd0cd018c9
@ -1,11 +1,6 @@
|
||||
import Select from 'component/common/select';
|
||||
import { type SelectChangeEvent, useTheme } from '@mui/material';
|
||||
import useUnleashContext from 'hooks/api/getters/useUnleashContext/useUnleashContext';
|
||||
|
||||
type OptionType = { key: string; label: string };
|
||||
|
||||
const DEFAULT_RANDOM_OPTION = 'random';
|
||||
const DEFAULT_STICKINESS_OPTION = 'default';
|
||||
import { useStickinessOptions } from 'hooks/useStickinessOptions';
|
||||
|
||||
interface IStickinessSelectProps {
|
||||
label: string;
|
||||
@ -21,37 +16,9 @@ export const StickinessSelect = ({
|
||||
onChange,
|
||||
dataTestId,
|
||||
}: IStickinessSelectProps) => {
|
||||
const { context } = useUnleashContext();
|
||||
const theme = useTheme();
|
||||
const stickinessOptions = useStickinessOptions(value);
|
||||
|
||||
const resolveStickinessOptions = () => {
|
||||
const options = context
|
||||
.filter((field) => field.stickiness)
|
||||
.map((c) => ({ key: c.name, label: c.name })) as OptionType[];
|
||||
|
||||
if (
|
||||
!options.find((option) => option.key === 'default') &&
|
||||
!context.find((field) => field.name === DEFAULT_STICKINESS_OPTION)
|
||||
) {
|
||||
options.push({ key: 'default', label: 'default' });
|
||||
}
|
||||
|
||||
if (
|
||||
!options.find((option) => option.key === 'random') &&
|
||||
!context.find((field) => field.name === DEFAULT_RANDOM_OPTION)
|
||||
) {
|
||||
options.push({ key: 'random', label: 'random' });
|
||||
}
|
||||
|
||||
// Add existing value to the options
|
||||
if (value && !options.find((option) => option.key === value)) {
|
||||
options.push({ key: value, label: value });
|
||||
}
|
||||
|
||||
return options;
|
||||
};
|
||||
|
||||
const stickinessOptions = resolveStickinessOptions();
|
||||
return (
|
||||
<Select
|
||||
id='stickiness-select'
|
||||
|
@ -34,10 +34,12 @@ const CreateProject = () => {
|
||||
projectName,
|
||||
projectDesc,
|
||||
projectMode,
|
||||
projectEnvironments,
|
||||
setProjectMode,
|
||||
setProjectId,
|
||||
setProjectName,
|
||||
setProjectDesc,
|
||||
setProjectEnvironments,
|
||||
getCreateProjectPayload,
|
||||
clearErrors,
|
||||
validateProjectId,
|
||||
@ -107,6 +109,8 @@ const CreateProject = () => {
|
||||
errors={errors}
|
||||
handleSubmit={handleSubmit}
|
||||
projectId={projectId}
|
||||
projectEnvironments={projectEnvironments}
|
||||
setProjectEnvironments={setProjectEnvironments}
|
||||
setProjectId={setProjectId}
|
||||
projectName={projectName}
|
||||
projectStickiness={projectStickiness}
|
||||
|
@ -3,6 +3,14 @@ import { v4 as uuidv4 } from 'uuid';
|
||||
import Input from 'component/common/Input/Input';
|
||||
import type { ProjectMode } from '../hooks/useProjectEnterpriseSettingsForm';
|
||||
import { ReactComponent as ProjectIcon } from 'assets/icons/projectIconSmall.svg';
|
||||
import { MultiselectList, SingleSelectList } from './SelectionButton';
|
||||
import { useEnvironments } from 'hooks/api/getters/useEnvironments/useEnvironments';
|
||||
import StickinessIcon from '@mui/icons-material/FormatPaint';
|
||||
import ProjectModeIcon from '@mui/icons-material/Adjust';
|
||||
import useUiConfig from 'hooks/api/getters/useUiConfig/useUiConfig';
|
||||
import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender';
|
||||
import EnvironmentsIcon from '@mui/icons-material/CloudCircle';
|
||||
import { useStickinessOptions } from 'hooks/useStickinessOptions';
|
||||
|
||||
const StyledForm = styled('form')(({ theme }) => ({
|
||||
background: theme.palette.background.default,
|
||||
@ -70,16 +78,18 @@ type FormProps = {
|
||||
projectId: string;
|
||||
projectName: string;
|
||||
projectDesc: string;
|
||||
projectStickiness?: string;
|
||||
projectStickiness: string;
|
||||
featureLimit?: string;
|
||||
featureCount?: number;
|
||||
projectMode?: string;
|
||||
setProjectStickiness?: React.Dispatch<React.SetStateAction<string>>;
|
||||
projectMode: string;
|
||||
projectEnvironments: Set<string>;
|
||||
setProjectStickiness: React.Dispatch<React.SetStateAction<string>>;
|
||||
setProjectEnvironments: React.Dispatch<React.SetStateAction<Set<string>>>;
|
||||
setProjectId: React.Dispatch<React.SetStateAction<string>>;
|
||||
setProjectName: React.Dispatch<React.SetStateAction<string>>;
|
||||
setProjectDesc: React.Dispatch<React.SetStateAction<string>>;
|
||||
setFeatureLimit?: React.Dispatch<React.SetStateAction<string>>;
|
||||
setProjectMode?: React.Dispatch<React.SetStateAction<ProjectMode>>;
|
||||
setProjectMode: React.Dispatch<React.SetStateAction<ProjectMode>>;
|
||||
handleSubmit: (e: any) => void;
|
||||
errors: { [key: string]: string };
|
||||
mode: 'Create' | 'Edit';
|
||||
@ -96,10 +106,12 @@ export const NewProjectForm: React.FC<FormProps> = ({
|
||||
projectName,
|
||||
projectDesc,
|
||||
projectStickiness,
|
||||
projectEnvironments,
|
||||
featureLimit,
|
||||
featureCount,
|
||||
projectMode,
|
||||
setProjectMode,
|
||||
setProjectEnvironments,
|
||||
setProjectId,
|
||||
setProjectName,
|
||||
setProjectDesc,
|
||||
@ -109,6 +121,10 @@ export const NewProjectForm: React.FC<FormProps> = ({
|
||||
mode,
|
||||
clearErrors,
|
||||
}) => {
|
||||
const { isEnterprise } = useUiConfig();
|
||||
const { environments: allEnvironments } = useEnvironments();
|
||||
const activeEnvironments = allEnvironments.filter((env) => env.enabled);
|
||||
|
||||
const handleProjectNameUpdate = (
|
||||
e: React.ChangeEvent<HTMLInputElement>,
|
||||
) => {
|
||||
@ -122,6 +138,18 @@ export const NewProjectForm: React.FC<FormProps> = ({
|
||||
setProjectId(maybeProjectId);
|
||||
};
|
||||
|
||||
const handleFilterChange = (envs: Set<string>) => {
|
||||
setProjectEnvironments(envs);
|
||||
};
|
||||
|
||||
const projectModeOptions = [
|
||||
{ value: 'open', label: 'open' },
|
||||
{ value: 'protected', label: 'protected' },
|
||||
{ value: 'private', label: 'private' },
|
||||
];
|
||||
|
||||
const stickinessOptions = useStickinessOptions(projectStickiness);
|
||||
|
||||
return (
|
||||
<StyledForm
|
||||
onSubmit={(submitEvent) => {
|
||||
@ -158,10 +186,65 @@ export const NewProjectForm: React.FC<FormProps> = ({
|
||||
/>
|
||||
</ProjectDescriptionContainer>
|
||||
</TopGrid>
|
||||
|
||||
<OptionButtons>
|
||||
<Button variant='outlined'>4 selected</Button>
|
||||
<Button variant='outlined'>clientId</Button>
|
||||
<Button variant='outlined'>Open</Button>
|
||||
<MultiselectList
|
||||
selectedOptions={projectEnvironments}
|
||||
options={activeEnvironments.map((env) => ({
|
||||
label: env.name,
|
||||
value: env.name,
|
||||
}))}
|
||||
onChange={handleFilterChange}
|
||||
button={{
|
||||
label:
|
||||
projectEnvironments.size > 0
|
||||
? `${projectEnvironments.size} selected`
|
||||
: 'Select environments',
|
||||
icon: <EnvironmentsIcon />,
|
||||
}}
|
||||
search={{
|
||||
label: 'Filter project environments',
|
||||
placeholder: 'Select project environments',
|
||||
}}
|
||||
/>
|
||||
|
||||
<SingleSelectList
|
||||
options={stickinessOptions.map(({ key, ...rest }) => ({
|
||||
value: key,
|
||||
...rest,
|
||||
}))}
|
||||
onChange={(value: any) => {
|
||||
setProjectStickiness(value);
|
||||
}}
|
||||
button={{
|
||||
label: projectStickiness,
|
||||
icon: <StickinessIcon />,
|
||||
}}
|
||||
search={{
|
||||
label: 'Filter stickiness options',
|
||||
placeholder: 'Select default stickiness',
|
||||
}}
|
||||
/>
|
||||
|
||||
<ConditionallyRender
|
||||
condition={isEnterprise()}
|
||||
show={
|
||||
<SingleSelectList
|
||||
options={projectModeOptions}
|
||||
onChange={(value: any) => {
|
||||
setProjectMode(value);
|
||||
}}
|
||||
button={{
|
||||
label: projectMode,
|
||||
icon: <ProjectModeIcon />,
|
||||
}}
|
||||
search={{
|
||||
label: 'Filter project mode options',
|
||||
placeholder: 'Select project mode',
|
||||
}}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
<Button variant='outlined'>1 environment configured</Button>
|
||||
</OptionButtons>
|
||||
<FormActions>{children}</FormActions>
|
||||
|
@ -0,0 +1,51 @@
|
||||
import { Checkbox, ListItem, Popover, TextField, styled } from '@mui/material';
|
||||
|
||||
export const StyledDropdown = styled('div')(({ theme }) => ({
|
||||
padding: theme.spacing(1.5),
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
gap: theme.spacing(1),
|
||||
maxHeight: '70vh',
|
||||
}));
|
||||
|
||||
export const StyledListItem = styled(ListItem)(({ theme }) => ({
|
||||
paddingLeft: theme.spacing(1),
|
||||
cursor: 'pointer',
|
||||
'&:hover, &:focus': {
|
||||
backgroundColor: theme.palette.action.hover,
|
||||
outline: 'none',
|
||||
},
|
||||
}));
|
||||
|
||||
export const StyledCheckbox = styled(Checkbox)(({ theme }) => ({
|
||||
padding: theme.spacing(1, 1, 1, 1.5),
|
||||
}));
|
||||
|
||||
export const StyledPopover = styled(Popover)(({ theme }) => ({
|
||||
'& .MuiPaper-root': {
|
||||
borderRadius: `${theme.shape.borderRadiusMedium}px`,
|
||||
},
|
||||
}));
|
||||
|
||||
export const StyledTextField = styled(TextField)(({ theme }) => ({
|
||||
'& .MuiInputBase-root': {
|
||||
padding: theme.spacing(0, 1.5),
|
||||
borderRadius: `${theme.shape.borderRadiusMedium}px`,
|
||||
},
|
||||
'& .MuiInputBase-input': {
|
||||
padding: theme.spacing(0.75, 0),
|
||||
fontSize: theme.typography.body2.fontSize,
|
||||
},
|
||||
|
||||
'& label': {
|
||||
border: 0,
|
||||
clip: 'rect(0 0 0 0)',
|
||||
height: 'auto',
|
||||
margin: 0,
|
||||
overflow: 'hidden',
|
||||
padding: 0,
|
||||
position: 'absolute',
|
||||
width: '1px',
|
||||
whiteSpace: 'nowrap',
|
||||
},
|
||||
}));
|
@ -0,0 +1,264 @@
|
||||
import Search from '@mui/icons-material/Search';
|
||||
import { Box, Button, InputAdornment, List, ListItemText } from '@mui/material';
|
||||
import { type FC, type ReactNode, useRef, useState } from 'react';
|
||||
import {
|
||||
StyledCheckbox,
|
||||
StyledDropdown,
|
||||
StyledListItem,
|
||||
StyledPopover,
|
||||
StyledTextField,
|
||||
} from './SelectionButton.styles';
|
||||
|
||||
export interface IFilterItemProps {
|
||||
label: ReactNode;
|
||||
options: Array<{ label: string; value: string }>;
|
||||
selectedOptions: Set<string>;
|
||||
onChange: (values: Set<string>) => void;
|
||||
}
|
||||
|
||||
export type FilterItemParams = {
|
||||
operator: string;
|
||||
values: string[];
|
||||
};
|
||||
|
||||
interface UseSelectionManagementProps {
|
||||
handleToggle: (value: string) => () => void;
|
||||
}
|
||||
|
||||
const useSelectionManagement = ({
|
||||
handleToggle,
|
||||
}: UseSelectionManagementProps) => {
|
||||
const listRefs = useRef<Array<HTMLInputElement | HTMLLIElement | null>>([]);
|
||||
|
||||
const handleSelection = (
|
||||
event: React.KeyboardEvent,
|
||||
index: number,
|
||||
filteredOptions: { label: string; value: string }[],
|
||||
) => {
|
||||
// we have to be careful not to prevent other keys e.g tab
|
||||
if (event.key === 'ArrowDown' && index < listRefs.current.length - 1) {
|
||||
event.preventDefault();
|
||||
listRefs.current[index + 1]?.focus();
|
||||
} else if (event.key === 'ArrowUp' && index > 0) {
|
||||
event.preventDefault();
|
||||
listRefs.current[index - 1]?.focus();
|
||||
} else if (
|
||||
event.key === 'Enter' &&
|
||||
index === 0 &&
|
||||
listRefs.current[0]?.value &&
|
||||
filteredOptions.length > 0
|
||||
) {
|
||||
// if the search field is not empty and the user presses
|
||||
// enter from the search field, toggle the topmost item in
|
||||
// the filtered list event.preventDefault();
|
||||
handleToggle(filteredOptions[0].value)();
|
||||
} else if (
|
||||
event.key === 'Enter' ||
|
||||
// allow selection with space when not in the search field
|
||||
(index !== 0 && event.key === ' ')
|
||||
) {
|
||||
event.preventDefault();
|
||||
if (index > 0) {
|
||||
const listItemIndex = index - 1;
|
||||
handleToggle(filteredOptions[listItemIndex].value)();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
return { listRefs, handleSelection };
|
||||
};
|
||||
|
||||
type CombinedSelectProps = {
|
||||
options: Array<{ label: string; value: string }>;
|
||||
onChange: (value: string) => void;
|
||||
button: { label: string; icon: ReactNode };
|
||||
search: {
|
||||
label: string;
|
||||
placeholder: string;
|
||||
};
|
||||
multiselect?: { selectedOptions: Set<string> };
|
||||
};
|
||||
|
||||
const CombinedSelect: FC<CombinedSelectProps> = ({
|
||||
options,
|
||||
onChange,
|
||||
button,
|
||||
search,
|
||||
multiselect,
|
||||
}) => {
|
||||
const ref = useRef<HTMLDivElement>(null);
|
||||
const [anchorEl, setAnchorEl] = useState<HTMLDivElement | null>();
|
||||
const [searchText, setSearchText] = useState('');
|
||||
|
||||
const open = () => {
|
||||
setSearchText('');
|
||||
setAnchorEl(ref.current);
|
||||
};
|
||||
|
||||
const onClose = () => {
|
||||
setAnchorEl(null);
|
||||
};
|
||||
|
||||
const onSelection = (selected: string) => {
|
||||
onChange(selected);
|
||||
if (!multiselect) {
|
||||
onClose();
|
||||
}
|
||||
};
|
||||
|
||||
const { listRefs, handleSelection } = useSelectionManagement({
|
||||
handleToggle: (selected: string) => () => onSelection(selected),
|
||||
});
|
||||
|
||||
const filteredOptions = options?.filter((option) =>
|
||||
option.label.toLowerCase().includes(searchText.toLowerCase()),
|
||||
);
|
||||
return (
|
||||
<>
|
||||
<Box ref={ref}>
|
||||
<Button
|
||||
variant='outlined'
|
||||
color='primary'
|
||||
startIcon={button.icon}
|
||||
onClick={() => {
|
||||
// todo: find out why this is clicked when you
|
||||
// press enter in the search bar (only in
|
||||
// single-select mode)
|
||||
open();
|
||||
}}
|
||||
>
|
||||
{button.label}
|
||||
</Button>
|
||||
</Box>
|
||||
<StyledPopover
|
||||
open={Boolean(anchorEl)}
|
||||
anchorEl={anchorEl}
|
||||
onClose={onClose}
|
||||
anchorOrigin={{
|
||||
vertical: 'bottom',
|
||||
horizontal: 'left',
|
||||
}}
|
||||
transformOrigin={{
|
||||
vertical: 'top',
|
||||
horizontal: 'left',
|
||||
}}
|
||||
>
|
||||
<StyledDropdown>
|
||||
<StyledTextField
|
||||
variant='outlined'
|
||||
size='small'
|
||||
value={searchText}
|
||||
onChange={(event) => setSearchText(event.target.value)}
|
||||
label={search.label}
|
||||
placeholder={search.placeholder}
|
||||
autoFocus
|
||||
InputProps={{
|
||||
startAdornment: (
|
||||
<InputAdornment position='start'>
|
||||
<Search fontSize='small' />
|
||||
</InputAdornment>
|
||||
),
|
||||
}}
|
||||
inputRef={(el) => {
|
||||
listRefs.current[0] = el;
|
||||
}}
|
||||
onKeyDown={(event) =>
|
||||
handleSelection(event, 0, filteredOptions)
|
||||
}
|
||||
/>
|
||||
<List sx={{ overflowY: 'auto' }} disablePadding>
|
||||
{filteredOptions.map((option, index) => {
|
||||
const labelId = `checkbox-list-label-${option.value}`;
|
||||
|
||||
return (
|
||||
<StyledListItem
|
||||
key={option.value}
|
||||
dense
|
||||
disablePadding
|
||||
tabIndex={0}
|
||||
onClick={() => {
|
||||
onSelection(option.value);
|
||||
}}
|
||||
ref={(el) => {
|
||||
listRefs.current[index + 1] = el;
|
||||
}}
|
||||
onKeyDown={(event) =>
|
||||
handleSelection(
|
||||
event,
|
||||
index + 1,
|
||||
filteredOptions,
|
||||
)
|
||||
}
|
||||
>
|
||||
{multiselect ? (
|
||||
<StyledCheckbox
|
||||
edge='start'
|
||||
checked={multiselect.selectedOptions.has(
|
||||
option.value,
|
||||
)}
|
||||
tabIndex={-1}
|
||||
inputProps={{
|
||||
'aria-labelledby': labelId,
|
||||
}}
|
||||
size='small'
|
||||
disableRipple
|
||||
/>
|
||||
) : null}
|
||||
<ListItemText
|
||||
id={labelId}
|
||||
primary={option.label}
|
||||
/>
|
||||
</StyledListItem>
|
||||
);
|
||||
})}
|
||||
</List>
|
||||
</StyledDropdown>
|
||||
</StyledPopover>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
type MultiselectListProps = Pick<
|
||||
CombinedSelectProps,
|
||||
'options' | 'button' | 'search'
|
||||
> & {
|
||||
selectedOptions: Set<string>;
|
||||
onChange: (values: Set<string>) => void;
|
||||
};
|
||||
|
||||
export const MultiselectList: FC<MultiselectListProps> = ({
|
||||
selectedOptions,
|
||||
onChange,
|
||||
...rest
|
||||
}) => {
|
||||
// todo: add "select all" and "deselect all"
|
||||
|
||||
const handleToggle = (value: string) => {
|
||||
if (selectedOptions.has(value)) {
|
||||
selectedOptions.delete(value);
|
||||
} else {
|
||||
selectedOptions.add(value);
|
||||
}
|
||||
|
||||
onChange(new Set(selectedOptions));
|
||||
};
|
||||
|
||||
return (
|
||||
<CombinedSelect
|
||||
{...rest}
|
||||
onChange={handleToggle}
|
||||
multiselect={{
|
||||
selectedOptions,
|
||||
}}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
type SingleSelectListProps = Pick<
|
||||
CombinedSelectProps,
|
||||
'options' | 'button' | 'search' | 'onChange'
|
||||
>;
|
||||
|
||||
export const SingleSelectList: FC<SingleSelectListProps> = (props) => {
|
||||
return <CombinedSelect {...props} />;
|
||||
};
|
@ -12,6 +12,7 @@ const useProjectForm = (
|
||||
initialProjectStickiness = DEFAULT_PROJECT_STICKINESS,
|
||||
initialFeatureLimit = '',
|
||||
initialProjectMode: ProjectMode = 'open',
|
||||
initialProjectEnvironments: Set<string> = new Set(),
|
||||
) => {
|
||||
const { isEnterprise } = useUiConfig();
|
||||
const [projectId, setProjectId] = useState(initialProjectId);
|
||||
@ -24,6 +25,9 @@ const useProjectForm = (
|
||||
);
|
||||
const [featureLimit, setFeatureLimit] =
|
||||
useState<string>(initialFeatureLimit);
|
||||
const [projectEnvironments, setProjectEnvironments] = useState<Set<string>>(
|
||||
initialProjectEnvironments,
|
||||
);
|
||||
|
||||
const [errors, setErrors] = useState({});
|
||||
|
||||
@ -54,6 +58,11 @@ const useProjectForm = (
|
||||
}, [initialProjectMode]);
|
||||
|
||||
const getCreateProjectPayload = () => {
|
||||
const environmentsPayload =
|
||||
projectEnvironments.size > 0
|
||||
? { environments: [...projectEnvironments] }
|
||||
: {};
|
||||
|
||||
return isEnterprise()
|
||||
? {
|
||||
id: projectId,
|
||||
@ -61,12 +70,14 @@ const useProjectForm = (
|
||||
description: projectDesc,
|
||||
defaultStickiness: projectStickiness,
|
||||
mode: projectMode,
|
||||
...environmentsPayload,
|
||||
}
|
||||
: {
|
||||
id: projectId,
|
||||
name: projectName,
|
||||
description: projectDesc,
|
||||
defaultStickiness: projectStickiness,
|
||||
...environmentsPayload,
|
||||
};
|
||||
};
|
||||
|
||||
@ -121,12 +132,14 @@ const useProjectForm = (
|
||||
projectMode,
|
||||
projectStickiness,
|
||||
featureLimit,
|
||||
projectEnvironments,
|
||||
setProjectId,
|
||||
setProjectName,
|
||||
setProjectDesc,
|
||||
setProjectStickiness,
|
||||
setFeatureLimit,
|
||||
setProjectMode,
|
||||
setProjectEnvironments,
|
||||
getCreateProjectPayload,
|
||||
getEditProjectPayload,
|
||||
validateName,
|
||||
|
35
frontend/src/hooks/useStickinessOptions.ts
Normal file
35
frontend/src/hooks/useStickinessOptions.ts
Normal file
@ -0,0 +1,35 @@
|
||||
import useUnleashContext from 'hooks/api/getters/useUnleashContext/useUnleashContext';
|
||||
|
||||
type OptionType = { key: string; label: string };
|
||||
|
||||
const DEFAULT_RANDOM_OPTION = 'random';
|
||||
const DEFAULT_STICKINESS_OPTION = 'default';
|
||||
|
||||
export const useStickinessOptions = (value: string | undefined) => {
|
||||
const { context } = useUnleashContext();
|
||||
|
||||
const options = context
|
||||
.filter((field) => field.stickiness)
|
||||
.map((c) => ({ key: c.name, label: c.name })) as OptionType[];
|
||||
|
||||
if (
|
||||
!options.find((option) => option.key === 'default') &&
|
||||
!context.find((field) => field.name === DEFAULT_STICKINESS_OPTION)
|
||||
) {
|
||||
options.push({ key: 'default', label: 'default' });
|
||||
}
|
||||
|
||||
if (
|
||||
!options.find((option) => option.key === 'random') &&
|
||||
!context.find((field) => field.name === DEFAULT_RANDOM_OPTION)
|
||||
) {
|
||||
options.push({ key: 'random', label: 'random' });
|
||||
}
|
||||
|
||||
// Add existing value to the options
|
||||
if (value && !options.find((option) => option.key === value)) {
|
||||
options.push({ key: value, label: value });
|
||||
}
|
||||
|
||||
return options;
|
||||
};
|
Loading…
Reference in New Issue
Block a user