Skip to content

Commit aea340c

Browse files
authored
Support groups inside rule-group (#1111)
* icon types: addRuleGroup -> addSubRuleSimple, addRuleGroupExt -> addSubRule, + addSubGroup * wip * apply defaultField inside rule-group * ui fix 1 * 6.6.4-alpha.0 * --no-git-checks * extend css vars * fix d-n-d * fix * defaultConjunction, defaultGroupConjunction * fix d-n-d - respect empty group for maxNesting * maxNumberOfRules * lev, parentFieldPathSize * fix drag icon * respect maxNesting in rule-group during d-n-d * fix showNot * support canRegroup and canReorder inside rule-group * fix * fix * fix
1 parent ade3ec2 commit aea340c

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

58 files changed

+645
-236
lines changed

.github/workflows/npm-publish.yml

+1-3
Original file line numberDiff line numberDiff line change
@@ -27,8 +27,6 @@ jobs:
2727
runs-on: ubuntu-latest
2828
steps:
2929
- uses: actions/checkout@v4
30-
with:
31-
ref: master
3230
- uses: pnpm/action-setup@v4
3331
with:
3432
version: 8
@@ -38,6 +36,6 @@ jobs:
3836
#cache: 'pnpm'
3937
registry-url: https://registry.npmjs.org/
4038
- run: pnpm i --frozen-lockfile
41-
- run: pnpm -r publish --access public
39+
- run: pnpm -r publish --access public --no-git-checks
4240
env:
4341
NODE_AUTH_TOKEN: ${{secrets.npm_token}}

CHANGELOG.md

+2
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
11
# Changelog
2+
- 6.6.4-alpha.0
3+
- Support groups inside rule-group (PR #1111) (issue #1108)
24
- 6.6.3
35
- Fixed `Utils.isValidTree` for prod build.
46
Removed `checkTree`, `isValidTree` from `Utils.Import` and `getTreeBadFields` from `Utils.TreeUtils`. (PR #1091) (issue #1075)

CONFIG.adoc

+2-1
Original file line numberDiff line numberDiff line change
@@ -321,7 +321,7 @@ Behaviour settings:
321321
|showNot |true |Show `NOT` together with `AND`/`OR`?
322322
|forceShowConj |false |Show conjuction for 1 rule in group?
323323
|maxNumberOfRules | |Maximum number of rules which can be added to the query builder
324-
|maxNesting | |Max nesting for rule groups. +
324+
|maxNesting | |Max nesting for groups. +
325325
Set `1` if you don't want to use groups at all. This will remove also `Add group` button.
326326
|maxNumberOfCases | |For ternary mode - maximum number of cases
327327
|canLeaveEmptyGroup |true |True - leave empty group after deletion of rules, false - automatically remove empty groups + add 1 empty rule to empty root
@@ -455,6 +455,7 @@ Localization:
455455
|addGroupLabel |Add group
456456
|addRuleLabel |Add rule
457457
|addSubRuleLabel |Add sub rule
458+
|addSubGroupLabel |Add sub group
458459
|notLabel |Not
459460
|fieldSourcesPopupTitle |Select source
460461
|valueSourcesPopupTitle |Select value source

lerna.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -4,5 +4,5 @@
44
"./packages/*"
55
],
66
"$schema": "node_modules/lerna/schemas/lerna-schema.json",
7-
"version": "6.6.3"
7+
"version": "6.6.4-alpha.0"
88
}

packages/antd/modules/widgets/core/Button.jsx

+4-2
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import React from "react";
22
import { Button } from "antd";
33

44
const hideLabelsFor = {
5-
"addRuleGroup": true,
5+
"addSubRuleSimple": true,
66
"delGroup": true,
77
"delRuleGroup": true,
88
"delRule": true,
@@ -14,7 +14,9 @@ const typeToClass = {
1414
"delRule": "action action--DELETE",
1515
"delGroup": "action action--DELETE",
1616
"delRuleGroup": "action action--DELETE",
17-
"addRuleGroup": "action action--ADD-RULE",
17+
"addSubRuleSimple": "action action--ADD-RULE",
18+
"addSubRule": "action action--ADD-RULE",
19+
"addSubGroup": "action action--ADD-GROUP",
1820
};
1921

2022
const typeToType = {

packages/antd/modules/widgets/core/Icon.jsx

+3-2
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,9 @@ const typeToIcon = {
99
"delRule": <DeleteFilled />,
1010
"delGroup": <DeleteFilled />,
1111
"delRuleGroup": <DeleteFilled />,
12-
"addRuleGroup": <PlusOutlined />,
13-
"addRuleGroupExt": <PlusOutlined />,
12+
"addSubRuleSimple": <PlusOutlined />,
13+
"addSubRule": <PlusOutlined />,
14+
"addSubGroup": <PlusCircleOutlined />,
1415
"drag": <HolderOutlined />,
1516
};
1617

packages/antd/package.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@react-awesome-query-builder/antd",
3-
"version": "6.6.3",
3+
"version": "6.6.4-alpha.0",
44
"description": "User-friendly query builder for React. AntDesign widgets",
55
"keywords": [
66
"query-builder",

packages/bootstrap/modules/widgets/core/BootstrapButton.jsx

+1-1
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import React from "react";
22
import { Button } from "reactstrap";
33

44
const hideLabelsFor = {
5-
"addRuleGroup": true,
5+
"addSubRuleSimple": true,
66
"delRuleGroup": true,
77
"delRule": true,
88
};

packages/bootstrap/modules/widgets/core/BootstrapIcon.jsx

+3-2
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,9 @@ const typeToIcon = {
88
delGroup: faTrashAlt,
99
delRuleGroup: faTrashAlt,
1010
delRule: faTrashAlt,
11-
addRuleGroup: faPlus,
12-
addRuleGroupExt: faPlus,
11+
addSubRuleSimple: faPlus,
12+
addSubRule: faPlus,
13+
addSubGroup: faPlus,
1314
addRule: faPlus,
1415
addGroup: faPlus,
1516
drag: faUpDown,

packages/bootstrap/package.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@react-awesome-query-builder/bootstrap",
3-
"version": "6.6.3",
3+
"version": "6.6.4-alpha.0",
44
"description": "User-friendly query builder for React. Bootstrap widgets",
55
"keywords": [
66
"query-builder",

packages/core/modules/actions/tree.js

+12-6
Original file line numberDiff line numberDiff line change
@@ -20,14 +20,17 @@ export const setTree = (config, tree) => ({
2020
* @param {Immutable.List} path
2121
* @param {Immutable.Map} properties
2222
*/
23-
export const addRule = (config, path, properties, ruleType = "rule", children = null, parentRuleGroupPath = null) => ({
23+
export const addRule = (config, path, properties, ruleType = "rule", children = null, parentRuleGroupField = null) => ({
2424
type: constants.ADD_RULE,
2525
ruleType: ruleType,
2626
children: children,
2727
path: toImmutableList(path),
2828
id: uuid(),
29-
properties: defaultRuleProperties(config, parentRuleGroupPath).merge(fromJS(properties) || {}),
30-
config: config
29+
properties: defaultRuleProperties(config, parentRuleGroupField).merge(fromJS(properties) || {}),
30+
config: config,
31+
meta: {
32+
parentRuleGroupField,
33+
},
3134
});
3235

3336
/**
@@ -76,13 +79,16 @@ export const addCaseGroup = (config, path, properties, children = null) => ({
7679
* @param {Immutable.List} path
7780
* @param {Immutable.Map} properties
7881
*/
79-
export const addGroup = (config, path, properties, children = null) => ({
82+
export const addGroup = (config, path, properties, children = null, parentRuleGroupField = null) => ({
8083
type: constants.ADD_GROUP,
8184
path: toImmutableList(path),
8285
children: children,
8386
id: uuid(),
84-
properties: defaultGroupProperties(config).merge(fromJS(properties) || {}),
85-
config: config
87+
properties: defaultGroupProperties(config, parentRuleGroupField).merge(fromJS(properties) || {}),
88+
config: config,
89+
meta: {
90+
parentRuleGroupField,
91+
},
8692
});
8793

8894
/**

packages/core/modules/config/default.js

+3
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,8 @@ export const settings = {
3333
setOpOnChangeField: ["keep", "default"], // 'default' (default if present), 'keep' (keep prev from last field), 'first', 'none'
3434
groupOperators: ["some", "all", "none"],
3535

36+
defaultConjunction: "AND",
37+
// todo: deprecated, remove this in favour of defaultConjunction
3638
defaultGroupConjunction: "AND",
3739

3840
// localization
@@ -56,6 +58,7 @@ export const settings = {
5658
defaultCaseLabel: "Default:",
5759
addRuleLabel: "Add rule",
5860
addSubRuleLabel: "Add sub rule",
61+
addSubGroupLabel: "Add sub group",
5962
delGroupLabel: "Delete",
6063
notLabel: "Not",
6164
fieldSourcesPopupTitle: "Select source",

packages/core/modules/export/jsonLogic.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -91,7 +91,7 @@ const formatGroup = (item, config, meta, _not = false, isRoot = false, parentFie
9191
if (!conjunction)
9292
conjunction = defaultConjunction(config);
9393
const conjunctionDefinition = config.conjunctions[conjunction];
94-
const conj = conjunctionDefinition.jsonLogicConj || conjunction.toLowerCase();
94+
const conj = conjunctionDefinition?.jsonLogicConj || conjunction.toLowerCase();
9595
const origNot = !!properties.get("not");
9696

9797
const isRuleGroup = (type === "rule_group" && !isRoot);

packages/core/modules/import/jsonLogic.js

+5-5
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import {getOpCardinality, isJsonLogic, shallowEqual, logger} from "../utils/stuf
33
import {getFieldConfig, extendConfig, normalizeField, getFuncConfig, iterateFuncs, getFieldParts} from "../utils/configUtils";
44
import {getWidgetForFieldOp} from "../utils/ruleUtils";
55
import {loadTree} from "./tree";
6-
import {defaultConjunction, defaultGroupConjunction} from "../utils/defaultUtils";
6+
import {defaultGroupConjunction} from "../utils/defaultUtils";
77

88
import moment from "moment";
99

@@ -669,16 +669,16 @@ const convertConj = (op, vals, conv, config, not, meta, parentField = null, isRu
669669
// return arr;
670670
// };
671671

672-
const wrapInDefaultConjRuleGroup = (rule, parentField, parentFieldConfig, config, conj = undefined, not = false) => {
672+
const wrapInDefaultConjRuleGroup = (rule, groupField, groupFieldConfig, config, conj = undefined, not = false) => {
673673
if (!rule) return undefined;
674674
return {
675675
type: "rule_group",
676676
id: uuid(),
677677
children1: { [rule.id]: rule },
678678
properties: {
679-
conjunction: conj || defaultGroupConjunction(config, parentFieldConfig),
679+
conjunction: conj || defaultGroupConjunction(config, groupFieldConfig),
680680
not: not,
681-
field: parentField,
681+
field: groupField,
682682
}
683683
};
684684
};
@@ -689,7 +689,7 @@ const wrapInDefaultConj = (rule, config, not = false) => {
689689
id: uuid(),
690690
children1: { [rule.id]: rule },
691691
properties: {
692-
conjunction: defaultConjunction(config),
692+
conjunction: defaultGroupConjunction(config),
693693
not: not
694694
}
695695
};

packages/core/modules/import/spel.js

+5-5
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import uuid from "../utils/uuid";
33
import {getFieldConfig, getFuncConfig, extendConfig, normalizeField, iterateFuncs} from "../utils/configUtils";
44
import {getWidgetForFieldOp} from "../utils/ruleUtils";
55
import {loadTree} from "./tree";
6-
import {defaultConjunction, defaultGroupConjunction} from "../utils/defaultUtils";
6+
import {defaultGroupConjunction} from "../utils/defaultUtils";
77
import {getOpCardinality, logger, isJsonCompatible} from "../utils/stuff";
88
import moment from "moment";
99
import {compareToSign} from "../export/spel";
@@ -1302,16 +1302,16 @@ const buildCaseValProperties = (config, meta, conv, val, spel = null) => {
13021302
return valProperties;
13031303
};
13041304

1305-
// const wrapInDefaultConjRuleGroup = (rule, parentField, parentFieldConfig, config, conj) => {
1305+
// const wrapInDefaultConjRuleGroup = (rule, groupField, groupFieldConfig, config, conj) => {
13061306
// if (!rule) return undefined;
13071307
// return {
13081308
// type: "rule_group",
13091309
// id: uuid(),
13101310
// children1: { [rule.id]: rule },
13111311
// properties: {
1312-
// conjunction: conj || defaultGroupConjunction(config, parentFieldConfig),
1312+
// conjunction: conj || defaultGroupConjunction(config, groupFieldConfig),
13131313
// not: false,
1314-
// field: parentField,
1314+
// field: groupField,
13151315
// }
13161316
// };
13171317
// };
@@ -1322,7 +1322,7 @@ const wrapInDefaultConj = (rule, config, not = false) => {
13221322
id: uuid(),
13231323
children1: { [rule.id]: rule },
13241324
properties: {
1325-
conjunction: defaultConjunction(config),
1325+
conjunction: defaultGroupConjunction(config),
13261326
not: not || false
13271327
}
13281328
};

packages/core/modules/index.d.ts

+16-6
Original file line numberDiff line numberDiff line change
@@ -543,18 +543,21 @@ interface ConfigUtils {
543543
applyJsonLogic(logic: any, data?: any): any;
544544
}
545545
interface DefaultUtils {
546-
getDefaultField(config: Config, canGetFirst?: boolean, parentRuleGroupPath?: IdPath): FieldValueI | null;
547-
getDefaultSubField(config: Config, parentRuleGroupPath?: IdPath): FieldValueI | null;
546+
getDefaultField(config: Config, canGetFirst?: boolean, parentRuleGroupField?: string): FieldValueI | null;
547+
getDefaultSubField(config: Config, parentRuleGroupField?: string): FieldValueI | null;
548548
getDefaultFieldSrc(config: Config, canGetFirst?: boolean): string;
549549
getDefaultOperator(config: Config, field: Field, canGetFirst?: boolean): string;
550550
defaultRule(id: string, config: Config): Record<string, ImmutableRule>;
551551
defaultRoot(config: Config, canAddDefaultRule?: boolean): ImmutableGroup;
552552
defaultItemProperties(config: Config, item: JsonItem): ImmutableItemProperties;
553-
defaultGroupProperties(config: Config, fieldConfig?: FieldValueOrConfig): ImmutableGroupProperties;
554-
defaultRuleProperties(config: Config, parentRuleGroupPath?: IdPath, item?: JsonItem, canUseDefaultFieldAndOp?: boolean, canGetFirst?: boolean): ImmutableRuleProperties;
553+
defaultGroupProperties(config: Config, groupFieldConfig?: FieldValueOrConfig): ImmutableGroupProperties;
554+
defaultRuleProperties(config: Config, parentRuleGroupField?: string, item?: JsonItem, canUseDefaultFieldAndOp?: boolean, canGetFirst?: boolean): ImmutableRuleProperties;
555+
/**
556+
* @deprecated Use defaultGroupConjunction() instead
557+
*/
555558
defaultConjunction(config: Config): string;
556559
defaultOperatorOptions(config: Config, operator: string, field: Field): OperatorOptionsI | null;
557-
defaultGroupConjunction(config: Config, fieldConfig?: FieldValueOrConfig): string;
560+
defaultGroupConjunction(config: Config, groupFieldConfig?: FieldValueOrConfig): string;
558561

559562
// createListWithOneElement<TItem>(el: TItem): ImmutableList<TItem>;
560563
// createListFromArray<TItem>(array: TItem[]): ImmutableList<TItem>;
@@ -590,7 +593,7 @@ interface TreeUtils {
590593
expandTreeSubpath(path: ImmutablePath, ...suffix: string[]): ImmutablePath;
591594
fixEmptyGroupsInTree(tree: ImmutableTree): ImmutableTree;
592595
fixPathsInTree(tree: ImmutableTree): ImmutableTree;
593-
getFlatTree(tree: ImmutableTree): FlatTree;
596+
getFlatTree(tree: ImmutableTree, config?: Config): FlatTree;
594597
getTotalReordableNodesCountInTree(tree: ImmutableTree): number;
595598
getTotalRulesCountInTree(tree: ImmutableTree): number;
596599
isEmptyTree(tree: ImmutableTree): boolean;
@@ -1269,6 +1272,11 @@ interface FieldGroupExt<FS = NumberFieldSettings<number>> extends BaseField {
12691272
initialEmptyWhere?: boolean;
12701273
showNot?: boolean;
12711274
conjunctions?: Array<string>;
1275+
defaultConjunction?: string;
1276+
maxNesting?: number;
1277+
maxNumberOfRules?: number;
1278+
canRegroup?: boolean;
1279+
canReorder?: boolean;
12721280
isSpelArray?: boolean;
12731281
isSpelItemMap?: boolean;
12741282
}
@@ -1339,6 +1347,7 @@ export interface LocaleSettings {
13391347
defaultCaseLabel?: string;
13401348
addRuleLabel?: string;
13411349
addSubRuleLabel?: string;
1350+
addSubGroupLabel?: string;
13421351
delGroupLabel?: string;
13431352
notLabel?: string;
13441353
fieldSourcesPopupTitle?: string;
@@ -1363,6 +1372,7 @@ export interface BehaviourSettings {
13631372
canShortMongoQuery?: boolean;
13641373
defaultField?: AnyFieldValue;
13651374
defaultOperator?: string;
1375+
defaultConjunction?: string;
13661376
fieldSources?: Array<FieldSource>;
13671377
valueSourcesInfo?: ValueSourcesInfo;
13681378
canCompareFieldWithField?: CanCompareFieldWithField | SerializedFunction;

packages/core/modules/stores/tree.js

+27-10
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import Immutable, { fromJS } from "immutable";
22
import {
3-
expandTreePath, expandTreeSubpath, getItemByPath, fixPathsInTree,
3+
expandTreePath, expandTreeSubpath, getItemByPath, getAncestorRuleGroups, fixPathsInTree,
44
getTotalRulesCountInTree, fixEmptyGroupsInTree, isEmptyTree, hasChildren, removeIsLockedInTree
55
} from "../utils/treeUtils";
66
import {
@@ -50,7 +50,7 @@ const addNewGroup = (state, path, type, generatedId, properties, config, childre
5050

5151
// Add one empty rule into new group
5252
if (canAddNewRule) {
53-
state = addItem(state, groupPath, "rule", uuid(), defaultRuleProperties(config), config);
53+
state = addItem(state, groupPath, "rule", uuid(), defaultRuleProperties(config, meta?.parentRuleGroupField), config);
5454
}
5555
}
5656

@@ -226,13 +226,30 @@ const addItem = (state, path, type, generatedId, properties, config, children =
226226
currentNumber = targetChildrenSize;
227227
maxNumber = maxNumberOfCases;
228228
} else if (type === "group") {
229-
currentNumber = path.size;
230-
maxNumber = maxNesting;
231-
} else if (targetItem?.get("type") === "rule_group") {
232-
// don't restrict
233-
} else {
234-
currentNumber = isTernary ? getTotalRulesCountInTree(caseGroup) : getTotalRulesCountInTree(state);
235-
maxNumber = maxNumberOfRules;
229+
const ruleGroups = getAncestorRuleGroups(state, path);
230+
if (ruleGroups.length) {
231+
// closest rule-group
232+
const { path: ruleGroupPath, field: ruleGroupField } = ruleGroups[0];
233+
const ruleGroupFieldConfig = getFieldConfig(config, ruleGroupField);
234+
currentNumber = path.size - ruleGroupPath.length;
235+
maxNumber = ruleGroupFieldConfig?.maxNesting;
236+
} else {
237+
currentNumber = path.size;
238+
maxNumber = maxNesting;
239+
}
240+
} else { // rule or rule_group
241+
const ruleGroups = getAncestorRuleGroups(state, path);
242+
if (ruleGroups.length) {
243+
// closest rule-group
244+
const { path: ruleGroupPath, field: ruleGroupField } = ruleGroups[0];
245+
const ruleGroupFieldConfig = getFieldConfig(config, ruleGroupField);
246+
const ruleGroupItem = getItemByPath(state, ruleGroupPath);
247+
maxNumber = ruleGroupFieldConfig?.maxNumberOfRules;
248+
currentNumber = getTotalRulesCountInTree(ruleGroupItem);
249+
} else {
250+
currentNumber = isTernary ? getTotalRulesCountInTree(caseGroup) : getTotalRulesCountInTree(state);
251+
maxNumber = maxNumberOfRules;
252+
}
236253
}
237254
const canAdd = maxNumber && currentNumber ? (currentNumber < maxNumber) : true;
238255

@@ -573,7 +590,7 @@ const setField = (state, path, newField, config, asyncListValues, _meta = {}) =>
573590
const {canReuseValue, newValue, newValueSrc, newValueType, operatorCardinality} = getNewValueForFieldOp(
574591
config, config, currentProperties, newField, newOperator, "field", canFix, isEndValue, canDropArgs
575592
);
576-
let groupProperties = defaultGroupProperties(config, newFieldConfig).merge({
593+
let groupProperties = defaultGroupProperties(config, newFieldConfig, newField).merge({
577594
field: newField,
578595
fieldSrc: "field",
579596
mode: newFieldConfig.mode,

0 commit comments

Comments
 (0)