1
0
mirror of https://github.com/Unleash/unleash.git synced 2025-03-23 00:16:25 +01:00

values truncation

This commit is contained in:
Tymoteusz Czech 2025-03-19 20:20:03 +01:00
parent a65c8baf56
commit bb779828d3
No known key found for this signature in database
GPG Key ID: 133555230D88D75F
6 changed files with 103 additions and 120 deletions

View File

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

View File

@ -1,11 +1,15 @@
import { Chip, type ChipProps, styled } from '@mui/material';
import { styled } from '@mui/material';
import {
Truncator,
type TruncatorProps,
} from 'component/common/Truncator/Truncator';
import type { FC, ReactNode } from 'react';
type StrategyItemProps = {
export type StrategyEvaluationItemProps = {
type?: ReactNode;
children?: ReactNode;
values?: string[];
};
} & Pick<TruncatorProps, 'onSetTruncated'>;
const StyledContainer = styled('div')(({ theme }) => ({
display: 'flex',
@ -17,7 +21,6 @@ const StyledContainer = styled('div')(({ theme }) => ({
const StyledContent = styled('div')(({ theme }) => ({
display: 'flex',
gap: theme.spacing(1),
flexWrap: 'wrap',
alignItems: 'center',
}));
@ -30,37 +33,33 @@ const StyledType = styled('span')(({ theme }) => ({
width: theme.spacing(10),
}));
const StyledValuesGroup = styled('div')(({ theme }) => ({
display: 'flex',
alignItems: 'center',
gap: theme.spacing(0.5),
}));
const StyledValue = styled(({ ...props }: ChipProps) => (
<Chip size='small' {...props} />
))(({ theme }) => ({
padding: theme.spacing(0.5),
background: theme.palette.background.elevation1,
}));
/**
* 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,
children,
values,
onSetTruncated,
}) => (
<StyledContainer>
<StyledType>{type}</StyledType>
<StyledContent>
{children}
{values && values?.length > 0 ? (
<StyledValuesGroup>
{values?.map((value, index) => (
<StyledValue key={`${value}#${index}`} label={value} />
))}
</StyledValuesGroup>
{values && values?.length === 1 ? (
<Truncator
title={values[0]}
arrow
lines={2}
onSetTruncated={() => onSetTruncated?.(false)}
>
{values[0]}
</Truncator>
) : null}
{values && values?.length > 1 ? (
<Truncator title='' lines={2} onSetTruncated={onSetTruncated}>
{values.join(', ')}
</Truncator>
) : null}
</StyledContent>
</StyledContainer>

View File

@ -4,6 +4,7 @@ import { ConstraintAccordionViewHeaderInfo } from './ConstraintAccordionViewHead
import { ConstraintAccordionHeaderActions } from '../../ConstraintAccordionHeaderActions/ConstraintAccordionHeaderActions';
import { styled } from '@mui/system';
import useUnleashContext from 'hooks/api/getters/useUnleashContext/useUnleashContext';
import { useUiFlag } from 'hooks/useUiFlag';
interface IConstraintAccordionViewHeaderProps {
constraint: IConstraint;
@ -38,6 +39,7 @@ export const ConstraintAccordionViewHeader = ({
disabled,
}: IConstraintAccordionViewHeaderProps) => {
const { context } = useUnleashContext();
const flagOverviewRedesign = useUiFlag('flagOverviewRedesign');
const { contextName } = constraint;
const disableEdit = !context
@ -46,7 +48,9 @@ export const ConstraintAccordionViewHeader = ({
return (
<StyledContainer>
<ConstraintIcon compact={compact} disabled={disabled} />
{!flagOverviewRedesign ? (
<ConstraintIcon compact={compact} disabled={disabled} />
) : null}
<ConstraintAccordionViewHeaderInfo
constraint={constraint}
singleValue={singleValue}

View File

@ -1,29 +1,9 @@
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 { IconButton, styled } from '@mui/material';
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',
},
}));
import { ConstraintItem } from 'component/common/ConstraintsList/ConstraintItem/ConstraintItem';
import { useState } from 'react';
import VisibilityIcon from '@mui/icons-material/Visibility';
import VisibilityOffIcon from '@mui/icons-material/VisibilityOff';
const StyledHeaderWrapper = styled('div')(({ theme }) => ({
display: 'flex',
@ -55,49 +35,26 @@ interface ConstraintAccordionViewHeaderMetaInfoProps {
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) => {
const [expandable, setExpandable] = useState(false);
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}
/>
}
<ConstraintItem
{...constraint}
onSetTruncated={(state: boolean) => {
setExpandable(state);
allowExpand(state);
}}
/>
{expandable ? (
<IconButton type='button'>
{expanded ? <VisibilityOffIcon /> : <VisibilityIcon />}
</IconButton>
) : null}
</StyledHeaderMetaInfo>
</StyledHeaderWrapper>
);

View File

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

View File

@ -29,6 +29,12 @@ const StyledPayloadHeader = styled('div')(({ theme }) => ({
marginBottom: theme.spacing(1),
}));
const StyledValuesContainer = styled('div')(({ theme }) => ({
display: 'flex',
gap: theme.spacing(0.75, 0.5),
flexWrap: 'wrap',
}));
export const RolloutVariants: FC<{
variants?: StrategyVariantSchema[];
}> = ({ variants }) => {
@ -38,34 +44,36 @@ export const RolloutVariants: FC<{
return (
<StrategyEvaluationItem type={`Variants (${variants.length})`}>
{variants.map((variant, i) => (
<HtmlTooltip
arrow
title={
variant.payload?.value ? (
<div>
<StyledPayloadHeader>
Payload:
</StyledPayloadHeader>
<code>{variant.payload?.value}</code>
</div>
) : null
}
key={variant.name}
>
<StyledVariantChip
key={variant.name}
order={i}
label={
<>
<span>
{variant.weight / 10}% {variant.name}
</span>
</>
<StyledValuesContainer>
{variants.map((variant, i) => (
<HtmlTooltip
arrow
title={
variant.payload?.value ? (
<div>
<StyledPayloadHeader>
Payload:
</StyledPayloadHeader>
<code>{variant.payload?.value}</code>
</div>
) : null
}
/>
</HtmlTooltip>
))}
key={variant.name}
>
<StyledVariantChip
key={variant.name}
order={i}
label={
<>
<span>
{variant.weight / 10}% {variant.name}
</span>
</>
}
/>
</HtmlTooltip>
))}
</StyledValuesContainer>
</StrategyEvaluationItem>
);
};