mirror of
https://github.com/Unleash/unleash.git
synced 2025-04-24 01:18:01 +02:00
refactor: add segment limit warnings (#851)
* refactor: fix environment name text alignment * refactor: use rounded corners for AutocompleteBox * refactor: add tooltips to the strategy segment icons * refactor: add segment limit warnings * refactor: improve segments warning text
This commit is contained in:
parent
42a81e6647
commit
f59ba567fb
@ -18,12 +18,17 @@ export const useStyles = makeStyles(theme => ({
|
|||||||
borderBottomLeftRadius: 50,
|
borderBottomLeftRadius: 50,
|
||||||
color: '#fff',
|
color: '#fff',
|
||||||
},
|
},
|
||||||
|
iconDisabled: {
|
||||||
|
background: theme.palette.primary.light,
|
||||||
|
},
|
||||||
autocomplete: {
|
autocomplete: {
|
||||||
flex: 1,
|
flex: 1,
|
||||||
},
|
},
|
||||||
inputRoot: {
|
inputRoot: {
|
||||||
borderTopLeftRadius: 0,
|
borderTopLeftRadius: 0,
|
||||||
borderBottomLeftRadius: 0,
|
borderBottomLeftRadius: 0,
|
||||||
|
borderTopRightRadius: 50,
|
||||||
|
borderBottomRightRadius: 50,
|
||||||
'& fieldset': {
|
'& fieldset': {
|
||||||
borderColor: theme.palette,
|
borderColor: theme.palette,
|
||||||
borderLeftColor: 'transparent',
|
borderLeftColor: 'transparent',
|
||||||
|
@ -2,12 +2,14 @@ import { useStyles } from 'component/common/AutocompleteBox/AutocompleteBox.styl
|
|||||||
import { Search, ArrowDropDown } from '@material-ui/icons';
|
import { Search, ArrowDropDown } from '@material-ui/icons';
|
||||||
import { Autocomplete, AutocompleteRenderInputParams } from '@material-ui/lab';
|
import { Autocomplete, AutocompleteRenderInputParams } from '@material-ui/lab';
|
||||||
import { TextField } from '@material-ui/core';
|
import { TextField } from '@material-ui/core';
|
||||||
|
import classNames from 'classnames';
|
||||||
|
|
||||||
interface IAutocompleteBoxProps {
|
interface IAutocompleteBoxProps {
|
||||||
label: string;
|
label: string;
|
||||||
options: IAutocompleteBoxOption[];
|
options: IAutocompleteBoxOption[];
|
||||||
value?: IAutocompleteBoxOption[];
|
value?: IAutocompleteBoxOption[];
|
||||||
onChange: (value: IAutocompleteBoxOption[]) => void;
|
onChange: (value: IAutocompleteBoxOption[]) => void;
|
||||||
|
disabled?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface IAutocompleteBoxOption {
|
export interface IAutocompleteBoxOption {
|
||||||
@ -20,6 +22,7 @@ export const AutocompleteBox = ({
|
|||||||
options,
|
options,
|
||||||
value = [],
|
value = [],
|
||||||
onChange,
|
onChange,
|
||||||
|
disabled,
|
||||||
}: IAutocompleteBoxProps) => {
|
}: IAutocompleteBoxProps) => {
|
||||||
const styles = useStyles();
|
const styles = useStyles();
|
||||||
|
|
||||||
@ -29,7 +32,13 @@ export const AutocompleteBox = ({
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={styles.container}>
|
<div className={styles.container}>
|
||||||
<div className={styles.icon} aria-hidden>
|
<div
|
||||||
|
className={classNames(
|
||||||
|
styles.icon,
|
||||||
|
disabled && styles.iconDisabled
|
||||||
|
)}
|
||||||
|
aria-hidden
|
||||||
|
>
|
||||||
<Search />
|
<Search />
|
||||||
</div>
|
</div>
|
||||||
<Autocomplete
|
<Autocomplete
|
||||||
@ -41,6 +50,7 @@ export const AutocompleteBox = ({
|
|||||||
onChange={(event, value) => onChange(value || [])}
|
onChange={(event, value) => onChange(value || [])}
|
||||||
renderInput={renderInput}
|
renderInput={renderInput}
|
||||||
getOptionLabel={value => value.label}
|
getOptionLabel={value => value.label}
|
||||||
|
disabled={disabled}
|
||||||
multiple
|
multiple
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
@ -10,8 +10,6 @@ export const useStyles = makeStyles(theme => ({
|
|||||||
color: 'inherit',
|
color: 'inherit',
|
||||||
},
|
},
|
||||||
envName: {
|
envName: {
|
||||||
position: 'relative',
|
|
||||||
top: '6px',
|
|
||||||
fontWeight: 'bold',
|
fontWeight: 'bold',
|
||||||
},
|
},
|
||||||
}));
|
}));
|
||||||
|
@ -7,6 +7,8 @@ import {
|
|||||||
} from 'component/common/AutocompleteBox/AutocompleteBox';
|
} from 'component/common/AutocompleteBox/AutocompleteBox';
|
||||||
import { FeatureStrategySegmentList } from 'component/feature/FeatureStrategy/FeatureStrategySegment/FeatureStrategySegmentList';
|
import { FeatureStrategySegmentList } from 'component/feature/FeatureStrategy/FeatureStrategySegment/FeatureStrategySegmentList';
|
||||||
import { useStyles } from 'component/feature/FeatureStrategy/FeatureStrategySegment/FeatureStrategySegment.styles';
|
import { useStyles } from 'component/feature/FeatureStrategy/FeatureStrategySegment/FeatureStrategySegment.styles';
|
||||||
|
import { SegmentDocsStrategyWarning } from 'component/segments/SegmentDocs/SegmentDocs';
|
||||||
|
import { STRATEGY_SEGMENTS_LIMIT } from 'utils/segmentLimits';
|
||||||
|
|
||||||
interface IFeatureStrategySegmentProps {
|
interface IFeatureStrategySegmentProps {
|
||||||
segments: ISegment[];
|
segments: ISegment[];
|
||||||
@ -17,6 +19,7 @@ export const FeatureStrategySegment = ({
|
|||||||
segments: selectedSegments,
|
segments: selectedSegments,
|
||||||
setSegments: setSelectedSegments,
|
setSegments: setSelectedSegments,
|
||||||
}: IFeatureStrategySegmentProps) => {
|
}: IFeatureStrategySegmentProps) => {
|
||||||
|
const atSegmentsLimit = selectedSegments.length >= STRATEGY_SEGMENTS_LIMIT;
|
||||||
const { segments: allSegments } = useSegments();
|
const { segments: allSegments } = useSegments();
|
||||||
const styles = useStyles();
|
const styles = useStyles();
|
||||||
|
|
||||||
@ -45,11 +48,13 @@ export const FeatureStrategySegment = ({
|
|||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<h3 className={styles.title}>Segmentation</h3>
|
<h3 className={styles.title}>Segmentation</h3>
|
||||||
|
{atSegmentsLimit && <SegmentDocsStrategyWarning />}
|
||||||
<p>Add a predefined segment to constrain this feature toggle:</p>
|
<p>Add a predefined segment to constrain this feature toggle:</p>
|
||||||
<AutocompleteBox
|
<AutocompleteBox
|
||||||
label="Select segments"
|
label="Select segments"
|
||||||
options={autocompleteOptions}
|
options={autocompleteOptions}
|
||||||
onChange={onChange}
|
onChange={onChange}
|
||||||
|
disabled={atSegmentsLimit}
|
||||||
/>
|
/>
|
||||||
<FeatureStrategySegmentList
|
<FeatureStrategySegmentList
|
||||||
segments={selectedSegments}
|
segments={selectedSegments}
|
||||||
|
@ -5,6 +5,7 @@ import { Clear, VisibilityOff, Visibility } from '@material-ui/icons';
|
|||||||
import { useStyles } from './FeatureStrategySegmentChip.styles';
|
import { useStyles } from './FeatureStrategySegmentChip.styles';
|
||||||
import ConditionallyRender from 'component/common/ConditionallyRender';
|
import ConditionallyRender from 'component/common/ConditionallyRender';
|
||||||
import { constraintAccordionListId } from 'component/common/ConstraintAccordion/ConstraintAccordionList/ConstraintAccordionList';
|
import { constraintAccordionListId } from 'component/common/ConstraintAccordion/ConstraintAccordionList/ConstraintAccordionList';
|
||||||
|
import { Tooltip } from '@material-ui/core';
|
||||||
|
|
||||||
interface IFeatureStrategySegmentListProps {
|
interface IFeatureStrategySegmentListProps {
|
||||||
segment: ISegment;
|
segment: ISegment;
|
||||||
@ -39,21 +40,16 @@ export const FeatureStrategySegmentChip = ({
|
|||||||
const togglePreviewIcon = (
|
const togglePreviewIcon = (
|
||||||
<ConditionallyRender
|
<ConditionallyRender
|
||||||
condition={segment === preview}
|
condition={segment === preview}
|
||||||
show={
|
show={<VisibilityOff titleAccess="Hide" className={styles.icon} />}
|
||||||
<VisibilityOff
|
elseShow={<Visibility titleAccess="Show" className={styles.icon} />}
|
||||||
titleAccess="Hide preview"
|
|
||||||
className={styles.icon}
|
|
||||||
/>
|
|
||||||
}
|
|
||||||
elseShow={
|
|
||||||
<Visibility
|
|
||||||
titleAccess="Show preview"
|
|
||||||
className={styles.icon}
|
|
||||||
/>
|
|
||||||
}
|
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const previewIconTooltip =
|
||||||
|
segment === preview
|
||||||
|
? 'Hide segment constraints'
|
||||||
|
: 'Preview segment constraints';
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<span className={styles.chip}>
|
<span className={styles.chip}>
|
||||||
<Link
|
<Link
|
||||||
@ -63,6 +59,7 @@ export const FeatureStrategySegmentChip = ({
|
|||||||
>
|
>
|
||||||
{segment.name}
|
{segment.name}
|
||||||
</Link>
|
</Link>
|
||||||
|
<Tooltip title={previewIconTooltip}>
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
onClick={onTogglePreview}
|
onClick={onTogglePreview}
|
||||||
@ -72,9 +69,16 @@ export const FeatureStrategySegmentChip = ({
|
|||||||
>
|
>
|
||||||
{togglePreviewIcon}
|
{togglePreviewIcon}
|
||||||
</button>
|
</button>
|
||||||
<button type="button" onClick={onRemove} className={styles.button}>
|
</Tooltip>
|
||||||
|
<Tooltip title="Remove segment">
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
onClick={onRemove}
|
||||||
|
className={styles.button}
|
||||||
|
>
|
||||||
<Clear titleAccess="Remove" className={styles.icon} />
|
<Clear titleAccess="Remove" className={styles.icon} />
|
||||||
</button>
|
</button>
|
||||||
|
</Tooltip>
|
||||||
</span>
|
</span>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
@ -12,6 +12,9 @@ import { formatUnknownError } from 'utils/formatUnknownError';
|
|||||||
import { useSegmentForm } from '../hooks/useSegmentForm';
|
import { useSegmentForm } from '../hooks/useSegmentForm';
|
||||||
import { SegmentForm } from '../SegmentForm/SegmentForm';
|
import { SegmentForm } from '../SegmentForm/SegmentForm';
|
||||||
import { feedbackCESContext } from 'component/feedback/FeedbackCESContext/FeedbackCESContext';
|
import { feedbackCESContext } from 'component/feedback/FeedbackCESContext/FeedbackCESContext';
|
||||||
|
import { segmentsDocsLink } from 'component/segments/SegmentDocs/SegmentDocs';
|
||||||
|
import { useSegmentValuesCount } from 'component/segments/hooks/useSegmentValuesCount';
|
||||||
|
import { SEGMENT_VALUES_LIMIT } from 'utils/segmentLimits';
|
||||||
|
|
||||||
export const CreateSegment = () => {
|
export const CreateSegment = () => {
|
||||||
const { uiConfig } = useUiConfig();
|
const { uiConfig } = useUiConfig();
|
||||||
@ -34,6 +37,8 @@ export const CreateSegment = () => {
|
|||||||
} = useSegmentForm();
|
} = useSegmentForm();
|
||||||
|
|
||||||
const hasValidConstraints = useConstraintsValidation(constraints);
|
const hasValidConstraints = useConstraintsValidation(constraints);
|
||||||
|
const segmentValuesCount = useSegmentValuesCount(constraints);
|
||||||
|
const atSegmentValuesLimit = segmentValuesCount >= SEGMENT_VALUES_LIMIT;
|
||||||
|
|
||||||
const formatApiCode = () => {
|
const formatApiCode = () => {
|
||||||
return `curl --location --request POST '${
|
return `curl --location --request POST '${
|
||||||
@ -71,7 +76,7 @@ export const CreateSegment = () => {
|
|||||||
loading={loading}
|
loading={loading}
|
||||||
title="Create segment"
|
title="Create segment"
|
||||||
description={segmentsFormDescription}
|
description={segmentsFormDescription}
|
||||||
documentationLink={segmentsFormDocsLink}
|
documentationLink={segmentsDocsLink}
|
||||||
documentationLinkLabel="More about segments"
|
documentationLinkLabel="More about segments"
|
||||||
formatApiCode={formatApiCode}
|
formatApiCode={formatApiCode}
|
||||||
>
|
>
|
||||||
@ -90,7 +95,7 @@ export const CreateSegment = () => {
|
|||||||
<CreateButton
|
<CreateButton
|
||||||
name="segment"
|
name="segment"
|
||||||
permission={CREATE_SEGMENT}
|
permission={CREATE_SEGMENT}
|
||||||
disabled={!hasValidConstraints}
|
disabled={!hasValidConstraints || atSegmentValuesLimit}
|
||||||
/>
|
/>
|
||||||
</SegmentForm>
|
</SegmentForm>
|
||||||
</FormTemplate>
|
</FormTemplate>
|
||||||
@ -102,6 +107,3 @@ export const segmentsFormDescription = `
|
|||||||
A segment is a reusable collection of constraints.
|
A segment is a reusable collection of constraints.
|
||||||
You can create and apply a segment when configuring activation strategies for a feature toggle or at any time from the segments page in the navigation menu.
|
You can create and apply a segment when configuring activation strategies for a feature toggle or at any time from the segments page in the navigation menu.
|
||||||
`;
|
`;
|
||||||
|
|
||||||
// TODO(olav): Update link when the segments docs are ready.
|
|
||||||
export const segmentsFormDocsLink = 'https://docs.getunleash.io';
|
|
||||||
|
@ -12,11 +12,11 @@ import { useHistory } from 'react-router-dom';
|
|||||||
import { formatUnknownError } from 'utils/formatUnknownError';
|
import { formatUnknownError } from 'utils/formatUnknownError';
|
||||||
import { useSegmentForm } from '../hooks/useSegmentForm';
|
import { useSegmentForm } from '../hooks/useSegmentForm';
|
||||||
import { SegmentForm } from '../SegmentForm/SegmentForm';
|
import { SegmentForm } from '../SegmentForm/SegmentForm';
|
||||||
import {
|
import { segmentsFormDescription } from 'component/segments/CreateSegment/CreateSegment';
|
||||||
segmentsFormDocsLink,
|
|
||||||
segmentsFormDescription,
|
|
||||||
} from 'component/segments/CreateSegment/CreateSegment';
|
|
||||||
import { UpdateButton } from 'component/common/UpdateButton/UpdateButton';
|
import { UpdateButton } from 'component/common/UpdateButton/UpdateButton';
|
||||||
|
import { segmentsDocsLink } from 'component/segments/SegmentDocs/SegmentDocs';
|
||||||
|
import { useSegmentValuesCount } from 'component/segments/hooks/useSegmentValuesCount';
|
||||||
|
import { SEGMENT_VALUES_LIMIT } from 'utils/segmentLimits';
|
||||||
|
|
||||||
export const EditSegment = () => {
|
export const EditSegment = () => {
|
||||||
const segmentId = useRequiredPathParam('segmentId');
|
const segmentId = useRequiredPathParam('segmentId');
|
||||||
@ -44,6 +44,8 @@ export const EditSegment = () => {
|
|||||||
);
|
);
|
||||||
|
|
||||||
const hasValidConstraints = useConstraintsValidation(constraints);
|
const hasValidConstraints = useConstraintsValidation(constraints);
|
||||||
|
const segmentValuesCount = useSegmentValuesCount(constraints);
|
||||||
|
const atSegmentValuesLimit = segmentValuesCount >= SEGMENT_VALUES_LIMIT;
|
||||||
|
|
||||||
const formatApiCode = () => {
|
const formatApiCode = () => {
|
||||||
return `curl --location --request PUT '${
|
return `curl --location --request PUT '${
|
||||||
@ -77,7 +79,7 @@ export const EditSegment = () => {
|
|||||||
loading={loading}
|
loading={loading}
|
||||||
title="Edit segment"
|
title="Edit segment"
|
||||||
description={segmentsFormDescription}
|
description={segmentsFormDescription}
|
||||||
documentationLink={segmentsFormDocsLink}
|
documentationLink={segmentsDocsLink}
|
||||||
documentationLinkLabel="More about segments"
|
documentationLinkLabel="More about segments"
|
||||||
formatApiCode={formatApiCode}
|
formatApiCode={formatApiCode}
|
||||||
>
|
>
|
||||||
@ -95,7 +97,7 @@ export const EditSegment = () => {
|
|||||||
>
|
>
|
||||||
<UpdateButton
|
<UpdateButton
|
||||||
permission={UPDATE_SEGMENT}
|
permission={UPDATE_SEGMENT}
|
||||||
disabled={!hasValidConstraints}
|
disabled={!hasValidConstraints || atSegmentValuesLimit}
|
||||||
/>
|
/>
|
||||||
</SegmentForm>
|
</SegmentForm>
|
||||||
</FormTemplate>
|
</FormTemplate>
|
||||||
|
@ -0,0 +1,18 @@
|
|||||||
|
import { makeStyles } from '@material-ui/core/styles';
|
||||||
|
|
||||||
|
export const useStyles = makeStyles(theme => ({
|
||||||
|
paragraph: {
|
||||||
|
[theme.breakpoints.down('md')]: {
|
||||||
|
display: 'inline',
|
||||||
|
'&:after': {
|
||||||
|
content: '" "',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
[theme.breakpoints.up('md')]: {
|
||||||
|
display: 'block',
|
||||||
|
'& + &': {
|
||||||
|
marginTop: '0.25rem',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}));
|
89
frontend/src/component/segments/SegmentDocs/SegmentDocs.tsx
Normal file
89
frontend/src/component/segments/SegmentDocs/SegmentDocs.tsx
Normal file
@ -0,0 +1,89 @@
|
|||||||
|
import { Alert } from '@material-ui/lab';
|
||||||
|
import { useStyles } from 'component/segments/SegmentDocs/SegmentDocs.styles';
|
||||||
|
import {
|
||||||
|
STRATEGY_SEGMENTS_LIMIT,
|
||||||
|
SEGMENT_VALUES_LIMIT,
|
||||||
|
} from 'utils/segmentLimits';
|
||||||
|
|
||||||
|
export const SegmentDocsWarning = () => {
|
||||||
|
const styles = useStyles();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Alert severity="warning">
|
||||||
|
<p className={styles.paragraph}>
|
||||||
|
Segments is an experimental feature available to select users.
|
||||||
|
</p>
|
||||||
|
<p className={styles.paragraph}>
|
||||||
|
This feature is currently in development. Future versions may
|
||||||
|
require to update your SDKs.
|
||||||
|
</p>
|
||||||
|
<p className={styles.paragraph}>
|
||||||
|
<SegmentDocsLink />
|
||||||
|
</p>
|
||||||
|
</Alert>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const SegmentDocsValuesWarning = () => {
|
||||||
|
return (
|
||||||
|
<Alert severity="warning">
|
||||||
|
Segments is an experimental feature available to select users.
|
||||||
|
Currently, segments are limited to at most {SEGMENT_VALUES_LIMIT}{' '}
|
||||||
|
values. <SegmentLimitsLink />
|
||||||
|
</Alert>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const SegmentDocsValuesError = (props: { values: number }) => {
|
||||||
|
return (
|
||||||
|
<Alert severity="error">
|
||||||
|
Segments are limited to at most {SEGMENT_VALUES_LIMIT} values. This
|
||||||
|
segment currently has {props.values}{' '}
|
||||||
|
{props.values === 1 ? 'value' : 'values'}.
|
||||||
|
</Alert>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const SegmentDocsStrategyWarning = () => {
|
||||||
|
return (
|
||||||
|
<Alert severity="warning">
|
||||||
|
Strategies are limited to {STRATEGY_SEGMENTS_LIMIT} segments.{' '}
|
||||||
|
<SegmentLimitsLink />
|
||||||
|
</Alert>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const SegmentDocsLink = () => {
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<a
|
||||||
|
href={segmentsDocsLink}
|
||||||
|
target="_blank"
|
||||||
|
rel="noreferrer"
|
||||||
|
style={{ color: 'inherit' }}
|
||||||
|
>
|
||||||
|
Read more about segments in the documentation
|
||||||
|
</a>
|
||||||
|
.
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const SegmentLimitsLink = () => {
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
Please{' '}
|
||||||
|
<a
|
||||||
|
href="https://slack.unleash.run"
|
||||||
|
target="_blank"
|
||||||
|
rel="noreferrer"
|
||||||
|
style={{ color: 'inherit' }}
|
||||||
|
>
|
||||||
|
get in touch
|
||||||
|
</a>{' '}
|
||||||
|
if you would like this limit increased.
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const segmentsDocsLink = 'https://docs.getunleash.io/reference/segments';
|
@ -1,7 +1,12 @@
|
|||||||
import { makeStyles } from '@material-ui/core/styles';
|
import { makeStyles } from '@material-ui/core/styles';
|
||||||
|
|
||||||
export const useStyles = makeStyles(theme => ({
|
export const useStyles = makeStyles(theme => ({
|
||||||
container: {},
|
warning: {
|
||||||
|
marginBottom: '1.5rem',
|
||||||
|
},
|
||||||
|
error: {
|
||||||
|
marginTop: '1.5rem',
|
||||||
|
},
|
||||||
form: {
|
form: {
|
||||||
display: 'flex',
|
display: 'flex',
|
||||||
flexDirection: 'column',
|
flexDirection: 'column',
|
||||||
@ -21,6 +26,9 @@ export const useStyles = makeStyles(theme => ({
|
|||||||
borderTop: `1px solid ${theme.palette.grey[300]}`,
|
borderTop: `1px solid ${theme.palette.grey[300]}`,
|
||||||
paddingTop: 15,
|
paddingTop: 15,
|
||||||
},
|
},
|
||||||
|
errorsContainer: {
|
||||||
|
marginTop: '1rem',
|
||||||
|
},
|
||||||
cancelButton: {
|
cancelButton: {
|
||||||
marginLeft: '1.5rem',
|
marginLeft: '1.5rem',
|
||||||
color: theme.palette.primary.light,
|
color: theme.palette.primary.light,
|
||||||
|
@ -19,6 +19,12 @@ import {
|
|||||||
AutocompleteBox,
|
AutocompleteBox,
|
||||||
IAutocompleteBoxOption,
|
IAutocompleteBoxOption,
|
||||||
} from 'component/common/AutocompleteBox/AutocompleteBox';
|
} from 'component/common/AutocompleteBox/AutocompleteBox';
|
||||||
|
import {
|
||||||
|
SegmentDocsValuesWarning,
|
||||||
|
SegmentDocsValuesError,
|
||||||
|
} from 'component/segments/SegmentDocs/SegmentDocs';
|
||||||
|
import { useSegmentValuesCount } from 'component/segments/hooks/useSegmentValuesCount';
|
||||||
|
import { SEGMENT_VALUES_LIMIT } from 'utils/segmentLimits';
|
||||||
|
|
||||||
interface ISegmentFormPartTwoProps {
|
interface ISegmentFormPartTwoProps {
|
||||||
constraints: IConstraint[];
|
constraints: IConstraint[];
|
||||||
@ -37,6 +43,8 @@ export const SegmentFormStepTwo: React.FC<ISegmentFormPartTwoProps> = ({
|
|||||||
const styles = useStyles();
|
const styles = useStyles();
|
||||||
const { context = [] } = useUnleashContext();
|
const { context = [] } = useUnleashContext();
|
||||||
const [open, setOpen] = useState(false);
|
const [open, setOpen] = useState(false);
|
||||||
|
const segmentValuesCount = useSegmentValuesCount(constraints);
|
||||||
|
const overSegmentValuesLimit = segmentValuesCount > SEGMENT_VALUES_LIMIT;
|
||||||
|
|
||||||
const autocompleteOptions = context.map(c => ({
|
const autocompleteOptions = context.map(c => ({
|
||||||
value: c.name,
|
value: c.name,
|
||||||
@ -48,8 +56,11 @@ export const SegmentFormStepTwo: React.FC<ISegmentFormPartTwoProps> = ({
|
|||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
<>
|
||||||
<div className={styles.form}>
|
<div className={styles.form}>
|
||||||
<div className={styles.container}>
|
<div className={styles.warning}>
|
||||||
|
<SegmentDocsValuesWarning />
|
||||||
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<p className={styles.inputDescription}>
|
<p className={styles.inputDescription}>
|
||||||
Select the context fields you want to include in the
|
Select the context fields you want to include in the
|
||||||
@ -87,8 +98,14 @@ export const SegmentFormStepTwo: React.FC<ISegmentFormPartTwoProps> = ({
|
|||||||
>
|
>
|
||||||
Add context field
|
Add context field
|
||||||
</PermissionButton>
|
</PermissionButton>
|
||||||
|
{overSegmentValuesLimit && (
|
||||||
|
<div className={styles.error}>
|
||||||
|
<SegmentDocsValuesError
|
||||||
|
values={segmentValuesCount}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<ConditionallyRender
|
<ConditionallyRender
|
||||||
condition={constraints.length === 0}
|
condition={constraints.length === 0}
|
||||||
show={
|
show={
|
||||||
@ -109,7 +126,6 @@ export const SegmentFormStepTwo: React.FC<ISegmentFormPartTwoProps> = ({
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className={styles.buttonContainer}>
|
<div className={styles.buttonContainer}>
|
||||||
<Button
|
<Button
|
||||||
type="button"
|
type="button"
|
||||||
@ -129,6 +145,6 @@ export const SegmentFormStepTwo: React.FC<ISegmentFormPartTwoProps> = ({
|
|||||||
Cancel
|
Cancel
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
@ -1,6 +1,9 @@
|
|||||||
import { makeStyles } from '@material-ui/core/styles';
|
import { makeStyles } from '@material-ui/core/styles';
|
||||||
|
|
||||||
export const useStyles = makeStyles(theme => ({
|
export const useStyles = makeStyles(theme => ({
|
||||||
|
docs: {
|
||||||
|
marginBottom: '2rem',
|
||||||
|
},
|
||||||
empty: {
|
empty: {
|
||||||
display: 'flex',
|
display: 'flex',
|
||||||
flexDirection: 'column',
|
flexDirection: 'column',
|
||||||
|
@ -27,6 +27,7 @@ import HeaderTitle from 'component/common/HeaderTitle';
|
|||||||
import PageContent from 'component/common/PageContent';
|
import PageContent from 'component/common/PageContent';
|
||||||
import PermissionButton from 'component/common/PermissionButton/PermissionButton';
|
import PermissionButton from 'component/common/PermissionButton/PermissionButton';
|
||||||
import { SegmentDelete } from '../SegmentDelete/SegmentDelete';
|
import { SegmentDelete } from '../SegmentDelete/SegmentDelete';
|
||||||
|
import { SegmentDocsWarning } from 'component/segments/SegmentDocs/SegmentDocs';
|
||||||
|
|
||||||
export const SegmentsList = () => {
|
export const SegmentsList = () => {
|
||||||
const history = useHistory();
|
const history = useHistory();
|
||||||
@ -107,6 +108,9 @@ export const SegmentsList = () => {
|
|||||||
/>
|
/>
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
|
<div className={styles.docs}>
|
||||||
|
<SegmentDocsWarning />
|
||||||
|
</div>
|
||||||
<Table>
|
<Table>
|
||||||
<TableHead>
|
<TableHead>
|
||||||
<TableRow className={styles.tableRow}>
|
<TableRow className={styles.tableRow}>
|
||||||
|
@ -0,0 +1,10 @@
|
|||||||
|
import { IConstraint } from 'interfaces/strategy';
|
||||||
|
import { useMemo } from 'react';
|
||||||
|
|
||||||
|
export const useSegmentValuesCount = (constraints: IConstraint[]): number => {
|
||||||
|
return useMemo(() => {
|
||||||
|
return constraints
|
||||||
|
.map(constraint => constraint.values)
|
||||||
|
.reduce((acc, values) => acc + (values?.length ?? 0), 0);
|
||||||
|
}, [constraints]);
|
||||||
|
};
|
2
frontend/src/utils/segmentLimits.ts
Normal file
2
frontend/src/utils/segmentLimits.ts
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
export const SEGMENT_VALUES_LIMIT = 100;
|
||||||
|
export const STRATEGY_SEGMENTS_LIMIT = 5;
|
Loading…
Reference in New Issue
Block a user