1
0
mirror of https://github.com/Unleash/unleash.git synced 2025-02-23 00:22:19 +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:
Fredrik Strand Oseberg 2024-01-17 13:20:39 +01:00 committed by GitHub
parent ee08bd8d42
commit 1deee10317
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
10 changed files with 75 additions and 29 deletions

View File

@ -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',

View File

@ -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,

View File

@ -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,

View File

@ -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`}
/>

View File

@ -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) => ({

View File

@ -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 />}
/>

View File

@ -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>
);
};

View File

@ -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>
))}
</>

View File

@ -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}

View File

@ -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
},