mirror of
				https://github.com/Unleash/unleash.git
				synced 2025-10-27 11:02:16 +01:00 
			
		
		
		
	Feat/disabled strategies (#5930)
This PR makes disabled strategies more prominent in the UI: <img width="1031" alt="Skjermbilde 2024-01-17 kl 11 26 11" src="https://github.com/Unleash/unleash/assets/16081982/4a07c0aa-8f86-4854-829e-1088abecfb4e">
This commit is contained in:
		
							parent
							
								
									ee08bd8d42
								
							
						
					
					
						commit
						1deee10317
					
				| @ -12,6 +12,7 @@ export const ConstraintIcon: VFC<IConstraintIconProps> = ({ | ||||
|     disabled, | ||||
| }) => ( | ||||
|     <Box | ||||
|         className='constraint-icon-container' | ||||
|         sx={(theme) => ({ | ||||
|             backgroundColor: disabled | ||||
|                 ? theme.palette.neutral.border | ||||
| @ -24,6 +25,7 @@ export const ConstraintIcon: VFC<IConstraintIconProps> = ({ | ||||
|         })} | ||||
|     > | ||||
|         <TrackChanges | ||||
|             className='constraint-icon' | ||||
|             sx={(theme) => ({ | ||||
|                 fill: theme.palette.common.white, | ||||
|                 display: 'block', | ||||
|  | ||||
| @ -15,7 +15,7 @@ const StyledContainer = styled('div')(({ theme }) => ({ | ||||
|     lineHeight: 1.25, | ||||
| })); | ||||
| 
 | ||||
| const StyledName = styled('div', { | ||||
| const StyledName = styled('p', { | ||||
|     shouldForwardProp: (prop) => prop !== 'disabled', | ||||
| })<{ disabled: boolean }>(({ theme, disabled }) => ({ | ||||
|     fontSize: theme.fontSizes.smallBody, | ||||
| @ -23,7 +23,7 @@ const StyledName = styled('div', { | ||||
|     color: disabled ? theme.palette.text.secondary : theme.palette.text.primary, | ||||
| })); | ||||
| 
 | ||||
| const StyledText = styled('div', { | ||||
| const StyledText = styled('p', { | ||||
|     shouldForwardProp: (prop) => prop !== 'disabled', | ||||
| })<{ disabled: boolean }>(({ theme, disabled }) => ({ | ||||
|     fontSize: theme.fontSizes.smallerBody, | ||||
|  | ||||
| @ -15,7 +15,7 @@ const StyledContainer = styled('div')(({ theme }) => ({ | ||||
|     lineHeight: 1.25, | ||||
| })); | ||||
| 
 | ||||
| const StyledName = styled('div', { | ||||
| const StyledName = styled('p', { | ||||
|     shouldForwardProp: (prop) => prop !== 'disabled', | ||||
| })<{ disabled: boolean }>(({ theme, disabled }) => ({ | ||||
|     fontSize: theme.fontSizes.smallBody, | ||||
| @ -23,7 +23,7 @@ const StyledName = styled('div', { | ||||
|     color: disabled ? theme.palette.text.secondary : theme.palette.text.primary, | ||||
| })); | ||||
| 
 | ||||
| const StyledText = styled('div', { | ||||
| const StyledText = styled('p', { | ||||
|     shouldForwardProp: (prop) => prop !== 'disabled', | ||||
| })<{ disabled: boolean }>(({ theme, disabled }) => ({ | ||||
|     fontSize: theme.fontSizes.smallerBody, | ||||
|  | ||||
| @ -4,11 +4,13 @@ import { CSSProperties } from 'react'; | ||||
| interface IPercentageCircleProps { | ||||
|     percentage: number; | ||||
|     size?: `${number}rem`; | ||||
|     disabled?: boolean | null; | ||||
| } | ||||
| 
 | ||||
| const PercentageCircle = ({ | ||||
|     percentage, | ||||
|     size = '4rem', | ||||
|     disabled = false, | ||||
| }: IPercentageCircleProps) => { | ||||
|     const theme = useTheme(); | ||||
| 
 | ||||
| @ -27,6 +29,10 @@ const PercentageCircle = ({ | ||||
|     const r = 100 / (2 * Math.PI); | ||||
|     const d = 2 * r; | ||||
| 
 | ||||
|     const color = disabled | ||||
|         ? theme.palette.neutral.border | ||||
|         : theme.palette.primary.light; | ||||
| 
 | ||||
|     return ( | ||||
|         <svg viewBox={`0 0 ${d} ${d}`} style={style} aria-hidden> | ||||
|             <title>A circle progress bar with {percentage}% completion.</title> | ||||
| @ -35,7 +41,7 @@ const PercentageCircle = ({ | ||||
|                 cx={r} | ||||
|                 cy={r} | ||||
|                 fill='none' | ||||
|                 stroke={theme.palette.primary.light} | ||||
|                 stroke={color} | ||||
|                 strokeWidth={d} | ||||
|                 strokeDasharray={`${percentage} 100`} | ||||
|             /> | ||||
|  | ||||
| @ -16,15 +16,16 @@ import { ConditionallyRender } from 'component/common/ConditionallyRender/Condit | ||||
| interface ISegmentItemProps { | ||||
|     segment: Partial<ISegment>; | ||||
|     isExpanded?: boolean; | ||||
|     disabled?: boolean; | ||||
|     disabled?: boolean | null; | ||||
|     constraintList?: JSX.Element; | ||||
|     headerContent?: JSX.Element; | ||||
| } | ||||
| 
 | ||||
| const StyledAccordion = styled(Accordion)(({ theme }) => ({ | ||||
| const StyledAccordion = styled(Accordion, { | ||||
|     shouldForwardProp: (prop) => prop !== 'isDisabled', | ||||
| })<{ isDisabled: boolean | null }>(({ theme, isDisabled }) => ({ | ||||
|     border: `1px solid ${theme.palette.divider}`, | ||||
|     borderRadius: theme.shape.borderRadiusMedium, | ||||
|     backgroundColor: theme.palette.background.paper, | ||||
|     boxShadow: 'none', | ||||
|     margin: 0, | ||||
|     transition: 'all 0.1s ease', | ||||
| @ -32,6 +33,9 @@ const StyledAccordion = styled(Accordion)(({ theme }) => ({ | ||||
|         opacity: '0 !important', | ||||
|     }, | ||||
|     '&.Mui-expanded': { backgroundColor: theme.palette.neutral.light }, | ||||
|     backgroundColor: isDisabled | ||||
|         ? theme.palette.envAccordion.disabled | ||||
|         : theme.palette.background.paper, | ||||
| })); | ||||
| 
 | ||||
| const StyledAccordionSummary = styled(AccordionSummary)(({ theme }) => ({ | ||||
| @ -52,7 +56,7 @@ const StyledLink = styled(Link)(({ theme }) => ({ | ||||
| })); | ||||
| const StyledText = styled('span', { | ||||
|     shouldForwardProp: (prop) => prop !== 'disabled', | ||||
| })<{ disabled: boolean }>(({ theme, disabled }) => ({ | ||||
| })<{ disabled: boolean | null }>(({ theme, disabled }) => ({ | ||||
|     color: disabled ? theme.palette.text.secondary : 'inherit', | ||||
| })); | ||||
| 
 | ||||
| @ -66,7 +70,7 @@ export const SegmentItem: VFC<ISegmentItemProps> = ({ | ||||
|     const [isOpen, setIsOpen] = useState(isExpanded || false); | ||||
| 
 | ||||
|     return ( | ||||
|         <StyledAccordion expanded={isOpen}> | ||||
|         <StyledAccordion isDisabled={disabled}> | ||||
|             <StyledAccordionSummary id={`segment-accordion-${segment.id}`}> | ||||
|                 <DonutLarge | ||||
|                     sx={(theme) => ({ | ||||
|  | ||||
| @ -6,7 +6,6 @@ import { StrategySeparator } from 'component/common/StrategySeparator/StrategySe | ||||
| import { ConstraintItem } from './ConstraintItem/ConstraintItem'; | ||||
| import { useStrategies } from 'hooks/api/getters/useStrategies/useStrategies'; | ||||
| import { useSegments } from 'hooks/api/getters/useSegments/useSegments'; | ||||
| import useUiConfig from 'hooks/api/getters/useUiConfig/useUiConfig'; | ||||
| import { FeatureOverviewSegment } from 'component/feature/FeatureView/FeatureOverview/FeatureOverviewSegment/FeatureOverviewSegment'; | ||||
| import { ConstraintAccordionList } from 'component/common/ConstraintAccordion/ConstraintAccordionList/ConstraintAccordionList'; | ||||
| import { | ||||
| @ -23,6 +22,25 @@ interface IStrategyExecutionProps { | ||||
|     strategy: IFeatureStrategyPayload | CreateFeatureStrategySchema; | ||||
| } | ||||
| 
 | ||||
| const StyledContainer = styled(Box, { | ||||
|     shouldForwardProp: (prop) => prop !== 'disabled', | ||||
| })<{ disabled?: boolean | null }>(({ theme, disabled }) => ({ | ||||
|     '& p, & span, & h1, & h2, & h3, & h4, & h5, & h6': { | ||||
|         color: disabled ? theme.palette.neutral.main : 'inherit', | ||||
|     }, | ||||
|     '.constraint-icon-container': { | ||||
|         backgroundColor: disabled | ||||
|             ? theme.palette.neutral.border | ||||
|             : theme.palette.primary.light, | ||||
|         borderRadius: '50%', | ||||
|     }, | ||||
|     '.constraint-icon': { | ||||
|         fill: disabled | ||||
|             ? theme.palette.neutral.light | ||||
|             : theme.palette.common.white, | ||||
|     }, | ||||
| })); | ||||
| 
 | ||||
| const NoItems: VFC = () => ( | ||||
|     <Box sx={{ px: 3, color: 'text.disabled' }}> | ||||
|         This strategy does not have constraints or parameters. | ||||
| @ -44,7 +62,6 @@ export const StrategyExecution: VFC<IStrategyExecutionProps> = ({ | ||||
| }) => { | ||||
|     const { parameters, constraints = [] } = strategy; | ||||
|     const { strategies } = useStrategies(); | ||||
|     const { uiConfig } = useUiConfig(); | ||||
|     const { segments } = useSegments(); | ||||
|     const strategySegments = segments?.filter((segment) => { | ||||
|         return strategy.segments?.includes(segment.id); | ||||
| @ -63,6 +80,8 @@ export const StrategyExecution: VFC<IStrategyExecutionProps> = ({ | ||||
|                 case 'Rollout': { | ||||
|                     const percentage = parseParameterNumber(parameters[key]); | ||||
| 
 | ||||
|                     const badgeType = strategy.disabled ? 'neutral' : 'success'; | ||||
| 
 | ||||
|                     return ( | ||||
|                         <StyledValueContainer | ||||
|                             sx={{ display: 'flex', alignItems: 'center' }} | ||||
| @ -71,15 +90,18 @@ export const StrategyExecution: VFC<IStrategyExecutionProps> = ({ | ||||
|                                 <PercentageCircle | ||||
|                                     percentage={percentage} | ||||
|                                     size='2rem' | ||||
|                                     disabled={strategy.disabled} | ||||
|                                 /> | ||||
|                             </Box> | ||||
|                             <div> | ||||
|                                 <Badge color='success'>{percentage}%</Badge> of | ||||
|                                 your base{' '} | ||||
|                                 {constraints.length > 0 | ||||
|                                     ? 'who match constraints' | ||||
|                                     : ''}{' '} | ||||
|                                 is included. | ||||
|                                 <Badge color={badgeType}>{percentage}%</Badge>{' '} | ||||
|                                 <span>of your base</span>{' '} | ||||
|                                 <span> | ||||
|                                     {constraints.length > 0 | ||||
|                                         ? 'who match constraints' | ||||
|                                         : ''}{' '} | ||||
|                                     is included. | ||||
|                                 </span> | ||||
|                             </div> | ||||
|                         </StyledValueContainer> | ||||
|                     ); | ||||
| @ -109,7 +131,7 @@ export const StrategyExecution: VFC<IStrategyExecutionProps> = ({ | ||||
|                     return null; | ||||
|             } | ||||
|         }); | ||||
|     }, [parameters, definition, constraints]); | ||||
|     }, [parameters, definition, constraints, strategy.disabled]); | ||||
| 
 | ||||
|     const customStrategyList = useMemo(() => { | ||||
|         if (!parameters || !definition?.editable) return null; | ||||
| @ -252,7 +274,10 @@ export const StrategyExecution: VFC<IStrategyExecutionProps> = ({ | ||||
| 
 | ||||
|     const listItems = [ | ||||
|         strategySegments && strategySegments.length > 0 && ( | ||||
|             <FeatureOverviewSegment segments={strategySegments} /> | ||||
|             <FeatureOverviewSegment | ||||
|                 segments={strategySegments} | ||||
|                 disabled={strategy.disabled} | ||||
|             /> | ||||
|         ), | ||||
|         constraints.length > 0 && ( | ||||
|             <ConstraintAccordionList | ||||
| @ -276,7 +301,7 @@ export const StrategyExecution: VFC<IStrategyExecutionProps> = ({ | ||||
|         <ConditionallyRender | ||||
|             condition={listItems.length > 0} | ||||
|             show={ | ||||
|                 <> | ||||
|                 <StyledContainer disabled={Boolean(strategy.disabled)}> | ||||
|                     {listItems.map((item, index) => ( | ||||
|                         // biome-ignore lint/suspicious/noArrayIndexKey: <explanation>
 | ||||
|                         <Fragment key={index}> | ||||
| @ -287,7 +312,7 @@ export const StrategyExecution: VFC<IStrategyExecutionProps> = ({ | ||||
|                             {item} | ||||
|                         </Fragment> | ||||
|                     ))} | ||||
|                 </> | ||||
|                 </StyledContainer> | ||||
|             } | ||||
|             elseShow={<NoItems />} | ||||
|         /> | ||||
|  | ||||
| @ -13,7 +13,7 @@ import { CopyStrategyIconMenu } from './CopyStrategyIconMenu/CopyStrategyIconMen | ||||
| import { StrategyItemContainer } from 'component/common/StrategyItemContainer/StrategyItemContainer'; | ||||
| import MenuStrategyRemove from './MenuStrategyRemove/MenuStrategyRemove'; | ||||
| import SplitPreviewSlider from 'component/feature/StrategyTypes/SplitPreviewSlider/SplitPreviewSlider'; | ||||
| 
 | ||||
| import { Box } from '@mui/material'; | ||||
| interface IStrategyItemProps { | ||||
|     environmentId: string; | ||||
|     strategy: IFeatureStrategy; | ||||
| @ -87,9 +87,16 @@ export const StrategyItem: FC<IStrategyItemProps> = ({ | ||||
|             } | ||||
|         > | ||||
|             <StrategyExecution strategy={strategy} /> | ||||
|             {strategy.variants ? ( | ||||
|                 <SplitPreviewSlider variants={strategy.variants} /> | ||||
|             ) : null} | ||||
| 
 | ||||
|             {strategy.variants && | ||||
|                 strategy.variants.length > 0 && | ||||
|                 (strategy.disabled ? ( | ||||
|                     <Box sx={{ opacity: '0.5' }}> | ||||
|                         <SplitPreviewSlider variants={strategy.variants} /> | ||||
|                     </Box> | ||||
|                 ) : ( | ||||
|                     <SplitPreviewSlider variants={strategy.variants} /> | ||||
|                 ))} | ||||
|         </StrategyItemContainer> | ||||
|     ); | ||||
| }; | ||||
|  | ||||
| @ -6,10 +6,12 @@ import { ISegment } from 'interfaces/segment'; | ||||
| 
 | ||||
| interface IFeatureOverviewSegmentProps { | ||||
|     segments?: ISegment[]; | ||||
|     disabled?: boolean | null; | ||||
| } | ||||
| 
 | ||||
| export const FeatureOverviewSegment = ({ | ||||
|     segments, | ||||
|     disabled = false, | ||||
| }: IFeatureOverviewSegmentProps) => { | ||||
|     if (!segments || segments.length === 0) { | ||||
|         return null; | ||||
| @ -23,7 +25,7 @@ export const FeatureOverviewSegment = ({ | ||||
|                         condition={index > 0} | ||||
|                         show={<StrategySeparator text='AND' />} | ||||
|                     /> | ||||
|                     <SegmentItem segment={segment} /> | ||||
|                     <SegmentItem segment={segment} disabled={disabled} /> | ||||
|                 </Fragment> | ||||
|             ))} | ||||
|         </> | ||||
|  | ||||
| @ -35,7 +35,7 @@ exports[`renders an empty list correctly 1`] = ` | ||||
|                 className="css-non55o" | ||||
|               > | ||||
|                 <div | ||||
|                   className="css-16ldy6v" | ||||
|                   className="css-1om4ep4" | ||||
|                 > | ||||
|                   <svg | ||||
|                     aria-hidden={true} | ||||
|  | ||||
| @ -137,7 +137,7 @@ export const theme = { | ||||
|             main: colors.grey[700], | ||||
|             light: colors.grey[100], | ||||
|             dark: colors.grey[800], | ||||
|             border: colors.grey[400], | ||||
|             border: colors.grey[500], | ||||
|             contrastText: colors.grey[800], // Color used for text inside badge
 | ||||
|         }, | ||||
| 
 | ||||
|  | ||||
		Loading…
	
		Reference in New Issue
	
	Block a user