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:
parent
7df59cf286
commit
c20aa300ce
@ -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,
|
||||||
|
@ -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,
|
||||||
},
|
},
|
||||||
}));
|
}));
|
||||||
|
@ -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>
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
@ -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%',
|
||||||
}));
|
}));
|
||||||
|
@ -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" />
|
||||||
</>
|
</>
|
||||||
}
|
}
|
||||||
|
@ -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,
|
||||||
|
@ -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',
|
||||||
},
|
},
|
||||||
|
@ -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}
|
||||||
|
Loading…
Reference in New Issue
Block a user