1
0
mirror of https://github.com/Unleash/unleash.git synced 2025-06-04 01:18:20 +02:00

feat: new constraint view for flag edit page (#9567)

Refactor components in Targeting (Edit strategy)
This commit is contained in:
Tymoteusz Czech 2025-03-20 13:04:24 +01:00 committed by GitHub
parent a10dca44f6
commit 2d47fb3827
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
15 changed files with 334 additions and 238 deletions

View File

@ -1,5 +1,8 @@
import type { FC } from 'react'; import type { FC } from 'react';
import { StrategyEvaluationItem } from '../StrategyEvaluationItem/StrategyEvaluationItem'; import {
StrategyEvaluationItem,
type StrategyEvaluationItemProps,
} from '../StrategyEvaluationItem/StrategyEvaluationItem';
import type { ConstraintSchema } from 'openapi'; import type { ConstraintSchema } from 'openapi';
import { formatOperatorDescription } from 'component/common/ConstraintAccordion/ConstraintOperator/formatOperatorDescription'; import { formatOperatorDescription } from 'component/common/ConstraintAccordion/ConstraintOperator/formatOperatorDescription';
import { StrategyEvaluationChip } from '../StrategyEvaluationChip/StrategyEvaluationChip'; import { StrategyEvaluationChip } from '../StrategyEvaluationChip/StrategyEvaluationChip';
@ -29,18 +32,25 @@ const StyledOperatorGroup = styled('div')(({ theme }) => ({
gap: theme.spacing(0.5), gap: theme.spacing(0.5),
})); }));
export const ConstraintItem: FC<ConstraintSchema> = ({ export const ConstraintItemHeader: FC<
ConstraintSchema & Pick<StrategyEvaluationItemProps, 'onSetTruncated'>
> = ({
caseInsensitive, caseInsensitive,
contextName, contextName,
inverted, inverted,
operator, operator,
value, value,
values, values,
onSetTruncated,
}) => { }) => {
const items = value ? [value, ...(values || [])] : values || []; const items = value ? [value, ...(values || [])] : values || [];
return ( return (
<StrategyEvaluationItem type='Constraint' values={items}> <StrategyEvaluationItem
type='Constraint'
values={items}
onSetTruncated={onSetTruncated}
>
{contextName} {contextName}
<StyledOperatorGroup> <StyledOperatorGroup>
{inverted ? <Inverted /> : null} {inverted ? <Inverted /> : null}

View File

@ -10,17 +10,21 @@ const StyledList = styled('ul')(({ theme }) => ({
gap: theme.spacing(1), gap: theme.spacing(1),
})); }));
const StyledListItem = styled('li')(({ theme }) => ({ export const ConstraintListItem = styled('div')(({ theme }) => ({
position: 'relative', position: 'relative',
border: `1px solid ${theme.palette.divider}`, border: `1px solid ${theme.palette.divider}`,
borderRadius: theme.shape.borderRadiusMedium, borderRadius: theme.shape.borderRadiusMedium,
background: theme.palette.background.default, background: theme.palette.background.default,
padding: theme.spacing(2, 3), padding: theme.spacing(1.5, 3),
display: 'flex', display: 'flex',
flexFlow: 'column', flexFlow: 'column',
gap: theme.spacing(2), gap: theme.spacing(1),
})); }));
const StyledListItem = styled('li')({
position: 'relative',
});
const StyledAnd = styled('div')(({ theme }) => ({ const StyledAnd = styled('div')(({ theme }) => ({
position: 'absolute', position: 'absolute',
top: theme.spacing(-0.5), top: theme.spacing(-0.5),

View File

@ -1,24 +1,28 @@
import { styled } from '@mui/material'; import { styled } from '@mui/material';
import {
Truncator,
type TruncatorProps,
} from 'component/common/Truncator/Truncator';
import { disabledStrategyClassName } from 'component/common/StrategyItemContainer/disabled-strategy-utils'; import { disabledStrategyClassName } from 'component/common/StrategyItemContainer/disabled-strategy-utils';
import type { FC, ReactNode } from 'react'; import type { FC, ReactNode } from 'react';
type StrategyItemProps = { export type StrategyEvaluationItemProps = {
type?: ReactNode; type?: ReactNode;
children?: ReactNode; children?: ReactNode;
values?: string[]; values?: string[];
}; } & Pick<TruncatorProps, 'onSetTruncated'>;
const StyledContainer = styled('div')(({ theme }) => ({ const StyledContainer = styled('div')(({ theme }) => ({
display: 'flex', display: 'flex',
gap: theme.spacing(1), gap: theme.spacing(1),
alignItems: 'center', alignItems: 'center',
fontSize: theme.typography.body2.fontSize, fontSize: theme.typography.body2.fontSize,
minHeight: theme.spacing(4),
})); }));
const StyledContent = styled('div')(({ theme }) => ({ const StyledContent = styled('div')(({ theme }) => ({
display: 'flex', display: 'flex',
gap: theme.spacing(1), gap: theme.spacing(1),
flexWrap: 'wrap',
alignItems: 'center', alignItems: 'center',
[`.${disabledStrategyClassName} &`]: { [`.${disabledStrategyClassName} &`]: {
filter: 'grayscale(1)', filter: 'grayscale(1)',
@ -35,45 +39,33 @@ const StyledType = styled('span')(({ theme }) => ({
width: theme.spacing(10), width: theme.spacing(10),
})); }));
const StyledValuesGroup = styled('ul')(({ theme }) => ({
display: 'flex',
flexFlow: 'row wrap',
alignItems: 'center',
gap: theme.spacing(0.5),
listStyle: 'none',
padding: 0,
}));
const StyledValue = styled('li')(({ theme }) => ({
[`.${disabledStrategyClassName} &`]: {
filter: 'grayscale(1)',
color: theme.palette.text.secondary,
},
':not(&:last-of-type)::after': {
content: '", "',
},
}));
/** /**
* Abstract building block for a list of constraints, segments and other items inside a strategy * Abstract building block for a list of constraints, segments and other items inside a strategy
*/ */
export const StrategyEvaluationItem: FC<StrategyItemProps> = ({ export const StrategyEvaluationItem: FC<StrategyEvaluationItemProps> = ({
type, type,
children, children,
values, values,
onSetTruncated,
}) => ( }) => (
<StyledContainer> <StyledContainer>
<StyledType>{type}</StyledType> <StyledType>{type}</StyledType>
<StyledContent> <StyledContent>
{children} {children}
{values && values?.length > 0 ? ( {values && values?.length === 1 ? (
<StyledValuesGroup> <Truncator
{values?.map((value, index) => ( title={values[0]}
<StyledValue key={`${value}#${index}`}> arrow
{value} lines={2}
</StyledValue> onSetTruncated={() => onSetTruncated?.(false)}
))} >
</StyledValuesGroup> {values[0]}
</Truncator>
) : null}
{values && values?.length > 1 ? (
<Truncator title='' lines={2} onSetTruncated={onSetTruncated}>
{values.join(', ')}
</Truncator>
) : null} ) : null}
</StyledContent> </StyledContent>
</StyledContainer> </StyledContainer>

View File

@ -1,9 +1,11 @@
import { ConstraintIcon } from 'component/common/ConstraintAccordion/ConstraintIcon'; import { ConstraintIcon } from 'component/common/ConstraintAccordion/ConstraintIcon';
import type { IConstraint } from 'interfaces/strategy'; import type { IConstraint } from 'interfaces/strategy';
import { ConstraintAccordionViewHeaderInfo } from './ConstraintAccordionViewHeaderInfo'; import { ConstraintAccordionViewHeaderInfo } from './ConstraintAccordionViewHeaderInfo';
import { ConstraintAccordionViewHeaderInfo as LegacyConstraintAccordionViewHeaderInfo } from './LegacyConstraintAccordionViewHeaderInfo';
import { ConstraintAccordionHeaderActions } from '../../ConstraintAccordionHeaderActions/ConstraintAccordionHeaderActions'; import { ConstraintAccordionHeaderActions } from '../../ConstraintAccordionHeaderActions/ConstraintAccordionHeaderActions';
import { styled } from '@mui/system'; import { styled } from '@mui/system';
import useUnleashContext from 'hooks/api/getters/useUnleashContext/useUnleashContext'; import useUnleashContext from 'hooks/api/getters/useUnleashContext/useUnleashContext';
import { useUiFlag } from 'hooks/useUiFlag';
interface IConstraintAccordionViewHeaderProps { interface IConstraintAccordionViewHeaderProps {
constraint: IConstraint; constraint: IConstraint;
@ -38,6 +40,7 @@ export const ConstraintAccordionViewHeader = ({
disabled, disabled,
}: IConstraintAccordionViewHeaderProps) => { }: IConstraintAccordionViewHeaderProps) => {
const { context } = useUnleashContext(); const { context } = useUnleashContext();
const flagOverviewRedesign = useUiFlag('flagOverviewRedesign');
const { contextName } = constraint; const { contextName } = constraint;
const disableEdit = !context const disableEdit = !context
@ -46,7 +49,10 @@ export const ConstraintAccordionViewHeader = ({
return ( return (
<StyledContainer> <StyledContainer>
{!flagOverviewRedesign ? (
<ConstraintIcon compact={compact} disabled={disabled} /> <ConstraintIcon compact={compact} disabled={disabled} />
) : null}
{flagOverviewRedesign ? (
<ConstraintAccordionViewHeaderInfo <ConstraintAccordionViewHeaderInfo
constraint={constraint} constraint={constraint}
singleValue={singleValue} singleValue={singleValue}
@ -54,6 +60,15 @@ export const ConstraintAccordionViewHeader = ({
expanded={expanded} expanded={expanded}
disabled={disabled} disabled={disabled}
/> />
) : (
<LegacyConstraintAccordionViewHeaderInfo
constraint={constraint}
singleValue={singleValue}
allowExpand={allowExpand}
expanded={expanded}
disabled={disabled}
/>
)}
<ConstraintAccordionHeaderActions <ConstraintAccordionHeaderActions
onEdit={onEdit} onEdit={onEdit}
onDelete={onDelete} onDelete={onDelete}

View File

@ -1,29 +1,9 @@
import { styled, Tooltip } from '@mui/material'; import { IconButton, styled } from '@mui/material';
import { ConstraintViewHeaderOperator } from './ConstraintViewHeaderOperator';
import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender';
import { ConstraintAccordionViewHeaderSingleValue } from './ConstraintAccordionViewHeaderSingleValue';
import { ConstraintAccordionViewHeaderMultipleValues } from './ConstraintAccordionViewHeaderMultipleValues';
import type { IConstraint } from 'interfaces/strategy'; import type { IConstraint } from 'interfaces/strategy';
import { ConstraintItemHeader } from 'component/common/ConstraintsList/ConstraintItemHeader/ConstraintItemHeader';
const StyledHeaderText = styled('span')(({ theme }) => ({ import { useState } from 'react';
display: '-webkit-box', import VisibilityIcon from '@mui/icons-material/Visibility';
WebkitLineClamp: 3, import VisibilityOffIcon from '@mui/icons-material/VisibilityOff';
WebkitBoxOrient: 'vertical',
overflow: 'hidden',
maxWidth: '100px',
minWidth: '100px',
marginRight: '10px',
marginTop: 'auto',
marginBottom: 'auto',
wordBreak: 'break-word',
fontSize: theme.fontSizes.smallBody,
[theme.breakpoints.down(710)]: {
textAlign: 'center',
padding: theme.spacing(1, 0),
marginRight: 'inherit',
maxWidth: 'inherit',
},
}));
const StyledHeaderWrapper = styled('div')(({ theme }) => ({ const StyledHeaderWrapper = styled('div')(({ theme }) => ({
display: 'flex', display: 'flex',
@ -55,49 +35,26 @@ interface ConstraintAccordionViewHeaderMetaInfoProps {
export const ConstraintAccordionViewHeaderInfo = ({ export const ConstraintAccordionViewHeaderInfo = ({
constraint, constraint,
singleValue,
allowExpand, allowExpand,
expanded, expanded,
disabled = false,
maxLength = 112, //The max number of characters in the values text for NOT allowing expansion
}: ConstraintAccordionViewHeaderMetaInfoProps) => { }: ConstraintAccordionViewHeaderMetaInfoProps) => {
const [expandable, setExpandable] = useState(false);
return ( return (
<StyledHeaderWrapper> <StyledHeaderWrapper>
<StyledHeaderMetaInfo> <StyledHeaderMetaInfo>
<Tooltip title={constraint.contextName} arrow> <ConstraintItemHeader
<StyledHeaderText {...constraint}
sx={(theme) => ({ onSetTruncated={(state: boolean) => {
color: disabled setExpandable(state);
? theme.palette.text.secondary allowExpand(state);
: 'inherit', }}
})}
>
{constraint.contextName}
</StyledHeaderText>
</Tooltip>
<ConstraintViewHeaderOperator
constraint={constraint}
disabled={disabled}
/>
<ConditionallyRender
condition={singleValue}
show={
<ConstraintAccordionViewHeaderSingleValue
constraint={constraint}
allowExpand={allowExpand}
disabled={disabled}
/>
}
elseShow={
<ConstraintAccordionViewHeaderMultipleValues
constraint={constraint}
expanded={expanded}
allowExpand={allowExpand}
maxLength={maxLength}
disabled={disabled}
/>
}
/> />
{expandable ? (
<IconButton type='button'>
{expanded ? <VisibilityOffIcon /> : <VisibilityIcon />}
</IconButton>
) : null}
</StyledHeaderMetaInfo> </StyledHeaderMetaInfo>
</StyledHeaderWrapper> </StyledHeaderWrapper>
); );

View File

@ -0,0 +1,104 @@
import { styled, Tooltip } from '@mui/material';
import { ConstraintViewHeaderOperator } from './ConstraintViewHeaderOperator';
import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender';
import { ConstraintAccordionViewHeaderSingleValue } from './ConstraintAccordionViewHeaderSingleValue';
import { ConstraintAccordionViewHeaderMultipleValues } from './ConstraintAccordionViewHeaderMultipleValues';
import type { IConstraint } from 'interfaces/strategy';
const StyledHeaderText = styled('span')(({ theme }) => ({
display: '-webkit-box',
WebkitLineClamp: 3,
WebkitBoxOrient: 'vertical',
overflow: 'hidden',
maxWidth: '100px',
minWidth: '100px',
marginRight: '10px',
marginTop: 'auto',
marginBottom: 'auto',
wordBreak: 'break-word',
fontSize: theme.fontSizes.smallBody,
[theme.breakpoints.down(710)]: {
textAlign: 'center',
padding: theme.spacing(1, 0),
marginRight: 'inherit',
maxWidth: 'inherit',
},
}));
const StyledHeaderWrapper = styled('div')(({ theme }) => ({
display: 'flex',
width: '100%',
justifyContent: 'space-between',
borderRadius: theme.spacing(1),
}));
const StyledHeaderMetaInfo = styled('div')(({ theme }) => ({
display: 'flex',
alignItems: 'stretch',
marginLeft: theme.spacing(1),
[theme.breakpoints.down('sm')]: {
marginLeft: 0,
flexDirection: 'column',
alignItems: 'center',
width: '100%',
},
}));
interface ConstraintAccordionViewHeaderMetaInfoProps {
constraint: IConstraint;
singleValue: boolean;
expanded: boolean;
allowExpand: (shouldExpand: boolean) => void;
disabled?: boolean;
maxLength?: number;
}
export const ConstraintAccordionViewHeaderInfo = ({
constraint,
singleValue,
allowExpand,
expanded,
disabled = false,
maxLength = 112, //The max number of characters in the values text for NOT allowing expansion
}: ConstraintAccordionViewHeaderMetaInfoProps) => {
return (
<StyledHeaderWrapper>
<StyledHeaderMetaInfo>
<Tooltip title={constraint.contextName} arrow>
<StyledHeaderText
sx={(theme) => ({
color: disabled
? theme.palette.text.secondary
: 'inherit',
})}
>
{constraint.contextName}
</StyledHeaderText>
</Tooltip>
<ConstraintViewHeaderOperator
constraint={constraint}
disabled={disabled}
/>
<ConditionallyRender
condition={singleValue}
show={
<ConstraintAccordionViewHeaderSingleValue
constraint={constraint}
allowExpand={allowExpand}
disabled={disabled}
/>
}
elseShow={
<ConstraintAccordionViewHeaderMultipleValues
constraint={constraint}
expanded={expanded}
allowExpand={allowExpand}
maxLength={maxLength}
disabled={disabled}
/>
}
/>
</StyledHeaderMetaInfo>
</StyledHeaderWrapper>
);
};

View File

@ -7,6 +7,9 @@ interface IConstraintIconProps {
disabled?: boolean; disabled?: boolean;
} }
/**
* @deprecated remove with `flagOverviewRedesign`
*/
export const ConstraintIcon: VFC<IConstraintIconProps> = ({ export const ConstraintIcon: VFC<IConstraintIconProps> = ({
compact, compact,
disabled, disabled,

View File

@ -1,5 +1,4 @@
import type { IConstraint } from 'interfaces/strategy'; import type { IConstraint } from 'interfaces/strategy';
import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender';
import { ConstraintAccordionEdit } from './ConstraintAccordionEdit/ConstraintAccordionEdit'; import { ConstraintAccordionEdit } from './ConstraintAccordionEdit/ConstraintAccordionEdit';
import { ConstraintAccordionView } from './ConstraintAccordionView/ConstraintAccordionView'; import { ConstraintAccordionView } from './ConstraintAccordionView/ConstraintAccordionView';
@ -27,10 +26,8 @@ export const NewConstraintAccordion = ({
}: IConstraintAccordionProps) => { }: IConstraintAccordionProps) => {
if (!constraint) return null; if (!constraint) return null;
if (editing && onSave) {
return ( return (
<ConditionallyRender
condition={Boolean(editing && onSave)}
show={
<ConstraintAccordionEdit <ConstraintAccordionEdit
constraint={constraint} constraint={constraint}
onCancel={onCancel} onCancel={onCancel}
@ -39,14 +36,14 @@ export const NewConstraintAccordion = ({
onAutoSave={onAutoSave!} onAutoSave={onAutoSave!}
compact={compact} compact={compact}
/> />
);
} }
elseShow={
return (
<ConstraintAccordionView <ConstraintAccordionView
constraint={constraint} constraint={constraint}
onEdit={onEdit} onEdit={onEdit}
onDelete={onDelete} onDelete={onDelete}
/> />
}
/>
); );
}; };

View File

@ -1,7 +1,6 @@
import type React from 'react'; import type React from 'react';
import { forwardRef, Fragment, useImperativeHandle } from 'react'; import { forwardRef, Fragment, useImperativeHandle } from 'react';
import { styled, Tooltip } from '@mui/material'; import { styled } from '@mui/material';
import HelpOutline from '@mui/icons-material/HelpOutline';
import type { IConstraint } from 'interfaces/strategy'; import type { IConstraint } from 'interfaces/strategy';
import produce from 'immer'; import produce from 'immer';
import useUnleashContext from 'hooks/api/getters/useUnleashContext/useUnleashContext'; import useUnleashContext from 'hooks/api/getters/useUnleashContext/useUnleashContext';
@ -43,30 +42,6 @@ const StyledContainer = styled('div')({
flexDirection: 'column', flexDirection: 'column',
}); });
const StyledHelpWrapper = styled(Tooltip)(({ theme }) => ({
marginLeft: theme.spacing(0.75),
height: theme.spacing(1.5),
}));
const StyledHelp = styled(HelpOutline)(({ theme }) => ({
fill: theme.palette.action.active,
[theme.breakpoints.down(860)]: {
display: 'none',
},
}));
const StyledConstraintLabel = styled('p')(({ theme }) => ({
marginBottom: theme.spacing(1),
color: theme.palette.text.secondary,
}));
const StyledAddCustomLabel = styled('div')(({ theme }) => ({
marginTop: theme.spacing(1),
marginBottom: theme.spacing(1),
color: theme.palette.text.primary,
display: 'flex',
}));
export const useConstraintAccordionList = ( export const useConstraintAccordionList = (
setConstraints: setConstraints:
| React.Dispatch<React.SetStateAction<IConstraint[]>> | React.Dispatch<React.SetStateAction<IConstraint[]>>

View File

@ -9,9 +9,12 @@ import {
styled, styled,
} from '@mui/material'; } from '@mui/material';
import { StrategyEvaluationItem } from 'component/common/ConstraintsList/StrategyEvaluationItem/StrategyEvaluationItem'; import { StrategyEvaluationItem } from 'component/common/ConstraintsList/StrategyEvaluationItem/StrategyEvaluationItem';
import { ConstraintItem } from 'component/common/ConstraintsList/ConstraintItem/ConstraintItem'; import { ConstraintItemHeader } from 'component/common/ConstraintsList/ConstraintItemHeader/ConstraintItemHeader';
import { objectId } from 'utils/objectId'; import { objectId } from 'utils/objectId';
import { ConstraintsList } from 'component/common/ConstraintsList/ConstraintsList'; import {
ConstraintListItem,
ConstraintsList,
} from 'component/common/ConstraintsList/ConstraintsList';
type SegmentItemProps = { type SegmentItemProps = {
segment: Partial<ISegment>; segment: Partial<ISegment>;
@ -21,7 +24,11 @@ type SegmentItemProps = {
headerContent?: JSX.Element; headerContent?: JSX.Element;
}; };
const StyledAccordion = styled(Accordion)(({ theme }) => ({ const StyledConstraintListItem = styled(ConstraintListItem)(() => ({
padding: 0,
}));
const StyledAccordion = styled(Accordion)(() => ({
boxShadow: 'none', boxShadow: 'none',
margin: 0, margin: 0,
padding: 0, padding: 0,
@ -32,16 +39,14 @@ const StyledAccordion = styled(Accordion)(({ theme }) => ({
})); }));
const StyledAccordionSummary = styled(AccordionSummary)(({ theme }) => ({ const StyledAccordionSummary = styled(AccordionSummary)(({ theme }) => ({
padding: 0, padding: theme.spacing(0, 3),
fontSize: theme.typography.body2.fontSize, fontSize: theme.typography.body2.fontSize,
minHeight: 'unset', minHeight: 'unset',
'.MuiAccordionSummary-content, .MuiAccordionSummary-content.Mui-expanded': {
margin: 0,
},
})); }));
const StyledAccordionDetails = styled(AccordionDetails)(({ theme }) => ({ const StyledAccordionDetails = styled(AccordionDetails)(({ theme }) => ({
padding: theme.spacing(2, 0, 1), borderTop: `1px dashed ${theme.palette.divider}`,
padding: theme.spacing(1.5, 3, 2.5),
})); }));
const StyledLink = styled(Link)({ const StyledLink = styled(Link)({
@ -55,8 +60,6 @@ const StyledActionsContainer = styled('div')(({ theme }) => ({
display: 'flex', display: 'flex',
alignItems: 'center', alignItems: 'center',
marginLeft: 'auto', marginLeft: 'auto',
marginTop: theme.spacing(-0.5),
marginBottom: theme.spacing(-0.5),
})); }));
const StyledButton = styled(Button)(({ theme }) => ({ const StyledButton = styled(Button)(({ theme }) => ({
@ -85,10 +88,12 @@ export const SegmentItem: FC<SegmentItemProps> = ({
return ( return (
<ConstraintsList> <ConstraintsList>
{segment.constraints.map((constraint, index) => ( {segment.constraints.map((constraint, index) => (
<ConstraintItem <ConstraintListItem
key={`${objectId(constraint)}-${index}`} key={`${objectId(constraint)}-${index}`}
{...constraint} >
/> {/* FIXME: use accordion */}
<ConstraintItemHeader {...constraint} />
</ConstraintListItem>
))} ))}
</ConstraintsList> </ConstraintsList>
); );
@ -102,6 +107,7 @@ export const SegmentItem: FC<SegmentItemProps> = ({
}, [constraintList, segment.constraints]); }, [constraintList, segment.constraints]);
return ( return (
<StyledConstraintListItem>
<StyledAccordion expanded={isOpen} disableGutters> <StyledAccordion expanded={isOpen} disableGutters>
<StyledAccordionSummary id={`segment-accordion-${segment.id}`}> <StyledAccordionSummary id={`segment-accordion-${segment.id}`}>
<StrategyEvaluationItem type='Segment'> <StrategyEvaluationItem type='Segment'>
@ -124,5 +130,6 @@ export const SegmentItem: FC<SegmentItemProps> = ({
</StyledAccordionSummary> </StyledAccordionSummary>
<StyledAccordionDetails>{constraints}</StyledAccordionDetails> <StyledAccordionDetails>{constraints}</StyledAccordionDetails>
</StyledAccordion> </StyledAccordion>
</StyledConstraintListItem>
); );
}; };

View File

@ -25,13 +25,14 @@ const StyledTruncatorContainer = styled(Box, {
type OverridableTooltipProps = Omit<TooltipProps, 'children'>; type OverridableTooltipProps = Omit<TooltipProps, 'children'>;
interface ITruncatorProps extends BoxProps { export type TruncatorProps = {
lines?: number; lines?: number;
title?: string; title?: string;
arrow?: boolean; arrow?: boolean;
tooltipProps?: OverridableTooltipProps; tooltipProps?: OverridableTooltipProps;
children: React.ReactNode; children: React.ReactNode;
} onSetTruncated?: (isTruncated: boolean) => void;
} & BoxProps;
export const Truncator = ({ export const Truncator = ({
lines = 1, lines = 1,
@ -40,8 +41,9 @@ export const Truncator = ({
tooltipProps, tooltipProps,
children, children,
component = 'span', component = 'span',
onSetTruncated,
...props ...props
}: ITruncatorProps) => { }: TruncatorProps) => {
const [isTruncated, setIsTruncated] = useState(false); const [isTruncated, setIsTruncated] = useState(false);
const ref = useRef<HTMLDivElement>(null); const ref = useRef<HTMLDivElement>(null);
@ -50,7 +52,6 @@ export const Truncator = ({
setIsTruncated(ref.current.scrollHeight > ref.current.offsetHeight); setIsTruncated(ref.current.scrollHeight > ref.current.offsetHeight);
} }
}; };
useEffect(() => { useEffect(() => {
const resizeObserver = new ResizeObserver(checkTruncation); const resizeObserver = new ResizeObserver(checkTruncation);
if (ref.current) { if (ref.current) {
@ -59,6 +60,10 @@ export const Truncator = ({
return () => resizeObserver.disconnect(); return () => resizeObserver.disconnect();
}, [title, children]); }, [title, children]);
useEffect(() => {
onSetTruncated?.(isTruncated);
}, [isTruncated]);
const overridableTooltipProps: OverridableTooltipProps = { const overridableTooltipProps: OverridableTooltipProps = {
title, title,
arrow, arrow,

View File

@ -29,6 +29,12 @@ const StyledPayloadHeader = styled('div')(({ theme }) => ({
marginBottom: theme.spacing(1), marginBottom: theme.spacing(1),
})); }));
const StyledValuesContainer = styled('div')(({ theme }) => ({
display: 'flex',
gap: theme.spacing(0.75, 0.5),
flexWrap: 'wrap',
}));
export const RolloutVariants: FC<{ export const RolloutVariants: FC<{
variants?: StrategyVariantSchema[]; variants?: StrategyVariantSchema[];
}> = ({ variants }) => { }> = ({ variants }) => {
@ -38,6 +44,7 @@ export const RolloutVariants: FC<{
return ( return (
<StrategyEvaluationItem type={`Variants (${variants.length})`}> <StrategyEvaluationItem type={`Variants (${variants.length})`}>
<StyledValuesContainer>
{variants.map((variant, i) => ( {variants.map((variant, i) => (
<HtmlTooltip <HtmlTooltip
arrow arrow
@ -66,6 +73,7 @@ export const RolloutVariants: FC<{
/> />
</HtmlTooltip> </HtmlTooltip>
))} ))}
</StyledValuesContainer>
</StrategyEvaluationItem> </StrategyEvaluationItem>
); );
}; };

View File

@ -3,14 +3,17 @@ import type { FeatureStrategySchema } from 'openapi';
import type { IFeatureStrategyPayload } from 'interfaces/strategy'; import type { IFeatureStrategyPayload } from 'interfaces/strategy';
import { useUiFlag } from 'hooks/useUiFlag'; import { useUiFlag } from 'hooks/useUiFlag';
import { StrategyExecution as LegacyStrategyExecution } from './LegacyStrategyExecution'; import { StrategyExecution as LegacyStrategyExecution } from './LegacyStrategyExecution';
import { ConstraintItem } from 'component/common/ConstraintsList/ConstraintItem/ConstraintItem'; import { ConstraintItemHeader } from 'component/common/ConstraintsList/ConstraintItemHeader/ConstraintItemHeader';
import { useStrategies } from 'hooks/api/getters/useStrategies/useStrategies'; import { useStrategies } from 'hooks/api/getters/useStrategies/useStrategies';
import { objectId } from 'utils/objectId'; import { objectId } from 'utils/objectId';
import { useCustomStrategyParameters } from './hooks/useCustomStrategyParameters'; import { useCustomStrategyParameters } from './hooks/useCustomStrategyParameters';
import { useStrategyParameters } from './hooks/useStrategyParameters'; import { useStrategyParameters } from './hooks/useStrategyParameters';
import { useSegments } from 'hooks/api/getters/useSegments/useSegments'; import { useSegments } from 'hooks/api/getters/useSegments/useSegments';
import { SegmentItem } from 'component/common/SegmentItem/SegmentItem'; import { SegmentItem } from 'component/common/SegmentItem/SegmentItem';
import { ConstraintsList } from 'component/common/ConstraintsList/ConstraintsList'; import {
ConstraintListItem,
ConstraintsList,
} from 'component/common/ConstraintsList/ConstraintsList';
type StrategyExecutionProps = { type StrategyExecutionProps = {
strategy: IFeatureStrategyPayload | FeatureStrategySchema; strategy: IFeatureStrategyPayload | FeatureStrategySchema;
@ -50,12 +53,16 @@ export const StrategyExecution: FC<StrategyExecutionProps> = ({
<SegmentItem segment={segment} key={segment.id} /> <SegmentItem segment={segment} key={segment.id} />
))} ))}
{constraints?.map((constraint, index) => ( {constraints?.map((constraint, index) => (
<ConstraintItem <ConstraintListItem key={`${objectId(constraint)}-${index}`}>
key={`${objectId(constraint)}-${index}`} {/* FIXME: use constraint accordion */}
{...constraint} <ConstraintItemHeader {...constraint} />
/> </ConstraintListItem>
))} ))}
{isCustomStrategy ? customStrategyItems : strategyParameters} {(isCustomStrategy ? customStrategyItems : strategyParameters).map(
(item, index) => (
<ConstraintListItem key={index}>{item}</ConstraintListItem>
),
)}
</ConstraintsList> </ConstraintsList>
); );
}; };

View File

@ -3,10 +3,11 @@ import type {
PlaygroundConstraintSchema, PlaygroundConstraintSchema,
PlaygroundRequestSchema, PlaygroundRequestSchema,
} from 'openapi'; } from 'openapi';
import { ConstraintItem } from 'component/common/ConstraintsList/ConstraintItem/ConstraintItem'; import { ConstraintItemHeader } from 'component/common/ConstraintsList/ConstraintItemHeader/ConstraintItemHeader';
import CheckCircle from '@mui/icons-material/CheckCircle'; import CheckCircle from '@mui/icons-material/CheckCircle';
import { styled } from '@mui/material'; import { styled } from '@mui/material';
import Cancel from '@mui/icons-material/Cancel'; import Cancel from '@mui/icons-material/Cancel';
import { ConstraintListItem } from 'component/common/ConstraintsList/ConstraintsList';
interface IConstraintExecutionProps { interface IConstraintExecutionProps {
constraint?: PlaygroundConstraintSchema; constraint?: PlaygroundConstraintSchema;
@ -20,7 +21,7 @@ const StyledContainer = styled('div', {
display: 'flex', display: 'flex',
alignItems: 'center', alignItems: 'center',
gap: theme.spacing(1), gap: theme.spacing(1),
paddingInline: theme.spacing(0.25), padding: theme.spacing(0.5, 0.25),
color: color:
variant === 'ok' variant === 'ok'
? theme.palette.success.dark ? theme.palette.success.dark
@ -67,13 +68,15 @@ export const ConstraintExecution: FC<IConstraintExecutionProps> = ({
}; };
return ( return (
<> <ConstraintListItem>
<ConstraintItem {...constraint} /> <div>
<ConstraintItemHeader {...constraint} />
{constraint.result ? ( {constraint.result ? (
<ConstraintOk /> <ConstraintOk />
) : ( ) : (
<ConstraintError text={errorText()} /> <ConstraintError text={errorText()} />
)} )}
</> </div>
</ConstraintListItem>
); );
}; };

View File

@ -6,7 +6,10 @@ import { ConstraintExecution } from './ConstraintExecution/ConstraintExecution';
import { formattedStrategyNames } from 'utils/strategyNames'; import { formattedStrategyNames } from 'utils/strategyNames';
import { StyledBoxSummary } from './StrategyExecution.styles'; import { StyledBoxSummary } from './StrategyExecution.styles';
import { Badge } from 'component/common/Badge/Badge'; import { Badge } from 'component/common/Badge/Badge';
import { ConstraintsList } from 'component/common/ConstraintsList/ConstraintsList'; import {
ConstraintListItem,
ConstraintsList,
} from 'component/common/ConstraintsList/ConstraintsList';
import { objectId } from 'utils/objectId'; import { objectId } from 'utils/objectId';
import type { FC } from 'react'; import type { FC } from 'react';
import { SegmentExecution } from './SegmentExecution/SegmentExecution'; import { SegmentExecution } from './SegmentExecution/SegmentExecution';
@ -60,8 +63,14 @@ export const StrategyExecution: FC<StrategyExecutionProps> = ({
/> />
)) ))
: []), : []),
hasExecutionParameters && params, hasExecutionParameters &&
isCustomStrategy && customStrategyItems, params.map((param, index) => (
<ConstraintListItem key={index}>{param}</ConstraintListItem>
)),
isCustomStrategy &&
customStrategyItems.map((param, index) => (
<ConstraintListItem key={index}>{param}</ConstraintListItem>
)),
name === 'default' && ( name === 'default' && (
<StyledBoxSummary sx={{ width: '100%' }}> <StyledBoxSummary sx={{ width: '100%' }}>
The standard strategy is <Badge color='success'>ON</Badge> for The standard strategy is <Badge color='success'>ON</Badge> for