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:
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