From 7ffa63652ac27240dcb17a51630efe7abf6f9624 Mon Sep 17 00:00:00 2001 From: Jovan Cvetkovic Date: Tue, 30 May 2023 10:52:19 +0200 Subject: [PATCH] [FEATURE] Improve "list" text area UX #589 Use expression builder instead of code editor Signed-off-by: Jovan Cvetkovic --- .../RuleEditor/DetectionVisualEditor.tsx | 1 - .../components/SelectionExpField.tsx | 73 +++++++++---------- public/utils/validation.ts | 3 +- 3 files changed, 38 insertions(+), 39 deletions(-) diff --git a/public/pages/Rules/components/RuleEditor/DetectionVisualEditor.tsx b/public/pages/Rules/components/RuleEditor/DetectionVisualEditor.tsx index 8c22a4a15..7eccd3cc2 100644 --- a/public/pages/Rules/components/RuleEditor/DetectionVisualEditor.tsx +++ b/public/pages/Rules/components/RuleEditor/DetectionVisualEditor.tsx @@ -719,7 +719,6 @@ export class DetectionVisualEditor extends React.Component< } > void; value: string; - mode: string; } interface UsedSelection { @@ -30,41 +29,35 @@ export const SelectionExpField: React.FC = ({ dataTestSubj, onChange, value, - mode, }) => { - const [isLoaded, setIsLoaded] = useState(false); + const DEFAULT_DESCRIPTION = 'CONDITION: '; + const OPERATORS = ['and', 'or', 'not']; const [usedExpressions, setUsedExpressions] = useState([]); useEffect(() => { let expressions: UsedSelection[] = []; if (value?.length) { - const values = value.split(' '); + let values = value.split(' '); + if (OPERATORS.indexOf(values[0]) === -1) values = ['', ...values]; + let counter = 0; values.map((val, idx) => { - if (idx === 0) { + if (idx % 2 === 0) { expressions.push({ - description: 'SELECTION', + description: val, isOpen: false, - name: val, + name: '', }); + counter++; } else { - if (idx % 2 !== 0) { - expressions.push({ - description: val, - isOpen: false, - name: '', - }); - counter++; - } else { - const currentIndex = idx - counter; - expressions[currentIndex] = { ...expressions[currentIndex], name: val }; - } + const currentIndex = idx - counter; + expressions[currentIndex] = { ...expressions[currentIndex], name: val }; } }); } else { expressions = [ { - description: 'SELECTION', + description: '', isOpen: false, name: selections[0]?.name || 'Selection_1', }, @@ -74,18 +67,9 @@ export const SelectionExpField: React.FC = ({ setUsedExpressions(expressions); }, [value]); - useEffect(() => { - if (mode !== 'edit' || isLoaded) { - onChange(getValue()); - } - setIsLoaded(true); - }, [usedExpressions]); - - const getValue = () => { - const expressions = usedExpressions.map((exp) => [_.toLower(exp.description), exp.name]); - let newExpressions = _.flattenDeep(expressions); - newExpressions.shift(); - return newExpressions.join(' '); + const getValue = (usedExp: UsedSelection[]) => { + const expressions = usedExp.map((exp) => [_.toLower(exp.description), exp.name]); + return _.flattenDeep(expressions).join(' '); }; const changeExtValue = ( @@ -96,6 +80,7 @@ export const SelectionExpField: React.FC = ({ const usedExp = _.cloneDeep(usedExpressions); usedExp[idx] = { ...usedExp[idx], name: event.target.value }; setUsedExpressions(usedExp); + onChange(getValue(usedExp)); }; const changeExtDescription = ( @@ -106,6 +91,7 @@ export const SelectionExpField: React.FC = ({ const usedExp = _.cloneDeep(usedExpressions); usedExp[idx] = { ...usedExp[idx], description: event.target.value }; setUsedExpressions(usedExp); + onChange(getValue(usedExp)); }; const openPopover = (idx: number) => { @@ -130,6 +116,7 @@ export const SelectionExpField: React.FC = ({ value={exp.description} onChange={(e) => changeExtDescription(e, exp, idx)} options={[ + { value: '', text: '' }, { value: 'and', text: 'AND' }, { value: 'or', text: 'OR' }, { value: 'not', text: 'NOT' }, @@ -169,11 +156,20 @@ export const SelectionExpField: React.FC = ({ return ( + + } + isOpen={false} + panelPaddingSize="s" + anchorPosition="rightDown" + /> + {usedExpressions.map((exp, idx) => ( 0 ? 'selection-exp-field-item-with-remove' : 'selection-exp-field-item'} + className={'selection-exp-field-item-with-remove'} > = ({ description={exp.description} value={exp.name} isActive={exp.isOpen} - onClick={(e) => { + onClick={(e: any) => { e.preventDefault(); openPopover(idx); }} @@ -194,15 +190,16 @@ export const SelectionExpField: React.FC = ({ panelPaddingSize="s" anchorPosition="rightDown" > - {exp.description === 'SELECTION' ? renderSelections(exp, idx) : renderOptions(exp, idx)} + {renderOptions(exp, idx)} - {idx ? ( + {usedExpressions.length > 1 ? ( { const usedExp = _.cloneDeep(usedExpressions); usedExp.splice(idx, 1); setUsedExpressions([...usedExp]); + onChange(getValue(usedExp)); }} color={'danger'} iconType="cross" @@ -217,14 +214,16 @@ export const SelectionExpField: React.FC = ({ onClick={() => { const usedExp = _.cloneDeep(usedExpressions); const differences = _.differenceBy(selections, usedExp, 'name'); - setUsedExpressions([ + const exp = [ ...usedExp, { description: 'AND', isOpen: false, name: differences[0]?.name, }, - ]); + ]; + setUsedExpressions(exp); + onChange(getValue(exp)); }} iconType="plusInCircle" aria-label={'Add one more condition'} diff --git a/public/utils/validation.ts b/public/utils/validation.ts index 07828964d..43e01923d 100644 --- a/public/utils/validation.ts +++ b/public/utils/validation.ts @@ -15,7 +15,7 @@ export const NAME_REGEX = new RegExp(/^[a-zA-Z0-9 _-]{5,50}$/); export const DETECTION_NAME_REGEX = new RegExp(/^[a-zA-Z0-9_.-]{5,50}$/); export const CONDITION_REGEX = new RegExp( - /^([a-zA-Z0-9_]+)?( (and|or|not) ?([a-zA-Z0-9_]+))*(?