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:
parent
79aa2d4ebe
commit
710ebe08b3
@ -1,51 +1,45 @@
|
||||
import { useTheme } from '@mui/material';
|
||||
import { CSSProperties } from 'react';
|
||||
|
||||
interface IPercentageCircleProps {
|
||||
styles?: object;
|
||||
percentage: number;
|
||||
secondaryPieColor?: string;
|
||||
className?: string;
|
||||
hideNumber?: boolean;
|
||||
size?: `${number}rem`;
|
||||
}
|
||||
|
||||
const PercentageCircle = ({
|
||||
styles,
|
||||
percentage,
|
||||
secondaryPieColor,
|
||||
hideNumber,
|
||||
...rest
|
||||
size = '4rem',
|
||||
}: IPercentageCircleProps) => {
|
||||
const theme = useTheme();
|
||||
|
||||
let circle = {
|
||||
height: '65px',
|
||||
width: '65px',
|
||||
borderRadius: '50%',
|
||||
color: '#fff',
|
||||
backgroundColor: theme.palette.grey[200],
|
||||
backgroundImage: `conic-gradient(${
|
||||
theme.palette.primary.light
|
||||
} ${percentage}%, ${secondaryPieColor || theme.palette.grey[200]} 1%)`,
|
||||
const style: CSSProperties = {
|
||||
display: 'block',
|
||||
borderRadius: '100%',
|
||||
transform: 'rotate(-90deg)',
|
||||
height: size,
|
||||
width: size,
|
||||
background: theme.palette.grey[200],
|
||||
};
|
||||
|
||||
if (percentage === 100) {
|
||||
return (
|
||||
<div
|
||||
style={{
|
||||
...circle,
|
||||
...styles,
|
||||
display: 'flex',
|
||||
justifyContent: 'center',
|
||||
alignItems: 'center',
|
||||
fontSize: '12px',
|
||||
}}
|
||||
>
|
||||
{hideNumber ? null : '100%'}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
// The percentage circle used to be drawn by CSS with a conic-gradient,
|
||||
// but the result was either jagged or blurry. SVG seems to look better.
|
||||
// See https://stackoverflow.com/a/70659532.
|
||||
const r = 100 / (2 * Math.PI);
|
||||
const d = 2 * r;
|
||||
|
||||
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;
|
||||
|
@ -13,24 +13,17 @@ export const FeatureStrategyIcons = ({
|
||||
return null;
|
||||
}
|
||||
|
||||
const strategyNames = strategies.map(strategy => strategy.name);
|
||||
const uniqueStrategyNames = uniqueValues(strategyNames);
|
||||
|
||||
return (
|
||||
<StyledList aria-label="Feature strategies">
|
||||
{uniqueStrategyNames.map(strategyName => (
|
||||
<StyledListItem key={strategyName}>
|
||||
<FeatureStrategyIcon strategyName={strategyName} />
|
||||
{strategies.map(strategy => (
|
||||
<StyledListItem key={strategy.name}>
|
||||
<FeatureStrategyIcon strategyName={strategy.name} />
|
||||
</StyledListItem>
|
||||
))}
|
||||
</StyledList>
|
||||
);
|
||||
};
|
||||
|
||||
const uniqueValues = <T,>(values: T[]): T[] => {
|
||||
return [...new Set(values)];
|
||||
};
|
||||
|
||||
const StyledList = styled('ul')(() => ({
|
||||
all: 'unset',
|
||||
display: 'flex',
|
||||
|
@ -43,29 +43,25 @@ export const StrategyExecution = ({ strategy }: IStrategyExecutionProps) => {
|
||||
switch (key) {
|
||||
case 'rollout':
|
||||
case 'Rollout':
|
||||
const percentage = parseParameterNumber(parameters[key]);
|
||||
return (
|
||||
<Box
|
||||
className={styles.summary}
|
||||
key={key}
|
||||
sx={{ display: 'flex', alignItems: 'center' }}
|
||||
>
|
||||
<Box sx={{ mr: '1rem' }}>
|
||||
<PercentageCircle
|
||||
hideNumber
|
||||
percentage={parseParameterNumber(
|
||||
parameters[key]
|
||||
)}
|
||||
styles={{
|
||||
width: '2rem',
|
||||
height: '2rem',
|
||||
marginRight: '1rem',
|
||||
}}
|
||||
percentage={percentage}
|
||||
size="2rem"
|
||||
/>
|
||||
</Box>
|
||||
<div>
|
||||
<Chip
|
||||
color="success"
|
||||
variant="outlined"
|
||||
size="small"
|
||||
label={`${parameters[key]}%`}
|
||||
label={`${percentage}%`}
|
||||
/>{' '}
|
||||
of your base{' '}
|
||||
{constraints.length > 0
|
||||
|
@ -23,6 +23,7 @@ export const useStyles = makeStyles()(theme => ({
|
||||
maxWidth: '270px',
|
||||
marginTop: '0.25rem',
|
||||
fontSize: theme.fontSizes.smallBody,
|
||||
textAlign: 'right',
|
||||
[theme.breakpoints.down(700)]: {
|
||||
display: 'none',
|
||||
},
|
||||
@ -33,7 +34,7 @@ export const useStyles = makeStyles()(theme => ({
|
||||
fontSize: theme.fontSizes.subHeader,
|
||||
},
|
||||
percentageCircle: {
|
||||
transform: 'scale(0.85)',
|
||||
margin: '0 1rem',
|
||||
[theme.breakpoints.down(500)]: {
|
||||
display: 'none',
|
||||
},
|
||||
|
@ -73,11 +73,9 @@ const FeatureOverviewEnvironmentMetrics = ({
|
||||
hour
|
||||
</p>
|
||||
</div>
|
||||
<PercentageCircle
|
||||
className={styles.percentageCircle}
|
||||
percentage={percentage}
|
||||
data-loading
|
||||
/>
|
||||
<div className={styles.percentageCircle} data-loading>
|
||||
<PercentageCircle percentage={percentage} size="3rem" />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
@ -97,7 +97,13 @@ const StrategyInputList = ({
|
||||
<ConditionallyRender
|
||||
condition={!disabled}
|
||||
show={
|
||||
<div style={{ display: 'flex', alignItems: 'center' }}>
|
||||
<div
|
||||
style={{
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
gap: '1rem',
|
||||
}}
|
||||
>
|
||||
<TextField
|
||||
name={`input_field`}
|
||||
variant="outlined"
|
||||
@ -116,6 +122,7 @@ const StrategyInputList = ({
|
||||
<Button
|
||||
onClick={setValue}
|
||||
data-testid={ADD_TO_STRATEGY_INPUT_LIST}
|
||||
variant="outlined"
|
||||
color="secondary"
|
||||
startIcon={<Add />}
|
||||
>
|
||||
|
@ -83,6 +83,7 @@ export const CreateStrategy = () => {
|
||||
handleCancel={handleCancel}
|
||||
strategyName={strategyName}
|
||||
setStrategyName={setStrategyName}
|
||||
validateStrategyName={validateStrategyName}
|
||||
strategyDesc={strategyDesc}
|
||||
setStrategyDesc={setStrategyDesc}
|
||||
params={params}
|
||||
|
@ -12,6 +12,7 @@ interface IStrategyFormProps {
|
||||
strategyDesc: string;
|
||||
params: IStrategyParameter[];
|
||||
setStrategyName: React.Dispatch<React.SetStateAction<string>>;
|
||||
validateStrategyName?: () => void;
|
||||
setStrategyDesc: React.Dispatch<React.SetStateAction<string>>;
|
||||
setParams: React.Dispatch<React.SetStateAction<IStrategyParameter[]>>;
|
||||
handleSubmit: (e: React.FormEvent<HTMLFormElement>) => void;
|
||||
@ -31,6 +32,7 @@ export const StrategyForm: React.FC<IStrategyFormProps> = ({
|
||||
params,
|
||||
setParams,
|
||||
setStrategyName,
|
||||
validateStrategyName,
|
||||
setStrategyDesc,
|
||||
errors,
|
||||
mode,
|
||||
@ -65,7 +67,8 @@ export const StrategyForm: React.FC<IStrategyFormProps> = ({
|
||||
onChange={e => setStrategyName(trim(e.target.value))}
|
||||
error={Boolean(errors.name)}
|
||||
errorText={errors.name}
|
||||
onFocus={() => clearErrors()}
|
||||
onFocus={clearErrors}
|
||||
onBlur={validateStrategyName}
|
||||
/>
|
||||
<p className={styles.inputDescription}>
|
||||
What is your strategy description?
|
||||
|
@ -29,12 +29,6 @@ export const useStyles = makeStyles()(theme => ({
|
||||
inputDescription: {
|
||||
marginBottom: '0.5rem',
|
||||
},
|
||||
typeDescription: {
|
||||
fontSize: theme.fontSizes.smallBody,
|
||||
color: theme.palette.grey[600],
|
||||
top: '-13px',
|
||||
position: 'relative',
|
||||
},
|
||||
errorMessage: {
|
||||
fontSize: theme.fontSizes.smallBody,
|
||||
color: theme.palette.error.main,
|
||||
|
@ -17,31 +17,22 @@ const paramTypesOptions = [
|
||||
{
|
||||
key: 'string',
|
||||
label: 'string',
|
||||
description: 'A string is a collection of characters',
|
||||
},
|
||||
{
|
||||
key: 'percentage',
|
||||
label: 'percentage',
|
||||
description:
|
||||
'Percentage is used when you want to make your feature visible to a process part of your customers',
|
||||
},
|
||||
{
|
||||
key: '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',
|
||||
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',
|
||||
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 });
|
||||
};
|
||||
|
||||
const renderParamTypeDescription = () => {
|
||||
return paramTypesOptions.find(param => param.key === input.type)
|
||||
?.description;
|
||||
};
|
||||
|
||||
return (
|
||||
<div className={styles.paramsContainer}>
|
||||
<Divider className={styles.divider} />
|
||||
@ -80,7 +66,16 @@ export const StrategyParameter = ({
|
||||
condition={index === 0}
|
||||
show={
|
||||
<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>
|
||||
}
|
||||
/>
|
||||
@ -114,9 +109,6 @@ export const StrategyParameter = ({
|
||||
id={`prop-type-${index}-select`}
|
||||
className={styles.input}
|
||||
/>
|
||||
<p className={styles.typeDescription}>
|
||||
{renderParamTypeDescription()}
|
||||
</p>
|
||||
<Input
|
||||
rows={2}
|
||||
multiline
|
||||
|
Loading…
Reference in New Issue
Block a user