1
0
mirror of https://github.com/Unleash/unleash.git synced 2025-02-09 00:18:00 +01:00

refactor: fix small issues around custom strategies (#1181)

* refactor: validate strategy name on blur

* refactor: remove strategy parameter type text in favor of docs

* refactor: improve pie chart rendering

* refactor: show icons for all feature strategies

* refactor: fix list parameter add button style
This commit is contained in:
olav 2022-08-02 10:10:01 +02:00 committed by GitHub
parent 79aa2d4ebe
commit 710ebe08b3
10 changed files with 67 additions and 88 deletions

View File

@ -1,51 +1,45 @@
import { useTheme } from '@mui/material'; import { useTheme } from '@mui/material';
import { CSSProperties } from 'react';
interface IPercentageCircleProps { interface IPercentageCircleProps {
styles?: object;
percentage: number; percentage: number;
secondaryPieColor?: string; size?: `${number}rem`;
className?: string;
hideNumber?: boolean;
} }
const PercentageCircle = ({ const PercentageCircle = ({
styles,
percentage, percentage,
secondaryPieColor, size = '4rem',
hideNumber,
...rest
}: IPercentageCircleProps) => { }: IPercentageCircleProps) => {
const theme = useTheme(); const theme = useTheme();
let circle = { const style: CSSProperties = {
height: '65px', display: 'block',
width: '65px', borderRadius: '100%',
borderRadius: '50%', transform: 'rotate(-90deg)',
color: '#fff', height: size,
backgroundColor: theme.palette.grey[200], width: size,
backgroundImage: `conic-gradient(${ background: theme.palette.grey[200],
theme.palette.primary.light
} ${percentage}%, ${secondaryPieColor || theme.palette.grey[200]} 1%)`,
}; };
if (percentage === 100) { // The percentage circle used to be drawn by CSS with a conic-gradient,
return ( // but the result was either jagged or blurry. SVG seems to look better.
<div // See https://stackoverflow.com/a/70659532.
style={{ const r = 100 / (2 * Math.PI);
...circle, const d = 2 * r;
...styles,
display: 'flex',
justifyContent: 'center',
alignItems: 'center',
fontSize: '12px',
}}
>
{hideNumber ? null : '100%'}
</div>
);
}
return <div style={{ ...circle, ...styles }} {...rest} />; return (
<svg viewBox={`0 0 ${d} ${d}`} style={style} aria-hidden>
<circle
r={r}
cx={r}
cy={r}
fill="none"
stroke={theme.palette.primary.light}
strokeWidth={d}
strokeDasharray={`${percentage} 100`}
/>
</svg>
);
}; };
export default PercentageCircle; export default PercentageCircle;

View File

@ -13,24 +13,17 @@ export const FeatureStrategyIcons = ({
return null; return null;
} }
const strategyNames = strategies.map(strategy => strategy.name);
const uniqueStrategyNames = uniqueValues(strategyNames);
return ( return (
<StyledList aria-label="Feature strategies"> <StyledList aria-label="Feature strategies">
{uniqueStrategyNames.map(strategyName => ( {strategies.map(strategy => (
<StyledListItem key={strategyName}> <StyledListItem key={strategy.name}>
<FeatureStrategyIcon strategyName={strategyName} /> <FeatureStrategyIcon strategyName={strategy.name} />
</StyledListItem> </StyledListItem>
))} ))}
</StyledList> </StyledList>
); );
}; };
const uniqueValues = <T,>(values: T[]): T[] => {
return [...new Set(values)];
};
const StyledList = styled('ul')(() => ({ const StyledList = styled('ul')(() => ({
all: 'unset', all: 'unset',
display: 'flex', display: 'flex',

View File

@ -43,29 +43,25 @@ export const StrategyExecution = ({ strategy }: IStrategyExecutionProps) => {
switch (key) { switch (key) {
case 'rollout': case 'rollout':
case 'Rollout': case 'Rollout':
const percentage = parseParameterNumber(parameters[key]);
return ( return (
<Box <Box
className={styles.summary} className={styles.summary}
key={key} key={key}
sx={{ display: 'flex', alignItems: 'center' }} sx={{ display: 'flex', alignItems: 'center' }}
> >
<PercentageCircle <Box sx={{ mr: '1rem' }}>
hideNumber <PercentageCircle
percentage={parseParameterNumber( percentage={percentage}
parameters[key] size="2rem"
)} />
styles={{ </Box>
width: '2rem',
height: '2rem',
marginRight: '1rem',
}}
/>
<div> <div>
<Chip <Chip
color="success" color="success"
variant="outlined" variant="outlined"
size="small" size="small"
label={`${parameters[key]}%`} label={`${percentage}%`}
/>{' '} />{' '}
of your base{' '} of your base{' '}
{constraints.length > 0 {constraints.length > 0

View File

@ -23,6 +23,7 @@ export const useStyles = makeStyles()(theme => ({
maxWidth: '270px', maxWidth: '270px',
marginTop: '0.25rem', marginTop: '0.25rem',
fontSize: theme.fontSizes.smallBody, fontSize: theme.fontSizes.smallBody,
textAlign: 'right',
[theme.breakpoints.down(700)]: { [theme.breakpoints.down(700)]: {
display: 'none', display: 'none',
}, },
@ -33,7 +34,7 @@ export const useStyles = makeStyles()(theme => ({
fontSize: theme.fontSizes.subHeader, fontSize: theme.fontSizes.subHeader,
}, },
percentageCircle: { percentageCircle: {
transform: 'scale(0.85)', margin: '0 1rem',
[theme.breakpoints.down(500)]: { [theme.breakpoints.down(500)]: {
display: 'none', display: 'none',
}, },

View File

@ -73,11 +73,9 @@ const FeatureOverviewEnvironmentMetrics = ({
hour hour
</p> </p>
</div> </div>
<PercentageCircle <div className={styles.percentageCircle} data-loading>
className={styles.percentageCircle} <PercentageCircle percentage={percentage} size="3rem" />
percentage={percentage} </div>
data-loading
/>
</div> </div>
); );
}; };

View File

@ -97,7 +97,13 @@ const StrategyInputList = ({
<ConditionallyRender <ConditionallyRender
condition={!disabled} condition={!disabled}
show={ show={
<div style={{ display: 'flex', alignItems: 'center' }}> <div
style={{
display: 'flex',
alignItems: 'center',
gap: '1rem',
}}
>
<TextField <TextField
name={`input_field`} name={`input_field`}
variant="outlined" variant="outlined"
@ -116,6 +122,7 @@ const StrategyInputList = ({
<Button <Button
onClick={setValue} onClick={setValue}
data-testid={ADD_TO_STRATEGY_INPUT_LIST} data-testid={ADD_TO_STRATEGY_INPUT_LIST}
variant="outlined"
color="secondary" color="secondary"
startIcon={<Add />} startIcon={<Add />}
> >

View File

@ -83,6 +83,7 @@ export const CreateStrategy = () => {
handleCancel={handleCancel} handleCancel={handleCancel}
strategyName={strategyName} strategyName={strategyName}
setStrategyName={setStrategyName} setStrategyName={setStrategyName}
validateStrategyName={validateStrategyName}
strategyDesc={strategyDesc} strategyDesc={strategyDesc}
setStrategyDesc={setStrategyDesc} setStrategyDesc={setStrategyDesc}
params={params} params={params}

View File

@ -12,6 +12,7 @@ interface IStrategyFormProps {
strategyDesc: string; strategyDesc: string;
params: IStrategyParameter[]; params: IStrategyParameter[];
setStrategyName: React.Dispatch<React.SetStateAction<string>>; setStrategyName: React.Dispatch<React.SetStateAction<string>>;
validateStrategyName?: () => void;
setStrategyDesc: React.Dispatch<React.SetStateAction<string>>; setStrategyDesc: React.Dispatch<React.SetStateAction<string>>;
setParams: React.Dispatch<React.SetStateAction<IStrategyParameter[]>>; setParams: React.Dispatch<React.SetStateAction<IStrategyParameter[]>>;
handleSubmit: (e: React.FormEvent<HTMLFormElement>) => void; handleSubmit: (e: React.FormEvent<HTMLFormElement>) => void;
@ -31,6 +32,7 @@ export const StrategyForm: React.FC<IStrategyFormProps> = ({
params, params,
setParams, setParams,
setStrategyName, setStrategyName,
validateStrategyName,
setStrategyDesc, setStrategyDesc,
errors, errors,
mode, mode,
@ -65,7 +67,8 @@ export const StrategyForm: React.FC<IStrategyFormProps> = ({
onChange={e => setStrategyName(trim(e.target.value))} onChange={e => setStrategyName(trim(e.target.value))}
error={Boolean(errors.name)} error={Boolean(errors.name)}
errorText={errors.name} errorText={errors.name}
onFocus={() => clearErrors()} onFocus={clearErrors}
onBlur={validateStrategyName}
/> />
<p className={styles.inputDescription}> <p className={styles.inputDescription}>
What is your strategy description? What is your strategy description?

View File

@ -29,12 +29,6 @@ export const useStyles = makeStyles()(theme => ({
inputDescription: { inputDescription: {
marginBottom: '0.5rem', marginBottom: '0.5rem',
}, },
typeDescription: {
fontSize: theme.fontSizes.smallBody,
color: theme.palette.grey[600],
top: '-13px',
position: 'relative',
},
errorMessage: { errorMessage: {
fontSize: theme.fontSizes.smallBody, fontSize: theme.fontSizes.smallBody,
color: theme.palette.error.main, color: theme.palette.error.main,

View File

@ -17,31 +17,22 @@ const paramTypesOptions = [
{ {
key: 'string', key: 'string',
label: 'string', label: 'string',
description: 'A string is a collection of characters',
}, },
{ {
key: 'percentage', key: 'percentage',
label: 'percentage', label: 'percentage',
description:
'Percentage is used when you want to make your feature visible to a process part of your customers',
}, },
{ {
key: 'list', key: 'list',
label: 'list', label: 'list',
description:
'A list is used when you want to define several parameters that must be met before your feature becomes visible to your customers',
}, },
{ {
key: 'number', key: 'number',
label: 'number', label: 'number',
description:
'Number is used when you have one or more digits that must be met for your feature to be visible to your customers',
}, },
{ {
key: 'boolean', key: 'boolean',
label: 'boolean', label: 'boolean',
description:
'A boolean value represents a truth value, which is either true or false',
}, },
]; ];
@ -68,11 +59,6 @@ export const StrategyParameter = ({
set({ type }); set({ type });
}; };
const renderParamTypeDescription = () => {
return paramTypesOptions.find(param => param.key === input.type)
?.description;
};
return ( return (
<div className={styles.paramsContainer}> <div className={styles.paramsContainer}>
<Divider className={styles.divider} /> <Divider className={styles.divider} />
@ -80,7 +66,16 @@ export const StrategyParameter = ({
condition={index === 0} condition={index === 0}
show={ show={
<p className={styles.input}> <p className={styles.input}>
The parameters define what the strategy will look like. Parameters let you provide arguments to your strategy
that it can access for evaluation. Read more in the{' '}
<a
href="https://docs.getunleash.io/advanced/custom_activation_strategy#parameter-types"
target="_blank"
rel="noreferrer"
>
parameter types documentation
</a>
.
</p> </p>
} }
/> />
@ -114,9 +109,6 @@ export const StrategyParameter = ({
id={`prop-type-${index}-select`} id={`prop-type-${index}-select`}
className={styles.input} className={styles.input}
/> />
<p className={styles.typeDescription}>
{renderParamTypeDescription()}
</p>
<Input <Input
rows={2} rows={2}
multiline multiline