Skip to content

Commit

Permalink
feat: Improves segment rule value validation and feedback (#4975)
Browse files Browse the repository at this point in the history
  • Loading branch information
tiagoapolo authored Jan 15, 2025
1 parent 5fc9fef commit 8db1a3d
Show file tree
Hide file tree
Showing 5 changed files with 209 additions and 2 deletions.
1 change: 1 addition & 0 deletions frontend/global.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ declare global {
const DYNATRACE_URL: string | undefined
const dtrum: undefined | { identifyUser: (id: string) => void }
const closeModal: () => void
const closeModal2: () => void
const toast: (message: string) => void
const Tooltip: FC<TooltipProps>
}
21 changes: 21 additions & 0 deletions frontend/web/components/Icon.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ export type IconName =
| 'email'
| 'eye'
| 'eye-off'
| 'expand'
| 'file-text'
| 'flash'
| 'github'
Expand Down Expand Up @@ -1367,6 +1368,26 @@ const Icon: FC<IconType> = ({ fill, fill2, height, name, width, ...rest }) => {
</svg>
)
}
case 'expand': {
return (
<svg
viewBox='0 0 24 24'
height={height ?? '24'}
width={width ?? '24'}
fill={fill ?? '#000000'}
xmlns='http://www.w3.org/2000/svg'
>
<g id='Outlined/expand'>
<path
id='Icon'
fillRule='evenodd'
clipRule='evenodd'
d='M19.0016 4.02543C19.5516 4.02843 19.9966 4.47443 19.9966 5.02443L19.9996 9.99943C20.0006 10.5514 19.5526 11.0004 19.0006 11.0004H18.9996C18.4476 11.0004 18.0006 10.5524 17.9996 10.0014L17.9976 7.41643L14.7066 10.7074C14.5116 10.9024 14.2556 11.0004 13.9996 11.0004C13.7446 11.0004 13.4886 10.9024 13.2926 10.7074C12.9026 10.3164 12.9026 9.68343 13.2926 9.29343L16.5726 6.01343L13.9956 6.00043C13.4426 5.99743 12.9966 5.54643 12.9996 4.99543C13.0026 4.44443 13.4506 4.00043 13.9996 4.00043H14.0046L19.0016 4.02543ZM9.29302 13.293C9.68401 12.902 10.316 12.902 10.707 13.293C11.098 13.684 11.098 14.316 10.707 14.707L7.41501 17.999L10 18C10.553 18 11 18.448 11 19.001C10.999 19.553 10.552 20 10 20H9.99901L5.02402 19.997C4.47402 19.997 4.02802 19.552 4.02502 19.002L4.00002 14.005C3.99702 13.453 4.44302 13.003 4.99502 13H5.00002C5.55001 13 5.99701 13.444 6.00002 13.995L6.01302 16.573L9.29302 13.293Z'
/>
</g>
</svg>
)
}
default:
return null
}
Expand Down
8 changes: 7 additions & 1 deletion frontend/web/components/Tooltip.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import React, { FC, ReactElement, ReactNode, useRef } from 'react'
import React, { FC, ReactNode } from 'react'
import ReactTooltip, { TooltipProps as _TooltipProps } from 'react-tooltip'
import Utils from 'common/utils/utils'
import classNames from 'classnames'
Expand All @@ -11,10 +11,14 @@ export type TooltipProps = {
plainText?: boolean
titleClassName?: string
tooltipClassName?: string
effect?: _TooltipProps['effect']
afterShow?: _TooltipProps['afterShow']
}

const Tooltip: FC<TooltipProps> = ({
afterShow,
children,
effect,
place,
plainText,
title,
Expand All @@ -39,6 +43,8 @@ const Tooltip: FC<TooltipProps> = ({
className={classNames('rounded', tooltipClassName)}
id={id}
place={place || 'top'}
effect={effect}
afterShow={afterShow}
>
{plainText ? (
`${children}`
Expand Down
3 changes: 2 additions & 1 deletion frontend/web/components/modals/Rule.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import { Operator, SegmentCondition, SegmentRule } from 'common/types/responses'
import Input from 'components/base/forms/Input'
import find from 'lodash/find'
import Button from 'components/base/forms/Button'
import RuleInputValue from './RuleInputValue'
const splitIfValue = (v: string | null | number, append: string) =>
append && typeof v === 'string' ? v.split(append) : [v === null ? '' : v]

Expand Down Expand Up @@ -100,7 +101,7 @@ export default class Rule extends PureComponent<{
style={{ width: '190px' }}
/>
)}
<Input
<RuleInputValue
readOnly={this.props.readOnly}
data-test={`${this.props['data-test']}-value-${i}`}
value={value || ''}
Expand Down
178 changes: 178 additions & 0 deletions frontend/web/components/modals/RuleInputValue.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,178 @@
import React from 'react'
import Input from 'components/base/forms/Input'
import Icon from 'components/Icon'
import InputGroup from 'components/base/forms/InputGroup'
import Button from 'components/base/forms/Button'
import Utils from 'common/utils/utils'
import ModalHR from './ModalHR'

type RuleInputValueProps = {
'data-test'?: string
value: string | number
style?: React.CSSProperties
placeholder?: string
onChange?: (e: InputEvent) => void
disabled?: boolean
readOnly?: boolean
isValid?: boolean
}

const TextAreaModal = ({
disabled,
isValid,
onChange,
placeholder,
readOnly,
style,
value,
}: RuleInputValueProps) => {
const [textAreaValue, setTextAreaValue] = React.useState(value)

return (
<div>
<div className='modal-body'>
<InputGroup
id='rule-value-textarea'
data-test='rule-value-textarea'
value={textAreaValue}
inputProps={{
style: style,
}}
isValid={isValid}
onChange={(e: InputEvent) => {
const value = Utils.safeParseEventValue(e)
setTextAreaValue(value.replace(/\n/g, ''))
}}
type='text'
className='w-100'
readOnly={readOnly}
placeholder={placeholder}
disabled={disabled}
textarea
/>
</div>
<ModalHR />
<div className='modal-footer'>
<Button
className='mr-2'
theme='secondary'
id='rule-value-textarea-cancel'
data-tests='rule-value-textarea-cancel'
onClick={closeModal2}
>
Cancel
</Button>
<Button
type='button'
id='rule-value-textarea-save'
data-tests='rule-value-textarea-save'
onClick={() => {
const event = new InputEvent('input', { bubbles: true })
Object.defineProperty(event, 'target', {
value: { value: textAreaValue },
writable: false,
})
onChange?.(event)
closeModal2()
}}
>
Apply
</Button>
</div>
</div>
)
}

const RuleInputValue = (props: RuleInputValueProps) => {
const value = props.value
const hasLeadingWhitespace = typeof value === 'string' && /^\s/.test(value)
const hasTrailingWhitespace = typeof value === 'string' && /\s$/.test(value)
const isOnlyWhitespace =
typeof value === 'string' && value.length >= 1 && value.trim() === ''

const hasBothLeadingAndTrailingWhitespace =
hasLeadingWhitespace && hasTrailingWhitespace
const hasWarning =
hasLeadingWhitespace ||
hasTrailingWhitespace ||
hasBothLeadingAndTrailingWhitespace ||
isOnlyWhitespace
const isLongText = String(value).length >= 10

const validate = () => {
if (isOnlyWhitespace) {
return 'This value is only whitespaces'
}
if (hasBothLeadingAndTrailingWhitespace) {
return 'This value starts and ends with whitespaces'
}
if (hasLeadingWhitespace) {
return 'This value starts with whitespaces'
}
if (hasTrailingWhitespace) {
return 'This value ends with whitespaces'
}
if (isLongText) {
return 'Click to edit text in a larger area'
}
return ''
}

const showIcon = hasWarning || isLongText
const isDarkMode = Utils.getFlagsmithHasFeature('dark_mode')

return (
<div className='relative'>
<Input
type='text'
{...props}
inputClassName={
showIcon ? `pr-5 ${hasWarning ? 'border-warning' : ''}` : ''
}
/>
{showIcon && (
<div style={{ position: 'absolute', right: 5, top: 9 }}>
<Tooltip
title={
<div
className={`flex ${
isDarkMode ? 'bg-white' : 'bg-black'
} bg-opacity-10 rounded-2 p-1 ${
hasWarning ? '' : 'cursor-pointer'
}`}
onClick={() => {
if (hasWarning) return
openModal2(
'Edit Value',
<TextAreaModal
value={value}
onChange={props.onChange}
isValid={props.isValid}
/>,
)
}}
>
<Icon
name={hasWarning ? 'warning' : 'expand'}
fill={
hasWarning
? undefined
: `${isDarkMode ? '#fff' : '#1A2634'}`
}
width={18}
height={18}
/>
</div>
}
place='top'
effect='solid'
>
{validate()}
</Tooltip>
</div>
)}
</div>
)
}

export default RuleInputValue

0 comments on commit 8db1a3d

Please sign in to comment.