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

Fix/strategy constraints (#283)

* feat: redesign strategy cards

* fix: add version check to constraints

* fix: use flags

* fix: update icon and add tooltips

* fix: remove console logs

Co-authored-by: Ivar Conradi Østhus <ivarconr@gmail.com>
This commit is contained in:
Fredrik Strand Oseberg 2021-05-04 21:25:06 +02:00 committed by GitHub
parent 367b5c8d85
commit e8de5bd816
20 changed files with 252 additions and 164 deletions

View File

@ -0,0 +1,6 @@
<svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M9.64645 19.3536C9.84171 19.5488 10.1583 19.5488 10.3536 19.3536L13.5355 16.1716C13.7308 15.9763 13.7308 15.6597 13.5355 15.4645C13.3403 15.2692 13.0237 15.2692 12.8284 15.4645L10 18.2929L7.17157 15.4645C6.97631 15.2692 6.65973 15.2692 6.46447 15.4645C6.2692 15.6597 6.2692 15.9763 6.46447 16.1716L9.64645 19.3536ZM9.5 1L9.5 19L10.5 19L10.5 1L9.5 1Z" fill="white"/>
<path d="M19.3536 10.3536C19.5488 10.1583 19.5488 9.84171 19.3536 9.64645L16.1716 6.46447C15.9763 6.2692 15.6597 6.2692 15.4645 6.46447C15.2692 6.65973 15.2692 6.97631 15.4645 7.17157L18.2929 10L15.4645 12.8284C15.2692 13.0237 15.2692 13.3403 15.4645 13.5355C15.6597 13.7308 15.9763 13.7308 16.1716 13.5355L19.3536 10.3536ZM1 10.5L19 10.5V9.5L1 9.5V10.5Z" fill="white"/>
<path d="M0.646446 9.64645C0.451185 9.84171 0.451185 10.1583 0.646446 10.3536L3.82843 13.5355C4.02369 13.7308 4.34027 13.7308 4.53553 13.5355C4.7308 13.3403 4.7308 13.0237 4.53553 12.8284L1.70711 10L4.53553 7.17157C4.7308 6.97631 4.7308 6.65973 4.53553 6.46446C4.34027 6.2692 4.02369 6.2692 3.82843 6.46446L0.646446 9.64645ZM19 9.5L1 9.5L1 10.5L19 10.5L19 9.5Z" fill="white"/>
<path d="M10.3536 0.646446C10.1583 0.451185 9.84171 0.451185 9.64645 0.646446L6.46447 3.82843C6.2692 4.02369 6.2692 4.34027 6.46447 4.53553C6.65973 4.7308 6.97631 4.7308 7.17157 4.53553L10 1.70711L12.8284 4.53553C13.0237 4.7308 13.3403 4.7308 13.5355 4.53553C13.7308 4.34027 13.7308 4.02369 13.5355 3.82843L10.3536 0.646446ZM10.5 19L10.5 1L9.5 1L9.5 19L10.5 19Z" fill="white"/>
</svg>

After

Width:  |  Height:  |  Size: 1.6 KiB

View File

@ -1,8 +1,20 @@
import { makeStyles } from '@material-ui/styles';
export const useStyles = makeStyles({
export const useStyles = makeStyles(theme => ({
strategyCard: {
width: '250px',
width: '337px',
height: '100%',
[theme.breakpoints.down('xs')]: {
width: '100%',
},
[theme.breakpoints.down('1250')]: {
width: '300px',
},
[theme.breakpoints.down('1035')]: {
width: '280px',
},
[theme.breakpoints.down('860')]: {
width: '380px',
},
},
});
}));

View File

@ -5,7 +5,7 @@ import { Typography } from '@material-ui/core';
import { Link } from 'react-router-dom';
import StrategyCardPercentage from '../common/StrategyCardPercentage/StrageyCardPercentage';
import StrategyCardConstraints from '../common/StrategyCardConstraints/StrategyCardConstraints';
import StrategyCardConstraints from '../common/StrategyCardConstraints';
import { useCommonStyles } from '../../../../../../common.styles';
import ConditionallyRender from '../../../../../common/ConditionallyRender';
@ -19,7 +19,9 @@ const StrategyCardContentCustom = ({ strategy, strategyDefinition }) => {
return (
<Typography className={commonStyles.textCenter}>
The strategy definition "{strategy.name}" does not exist.{' '}
<Link to={`/strategies/create?name=${strategy.name}`}>Create a strategy named {strategy.name}</Link>
<Link to={`/strategies/create?name=${strategy.name}`}>
Create a strategy named {strategy.name}
</Link>
</Typography>
);
if (strategyDefinition.name === 'Loading') return null;
@ -42,7 +44,7 @@ const StrategyCardContentCustom = ({ strategy, strategyDefinition }) => {
case 'list':
/* eslint-disable-next-line */
const paramList = param
? param.split(",").filter(listItem => listItem)
? param.split(',').filter(listItem => listItem)
: [];
return (
@ -51,7 +53,10 @@ const StrategyCardContentCustom = ({ strategy, strategyDefinition }) => {
condition={paramList.length > 0}
show={
<>
<StrategyCardList list={paramList} valuesName={paramDefinition.name} />
<StrategyCardList
list={paramList}
valuesName={paramDefinition.name}
/>
<div className={commonStyles.divider} />
</>
}
@ -64,29 +69,28 @@ const StrategyCardContentCustom = ({ strategy, strategyDefinition }) => {
<ConditionallyRender
key={paramDefinition.name}
condition={param || param === false}
show={<StrategyCardField title={paramDefinition.name} value={param} />}
show={
<StrategyCardField
title={paramDefinition.name}
value={param}
/>
}
/>
);
default:
return null
return null;
}
};
const renderCustomSections = () =>
strategyDefinition.parameters.map(paramDefinition => getSection(paramDefinition));
strategyDefinition.parameters.map(paramDefinition =>
getSection(paramDefinition)
);
return (
<div>
<StrategyCardConstraints constraints={constraints} />
{renderCustomSections()}
<ConditionallyRender
condition={constraints && constraints.length > 0}
show={
<>
<div className={commonStyles.divider} />
<StrategyCardConstraints constraints={constraints} />
</>
}
/>
</div>
);
};

View File

@ -3,7 +3,6 @@ import React from 'react';
import { Typography } from '@material-ui/core';
import { useCommonStyles } from '../../../../../../common.styles';
import ConditionallyRender from '../../../../../common/ConditionallyRender';
import StrategyCardConstraints from '../common/StrategyCardConstraints/StrategyCardConstraints';
const StrategyCardContentDefault = ({ strategy }) => {
@ -13,18 +12,11 @@ const StrategyCardContentDefault = ({ strategy }) => {
return (
<>
<StrategyCardConstraints constraints={constraints} />
<div className={commonStyles.divider} />
<Typography className={commonStyles.textCenter}>
The default strategy is either on or off for all users.
The default strategy is on for all users.
</Typography>
<ConditionallyRender
condition={constraints && constraints.length > 0}
show={
<>
<div className={commonStyles.divider} />
<StrategyCardConstraints constraints={constraints} />
</>
}
/>
</>
);
};

View File

@ -2,11 +2,10 @@ import React from 'react';
import PropTypes from 'prop-types';
import StrategyCardPercentage from '../common/StrategyCardPercentage/StrageyCardPercentage';
import StrategyCardConstraints from '../common/StrategyCardConstraints/StrategyCardConstraints';
import StrategyCardConstraints from '../common/StrategyCardConstraints';
import StrategyCardField from '../common/StrategyCardField/StrategyCardField';
import { useCommonStyles } from '../../../../../../common.styles';
import ConditionallyRender from '../../../../../common/ConditionallyRender';
const StrategyCardContentFlexible = ({ strategy }) => {
const commonStyles = useCommonStyles();
@ -18,16 +17,9 @@ const StrategyCardContentFlexible = ({ strategy }) => {
return (
<div>
<StrategyCardConstraints constraints={constraints} />
<div className={commonStyles.divider} />
<StrategyCardPercentage percentage={rolloutPercentage} />
<ConditionallyRender
condition={constraints && constraints.length > 0}
show={
<>
<div className={commonStyles.divider} />
<StrategyCardConstraints constraints={constraints} />
</>
}
/>
<div className={commonStyles.divider} />
<StrategyCardField title="Sticky on" value={stickyField} />

View File

@ -2,10 +2,9 @@ import React from 'react';
import PropTypes from 'prop-types';
import StrategyCardPercentage from '../common/StrategyCardPercentage/StrageyCardPercentage';
import StrategyCardConstraints from '../common/StrategyCardConstraints/StrategyCardConstraints';
import StrategyCardConstraints from '../common/StrategyCardConstraints';
import { useCommonStyles } from '../../../../../../common.styles';
import ConditionallyRender from '../../../../../common/ConditionallyRender';
const StrategyCardContentGradRandom = ({ strategy }) => {
const commonStyles = useCommonStyles();
@ -15,16 +14,9 @@ const StrategyCardContentGradRandom = ({ strategy }) => {
return (
<div>
<StrategyCardConstraints constraints={constraints} />
<div className={commonStyles.divider} />
<StrategyCardPercentage percentage={rolloutPercentage} />
<ConditionallyRender
condition={constraints && constraints.length > 0}
show={
<>
<div className={commonStyles.divider} />
<StrategyCardConstraints constraints={constraints} />
</>
}
/>
</div>
);
};

View File

@ -1,7 +1,7 @@
import React from 'react';
import PropTypes from 'prop-types';
import StrategyCardConstraints from '../common/StrategyCardConstraints/StrategyCardConstraints';
import StrategyCardConstraints from '../common/StrategyCardConstraints';
import { useCommonStyles } from '../../../../../../common.styles';
import ConditionallyRender from '../../../../../common/ConditionallyRender';
@ -15,16 +15,14 @@ const StrategyCardContentList = ({ strategy, parameter, valuesName }) => {
return (
<div>
<StrategyCardConstraints constraints={constraints} />
<ConditionallyRender
condition={list.length > 0}
show={<StrategyCardList list={list} valuesName={valuesName} />}
/>
<ConditionallyRender
condition={constraints && constraints.length > 0}
show={
<>
<div className={commonStyles.divider} />
<StrategyCardConstraints constraints={constraints} />
<StrategyCardList list={list} valuesName={valuesName} />
</>
}
/>

View File

@ -2,11 +2,10 @@ import React from 'react';
import PropTypes from 'prop-types';
import StrategyCardPercentage from '../common/StrategyCardPercentage/StrageyCardPercentage';
import StrategyCardConstraints from '../common/StrategyCardConstraints/StrategyCardConstraints';
import StrategyCardConstraints from '../common/StrategyCardConstraints';
import StrategyCardField from '../common/StrategyCardField/StrategyCardField';
import { useCommonStyles } from '../../../../../../common.styles';
import ConditionallyRender from '../../../../../common/ConditionallyRender';
const StrategyCardContentRollout = ({ strategy }) => {
const commonStyles = useCommonStyles();
@ -17,16 +16,9 @@ const StrategyCardContentRollout = ({ strategy }) => {
return (
<div>
<StrategyCardConstraints constraints={constraints} />
<div className={commonStyles.divider} />
<StrategyCardPercentage percentage={rolloutPercentage} />
<ConditionallyRender
condition={constraints && constraints.length > 0}
show={
<>
<div className={commonStyles.divider} />
<StrategyCardConstraints constraints={constraints} />
</>
}
/>
<div className={commonStyles.divider} />
<StrategyCardField title="Group id" value={groupId} />

View File

@ -1,55 +1,122 @@
import { Typography } from '@material-ui/core';
import PropTypes from 'prop-types';
import classnames from 'classnames';
import React from 'react';
import { useStyles } from './StrategyCardConstraints.styles.js';
import { useCommonStyles } from '../../../../../../../common.styles.js';
import ConditionallyRender from '../../../../../../common/ConditionallyRender/ConditionallyRender';
import { C } from '../../../../../../common/flags.js';
const StrategyCardConstraints = ({ constraints }) => {
const StrategyCardConstraints = ({ constraints, flags }) => {
const styles = useStyles();
const commonStyles = useCommonStyles();
const renderConstraintValues = constraint =>
constraint.values.map(value => (
<Typography variant="body2" key={value} className={styles.constraintValue}>
{value}
</Typography>
));
const isEnterprise = () => {
if (!flags) return false;
if (flags[C]) {
return true;
}
return false;
};
const renderConstraints = () =>
constraints.map((constraint, i) => (
<div key={`${constraint.contextName}-${i}`} className={styles.constraintContainer}>
<div className={styles.constraintDisplayContainer}>
<Typography variant="body2" className={styles.label}>
context
</Typography>
<Typography variant="body2">{constraint.contextName}</Typography>
</div>
<div className={styles.constraintDisplayContainer}>
<Typography variant="body2" className={styles.label}>
operator
</Typography>
<Typography variant="body2">{constraint.operator}</Typography>
</div>
const renderConstraintValues = constraint => {
const multiple = constraint.values.length > 1;
return constraint.values.map((value, index) => {
const notLastItem = index !== constraint.values.length - 1;
return (
<ConditionallyRender
key={value}
condition={multiple && notLastItem}
show={<span>'{value}',</span>}
elseShow={<span>'{value}'</span>}
/>
);
});
};
<div className={classnames(commonStyles.flexColumn, styles.constraintValuesContainer)}>
<Typography variant="body2" className={styles.label}>
values
</Typography>
<div className={classnames(commonStyles.flexRow, commonStyles.flexWrap)}>
{renderConstraintValues(constraint)}
</div>
</div>
const renderConstraints = () => {
return constraints.map((constraint, i) => (
<div key={`${constraint.contextName}-${constraint.operator}`}>
<ConditionallyRender
condition={i > 0}
show={<span>and</span>}
/>
<pre
key={`${constraint.contextName}-${i}`}
className={classnames(styles.constraintContainer)}
>
<span>{constraint.contextName}</span>
<span>{constraint.operator}</span>
{renderConstraintValues(constraint)}
</pre>
</div>
));
};
return (
<>
<Typography className={styles.title} variant="subtitle1">
Constraints
</Typography>
{renderConstraints()}
<ConditionallyRender
condition={constraints && constraints.length > 0}
show={
<>
<Typography variant="body2">
The following pre-conditions must be fulfilled for
this strategy to be executed
</Typography>
<div className={styles.constraints}>
{renderConstraints()}
</div>
</>
}
elseShow={
<>
<Typography variant="body2">
No pre-conditions defined for this strategy.
</Typography>
<ConditionallyRender
condition={isEnterprise()}
show={
<Typography
variant="body2"
className={styles.placeholderText}
>
Constraints allow you fine grained control
over how to execute your strategies.
<a
target="_blank"
rel="noopener noreferrer"
className={styles.link}
href="https://docs.getunleash.io/docs/advanced/strategy_constraints"
>
Learn more
</a>
</Typography>
}
elseShow={
<Typography
variant="body2"
className={styles.placeholderText}
>
Constraints are only available as an
enterprise feature.{' '}
<a
target="_blank"
rel="noopener noreferrer"
className={styles.link}
href="https://docs.getunleash.io/docs/advanced/strategy_constraints"
>
Learn more
</a>
</Typography>
}
/>
</>
}
/>
</>
);
};

View File

@ -1,34 +1,27 @@
import { makeStyles } from '@material-ui/styles';
export const useStyles = makeStyles(theme => ({
constraints: {
marginTop: '1rem',
},
constraintContainer: {
backgroundColor: theme.palette.cards.container.bg,
margin: '0.5rem 0',
borderRadius: theme.borders.radius.main,
padding: '0.8rem',
overflow: 'scroll',
display: 'flex',
alignItems: 'center',
flexWrap: 'wrap',
'& span': {
marginRight: '0.4rem',
fontSize: '0.9rem',
},
},
verticalSpacer: {
margin: '0 0.25rem',
},
title: {
fontWeight: theme.fontWeight.semi,
},
constraintDisplayContainer: {
display: 'flex',
flexDirection: 'column',
width: '50%',
},
label: {
fontWeight: theme.fontWeight.bold,
},
constraintValuesContainer: {
placeholderText: {
marginTop: '0.25rem',
},
constraintValue: {
marginRight: '0.25rem',
link: {
display: 'block',
marginTop: '0.2rem',
},
}));

View File

@ -0,0 +1,9 @@
import { connect } from 'react-redux';
import StrategyCardConstraints from './StrategyCardConstraints';
const mapStateToProps = (state, ownProps) => ({
flags: state.uiConfig.toJS().flags,
constraints: ownProps.constraints,
});
export default connect(mapStateToProps)(StrategyCardConstraints);

View File

@ -8,9 +8,7 @@ const StrategyCardField = ({ title, value }) => {
const styles = useStyles();
return (
<div className={styles.fieldContainer}>
<Typography variant="body1" className={styles.fieldTitle}>
{title}
</Typography>
<Typography variant="body1">{title}</Typography>
<Typography className={styles.fieldValue} variant="body1">
{value}
</Typography>

View File

@ -6,9 +6,6 @@ export const useStyles = makeStyles(theme => ({
justifyContent: 'space-between',
margin: '0.4rem 0',
},
fieldTitle: {
fontWeight: theme.fontWeight.semi,
},
fieldValue: {
maxWidth: '50%',
},

View File

@ -10,9 +10,15 @@ const StrategyCardList = ({ list, valuesName }) => {
return (
<div className={styles.strategyList}>
<Typography className={styles.strategyListHeader}>List of {valuesName}</Typography>
<Typography className={styles.strategyListHeader}>
List of {valuesName}
</Typography>
{list.map(listItem => (
<Chip key={listItem} label={listItem} className={styles.strategyListChip} />
<Chip
key={listItem}
label={listItem}
className={styles.strategyListChip}
/>
))}
</div>
);

View File

@ -8,7 +8,6 @@ export const useStyles = makeStyles(theme => ({
margin: '0.25rem',
},
strategyListHeader: {
fontWeight: theme.fontWeight.semi,
marginBottom: '0.5rem',
},
}));

View File

@ -5,11 +5,7 @@ export const useStyles = makeStyles(theme => ({
display: 'flex',
justifyContent: 'space-between',
},
title: {
fontWeight: theme.fontWeight.semi,
},
percentage: {
color: theme.palette.success.main,
fontWeight: 'bold',
},
}));

View File

@ -1,11 +1,23 @@
import React from 'react';
import PropTypes from 'prop-types';
import { CardHeader, Typography, IconButton, Icon } from '@material-ui/core';
import {
CardHeader,
Typography,
IconButton,
Icon,
Tooltip,
} from '@material-ui/core';
import { useStyles } from './StrategyCardHeader.styles.js';
import { ReactComponent as ReorderIcon } from '../../../../../assets/icons/reorder.svg';
const StrategyCardHeader = ({ name, connectDragSource, removeStrategy, editStrategy }) => {
const StrategyCardHeader = ({
name,
connectDragSource,
removeStrategy,
editStrategy,
}) => {
const styles = useStyles();
return (
@ -16,23 +28,36 @@ const StrategyCardHeader = ({ name, connectDragSource, removeStrategy, editStrat
}}
title={
<>
<Typography variant="subtitle1" className={styles.strategyCardHeaderTitle}>
<Typography
variant="subtitle1"
className={styles.strategyCardHeaderTitle}
>
{name}
</Typography>
<div className={styles.strategyCardHeaderActions}>
<IconButton onClick={editStrategy}>
<Icon className={styles.strateyCardHeaderIcon}>edit</Icon>
</IconButton>
<Tooltip title="Edit strategy">
<IconButton onClick={editStrategy}>
<Icon className={styles.strateyCardHeaderIcon}>
edit
</Icon>
</IconButton>
</Tooltip>
{connectDragSource(
<span>
<IconButton>
<Icon className={styles.strateyCardHeaderIcon}>reorder</Icon>
</IconButton>
<Tooltip title="Drag and drop strategy to reorder. This only affects the order of which your strategies are evaluated.">
<IconButton>
<ReorderIcon />
</IconButton>
</Tooltip>
</span>
)}
<IconButton onClick={removeStrategy}>
<Icon className={styles.strateyCardHeaderIcon}>delete</Icon>
</IconButton>
<Tooltip title="Delete strategy">
<IconButton onClick={removeStrategy}>
<Icon className={styles.strateyCardHeaderIcon}>
delete
</Icon>
</IconButton>
</Tooltip>
</div>
</>
}

View File

@ -3,23 +3,21 @@ import { makeStyles } from '@material-ui/styles';
export const useStyles = makeStyles(theme => ({
strategyCardHeaderContent: {
width: '100%',
display: 'flex',
alignItems: 'center',
justifyContent: 'space-between',
},
strategyCardHeader: {
display: 'flex',
flexDirection: 'column',
background: `linear-gradient(${theme.palette.cards.gradient.top}, ${theme.palette.cards.gradient.bottom})`,
color: '#fff',
textAlign: 'center',
width: '100%',
textAlign: 'left',
},
strategyCardHeaderTitle: {
fontWeight: 'bold',
fontSize: theme.fontSizes.mainHeader,
marginBottom: '0.7rem',
fontSize: theme.fontSizes.subHeader,
},
strategyCardHeaderActions: {
display: 'flex',
justifyContent: 'space-between',
color: '#fff',
},
strateyCardHeaderIcon: {

View File

@ -187,16 +187,20 @@ const StrategiesList = props => {
className={styles.infoCard}
onClose={() => setShowAlert(false)}
>
Strategies allow you fine grained control over how to
activate your features, and are composable blocks that
are executed in an OR fashion. As an example, you can
have a gradual rollout that targets 80% of users in a
region of the world (using the enterprise feature of
constraints), and another gradual rollout that targets
20% of the users in another region. If you don't add a
strategy, the default strategy is activated which means
that the feature will be strictly on/off for your entire
userbase.
<div style={{ maxWidth: '800px' }}>
Activation strategies defines how you enable the new
feature to your users. Changes to the activation
strategies does not require redeployment of the
code.
<br />
<br />
Multiple activation strategies are composable blocks
that is executed in an OR fashion.
<br />
E.g. A gradual rollout activation strategy allows
you to gradually enable to feature to a subset of
your users without redeploy to production.
</div>
</Alert>
}
/>

View File

@ -11,7 +11,7 @@
width: 100%;
display: block;
background-color: #f2f9fc;
overflow: "visible";
overflow: 'visible';
}
.paragraph {
@ -156,3 +156,11 @@
padding: 0;
}
}
@media (max-width: 860px) {
.strategyListCards {
flex-direction: column;
flex-wrap: none;
align-items: center;
}
}