mirror of
				https://github.com/Unleash/unleash.git
				synced 2025-10-27 11:02:16 +01: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, | ||||
|         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', | ||||
|  | ||||
| @ -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 ( | ||||
|         <div className={styles.container}> | ||||
|             <div className={styles.icon} aria-hidden> | ||||
|             <div | ||||
|                 className={classNames( | ||||
|                     styles.icon, | ||||
|                     disabled && styles.iconDisabled | ||||
|                 )} | ||||
|                 aria-hidden | ||||
|             > | ||||
|                 <Search /> | ||||
|             </div> | ||||
|             <Autocomplete | ||||
| @ -41,6 +50,7 @@ export const AutocompleteBox = ({ | ||||
|                 onChange={(event, value) => onChange(value || [])} | ||||
|                 renderInput={renderInput} | ||||
|                 getOptionLabel={value => value.label} | ||||
|                 disabled={disabled} | ||||
|                 multiple | ||||
|             /> | ||||
|         </div> | ||||
|  | ||||
| @ -10,8 +10,6 @@ export const useStyles = makeStyles(theme => ({ | ||||
|         color: 'inherit', | ||||
|     }, | ||||
|     envName: { | ||||
|         position: 'relative', | ||||
|         top: '6px', | ||||
|         fontWeight: 'bold', | ||||
|     }, | ||||
| })); | ||||
|  | ||||
| @ -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 ( | ||||
|         <> | ||||
|             <h3 className={styles.title}>Segmentation</h3> | ||||
|             {atSegmentsLimit && <SegmentDocsStrategyWarning />} | ||||
|             <p>Add a predefined segment to constrain this feature toggle:</p> | ||||
|             <AutocompleteBox | ||||
|                 label="Select segments" | ||||
|                 options={autocompleteOptions} | ||||
|                 onChange={onChange} | ||||
|                 disabled={atSegmentsLimit} | ||||
|             /> | ||||
|             <FeatureStrategySegmentList | ||||
|                 segments={selectedSegments} | ||||
|  | ||||
| @ -5,6 +5,7 @@ import { Clear, VisibilityOff, Visibility } from '@material-ui/icons'; | ||||
| import { useStyles } from './FeatureStrategySegmentChip.styles'; | ||||
| import ConditionallyRender from 'component/common/ConditionallyRender'; | ||||
| import { constraintAccordionListId } from 'component/common/ConstraintAccordion/ConstraintAccordionList/ConstraintAccordionList'; | ||||
| import { Tooltip } from '@material-ui/core'; | ||||
| 
 | ||||
| interface IFeatureStrategySegmentListProps { | ||||
|     segment: ISegment; | ||||
| @ -39,21 +40,16 @@ export const FeatureStrategySegmentChip = ({ | ||||
|     const togglePreviewIcon = ( | ||||
|         <ConditionallyRender | ||||
|             condition={segment === preview} | ||||
|             show={ | ||||
|                 <VisibilityOff | ||||
|                     titleAccess="Hide preview" | ||||
|                     className={styles.icon} | ||||
|                 /> | ||||
|             } | ||||
|             elseShow={ | ||||
|                 <Visibility | ||||
|                     titleAccess="Show preview" | ||||
|                     className={styles.icon} | ||||
|                 /> | ||||
|             } | ||||
|             show={<VisibilityOff titleAccess="Hide" className={styles.icon} />} | ||||
|             elseShow={<Visibility titleAccess="Show" className={styles.icon} />} | ||||
|         /> | ||||
|     ); | ||||
| 
 | ||||
|     const previewIconTooltip = | ||||
|         segment === preview | ||||
|             ? 'Hide segment constraints' | ||||
|             : 'Preview segment constraints'; | ||||
| 
 | ||||
|     return ( | ||||
|         <span className={styles.chip}> | ||||
|             <Link | ||||
| @ -63,18 +59,26 @@ export const FeatureStrategySegmentChip = ({ | ||||
|             > | ||||
|                 {segment.name} | ||||
|             </Link> | ||||
|             <button | ||||
|                 type="button" | ||||
|                 onClick={onTogglePreview} | ||||
|                 className={styles.button} | ||||
|                 aria-expanded={segment === preview} | ||||
|                 aria-controls={constraintAccordionListId} | ||||
|             > | ||||
|                 {togglePreviewIcon} | ||||
|             </button> | ||||
|             <button type="button" onClick={onRemove} className={styles.button}> | ||||
|                 <Clear titleAccess="Remove" className={styles.icon} /> | ||||
|             </button> | ||||
|             <Tooltip title={previewIconTooltip}> | ||||
|                 <button | ||||
|                     type="button" | ||||
|                     onClick={onTogglePreview} | ||||
|                     className={styles.button} | ||||
|                     aria-expanded={segment === preview} | ||||
|                     aria-controls={constraintAccordionListId} | ||||
|                 > | ||||
|                     {togglePreviewIcon} | ||||
|                 </button> | ||||
|             </Tooltip> | ||||
|             <Tooltip title="Remove segment"> | ||||
|                 <button | ||||
|                     type="button" | ||||
|                     onClick={onRemove} | ||||
|                     className={styles.button} | ||||
|                 > | ||||
|                     <Clear titleAccess="Remove" className={styles.icon} /> | ||||
|                 </button> | ||||
|             </Tooltip> | ||||
|         </span> | ||||
|     ); | ||||
| }; | ||||
|  | ||||
| @ -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 = () => { | ||||
|                 <CreateButton | ||||
|                     name="segment" | ||||
|                     permission={CREATE_SEGMENT} | ||||
|                     disabled={!hasValidConstraints} | ||||
|                     disabled={!hasValidConstraints || atSegmentValuesLimit} | ||||
|                 /> | ||||
|             </SegmentForm> | ||||
|         </FormTemplate> | ||||
| @ -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'; | ||||
|  | ||||
| @ -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 = () => { | ||||
|             > | ||||
|                 <UpdateButton | ||||
|                     permission={UPDATE_SEGMENT} | ||||
|                     disabled={!hasValidConstraints} | ||||
|                     disabled={!hasValidConstraints || atSegmentValuesLimit} | ||||
|                 /> | ||||
|             </SegmentForm> | ||||
|         </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'; | ||||
| 
 | ||||
| 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, | ||||
|  | ||||
| @ -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<ISegmentFormPartTwoProps> = ({ | ||||
|     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<ISegmentFormPartTwoProps> = ({ | ||||
|     }; | ||||
| 
 | ||||
|     return ( | ||||
|         <div className={styles.form}> | ||||
|             <div className={styles.container}> | ||||
|         <> | ||||
|             <div className={styles.form}> | ||||
|                 <div className={styles.warning}> | ||||
|                     <SegmentDocsValuesWarning /> | ||||
|                 </div> | ||||
|                 <div> | ||||
|                     <p className={styles.inputDescription}> | ||||
|                         Select the context fields you want to include in the | ||||
| @ -87,8 +98,14 @@ export const SegmentFormStepTwo: React.FC<ISegmentFormPartTwoProps> = ({ | ||||
|                     > | ||||
|                         Add context field | ||||
|                     </PermissionButton> | ||||
|                     {overSegmentValuesLimit && ( | ||||
|                         <div className={styles.error}> | ||||
|                             <SegmentDocsValuesError | ||||
|                                 values={segmentValuesCount} | ||||
|                             /> | ||||
|                         </div> | ||||
|                     )} | ||||
|                 </div> | ||||
| 
 | ||||
|                 <ConditionallyRender | ||||
|                     condition={constraints.length === 0} | ||||
|                     show={ | ||||
| @ -109,7 +126,6 @@ export const SegmentFormStepTwo: React.FC<ISegmentFormPartTwoProps> = ({ | ||||
|                     /> | ||||
|                 </div> | ||||
|             </div> | ||||
| 
 | ||||
|             <div className={styles.buttonContainer}> | ||||
|                 <Button | ||||
|                     type="button" | ||||
| @ -129,6 +145,6 @@ export const SegmentFormStepTwo: React.FC<ISegmentFormPartTwoProps> = ({ | ||||
|                     Cancel | ||||
|                 </Button> | ||||
|             </div> | ||||
|         </div> | ||||
|         </> | ||||
|     ); | ||||
| }; | ||||
|  | ||||
| @ -1,6 +1,9 @@ | ||||
| import { makeStyles } from '@material-ui/core/styles'; | ||||
| 
 | ||||
| export const useStyles = makeStyles(theme => ({ | ||||
|     docs: { | ||||
|         marginBottom: '2rem', | ||||
|     }, | ||||
|     empty: { | ||||
|         display: 'flex', | ||||
|         flexDirection: 'column', | ||||
|  | ||||
| @ -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 = () => { | ||||
|                 /> | ||||
|             } | ||||
|         > | ||||
|             <div className={styles.docs}> | ||||
|                 <SegmentDocsWarning /> | ||||
|             </div> | ||||
|             <Table> | ||||
|                 <TableHead> | ||||
|                     <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