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