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

Visual updates to constraints (#1157)

This commit is contained in:
Tymoteusz Czech 2022-07-28 09:34:15 +02:00 committed by GitHub
parent 7df59cf286
commit c20aa300ce
8 changed files with 148 additions and 120 deletions

View File

@ -52,10 +52,13 @@ export const useStyles = makeStyles()(theme => ({
headerValuesContainerWrapper: { headerValuesContainerWrapper: {
display: 'flex', display: 'flex',
alignItems: 'stretch', alignItems: 'stretch',
margin: 'auto 0',
}, },
headerValuesContainer: { headerValuesContainer: {
display: 'flex', display: 'flex',
alignItems: 'stretch', justifyContent: 'stretch',
margin: 'auto 0',
flexDirection: 'column',
}, },
headerValues: { headerValues: {
fontSize: theme.fontSizes.smallBody, fontSize: theme.fontSizes.smallBody,

View File

@ -23,6 +23,7 @@ export const useStyles = makeStyles()(theme => ({
margin: '0.75rem 0 ', margin: '0.75rem 0 ',
}, },
customConstraintLabel: { customConstraintLabel: {
marginBottom: theme.spacing(1),
color: theme.palette.text.secondary, color: theme.palette.text.secondary,
}, },
})); }));

View File

@ -1,4 +1,4 @@
import React, { forwardRef, useImperativeHandle } from 'react'; import React, { forwardRef, Fragment, useImperativeHandle } from 'react';
import { Button, Tooltip } from '@mui/material'; import { Button, Tooltip } from '@mui/material';
import { HelpOutline } from '@mui/icons-material'; import { HelpOutline } from '@mui/icons-material';
import { IConstraint } from 'interfaces/strategy'; import { IConstraint } from 'interfaces/strategy';
@ -10,11 +10,14 @@ import { objectId } from 'utils/objectId';
import { useStyles } from './ConstraintAccordionList.styles'; import { useStyles } from './ConstraintAccordionList.styles';
import { createEmptyConstraint } from 'component/common/ConstraintAccordion/ConstraintAccordionList/createEmptyConstraint'; import { createEmptyConstraint } from 'component/common/ConstraintAccordion/ConstraintAccordionList/createEmptyConstraint';
import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender'; import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender';
import { StrategySeparator } from 'component/common/StrategySeparator/StrategySeparator';
interface IConstraintAccordionListProps { interface IConstraintAccordionListProps {
constraints: IConstraint[]; constraints: IConstraint[];
setConstraints?: React.Dispatch<React.SetStateAction<IConstraint[]>>; setConstraints?: React.Dispatch<React.SetStateAction<IConstraint[]>>;
showCreateButton?: boolean; showCreateButton?: boolean;
/* Add "Custom constraints" title on the top - default `true` */
showLabel?: boolean;
} }
// Ref methods exposed by this component. // Ref methods exposed by this component.
@ -35,124 +38,138 @@ export const constraintAccordionListId = 'constraintAccordionListId';
export const ConstraintAccordionList = forwardRef< export const ConstraintAccordionList = forwardRef<
IConstraintAccordionListRef | undefined, IConstraintAccordionListRef | undefined,
IConstraintAccordionListProps IConstraintAccordionListProps
>(({ constraints, setConstraints, showCreateButton }, ref) => { >(
const state = useWeakMap<IConstraint, IConstraintAccordionListItemState>(); (
const { context } = useUnleashContext(); { constraints, setConstraints, showCreateButton, showLabel = true },
const { classes: styles } = useStyles(); ref
) => {
const state = useWeakMap<
IConstraint,
IConstraintAccordionListItemState
>();
const { context } = useUnleashContext();
const { classes: styles } = useStyles();
const addConstraint = const addConstraint =
setConstraints && setConstraints &&
((contextName: string) => { ((contextName: string) => {
const constraint = createEmptyConstraint(contextName); const constraint = createEmptyConstraint(contextName);
state.set(constraint, { editing: true, new: true }); state.set(constraint, { editing: true, new: true });
setConstraints(prev => [...prev, constraint]); setConstraints(prev => [...prev, constraint]);
}); });
useImperativeHandle(ref, () => ({ useImperativeHandle(ref, () => ({
addConstraint, addConstraint,
})); }));
const onAdd = const onAdd =
addConstraint && addConstraint &&
(() => { (() => {
addConstraint(context[0].name); addConstraint(context[0].name);
}); });
const onEdit = const onEdit =
setConstraints && setConstraints &&
((constraint: IConstraint) => { ((constraint: IConstraint) => {
state.set(constraint, { editing: true }); state.set(constraint, { editing: true });
}); });
const onRemove = const onRemove =
setConstraints && setConstraints &&
((index: number) => { ((index: number) => {
const constraint = constraints[index];
state.set(constraint, {});
setConstraints(
produce(draft => {
draft.splice(index, 1);
})
);
});
const onSave =
setConstraints &&
((index: number, constraint: IConstraint) => {
state.set(constraint, {});
setConstraints(
produce(draft => {
draft[index] = constraint;
})
);
});
const onCancel = (index: number) => {
const constraint = constraints[index]; const constraint = constraints[index];
state.get(constraint)?.new && onRemove?.(index);
state.set(constraint, {}); state.set(constraint, {});
setConstraints( };
produce(draft => {
draft.splice(index, 1);
})
);
});
const onSave = if (context.length === 0) {
setConstraints && return null;
((index: number, constraint: IConstraint) => { }
state.set(constraint, {});
setConstraints(
produce(draft => {
draft[index] = constraint;
})
);
});
const onCancel = (index: number) => { return (
const constraint = constraints[index]; <div className={styles.container} id={constraintAccordionListId}>
state.get(constraint)?.new && onRemove?.(index); <ConditionallyRender
state.set(constraint, {}); condition={
}; constraints && constraints.length > 0 && showLabel
}
if (context.length === 0) { show={
return null; <p className={styles.customConstraintLabel}>
} Custom constraints
</p>
return ( }
<div className={styles.container} id={constraintAccordionListId}>
<ConditionallyRender
condition={constraints && constraints.length > 0}
show={
<p className={styles.customConstraintLabel}>
Custom constraints
</p>
}
/>
{constraints.map((constraint, index) => (
<ConstraintAccordion
key={objectId(constraint)}
constraint={constraint}
onEdit={onEdit && onEdit.bind(null, constraint)}
onCancel={onCancel.bind(null, index)}
onDelete={onRemove && onRemove.bind(null, index)}
onSave={onSave && onSave.bind(null, index)}
editing={Boolean(state.get(constraint)?.editing)}
compact
/> />
))} {constraints.map((constraint, index) => (
<ConditionallyRender <Fragment key={objectId(constraint)}>
condition={Boolean(showCreateButton && onAdd)} <ConditionallyRender
show={ condition={index > 0}
<div> show={<StrategySeparator text="AND" />}
<div className={styles.addCustomLabel}> />
<p>Add any number of custom constraints</p> <ConstraintAccordion
<Tooltip constraint={constraint}
title="Help" onEdit={onEdit && onEdit.bind(null, constraint)}
arrow onCancel={onCancel.bind(null, index)}
className={styles.helpWrapper} onDelete={onRemove && onRemove.bind(null, index)}
> onSave={onSave && onSave.bind(null, index)}
<a editing={Boolean(state.get(constraint)?.editing)}
href={ compact
'https://docs.getunleash.io/advanced/strategy_constraints' />
} </Fragment>
target="_blank" ))}
rel="noopener noreferrer" <ConditionallyRender
condition={Boolean(showCreateButton && onAdd)}
show={
<div>
<div className={styles.addCustomLabel}>
<p>Add any number of custom constraints</p>
<Tooltip
title="Help"
arrow
className={styles.helpWrapper}
> >
<HelpOutline className={styles.help} /> <a
</a> href={
</Tooltip> 'https://docs.getunleash.io/advanced/strategy_constraints'
}
target="_blank"
rel="noopener noreferrer"
>
<HelpOutline className={styles.help} />
</a>
</Tooltip>
</div>
<Button
type="button"
onClick={onAdd}
variant="outlined"
color="secondary"
>
Add custom constraint
</Button>
</div> </div>
<Button }
type="button" />
onClick={onAdd} </div>
variant="outlined" );
color="secondary" }
sx={{ mb: 2 }} );
>
Add custom constraint
</Button>
</div>
}
/>
</div>
);
});

View File

@ -6,7 +6,7 @@ interface IStrategySeparatorProps {
} }
const StyledContainer = styled('div')(({ theme }) => ({ const StyledContainer = styled('div')(({ theme }) => ({
height: theme.spacing(2), height: theme.spacing(1),
position: 'relative', position: 'relative',
width: '100%', width: '100%',
})); }));

View File

@ -256,7 +256,10 @@ export const StrategyExecution = ({ strategy }: IStrategyExecutionProps) => {
condition={constraints.length > 0} condition={constraints.length > 0}
show={ show={
<> <>
<ConstraintAccordionList constraints={constraints} /> <ConstraintAccordionList
constraints={constraints}
showLabel={false}
/>
<StrategySeparator text="AND" /> <StrategySeparator text="AND" />
</> </>
} }

View File

@ -16,6 +16,7 @@ export const useStyles = makeStyles()(theme => ({
alignItems: 'center', alignItems: 'center',
borderBottom: `1px solid ${theme.palette.grey[300]}`, borderBottom: `1px solid ${theme.palette.grey[300]}`,
fontWeight: theme.typography.fontWeightMedium, fontWeight: theme.typography.fontWeightMedium,
fontSize: theme.fontSizes.smallBody,
}, },
icon: { icon: {
fill: theme.palette.inactiveIcon, fill: theme.palette.inactiveIcon,

View File

@ -3,16 +3,18 @@ import { makeStyles } from 'tss-react/mui';
export const useStyles = makeStyles()(theme => ({ export const useStyles = makeStyles()(theme => ({
container: { container: {
width: '100%', width: '100%',
padding: '1rem', padding: theme.spacing(2, 3),
display: 'flex',
alignItems: 'center',
justifyContent: 'flex-start',
fontSize: theme.fontSizes.smallBody, fontSize: theme.fontSizes.smallBody,
backgroundColor: theme.palette.grey[200], border: `1px solid ${theme.palette.dividerAlternative}`,
position: 'relative', position: 'relative',
borderRadius: '5px', borderRadius: '5px',
textAlign: 'center',
}, },
link: { link: {
textDecoration: 'none', textDecoration: 'none',
fontWeight: theme.fontWeight.bold, marginLeft: theme.spacing(1),
'&:hover': { '&:hover': {
textDecoration: 'underline', textDecoration: 'underline',
}, },

View File

@ -1,8 +1,9 @@
import { Fragment } from 'react';
import { Link } from 'react-router-dom';
import { DonutLarge } from '@mui/icons-material';
import { useStyles } from 'component/feature/FeatureView/FeatureOverview/FeatureOverviewSegment/FeatureOverviewSegment.styles'; import { useStyles } from 'component/feature/FeatureView/FeatureOverview/FeatureOverviewSegment/FeatureOverviewSegment.styles';
import { useSegments } from 'hooks/api/getters/useSegments/useSegments'; import { useSegments } from 'hooks/api/getters/useSegments/useSegments';
import { StrategySeparator } from 'component/common/StrategySeparator/StrategySeparator'; import { StrategySeparator } from 'component/common/StrategySeparator/StrategySeparator';
import { Link } from 'react-router-dom';
import { Fragment } from 'react';
interface IFeatureOverviewSegmentProps { interface IFeatureOverviewSegmentProps {
strategyId: string; strategyId: string;
@ -23,7 +24,7 @@ export const FeatureOverviewSegment = ({
{segments.map(segment => ( {segments.map(segment => (
<Fragment key={segment.id}> <Fragment key={segment.id}>
<div className={styles.container}> <div className={styles.container}>
Segment{' '} <DonutLarge color="secondary" sx={{ mr: 1 }} /> Segment:{' '}
<Link <Link
to={segmentPath(segment.id)} to={segmentPath(segment.id)}
className={styles.link} className={styles.link}