diff --git a/client/src/components/dynamic-groups/condition-builder.tsx b/client/src/components/dynamic-groups/condition-builder.tsx index 3492153..9f4b6a5 100644 --- a/client/src/components/dynamic-groups/condition-builder.tsx +++ b/client/src/components/dynamic-groups/condition-builder.tsx @@ -194,135 +194,22 @@ const ConditionBuilder: React.FC = ({ ); }; - // Render all conditions with a flat approach to avoid recursion + // Render all conditions with a safer, iterative approach const renderAllConditions = () => { - // First, build a map of parentId to array of children conditions - const conditionsByParent = new Map(); - - // Group conditions by parentId - for (const condition of conditions) { - const parentId = condition.parentId; - if (!conditionsByParent.has(parentId)) { - conditionsByParent.set(parentId, []); - } - conditionsByParent.get(parentId)!.push(condition); - } - - // Render a single condition - const renderSingleCondition = (condition: Condition, index: number, level = 0) => { - if (!condition.isGroup) { - // Render regular condition (not a group) + // Create a separate component for rendering conditions + const ConditionItem = ({ condition, index, level = 0 }: { condition: Condition, index: number, level?: number }) => { + // Get all child conditions if this is a group + const childConditions = condition.isGroup && condition.id + ? conditions.filter(c => c.parentId === condition.id) + : []; + + const isExpanded = condition.id ? expandedGroups.has(condition.id) : false; + + if (condition.isGroup) { return ( -
0 ? `${level * 20}px` : '0' }} - > -
- -
- - {index > 0 && !condition.isGroup && condition.parentId === conditions[index-1].parentId && ( -
- -
- )} - - {(index === 0 || condition.parentId !== conditions[index-1].parentId) && ( -
- )} - -
- - - {condition.attribute === 'custom' && ( - updateCondition(index, 'customAttribute', e.target.value)} - /> - )} -
- -
- -
- -
- {condition.operator !== 'present' && condition.operator !== 'notPresent' && ( - updateCondition(index, 'value', e.target.value)} - /> - )} -
- -
- -
-
- ); - } else { - // Render condition group - const hasChildren = condition.id && conditionsByParent.has(condition.id); - const isExpanded = condition.id && expandedGroups.has(condition.id); - - return ( -
+
0 ? `${level * 20}px` : '0' }} >
@@ -392,15 +279,20 @@ const ConditionBuilder: React.FC = ({ {isExpanded && (
- {hasChildren ? ( - conditionsByParent.get(condition.id)!.map((childCondition) => { - const childIndex = conditions.indexOf(childCondition); - return ( -
- {renderSingleCondition(childCondition, childIndex, level + 1)} -
- ); - }) + {childConditions.length > 0 ? ( +
+ {childConditions.map(childCondition => { + const childIndex = conditions.indexOf(childCondition); + return ( + + ); + })} +
) : (
No conditions in this group. Add one using the buttons above. @@ -412,14 +304,130 @@ const ConditionBuilder: React.FC = ({
); } + + // Regular condition + return ( +
0 ? `${level * 20}px` : '0' }} + > +
+ +
+ + {index > 0 && !condition.isGroup && condition.parentId === conditions[index-1].parentId && ( +
+ +
+ )} + + {(index === 0 || condition.parentId !== conditions[index-1].parentId) && ( +
+ )} + +
+ + + {condition.attribute === 'custom' && ( + updateCondition(index, 'customAttribute', e.target.value)} + /> + )} +
+ +
+ +
+ +
+ {condition.operator !== 'present' && condition.operator !== 'notPresent' && ( + updateCondition(index, 'value', e.target.value)} + /> + )} +
+ +
+ +
+
+ ); }; - // Render only root conditions (those without a parent) - const rootConditions = conditionsByParent.get(null) || []; - return rootConditions.map((condition) => { - const index = conditions.indexOf(condition); - return renderSingleCondition(condition, index, 0); - }); + // Get top-level conditions + const rootConditions = conditions.filter(c => + c.parentId === null || c.parentId === undefined + ); + + return ( +
+ {rootConditions.map(condition => { + const index = conditions.indexOf(condition); + return ( + + ); + })} +
+ ); }; return ( @@ -498,6 +506,8 @@ const generateLdapFilter = (conditions: Condition[]): string => { ? condition.customAttribute : condition.attribute; + if (!attributeName) return ''; + // Regular condition switch (condition.operator) { case '=': @@ -519,27 +529,62 @@ const generateLdapFilter = (conditions: Condition[]): string => { } }; - // Find all root conditions first (those without parents) - const rootConditions = conditions.filter(c => - c.parentId === null || c.parentId === undefined - ); - - // Simple version that just concatenates all conditions without proper nesting - // This avoids recursion completely, but doesn't handle nested groups properly - let allFilters = ''; + // Build a map of parent IDs to their child conditions + const childrenByParentId = new Map(); + // Organize conditions by their parent for (const condition of conditions) { - if (!condition.isGroup) { - allFilters += generateRegularFilter(condition); + const parentId = condition.parentId; + if (!childrenByParentId.has(parentId)) { + childrenByParentId.set(parentId, []); } + childrenByParentId.get(parentId)!.push(condition); } - if (allFilters === '') { - return '(objectClass=*)'; - } + // Generate filter for a group using an iterative approach with a stack + const generateGroupFilter = (parentId: number | null | undefined): string => { + const childConditions = childrenByParentId.get(parentId) || []; + if (childConditions.length === 0) return '(objectClass=*)'; + + // Find the logical operator for this group + const logicalOp = parentId === null || parentId === undefined + ? 'AND' // Default for root level + : conditions.find(c => c.id === parentId)?.logicalOperator || 'AND'; + + const operator = logicalOp === 'AND' ? '&' : '|'; + let groupFilter = `(${operator}`; + + // Process regular conditions in this group + for (const condition of childConditions) { + if (!condition.isGroup) { + const filter = generateRegularFilter(condition); + if (filter) groupFilter += filter; + } + } + + // Process subgroups in this group + for (const condition of childConditions) { + if (condition.isGroup && condition.id) { + // Use another group filter here + const subGroupFilter = generateGroupFilter(condition.id); + if (subGroupFilter !== '(objectClass=*)') { + groupFilter += subGroupFilter; + } + } + } + + groupFilter += ')'; + + // If there are no actual conditions, return a default + if (groupFilter === `(${operator})`) { + return '(objectClass=*)'; + } + + return groupFilter; + }; - // Wrap all filters in an AND operator for simplicity - return `(&${allFilters})`; + // Start with the root level conditions + return generateGroupFilter(null); }; export default ConditionBuilder; \ No newline at end of file