Skip to content

Commit a148b36

Browse files
committed
feat: add multi-select on envs and refactor layout
1 parent 7474ba9 commit a148b36

File tree

8 files changed

+152
-130
lines changed

8 files changed

+152
-130
lines changed

frontend/web/components/base/select/MultiSelect.tsx

Lines changed: 13 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import classNames from 'classnames'
12
import React from 'react'
23
import { MultiValueProps } from 'react-select/lib/components/MultiValue'
34

@@ -36,6 +37,7 @@ const CustomMultiValue = ({
3637
padding: '2px 6px',
3738
textOverflow: 'ellipsis',
3839
whiteSpace: 'nowrap',
40+
maxHeight: '24px'
3941
}}
4042
>
4143
<span
@@ -104,7 +106,10 @@ const MultiSelect: React.FC<MultiSelectProps> = ({
104106
size = 'default',
105107
}) => {
106108
return (
107-
<div className={`${className} d-flex flex-column gap-2`}>
109+
<div className={classNames(
110+
className,
111+
label ? `d-flex flex-column gap-2` : ''
112+
)}>
108113
{label && <label>{label}</label>}
109114
<Select
110115
isMulti
@@ -122,38 +127,25 @@ const MultiSelect: React.FC<MultiSelectProps> = ({
122127
MultiValue: (props: MultiValueProps<MultiSelectOption>) => (
123128
<CustomMultiValue
124129
{...props}
130+
className={classNames(props.className, 'h-[24px]')}
125131
color={colorMap?.get(props.data.value) || '#5D6D7E'}
126132
/>
127133
),
128-
Option: colorMap
129-
? (props: any) => (
130-
<CustomOption
131-
{...props}
132-
color={colorMap.get(props.data.value)}
133-
/>
134-
)
135-
: undefined,
134+
...(colorMap ? {Option: (props: any) => <CustomOption
135+
{...props}
136+
color={colorMap.get(props.data.value)}
137+
/>} : {}),
136138
}}
137139
value={selectedValues.map((value) => ({
138140
label: options.find((opt) => opt.value === value)?.label || value,
139141
value,
140142
}))}
141143
options={options}
142-
className='react-select react-select__extensible cursor-pointer'
144+
className='react-select react-select__extensible w-100'
143145
styles={{
144-
container: (base: any) => ({
145-
...base,
146-
cursor: 'pointer',
147-
maxWidth: '100%',
148-
minWidth: '300px',
149-
width: 'fit-content',
150-
}),
151146
control: (base: any) => ({
152147
...base,
153-
height: 'auto',
154-
minHeight: '44px',
155-
minWidth: '300px',
156-
width: 'fit-content',
148+
cursor: 'pointer',
157149
}),
158150
multiValue: (base: any) => ({
159151
...base,

frontend/web/components/modals/CreateSegment.tsx

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -498,7 +498,7 @@ const CreateSegment: FC<CreateSegmentType> = ({
498498
</Row>
499499
}
500500
>
501-
<div className='my-4 col-lg-8 mx-auto'>
501+
<div className='my-4'>
502502
<CreateSegmentRulesTabForm
503503
is4Eyes={is4Eyes}
504504
onCreateChangeRequest={onCreateChangeRequest}
@@ -526,7 +526,7 @@ const CreateSegment: FC<CreateSegmentType> = ({
526526
</div>
527527
</TabItem>
528528
<TabItem tabLabel='Features'>
529-
<div className='my-4 col-lg-8 mx-auto'>
529+
<div className='my-4'>
530530
<AssociatedSegmentOverrides
531531
onUnsavedChange={() => {
532532
setValueChanged(true)
@@ -539,7 +539,7 @@ const CreateSegment: FC<CreateSegmentType> = ({
539539
</div>
540540
</TabItem>
541541
<TabItem tabLabel='Users'>
542-
<div className='my-4 col-lg-8 mx-auto'>
542+
<div className='my-4'>
543543
<CreateSegmentUsersTabContent
544544
projectId={projectId}
545545
environmentId={environmentId}
@@ -566,7 +566,7 @@ const CreateSegment: FC<CreateSegmentType> = ({
566566
</Row>
567567
}
568568
>
569-
<div className='my-4 col-lg-8 mx-auto'>{MetadataTab}</div>
569+
<div className='my-4 col-lg-8'>{MetadataTab}</div>
570570
</TabItem>
571571
)}
572572
</Tabs>

frontend/web/components/modals/CreateSegmentRulesTabForm.tsx

Lines changed: 37 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -131,49 +131,50 @@ const CreateSegmentRulesTabForm: React.FC<CreateSegmentRulesTabFormProps> = ({
131131
Utils.displayLimitAlert('segments', segmentsLimitAlert.percentage)}
132132
</div>
133133
)}
134-
135-
<div className='mb-3'>
136-
<label htmlFor='segmentID'>Name*</label>
137-
<Flex>
138-
<Input
139-
data-test='segmentID'
140-
name='id'
141-
id='segmentID'
142-
maxLength={SEGMENT_ID_MAXLENGTH}
143-
value={name}
134+
<div className='d-flex flex-md-row flex-sm-column gap-2'>
135+
<div className='col-md-6'>
136+
<label htmlFor='segmentID'>Name*</label>
137+
<Flex>
138+
<Input
139+
data-test='segmentID'
140+
name='id'
141+
id='segmentID'
142+
maxLength={SEGMENT_ID_MAXLENGTH}
143+
value={name}
144+
onChange={(e: InputEvent) => {
145+
setValueChanged(true)
146+
setName(
147+
Format.enumeration
148+
.set(Utils.safeParseEventValue(e))
149+
.toLowerCase(),
150+
)
151+
}}
152+
isValid={name && name.length}
153+
type='text'
154+
placeholder='E.g. power_users'
155+
/>
156+
</Flex>
157+
</div>
158+
{!condensed && (
159+
<InputGroup
160+
className='col-md-6'
161+
value={description}
162+
inputProps={{
163+
className: 'full-width',
164+
name: 'featureDesc',
165+
readOnly: !!identity || readOnly,
166+
}}
144167
onChange={(e: InputEvent) => {
145168
setValueChanged(true)
146-
setName(
147-
Format.enumeration
148-
.set(Utils.safeParseEventValue(e))
149-
.toLowerCase(),
150-
)
169+
setDescription(Utils.safeParseEventValue(e))
151170
}}
152171
isValid={name && name.length}
153172
type='text'
154-
placeholder='E.g. power_users'
173+
title='Description'
174+
placeholder="e.g. 'People who have spent over $100' "
155175
/>
156-
</Flex>
176+
)}
157177
</div>
158-
{!condensed && (
159-
<InputGroup
160-
className='mb-3'
161-
value={description}
162-
inputProps={{
163-
className: 'full-width',
164-
name: 'featureDesc',
165-
readOnly: !!identity || readOnly,
166-
}}
167-
onChange={(e: InputEvent) => {
168-
setValueChanged(true)
169-
setDescription(Utils.safeParseEventValue(e))
170-
}}
171-
isValid={name && name.length}
172-
type='text'
173-
title='Description'
174-
placeholder="e.g. 'People who have spent over $100' "
175-
/>
176-
)}
177178

178179
<div className='form-group '>
179180
<Row className='mb-3'>

frontend/web/components/segments/Rule/components/RuleConditionRow.tsx

Lines changed: 90 additions & 64 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import React from 'react'
1+
import React, { useMemo, useState } from 'react'
22
import Icon from 'components/Icon'
33
import Utils from 'common/utils/utils'
44
import {
@@ -13,6 +13,8 @@ import RuleConditionPropertySelect from './RuleConditionPropertySelect'
1313
import RuleConditionValueInput from './RuleConditionValueInput'
1414
import { RuleContextValues } from 'common/types/rules.types'
1515
import { useRuleOperator, useRuleContext } from 'components/segments/Rule/hooks'
16+
import MultiSelect from 'components/base/select/MultiSelect'
17+
import { useGetEnvironmentsQuery } from 'common/services/useEnvironment'
1618

1719
interface RuleConditionRowProps {
1820
rule: SegmentCondition
@@ -47,12 +49,17 @@ const RuleConditionRow: React.FC<RuleConditionRowProps> = ({
4749
setRuleProperty,
4850
showDescription,
4951
}) => {
52+
const [selectedEnvironments, setSelectedEnvironments] = useState<string[]>([])
5053
const lastIndex = rules.reduce((acc, v, i) => {
5154
if (!v.delete) {
5255
return i
5356
}
5457
return acc
5558
}, 0)
59+
const { data } = useGetEnvironmentsQuery({ projectId: projectId?.toString() })
60+
const environments = data?.results
61+
62+
const environmentOptions = useMemo(() => environments ? environments?.map(({ name }) => ({ label: name, value: name })) : [],[environments])
5663

5764
const isLastRule = ruleIndex === lastIndex
5865
const hasOr = ruleIndex > 0
@@ -72,6 +79,9 @@ const RuleConditionRow: React.FC<RuleConditionRowProps> = ({
7279
operator === 'PERCENTAGE_SPLIT' &&
7380
rule.property === RuleContextValues.IDENTITY_KEY
7481

82+
83+
console.log('showEnvironmentDropdown', showEnvironmentDropdown)
84+
console.log('rule', rule)
7585
return (
7686
<div className='rule__row reveal' key={ruleIndex}>
7787
{hasOr && (
@@ -84,74 +94,90 @@ const RuleConditionRow: React.FC<RuleConditionRowProps> = ({
8494
<Flex className='or-divider__line' />
8595
</Row>
8696
)}
87-
<Row
88-
noWrap
89-
className='rule align-items-center justify-content-between gap-1'
97+
<div
98+
className='d-flex flex-row align-items-center gap-1'
9099
>
91-
<RuleConditionPropertySelect
92-
dataTest={`${dataTest}-property-${ruleIndex}`}
93-
ruleIndex={ruleIndex}
94-
setRuleProperty={setRuleProperty}
95-
propertyValue={rule.property}
96-
operator={rule.operator}
97-
allowedContextValues={allowedContextValues}
98-
isValueFromContext={isValueFromContext}
99-
/>
100-
{readOnly ? (
101-
!!find(operators, { value: operator })?.label
102-
) : (
103-
<Select
104-
data-test={`${dataTest}-operator-${ruleIndex}`}
105-
value={operator && find(operators, { value: operator })}
106-
onChange={(value: { value: string }) => {
107-
setRuleProperty(ruleIndex, 'operator', value)
100+
<div
101+
className='d-flex flex-1 flex-row rule align-items-center justify-content-between gap-1 col-md-10'
102+
>
103+
<div className='col-10 col-md-4'>
104+
<RuleConditionPropertySelect
105+
dataTest={`${dataTest}-property-${ruleIndex}`}
106+
ruleIndex={ruleIndex}
107+
setRuleProperty={setRuleProperty}
108+
propertyValue={rule.property}
109+
operator={rule.operator}
110+
allowedContextValues={allowedContextValues}
111+
isValueFromContext={isValueFromContext}
112+
/>
113+
</div>
114+
{readOnly ? (
115+
!!find(operators, { value: operator })?.label
116+
) : (
117+
<Select
118+
data-test={`${dataTest}-operator-${ruleIndex}`}
119+
value={operator && find(operators, { value: operator })}
120+
onChange={(value: { value: string }) => {
121+
setRuleProperty(ruleIndex, 'operator', value)
122+
}}
123+
options={operators}
124+
className="col-10 col-md-3"
125+
/>
126+
)}
127+
{operator === 'IN' && rule.property === RuleContextValues.ENVIRONMENT_NAME ? (
128+
<MultiSelect
129+
selectedValues={selectedEnvironments}
130+
onSelectionChange={(selectedValues: string[]) => setSelectedEnvironments(selectedValues)}
131+
options={environmentOptions}
132+
className='col-10 col-md-4'
133+
/>
134+
) : (
135+
<RuleConditionValueInput
136+
readOnly={readOnly}
137+
data-test={`${dataTest}-value-${ruleIndex}`}
138+
value={displayValue || ''}
139+
placeholder={valuePlaceholder}
140+
disabled={operatorObj && operatorObj.hideValue}
141+
projectId={projectId}
142+
showEnvironmentDropdown={showEnvironmentDropdown}
143+
operator={operator}
144+
onChange={(value: string) => {
145+
setRuleProperty(ruleIndex, 'value', {
146+
value:
147+
operatorObj && operatorObj.append
148+
? `${value}${operatorObj.append}`
149+
: value,
150+
})
108151
}}
109-
options={operators}
110-
style={{ width: '190px' }}
152+
isValid={Utils.validateRule(rule) && !ruleErrors?.value}
153+
className='col-10 col-md-4'
111154
/>
112-
)}
113-
<RuleConditionValueInput
114-
readOnly={readOnly}
115-
data-test={`${dataTest}-value-${ruleIndex}`}
116-
value={displayValue || ''}
117-
placeholder={valuePlaceholder}
118-
disabled={operatorObj && operatorObj.hideValue}
119-
style={{ width: '135px' }}
120-
projectId={projectId}
121-
showEnvironmentDropdown={showEnvironmentDropdown}
122-
operator={operator}
123-
onChange={(value: string) => {
124-
setRuleProperty(ruleIndex, 'value', {
125-
value:
126-
operatorObj && operatorObj.append
127-
? `${value}${operatorObj.append}`
128-
: value,
129-
})
130-
}}
131-
isValid={Utils.validateRule(rule) && !ruleErrors?.value}
132-
/>
133-
{isLastRule && !readOnly ? (
134-
<Button
135-
theme='outline'
136-
data-test={`${dataTest}-or`}
155+
)}
156+
</div>
157+
<div className='d-flex flex-sm-column flex-md-row gap-2'>
158+
{isLastRule && !readOnly ? (
159+
<Button
160+
theme='outline'
161+
data-test={`${dataTest}-or`}
162+
type='button'
163+
onClick={addRule}
164+
>
165+
Or
166+
</Button>
167+
) : (
168+
<div style={{ width: 64 }} />
169+
)}
170+
<button
171+
data-test={`${dataTest}-remove`}
137172
type='button'
138-
onClick={addRule}
173+
id='remove-feature'
174+
onClick={() => removeRule(ruleIndex)}
175+
className='btn btn-with-icon'
139176
>
140-
Or
141-
</Button>
142-
) : (
143-
<div style={{ width: 64 }} />
144-
)}
145-
<button
146-
data-test={`${dataTest}-remove`}
147-
type='button'
148-
id='remove-feature'
149-
onClick={() => removeRule(ruleIndex)}
150-
className='btn btn-with-icon'
151-
>
152-
<Icon name='trash-2' width={20} fill={'#656D7B'} />
153-
</button>
154-
</Row>
177+
<Icon name='trash-2' width={20} fill={'#656D7B'} />
178+
</button>
179+
</div>
180+
</div>
155181
{showDescription && (
156182
<Row noWrap className='rule'>
157183
<textarea

0 commit comments

Comments
 (0)