diff --git a/frontend/src/component/common/AutocompleteBox/AutocompleteBox.styles.ts b/frontend/src/component/common/AutocompleteBox/AutocompleteBox.styles.ts
index 48a20e5f32..235b26f8eb 100644
--- a/frontend/src/component/common/AutocompleteBox/AutocompleteBox.styles.ts
+++ b/frontend/src/component/common/AutocompleteBox/AutocompleteBox.styles.ts
@@ -18,12 +18,17 @@ export const useStyles = makeStyles(theme => ({
borderBottomLeftRadius: 50,
color: '#fff',
},
+ iconDisabled: {
+ background: theme.palette.primary.light,
+ },
autocomplete: {
flex: 1,
},
inputRoot: {
borderTopLeftRadius: 0,
borderBottomLeftRadius: 0,
+ borderTopRightRadius: 50,
+ borderBottomRightRadius: 50,
'& fieldset': {
borderColor: theme.palette,
borderLeftColor: 'transparent',
diff --git a/frontend/src/component/common/AutocompleteBox/AutocompleteBox.tsx b/frontend/src/component/common/AutocompleteBox/AutocompleteBox.tsx
index 370dbbdf2f..9fb5d8be03 100644
--- a/frontend/src/component/common/AutocompleteBox/AutocompleteBox.tsx
+++ b/frontend/src/component/common/AutocompleteBox/AutocompleteBox.tsx
@@ -2,12 +2,14 @@ import { useStyles } from 'component/common/AutocompleteBox/AutocompleteBox.styl
import { Search, ArrowDropDown } from '@material-ui/icons';
import { Autocomplete, AutocompleteRenderInputParams } from '@material-ui/lab';
import { TextField } from '@material-ui/core';
+import classNames from 'classnames';
interface IAutocompleteBoxProps {
label: string;
options: IAutocompleteBoxOption[];
value?: IAutocompleteBoxOption[];
onChange: (value: IAutocompleteBoxOption[]) => void;
+ disabled?: boolean;
}
export interface IAutocompleteBoxOption {
@@ -20,6 +22,7 @@ export const AutocompleteBox = ({
options,
value = [],
onChange,
+ disabled,
}: IAutocompleteBoxProps) => {
const styles = useStyles();
@@ -29,7 +32,13 @@ export const AutocompleteBox = ({
return (
-
+
onChange(value || [])}
renderInput={renderInput}
getOptionLabel={value => value.label}
+ disabled={disabled}
multiple
/>
diff --git a/frontend/src/component/feature/FeatureStrategy/FeatureStrategyEmpty/FeatureStrategyEmpty.styles.ts b/frontend/src/component/feature/FeatureStrategy/FeatureStrategyEmpty/FeatureStrategyEmpty.styles.ts
index 7ceba774c4..91348b66e9 100644
--- a/frontend/src/component/feature/FeatureStrategy/FeatureStrategyEmpty/FeatureStrategyEmpty.styles.ts
+++ b/frontend/src/component/feature/FeatureStrategy/FeatureStrategyEmpty/FeatureStrategyEmpty.styles.ts
@@ -10,8 +10,6 @@ export const useStyles = makeStyles(theme => ({
color: 'inherit',
},
envName: {
- position: 'relative',
- top: '6px',
fontWeight: 'bold',
},
}));
diff --git a/frontend/src/component/feature/FeatureStrategy/FeatureStrategySegment/FeatureStrategySegment.tsx b/frontend/src/component/feature/FeatureStrategy/FeatureStrategySegment/FeatureStrategySegment.tsx
index 8dce966001..7f35a70305 100644
--- a/frontend/src/component/feature/FeatureStrategy/FeatureStrategySegment/FeatureStrategySegment.tsx
+++ b/frontend/src/component/feature/FeatureStrategy/FeatureStrategySegment/FeatureStrategySegment.tsx
@@ -7,6 +7,8 @@ import {
} from 'component/common/AutocompleteBox/AutocompleteBox';
import { FeatureStrategySegmentList } from 'component/feature/FeatureStrategy/FeatureStrategySegment/FeatureStrategySegmentList';
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 {
segments: ISegment[];
@@ -17,6 +19,7 @@ export const FeatureStrategySegment = ({
segments: selectedSegments,
setSegments: setSelectedSegments,
}: IFeatureStrategySegmentProps) => {
+ const atSegmentsLimit = selectedSegments.length >= STRATEGY_SEGMENTS_LIMIT;
const { segments: allSegments } = useSegments();
const styles = useStyles();
@@ -45,11 +48,13 @@ export const FeatureStrategySegment = ({
return (
<>
Segmentation
+ {atSegmentsLimit &&
}
Add a predefined segment to constrain this feature toggle:
- }
- elseShow={
-
- }
+ show={
}
+ elseShow={
}
/>
);
+ const previewIconTooltip =
+ segment === preview
+ ? 'Hide segment constraints'
+ : 'Preview segment constraints';
+
return (
{segment.name}
-
- {togglePreviewIcon}
-
-
-
-
+
+
+ {togglePreviewIcon}
+
+
+
+
+
+
+
);
};
diff --git a/frontend/src/component/segments/CreateSegment/CreateSegment.tsx b/frontend/src/component/segments/CreateSegment/CreateSegment.tsx
index 80e4755366..521c704ec6 100644
--- a/frontend/src/component/segments/CreateSegment/CreateSegment.tsx
+++ b/frontend/src/component/segments/CreateSegment/CreateSegment.tsx
@@ -12,6 +12,9 @@ import { formatUnknownError } from 'utils/formatUnknownError';
import { useSegmentForm } from '../hooks/useSegmentForm';
import { SegmentForm } from '../SegmentForm/SegmentForm';
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 = () => {
const { uiConfig } = useUiConfig();
@@ -34,6 +37,8 @@ export const CreateSegment = () => {
} = useSegmentForm();
const hasValidConstraints = useConstraintsValidation(constraints);
+ const segmentValuesCount = useSegmentValuesCount(constraints);
+ const atSegmentValuesLimit = segmentValuesCount >= SEGMENT_VALUES_LIMIT;
const formatApiCode = () => {
return `curl --location --request POST '${
@@ -71,7 +76,7 @@ export const CreateSegment = () => {
loading={loading}
title="Create segment"
description={segmentsFormDescription}
- documentationLink={segmentsFormDocsLink}
+ documentationLink={segmentsDocsLink}
documentationLinkLabel="More about segments"
formatApiCode={formatApiCode}
>
@@ -90,7 +95,7 @@ export const CreateSegment = () => {
@@ -102,6 +107,3 @@ export const segmentsFormDescription = `
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.
`;
-
-// TODO(olav): Update link when the segments docs are ready.
-export const segmentsFormDocsLink = 'https://docs.getunleash.io';
diff --git a/frontend/src/component/segments/EditSegment/EditSegment.tsx b/frontend/src/component/segments/EditSegment/EditSegment.tsx
index 62c961bffd..7d0b1dd615 100644
--- a/frontend/src/component/segments/EditSegment/EditSegment.tsx
+++ b/frontend/src/component/segments/EditSegment/EditSegment.tsx
@@ -12,11 +12,11 @@ import { useHistory } from 'react-router-dom';
import { formatUnknownError } from 'utils/formatUnknownError';
import { useSegmentForm } from '../hooks/useSegmentForm';
import { SegmentForm } from '../SegmentForm/SegmentForm';
-import {
- segmentsFormDocsLink,
- segmentsFormDescription,
-} from 'component/segments/CreateSegment/CreateSegment';
+import { segmentsFormDescription } from 'component/segments/CreateSegment/CreateSegment';
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 = () => {
const segmentId = useRequiredPathParam('segmentId');
@@ -44,6 +44,8 @@ export const EditSegment = () => {
);
const hasValidConstraints = useConstraintsValidation(constraints);
+ const segmentValuesCount = useSegmentValuesCount(constraints);
+ const atSegmentValuesLimit = segmentValuesCount >= SEGMENT_VALUES_LIMIT;
const formatApiCode = () => {
return `curl --location --request PUT '${
@@ -77,7 +79,7 @@ export const EditSegment = () => {
loading={loading}
title="Edit segment"
description={segmentsFormDescription}
- documentationLink={segmentsFormDocsLink}
+ documentationLink={segmentsDocsLink}
documentationLinkLabel="More about segments"
formatApiCode={formatApiCode}
>
@@ -95,7 +97,7 @@ export const EditSegment = () => {
>
diff --git a/frontend/src/component/segments/SegmentDocs/SegmentDocs.styles.ts b/frontend/src/component/segments/SegmentDocs/SegmentDocs.styles.ts
new file mode 100644
index 0000000000..931eee86b6
--- /dev/null
+++ b/frontend/src/component/segments/SegmentDocs/SegmentDocs.styles.ts
@@ -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',
+ },
+ },
+ },
+}));
diff --git a/frontend/src/component/segments/SegmentDocs/SegmentDocs.tsx b/frontend/src/component/segments/SegmentDocs/SegmentDocs.tsx
new file mode 100644
index 0000000000..de281e000d
--- /dev/null
+++ b/frontend/src/component/segments/SegmentDocs/SegmentDocs.tsx
@@ -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 (
+
+
+ Segments is an experimental feature available to select users.
+
+
+ This feature is currently in development. Future versions may
+ require to update your SDKs.
+
+
+
+
+
+ );
+};
+
+export const SegmentDocsValuesWarning = () => {
+ return (
+
+ Segments is an experimental feature available to select users.
+ Currently, segments are limited to at most {SEGMENT_VALUES_LIMIT}{' '}
+ values.
+
+ );
+};
+
+export const SegmentDocsValuesError = (props: { values: number }) => {
+ return (
+
+ Segments are limited to at most {SEGMENT_VALUES_LIMIT} values. This
+ segment currently has {props.values}{' '}
+ {props.values === 1 ? 'value' : 'values'}.
+
+ );
+};
+
+export const SegmentDocsStrategyWarning = () => {
+ return (
+
+ Strategies are limited to {STRATEGY_SEGMENTS_LIMIT} segments.{' '}
+
+
+ );
+};
+
+const SegmentDocsLink = () => {
+ return (
+ <>
+
+ Read more about segments in the documentation
+
+ .
+ >
+ );
+};
+
+const SegmentLimitsLink = () => {
+ return (
+ <>
+ Please{' '}
+
+ get in touch
+ {' '}
+ if you would like this limit increased.
+ >
+ );
+};
+
+export const segmentsDocsLink = 'https://docs.getunleash.io/reference/segments';
diff --git a/frontend/src/component/segments/SegmentFormStepTwo/SegmentFormStepTwo.styles.ts b/frontend/src/component/segments/SegmentFormStepTwo/SegmentFormStepTwo.styles.ts
index dfbeaf045b..5bd19d7e9a 100644
--- a/frontend/src/component/segments/SegmentFormStepTwo/SegmentFormStepTwo.styles.ts
+++ b/frontend/src/component/segments/SegmentFormStepTwo/SegmentFormStepTwo.styles.ts
@@ -1,7 +1,12 @@
import { makeStyles } from '@material-ui/core/styles';
export const useStyles = makeStyles(theme => ({
- container: {},
+ warning: {
+ marginBottom: '1.5rem',
+ },
+ error: {
+ marginTop: '1.5rem',
+ },
form: {
display: 'flex',
flexDirection: 'column',
@@ -21,6 +26,9 @@ export const useStyles = makeStyles(theme => ({
borderTop: `1px solid ${theme.palette.grey[300]}`,
paddingTop: 15,
},
+ errorsContainer: {
+ marginTop: '1rem',
+ },
cancelButton: {
marginLeft: '1.5rem',
color: theme.palette.primary.light,
diff --git a/frontend/src/component/segments/SegmentFormStepTwo/SegmentFormStepTwo.tsx b/frontend/src/component/segments/SegmentFormStepTwo/SegmentFormStepTwo.tsx
index 54c9d86cb9..90411120d8 100644
--- a/frontend/src/component/segments/SegmentFormStepTwo/SegmentFormStepTwo.tsx
+++ b/frontend/src/component/segments/SegmentFormStepTwo/SegmentFormStepTwo.tsx
@@ -19,6 +19,12 @@ import {
AutocompleteBox,
IAutocompleteBoxOption,
} 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 {
constraints: IConstraint[];
@@ -37,6 +43,8 @@ export const SegmentFormStepTwo: React.FC
= ({
const styles = useStyles();
const { context = [] } = useUnleashContext();
const [open, setOpen] = useState(false);
+ const segmentValuesCount = useSegmentValuesCount(constraints);
+ const overSegmentValuesLimit = segmentValuesCount > SEGMENT_VALUES_LIMIT;
const autocompleteOptions = context.map(c => ({
value: c.name,
@@ -48,8 +56,11 @@ export const SegmentFormStepTwo: React.FC = ({
};
return (
-
-
+ <>
+
+
+
+
Select the context fields you want to include in the
@@ -87,8 +98,14 @@ export const SegmentFormStepTwo: React.FC = ({
>
Add context field
+ {overSegmentValuesLimit && (
+
+
+
+ )}
-
= ({
/>
-
= ({
Cancel
-
+ >
);
};
diff --git a/frontend/src/component/segments/SegmentList/SegmentList.styles.ts b/frontend/src/component/segments/SegmentList/SegmentList.styles.ts
index 1788c1678d..f2d69b45bb 100644
--- a/frontend/src/component/segments/SegmentList/SegmentList.styles.ts
+++ b/frontend/src/component/segments/SegmentList/SegmentList.styles.ts
@@ -1,6 +1,9 @@
import { makeStyles } from '@material-ui/core/styles';
export const useStyles = makeStyles(theme => ({
+ docs: {
+ marginBottom: '2rem',
+ },
empty: {
display: 'flex',
flexDirection: 'column',
diff --git a/frontend/src/component/segments/SegmentList/SegmentList.tsx b/frontend/src/component/segments/SegmentList/SegmentList.tsx
index 291703a195..2f3e763c91 100644
--- a/frontend/src/component/segments/SegmentList/SegmentList.tsx
+++ b/frontend/src/component/segments/SegmentList/SegmentList.tsx
@@ -27,6 +27,7 @@ import HeaderTitle from 'component/common/HeaderTitle';
import PageContent from 'component/common/PageContent';
import PermissionButton from 'component/common/PermissionButton/PermissionButton';
import { SegmentDelete } from '../SegmentDelete/SegmentDelete';
+import { SegmentDocsWarning } from 'component/segments/SegmentDocs/SegmentDocs';
export const SegmentsList = () => {
const history = useHistory();
@@ -107,6 +108,9 @@ export const SegmentsList = () => {
/>
}
>
+
+
+
diff --git a/frontend/src/component/segments/hooks/useSegmentValuesCount.ts b/frontend/src/component/segments/hooks/useSegmentValuesCount.ts
new file mode 100644
index 0000000000..27cfb5ddd4
--- /dev/null
+++ b/frontend/src/component/segments/hooks/useSegmentValuesCount.ts
@@ -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]);
+};
diff --git a/frontend/src/utils/segmentLimits.ts b/frontend/src/utils/segmentLimits.ts
new file mode 100644
index 0000000000..9837119c2a
--- /dev/null
+++ b/frontend/src/utils/segmentLimits.ts
@@ -0,0 +1,2 @@
+export const SEGMENT_VALUES_LIMIT = 100;
+export const STRATEGY_SEGMENTS_LIMIT = 5;