mirror of
https://github.com/Unleash/unleash.git
synced 2025-02-23 00:22:19 +01:00
fix: proper spacing and dividers between strategies (#1187)
* fix: proper spacing and dividers between strategies * fix: improve strategy execution list logic * update custom strategy execution styles * interpret not defined custom strategy parameters
This commit is contained in:
parent
6bf0211140
commit
537bcdc1b7
@ -2,17 +2,11 @@ import { makeStyles } from 'tss-react/mui';
|
||||
|
||||
export const useStyles = makeStyles()(theme => ({
|
||||
valueContainer: {
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
gap: '1ch',
|
||||
padding: theme.spacing(2, 3),
|
||||
border: `1px solid ${theme.palette.dividerAlternative}`,
|
||||
borderRadius: theme.shape.borderRadius,
|
||||
},
|
||||
valueSeparator: {
|
||||
color: theme.palette.grey[700],
|
||||
},
|
||||
summary: {
|
||||
width: '100%',
|
||||
padding: theme.spacing(2, 3),
|
||||
borderRadius: theme.shape.borderRadius,
|
||||
border: `1px solid ${theme.palette.divider}`,
|
||||
},
|
||||
}));
|
||||
|
@ -1,56 +1,62 @@
|
||||
import { Fragment } from 'react';
|
||||
import { Box, Chip } from '@mui/material';
|
||||
import { Fragment, useMemo, VFC } from 'react';
|
||||
import { Box, Chip, Tooltip } from '@mui/material';
|
||||
import { IFeatureStrategy } from 'interfaces/strategy';
|
||||
import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender';
|
||||
import PercentageCircle from 'component/common/PercentageCircle/PercentageCircle';
|
||||
import { StrategySeparator } from 'component/common/StrategySeparator/StrategySeparator';
|
||||
import { ConstraintItem } from './ConstraintItem/ConstraintItem';
|
||||
import { useStrategies } from 'hooks/api/getters/useStrategies/useStrategies';
|
||||
import StringTruncator from 'component/common/StringTruncator/StringTruncator';
|
||||
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 { useStyles } from './StrategyExecution.styles';
|
||||
import {
|
||||
parseParameterString,
|
||||
parseParameterNumber,
|
||||
parseParameterString,
|
||||
parseParameterStrings,
|
||||
} from 'utils/parseParameter';
|
||||
import StringTruncator from 'component/common/StringTruncator/StringTruncator';
|
||||
|
||||
interface IStrategyExecutionProps {
|
||||
strategy: IFeatureStrategy;
|
||||
percentageFill?: string;
|
||||
}
|
||||
|
||||
export const StrategyExecution = ({ strategy }: IStrategyExecutionProps) => {
|
||||
const NoItems: VFC = () => (
|
||||
<Box sx={{ px: 3, color: 'text.disabled' }}>
|
||||
This strategy does not have constraints or parameters.
|
||||
</Box>
|
||||
);
|
||||
|
||||
export const StrategyExecution: VFC<IStrategyExecutionProps> = ({
|
||||
strategy,
|
||||
}) => {
|
||||
const { parameters, constraints = [] } = strategy;
|
||||
const { classes: styles } = useStyles();
|
||||
const { strategies } = useStrategies();
|
||||
const { uiConfig } = useUiConfig();
|
||||
|
||||
if (!parameters) {
|
||||
return null;
|
||||
}
|
||||
const { segments } = useSegments(strategy.id);
|
||||
|
||||
const definition = strategies.find(strategyDefinition => {
|
||||
return strategyDefinition.name === strategy.name;
|
||||
});
|
||||
|
||||
const renderParameters = () => {
|
||||
if (definition?.editable) return null;
|
||||
const parametersList = useMemo(() => {
|
||||
if (!parameters || definition?.editable) return null;
|
||||
|
||||
return Object.keys(parameters).map(key => {
|
||||
switch (key) {
|
||||
case 'rollout':
|
||||
case 'Rollout':
|
||||
const percentage = parseParameterNumber(parameters[key]);
|
||||
|
||||
return (
|
||||
<Box
|
||||
className={styles.summary}
|
||||
key={key}
|
||||
className={styles.valueContainer}
|
||||
sx={{ display: 'flex', alignItems: 'center' }}
|
||||
>
|
||||
<Box sx={{ mr: '1rem' }}>
|
||||
<Box sx={{ mr: 2 }}>
|
||||
<PercentageCircle
|
||||
percentage={percentage}
|
||||
size="2rem"
|
||||
@ -93,191 +99,195 @@ export const StrategyExecution = ({ strategy }: IStrategyExecutionProps) => {
|
||||
return null;
|
||||
}
|
||||
});
|
||||
};
|
||||
}, [parameters, definition, constraints, styles]);
|
||||
|
||||
const customStrategyList = useMemo(() => {
|
||||
if (!parameters || !definition?.editable) return null;
|
||||
const isSetTo = (
|
||||
<span className={styles.valueSeparator}>{' is set to '}</span>
|
||||
);
|
||||
|
||||
return definition?.parameters.map(param => {
|
||||
const { type, name } = { ...param };
|
||||
if (!type || !name || parameters[name] === undefined) {
|
||||
return null;
|
||||
}
|
||||
const nameItem = (
|
||||
<StringTruncator maxLength={15} maxWidth="150" text={name} />
|
||||
);
|
||||
|
||||
const renderCustomStrategy = () => {
|
||||
if (!definition?.editable) return null;
|
||||
return definition?.parameters.map((param: any, index: number) => {
|
||||
const notLastItem = index !== definition?.parameters?.length - 1;
|
||||
switch (param?.type) {
|
||||
case 'list':
|
||||
const values = parseParameterStrings(
|
||||
strategy?.parameters[param.name]
|
||||
);
|
||||
return (
|
||||
<Fragment key={param?.name}>
|
||||
<ConstraintItem value={values} text={param.name} />
|
||||
<ConditionallyRender
|
||||
condition={notLastItem}
|
||||
show={<StrategySeparator text="AND" />}
|
||||
/>
|
||||
</Fragment>
|
||||
);
|
||||
case 'percentage':
|
||||
return (
|
||||
<Fragment key={param?.name}>
|
||||
<div>
|
||||
<Chip
|
||||
size="small"
|
||||
variant="outlined"
|
||||
color="success"
|
||||
label={`${
|
||||
strategy?.parameters[param.name]
|
||||
}%`}
|
||||
/>{' '}
|
||||
of your base{' '}
|
||||
{constraints?.length > 0
|
||||
? 'who match constraints'
|
||||
: ''}{' '}
|
||||
is included.
|
||||
</div>
|
||||
<PercentageCircle
|
||||
percentage={parseParameterNumber(
|
||||
strategy.parameters[param.name]
|
||||
)}
|
||||
/>
|
||||
<ConditionallyRender
|
||||
condition={notLastItem}
|
||||
show={<StrategySeparator text="AND" />}
|
||||
/>
|
||||
</Fragment>
|
||||
);
|
||||
case 'boolean':
|
||||
return (
|
||||
<Fragment key={param.name}>
|
||||
<p key={param.name}>
|
||||
<StringTruncator
|
||||
maxLength={15}
|
||||
maxWidth="150"
|
||||
text={param.name}
|
||||
/>{' '}
|
||||
{strategy.parameters[param.name]}
|
||||
</p>
|
||||
<ConditionallyRender
|
||||
condition={
|
||||
typeof strategy.parameters[param.name] !==
|
||||
'undefined'
|
||||
}
|
||||
show={
|
||||
<ConditionallyRender
|
||||
condition={notLastItem}
|
||||
show={<StrategySeparator text="AND" />}
|
||||
const values = parseParameterStrings(parameters[name]);
|
||||
|
||||
return values.length > 0 ? (
|
||||
<div className={styles.valueContainer}>
|
||||
{nameItem}{' '}
|
||||
<span className={styles.valueSeparator}>
|
||||
has {values.length}{' '}
|
||||
{values.length > 1 ? `items` : 'item'}:{' '}
|
||||
{values.map((item: string) => (
|
||||
<Chip
|
||||
key={item}
|
||||
label={
|
||||
<StringTruncator
|
||||
maxWidth="300"
|
||||
text={item}
|
||||
maxLength={50}
|
||||
/>
|
||||
}
|
||||
sx={{ mr: 0.5 }}
|
||||
/>
|
||||
}
|
||||
))}
|
||||
</span>
|
||||
</div>
|
||||
) : null;
|
||||
|
||||
case 'percentage':
|
||||
const percentage = parseParameterNumber(parameters[name]);
|
||||
return parameters[name] !== '' ? (
|
||||
<Box
|
||||
className={styles.valueContainer}
|
||||
sx={{ display: 'flex', alignItems: 'center' }}
|
||||
>
|
||||
<Box sx={{ mr: 2 }}>
|
||||
<PercentageCircle
|
||||
percentage={percentage}
|
||||
size="2rem"
|
||||
/>
|
||||
</Box>
|
||||
<div>
|
||||
{nameItem}
|
||||
{isSetTo}
|
||||
<Chip
|
||||
color="success"
|
||||
variant="outlined"
|
||||
size="small"
|
||||
label={`${percentage}%`}
|
||||
/>
|
||||
</div>
|
||||
</Box>
|
||||
) : null;
|
||||
|
||||
case 'boolean':
|
||||
return parameters[name] === 'true' ||
|
||||
parameters[name] === 'false' ? (
|
||||
<div className={styles.valueContainer}>
|
||||
<StringTruncator
|
||||
maxLength={15}
|
||||
maxWidth="150"
|
||||
text={name}
|
||||
/>
|
||||
</Fragment>
|
||||
);
|
||||
{isSetTo}
|
||||
<Chip
|
||||
color={
|
||||
parameters[name] === 'true'
|
||||
? 'success'
|
||||
: 'error'
|
||||
}
|
||||
variant="outlined"
|
||||
size="small"
|
||||
label={parameters[name]}
|
||||
/>
|
||||
</div>
|
||||
) : null;
|
||||
|
||||
case 'string':
|
||||
const value = parseParameterString(
|
||||
strategy.parameters[param.name]
|
||||
);
|
||||
return (
|
||||
<ConditionallyRender
|
||||
condition={
|
||||
typeof strategy.parameters[param.name] !==
|
||||
'undefined'
|
||||
}
|
||||
key={param.name}
|
||||
show={
|
||||
<>
|
||||
<p className={styles.valueContainer}>
|
||||
<StringTruncator
|
||||
maxWidth="150"
|
||||
maxLength={15}
|
||||
text={param.name}
|
||||
/>
|
||||
<span className={styles.valueSeparator}>
|
||||
is set to
|
||||
</span>
|
||||
const value = parseParameterString(parameters[name]);
|
||||
return typeof parameters[name] !== 'undefined' ? (
|
||||
<div className={styles.valueContainer}>
|
||||
{nameItem}
|
||||
<ConditionallyRender
|
||||
condition={value === ''}
|
||||
show={
|
||||
<span className={styles.valueSeparator}>
|
||||
{' is an empty string'}
|
||||
</span>
|
||||
}
|
||||
elseShow={
|
||||
<>
|
||||
{isSetTo}
|
||||
<StringTruncator
|
||||
maxWidth="300"
|
||||
text={value}
|
||||
maxLength={50}
|
||||
/>
|
||||
</p>
|
||||
<ConditionallyRender
|
||||
condition={notLastItem}
|
||||
show={<StrategySeparator text="AND" />}
|
||||
/>
|
||||
</>
|
||||
}
|
||||
/>
|
||||
);
|
||||
</>
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
) : null;
|
||||
|
||||
case 'number':
|
||||
const number = parseParameterNumber(
|
||||
strategy.parameters[param.name]
|
||||
);
|
||||
return (
|
||||
<ConditionallyRender
|
||||
condition={number !== undefined}
|
||||
key={param.name}
|
||||
show={
|
||||
<>
|
||||
<p className={styles.valueContainer}>
|
||||
<StringTruncator
|
||||
maxLength={15}
|
||||
maxWidth="150"
|
||||
text={param.name}
|
||||
/>
|
||||
<span className={styles.valueSeparator}>
|
||||
is set to
|
||||
</span>
|
||||
<StringTruncator
|
||||
maxWidth="300"
|
||||
text={String(number)}
|
||||
maxLength={50}
|
||||
/>
|
||||
</p>
|
||||
<ConditionallyRender
|
||||
condition={notLastItem}
|
||||
show={<StrategySeparator text="AND" />}
|
||||
/>
|
||||
</>
|
||||
}
|
||||
/>
|
||||
);
|
||||
const number = parseParameterNumber(parameters[name]);
|
||||
return parameters[name] !== '' && number !== undefined ? (
|
||||
<div className={styles.valueContainer}>
|
||||
{nameItem}
|
||||
{isSetTo}
|
||||
<StringTruncator
|
||||
maxWidth="300"
|
||||
text={String(number)}
|
||||
maxLength={50}
|
||||
/>
|
||||
</div>
|
||||
) : null;
|
||||
case 'default':
|
||||
return null;
|
||||
}
|
||||
|
||||
return null;
|
||||
});
|
||||
};
|
||||
}, [parameters, definition, styles]);
|
||||
|
||||
if (!parameters) {
|
||||
return <NoItems />;
|
||||
}
|
||||
|
||||
const listItems = [
|
||||
Boolean(uiConfig.flags.SE) && segments && segments.length > 0 && (
|
||||
<FeatureOverviewSegment strategyId={strategy.id} />
|
||||
),
|
||||
constraints.length > 0 && (
|
||||
<ConstraintAccordionList
|
||||
constraints={constraints}
|
||||
showLabel={false}
|
||||
/>
|
||||
),
|
||||
strategy.name === 'default' && (
|
||||
<>
|
||||
<Box sx={{ width: '100%' }} className={styles.valueContainer}>
|
||||
The standard strategy is{' '}
|
||||
<Chip
|
||||
variant="outlined"
|
||||
size="small"
|
||||
color="success"
|
||||
label="ON"
|
||||
/>{' '}
|
||||
for all users.
|
||||
</Box>
|
||||
</>
|
||||
),
|
||||
...(parametersList ?? []),
|
||||
...(customStrategyList ?? []),
|
||||
].filter(Boolean);
|
||||
|
||||
return (
|
||||
<>
|
||||
<ConditionallyRender
|
||||
condition={Boolean(uiConfig.flags.SE)}
|
||||
show={<FeatureOverviewSegment strategyId={strategy.id} />}
|
||||
/>
|
||||
<ConditionallyRender
|
||||
condition={constraints.length > 0}
|
||||
show={
|
||||
<>
|
||||
<ConstraintAccordionList
|
||||
constraints={constraints}
|
||||
showLabel={false}
|
||||
/>
|
||||
<StrategySeparator text="AND" />
|
||||
</>
|
||||
}
|
||||
/>
|
||||
<ConditionallyRender
|
||||
condition={strategy.name === 'default'}
|
||||
show={
|
||||
<Box sx={{ width: '100%' }} className={styles.summary}>
|
||||
The standard strategy is{' '}
|
||||
<Chip
|
||||
variant="outlined"
|
||||
size="small"
|
||||
color="success"
|
||||
label="ON"
|
||||
/>{' '}
|
||||
for all users.
|
||||
</Box>
|
||||
}
|
||||
/>
|
||||
{renderParameters()}
|
||||
{renderCustomStrategy()}
|
||||
</>
|
||||
<ConditionallyRender
|
||||
condition={listItems.length > 0}
|
||||
show={
|
||||
<>
|
||||
{listItems.map((item, index) => (
|
||||
<Fragment key={index}>
|
||||
<ConditionallyRender
|
||||
condition={index > 0}
|
||||
show={<StrategySeparator text="AND" />}
|
||||
/>
|
||||
{item}
|
||||
</Fragment>
|
||||
))}
|
||||
</>
|
||||
}
|
||||
elseShow={<NoItems />}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
@ -3,7 +3,6 @@ import { Link } from 'react-router-dom';
|
||||
import { DonutLarge } from '@mui/icons-material';
|
||||
import { useStyles } from 'component/feature/FeatureView/FeatureOverview/FeatureOverviewSegment/FeatureOverviewSegment.styles';
|
||||
import { useSegments } from 'hooks/api/getters/useSegments/useSegments';
|
||||
import { StrategySeparator } from 'component/common/StrategySeparator/StrategySeparator';
|
||||
|
||||
interface IFeatureOverviewSegmentProps {
|
||||
strategyId: string;
|
||||
@ -32,7 +31,6 @@ export const FeatureOverviewSegment = ({
|
||||
{segment.name}
|
||||
</Link>
|
||||
</div>
|
||||
<StrategySeparator text="AND" />
|
||||
</Fragment>
|
||||
))}
|
||||
</>
|
||||
|
@ -309,6 +309,11 @@ export default createTheme({
|
||||
...(ownerState.color === 'default' && {
|
||||
color: theme.palette.text.secondary,
|
||||
}),
|
||||
...(ownerState.color === 'error' && {
|
||||
color: theme.palette.error.dark,
|
||||
background: theme.palette.error.light,
|
||||
borderColor: theme.palette.error.border,
|
||||
}),
|
||||
}),
|
||||
}),
|
||||
},
|
||||
|
Loading…
Reference in New Issue
Block a user