Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ const ConditionBuilderContent: React.FC = () => {
const isYesNoQuestion = useServiceStore((state) => state.isYesNoQuestion);
const rules = useServiceStore((state) => state.rules);
const node = useServiceStore((state) => state.selectedNode);
const seedGroup = node?.data?.rules || (Array.isArray(rules) && rules.length > 0 ? rules : undefined);
const seedGroup = node?.data?.rules || (rules && rules.children.length > 0 ? rules : undefined);

return (
<Track direction="vertical" align="stretch">
Expand All @@ -34,7 +34,7 @@ const ConditionBuilderContent: React.FC = () => {
{isYesNoQuestion && <YesNoPopupContent />}
{!isYesNoQuestion && (
<RuleBuilder
onChange={(group: Group) => useServiceStore.getState().changeRulesNode(group.children)}
onChange={(group: Group) => useServiceStore.getState().changeRulesNode(group)}
seedGroup={seedGroup}
/>
)}
Expand Down
4 changes: 2 additions & 2 deletions GUI/src/components/FlowElementsPopup/ConditionContent.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,12 +14,12 @@ type ConditionContentProps = {

const ConditionContent: FC<ConditionContentProps> = ({ node }) => {
const rules = useServiceStore((state) => state.rules);
const seedGroup = node.data?.rules || (Array.isArray(rules) && rules.length > 0 ? rules : undefined);
const seedGroup = node.data?.rules || (rules && rules.children.length > 0 ? rules : undefined);

return (
<Track direction="vertical" align="stretch">
<RuleBuilder
onChange={(group: Group) => useServiceStore.getState().changeRulesNode(group.children)}
onChange={(group: Group) => useServiceStore.getState().changeRulesNode(group)}
seedGroup={seedGroup}
/>
<PreviousVariables node={node} />
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
import { Track } from 'components';
import React from 'react';
import { useTranslation } from 'react-i18next';

import { GroupType } from './types';

interface ConnectorToggleProps {
readonly connector: GroupType;
readonly connectorNot: boolean;
readonly onChangeConnector: (connector: GroupType) => void;
readonly onToggleNot: () => void;
}
Comment thread
trevorling marked this conversation as resolved.

const ConnectorToggle: React.FC<ConnectorToggleProps> = ({
connector,
connectorNot,
onChangeConnector,
onToggleNot,
}) => {
const { t } = useTranslation();

const notButtonClassName = connectorNot ? 'rule-red' : 'rule-gray';
const andButtonClassName = connector === 'and' ? 'rule-green' : 'rule-gray';
const orButtonClassName = connector === 'or' ? 'rule-green' : 'rule-gray';

return (
<Track>
<button className={`small-rule-group-button ${notButtonClassName}`} onClick={onToggleNot}>
{t('serviceFlow.popup.not')}
</button>

Check warning on line 30 in GUI/src/components/FlowElementsPopup/RuleBuilder/ConnectorToggle.tsx

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Add an explicit "type" attribute to this button.

See more on https://sonarcloud.io/project/issues?id=buerokratt_Service-Module&issues=AZ-gwefBq2mxmIuLIzXC&open=AZ-gwefBq2mxmIuLIzXC&pullRequest=1132
<button className={`small-rule-group-button ${andButtonClassName}`} onClick={() => onChangeConnector('and')}>
{t('serviceFlow.popup.and')}
</button>

Check warning on line 33 in GUI/src/components/FlowElementsPopup/RuleBuilder/ConnectorToggle.tsx

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Add an explicit "type" attribute to this button.

See more on https://sonarcloud.io/project/issues?id=buerokratt_Service-Module&issues=AZ-gwefBq2mxmIuLIzXD&open=AZ-gwefBq2mxmIuLIzXD&pullRequest=1132
<button className={`small-rule-group-button ${orButtonClassName}`} onClick={() => onChangeConnector('or')}>
{t('serviceFlow.popup.or')}
</button>

Check warning on line 36 in GUI/src/components/FlowElementsPopup/RuleBuilder/ConnectorToggle.tsx

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Add an explicit "type" attribute to this button.

See more on https://sonarcloud.io/project/issues?id=buerokratt_Service-Module&issues=AZ-gwefBq2mxmIuLIzXE&open=AZ-gwefBq2mxmIuLIzXE&pullRequest=1132
</Track>
);
};

export default ConnectorToggle;
44 changes: 21 additions & 23 deletions GUI/src/components/FlowElementsPopup/RuleBuilder/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,9 @@ import React from 'react';
import { useTranslation } from 'react-i18next';
import { MdDeleteOutline } from 'react-icons/md';

import ConnectorToggle from './ConnectorToggle';
import RuleElement from './ruleElement';
import { Group, isInstanceOfRule, Rule, RuleGroupBuilderProps } from './types';
import { Group, GroupOrRule, isInstanceOfRule, Rule, RuleGroupBuilderProps } from './types';
import { useRuleBuilder } from './useRuleBuilder';
import '../styles.scss';

Expand All @@ -17,8 +18,8 @@ const RuleBuilder: React.FC<RuleGroupBuilderProps> = ({ group, onRemove, onChang
addGroup,
remove,
toggleNot,
changeToAnd,
changeToOr,
changeConnector,
toggleConnectorNot,
changeRule,
onSubGroupChange,
} = useRuleBuilder({
Expand All @@ -28,8 +29,6 @@ const RuleBuilder: React.FC<RuleGroupBuilderProps> = ({ group, onRemove, onChang
seedGroup,
});

const andButtonClassName = groupInfo.type === 'and' ? 'rule-green' : 'rule-gray';
const orButtonClassName = groupInfo.type === 'or' ? 'rule-green' : 'rule-gray';
const notButtonClassName = groupInfo.not ? 'rule-red' : 'rule-gray';

return (
Expand All @@ -39,12 +38,6 @@ const RuleBuilder: React.FC<RuleGroupBuilderProps> = ({ group, onRemove, onChang
<button className={`small-rule-group-button ${notButtonClassName}`} onClick={toggleNot}>
{t('serviceFlow.popup.not')}
</button>
<button className={`small-rule-group-button ${andButtonClassName}`} onClick={changeToAnd}>
{t('serviceFlow.popup.and')}
</button>
<button className={`small-rule-group-button ${orButtonClassName}`} onClick={changeToOr}>
{t('serviceFlow.popup.or')}
</button>
</Track>
<Track gap={8}>
<button className="small-rule-button rule-blue" onClick={addRule}>
Expand All @@ -60,18 +53,23 @@ const RuleBuilder: React.FC<RuleGroupBuilderProps> = ({ group, onRemove, onChang
)}
</Track>
</Track>
{elements?.map((element) =>
isInstanceOfRule(element) ? (
<RuleElement key={element.id} rule={element as Rule} onRemove={remove} onChange={changeRule} />
) : (
<RuleBuilder
key={element.id}
group={element as Group}
onRemove={remove}
onChange={onSubGroupChange(element.id)}
/>
),
)}
{elements?.map((element: GroupOrRule, index: number) => (
<React.Fragment key={element.id}>
{index > 0 && (
<ConnectorToggle
connector={element.connector ?? 'and'}
connectorNot={!!element.connectorNot}
onChangeConnector={(connector) => changeConnector(element.id, connector)}
onToggleNot={() => toggleConnectorNot(element.id)}
/>
)}
{isInstanceOfRule(element) ? (
<RuleElement rule={element as Rule} onRemove={remove} onChange={changeRule} />
) : (
<RuleBuilder group={element as Group} onRemove={remove} onChange={onSubGroupChange(element.id)} />
)}
</React.Fragment>
))}
</Track>
);
};
Expand Down
31 changes: 23 additions & 8 deletions GUI/src/components/FlowElementsPopup/RuleBuilder/types.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,14 @@ export interface RuleGroupBuilderProps {
seedGroup?: Group | GroupOrRule[];
}

export interface Rule {
export type GroupType = 'and' | 'or';

interface WithConnector {
readonly connector?: GroupType;
readonly connectorNot?: boolean;
}
Comment thread
trevorling marked this conversation as resolved.

export interface Rule extends WithConnector {
id: string;
field: string;
operator: string;
Expand All @@ -19,20 +26,18 @@ export interface Rule {
isValueManual?: boolean;
}

export type GroupType = 'and' | 'or';

export interface Group {
export interface Group extends WithConnector {
id: string;
children: GroupOrRule[];
type: GroupType;
not: boolean;
type?: GroupType;
}

export type GroupOrRule = Group | Rule;

export const isInstanceOfRule = (x: GroupOrRule): boolean => 'operator' in x;

export const getInitialRule = () => {
export const getInitialRule = (connector: GroupType = 'and') => {
return {
id: uuidv4(),
field: '',
Expand All @@ -42,14 +47,24 @@ export const getInitialRule = () => {
valueDragElement: undefined,
isFieldManual: false,
isValueManual: false,
connector,
connectorNot: false,
};
};

export const getInitialGroup = () => {
export const getInitialGroup = (connector: GroupType = 'and') => {
return {
id: uuidv4(),
children: [getInitialRule()],
type: 'and',
not: false,
connector,
connectorNot: false,
} as Group;
};

export const withMigratedConnectors = (children: GroupOrRule[], legacyType?: GroupType): GroupOrRule[] =>
children.map((child, index) => ({
...child,
connector: child.connector ?? (index === 0 ? undefined : (legacyType ?? 'and')),
connectorNot: child.connectorNot ?? false,
}));
39 changes: 29 additions & 10 deletions GUI/src/components/FlowElementsPopup/RuleBuilder/useRuleBuilder.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import { useEffect, useRef, useState } from 'react';

import { getInitialGroup, getInitialRule, Group, GroupOrRule, GroupType, Rule } from './types';
import { getInitialGroup, getInitialRule, Group, GroupOrRule, GroupType, Rule, withMigratedConnectors } from './types';

type SeedGroup = Group | GroupOrRule[] | undefined;

interface UseRuleBuilderProps {
group?: Group;
Expand All @@ -10,15 +12,21 @@ interface UseRuleBuilderProps {
}

export const useRuleBuilder = (config: UseRuleBuilderProps) => {
const getSeedGroupChildren = (seedGroup: Group | GroupOrRule[] | undefined): GroupOrRule[] => {
const getSeedGroupChildren = (seedGroup: SeedGroup): GroupOrRule[] => {
if (!seedGroup) return [];
if (Array.isArray(seedGroup)) return seedGroup;
return seedGroup.children;
};

const elementsInitialValue = config.root ? getSeedGroupChildren(config.seedGroup) : config.group!.children;
const getSeedGroupLegacyType = (seedGroup: SeedGroup): GroupType | undefined =>
seedGroup && !Array.isArray(seedGroup) ? seedGroup.type : undefined;

const elementsInitialValue = withMigratedConnectors(
config.root ? getSeedGroupChildren(config.seedGroup) : config.group!.children,
config.root ? getSeedGroupLegacyType(config.seedGroup) : config.group!.type,
);

const isSeedGroupValid = (seedGroup: Group | GroupOrRule[] | undefined): boolean => {
const isSeedGroupValid = (seedGroup: SeedGroup): boolean => {
if (!seedGroup) return false;
if (Array.isArray(seedGroup)) return seedGroup.length > 0;
return seedGroup.children?.length > 0;
Expand All @@ -41,6 +49,16 @@ export const useRuleBuilder = (config: UseRuleBuilderProps) => {
const onChangeRef = useRef(config.onChange);
onChangeRef.current = config.onChange;

useEffect(() => {
if (config.root || !config.group) return;
setGroupInfo((prev) => ({
...prev,
connector: config.group!.connector,
connectorNot: config.group!.connectorNot,
}));
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [config.group?.connector, config.group?.connectorNot]);

useEffect(() => {
onChangeRef.current({
...groupInfo,
Expand All @@ -67,12 +85,13 @@ export const useRuleBuilder = (config: UseRuleBuilderProps) => {
});
};

const changeType = (type: GroupType) => () => {
setGroupInfo({ ...groupInfo, type });
const changeConnector = (id: string, connector: GroupType) => {
setElements(elements.map((x) => (x.id === id ? { ...x, connector } : x)));
};

const changeToAnd = changeType('and');
const changeToOr = changeType('or');
const toggleConnectorNot = (id: string) => {
setElements(elements.map((x) => (x.id === id ? { ...x, connectorNot: !x.connectorNot } : x)));
};

const changeRule = (rule: Rule) => setElementById(rule.id, rule);

Expand All @@ -90,8 +109,8 @@ export const useRuleBuilder = (config: UseRuleBuilderProps) => {
addGroup,
remove,
toggleNot,
changeToAnd,
changeToOr,
changeConnector,
toggleConnectorNot,
changeRule,
onSubGroupChange,
};
Expand Down
28 changes: 13 additions & 15 deletions GUI/src/components/FlowElementsPopup/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -138,9 +138,9 @@ const FlowElementsPopup: React.FC = () => {
case StepType.Input:
case StepType.Condition:
if (node.data?.rules && Array.isArray(node.data.rules.children)) {
useServiceStore.getState().changeRulesNode(node.data.rules.children);
useServiceStore.getState().changeRulesNode(node.data.rules);
} else {
useServiceStore.getState().changeRulesNode([]);
useServiceStore.getState().changeRulesNode(undefined);
}
break;

Expand Down Expand Up @@ -309,21 +309,19 @@ const FlowElementsPopup: React.FC = () => {
};

const prepareRulesForSaving = (updatedNode: Node<NodeDataProps>) => {
const rulesArray = Array.isArray(rules) ? rules : [];
for (const item of rulesArray) {
const processRuleField = (obj: GroupOrRule) => {
if ('field' in obj) obj.field = removeTrailingUnderscores(obj.field);
else {
for (const child of obj.children) {
processRuleField(child);
}
const rootGroup = rules ?? getInitialGroup();
const processRuleField = (obj: GroupOrRule) => {
if ('field' in obj) obj.field = removeTrailingUnderscores(obj.field);
else {
for (const child of obj.children) {
processRuleField(child);
}
};
processRuleField(item);
}
};
for (const child of rootGroup.children) {
processRuleField(child);
}
updatedNode.data.rules = node.data.rules
? { ...node.data.rules, children: rulesArray }
: { ...getInitialGroup(), children: rulesArray };
updatedNode.data.rules = rootGroup;
};

const saveMultiChoicePopup = (originalNode: Node<NodeDataProps>, updatedNode: Node<NodeDataProps>) => {
Expand Down
Loading
Loading