mirror of
				https://github.com/Unleash/unleash.git
				synced 2025-10-27 11:02:16 +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' }} | ||||
|                         > | ||||
|                             <PercentageCircle | ||||
|                                 hideNumber | ||||
|                                 percentage={parseParameterNumber( | ||||
|                                     parameters[key] | ||||
|                                 )} | ||||
|                                 styles={{ | ||||
|                                     width: '2rem', | ||||
|                                     height: '2rem', | ||||
|                                     marginRight: '1rem', | ||||
|                                 }} | ||||
|                             /> | ||||
|                             <Box sx={{ mr: '1rem' }}> | ||||
|                                 <PercentageCircle | ||||
|                                     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