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: {
display: 'flex',
alignItems: 'stretch',
margin: 'auto 0',
},
headerValuesContainer: {
display: 'flex',
alignItems: 'stretch',
justifyContent: 'stretch',
margin: 'auto 0',
flexDirection: 'column',
},
headerValues: {
fontSize: theme.fontSizes.smallBody,

View File

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

View File

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

View File

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

View File

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

View File

@ -3,16 +3,18 @@ import { makeStyles } from 'tss-react/mui';
export const useStyles = makeStyles()(theme => ({
container: {
width: '100%',
padding: '1rem',
padding: theme.spacing(2, 3),
display: 'flex',
alignItems: 'center',
justifyContent: 'flex-start',
fontSize: theme.fontSizes.smallBody,
backgroundColor: theme.palette.grey[200],
border: `1px solid ${theme.palette.dividerAlternative}`,
position: 'relative',
borderRadius: '5px',
textAlign: 'center',
},
link: {
textDecoration: 'none',
fontWeight: theme.fontWeight.bold,
marginLeft: theme.spacing(1),
'&:hover': {
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 { useSegments } from 'hooks/api/getters/useSegments/useSegments';
import { StrategySeparator } from 'component/common/StrategySeparator/StrategySeparator';
import { Link } from 'react-router-dom';
import { Fragment } from 'react';
interface IFeatureOverviewSegmentProps {
strategyId: string;
@ -23,7 +24,7 @@ export const FeatureOverviewSegment = ({
{segments.map(segment => (
<Fragment key={segment.id}>
<div className={styles.container}>
Segment{' '}
<DonutLarge color="secondary" sx={{ mr: 1 }} /> Segment:{' '}
<Link
to={segmentPath(segment.id)}
className={styles.link}