@finos/legend-query-builder 4.14.55 → 4.14.56

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (68) hide show
  1. package/lib/__lib__/QueryBuilderTesting.d.ts +5 -1
  2. package/lib/__lib__/QueryBuilderTesting.d.ts.map +1 -1
  3. package/lib/__lib__/QueryBuilderTesting.js +10 -1
  4. package/lib/__lib__/QueryBuilderTesting.js.map +1 -1
  5. package/lib/components/QueryBuilder.js +3 -3
  6. package/lib/components/QueryBuilder.js.map +1 -1
  7. package/lib/components/QueryBuilderPropertyExpressionEditor.d.ts +9 -1
  8. package/lib/components/QueryBuilderPropertyExpressionEditor.d.ts.map +1 -1
  9. package/lib/components/QueryBuilderPropertyExpressionEditor.js +10 -8
  10. package/lib/components/QueryBuilderPropertyExpressionEditor.js.map +1 -1
  11. package/lib/components/QueryLoader.js +3 -3
  12. package/lib/components/QueryLoader.js.map +1 -1
  13. package/lib/components/execution-plan/ExecutionPlanViewer.js +2 -2
  14. package/lib/components/execution-plan/ExecutionPlanViewer.js.map +1 -1
  15. package/lib/components/explorer/QueryBuilderExplorerPanel.d.ts +12 -2
  16. package/lib/components/explorer/QueryBuilderExplorerPanel.d.ts.map +1 -1
  17. package/lib/components/explorer/QueryBuilderExplorerPanel.js +4 -4
  18. package/lib/components/explorer/QueryBuilderExplorerPanel.js.map +1 -1
  19. package/lib/components/explorer/QueryBuilderFunctionsExplorerPanel.js +2 -2
  20. package/lib/components/explorer/QueryBuilderFunctionsExplorerPanel.js.map +1 -1
  21. package/lib/components/fetch-structure/QueryBuilderPostFilterPanel.d.ts +1 -1
  22. package/lib/components/fetch-structure/QueryBuilderPostFilterPanel.d.ts.map +1 -1
  23. package/lib/components/fetch-structure/QueryBuilderPostFilterPanel.js +55 -45
  24. package/lib/components/fetch-structure/QueryBuilderPostFilterPanel.js.map +1 -1
  25. package/lib/components/fetch-structure/QueryBuilderTDSPanel.js +2 -2
  26. package/lib/components/fetch-structure/QueryBuilderTDSPanel.js.map +1 -1
  27. package/lib/components/fetch-structure/QueryBuilderTDSWindowPanel.js +5 -5
  28. package/lib/components/fetch-structure/QueryBuilderTDSWindowPanel.js.map +1 -1
  29. package/lib/components/filter/QueryBuilderFilterPanel.js +3 -3
  30. package/lib/components/filter/QueryBuilderFilterPanel.js.map +1 -1
  31. package/lib/components/result/QueryBuilderResultPanel.js +3 -3
  32. package/lib/components/result/QueryBuilderResultPanel.js.map +1 -1
  33. package/lib/index.css +2 -2
  34. package/lib/index.css.map +1 -1
  35. package/lib/index.d.ts +12 -0
  36. package/lib/index.d.ts.map +1 -1
  37. package/lib/index.js +12 -0
  38. package/lib/index.js.map +1 -1
  39. package/lib/package.json +3 -3
  40. package/lib/stores/fetch-structure/tds/post-filter/QueryBuilderPostFilterState.d.ts +4 -2
  41. package/lib/stores/fetch-structure/tds/post-filter/QueryBuilderPostFilterState.d.ts.map +1 -1
  42. package/lib/stores/fetch-structure/tds/post-filter/QueryBuilderPostFilterState.js +12 -4
  43. package/lib/stores/fetch-structure/tds/post-filter/QueryBuilderPostFilterState.js.map +1 -1
  44. package/lib/stores/filter/QueryBuilderFilterStateBuilder.d.ts +2 -1
  45. package/lib/stores/filter/QueryBuilderFilterStateBuilder.d.ts.map +1 -1
  46. package/lib/stores/filter/QueryBuilderFilterStateBuilder.js +15 -0
  47. package/lib/stores/filter/QueryBuilderFilterStateBuilder.js.map +1 -1
  48. package/lib/stores/filter/QueryBuilderFilterValueSpecificationBuilder.d.ts +3 -2
  49. package/lib/stores/filter/QueryBuilderFilterValueSpecificationBuilder.d.ts.map +1 -1
  50. package/lib/stores/filter/QueryBuilderFilterValueSpecificationBuilder.js +1 -1
  51. package/lib/stores/filter/QueryBuilderFilterValueSpecificationBuilder.js.map +1 -1
  52. package/package.json +11 -11
  53. package/src/__lib__/QueryBuilderTesting.ts +10 -1
  54. package/src/components/QueryBuilder.tsx +5 -5
  55. package/src/components/QueryBuilderPropertyExpressionEditor.tsx +35 -16
  56. package/src/components/QueryLoader.tsx +5 -5
  57. package/src/components/execution-plan/ExecutionPlanViewer.tsx +3 -3
  58. package/src/components/explorer/QueryBuilderExplorerPanel.tsx +5 -5
  59. package/src/components/explorer/QueryBuilderFunctionsExplorerPanel.tsx +3 -3
  60. package/src/components/fetch-structure/QueryBuilderPostFilterPanel.tsx +181 -160
  61. package/src/components/fetch-structure/QueryBuilderTDSPanel.tsx +3 -3
  62. package/src/components/fetch-structure/QueryBuilderTDSWindowPanel.tsx +9 -9
  63. package/src/components/filter/QueryBuilderFilterPanel.tsx +5 -5
  64. package/src/components/result/QueryBuilderResultPanel.tsx +5 -5
  65. package/src/index.ts +24 -2
  66. package/src/stores/fetch-structure/tds/post-filter/QueryBuilderPostFilterState.ts +15 -3
  67. package/src/stores/filter/QueryBuilderFilterStateBuilder.ts +39 -0
  68. package/src/stores/filter/QueryBuilderFilterValueSpecificationBuilder.ts +1 -1
@@ -20,12 +20,9 @@ import {
20
20
  type TreeNodeViewProps,
21
21
  BlankPanelPlaceholder,
22
22
  CaretDownIcon,
23
- ChevronDownIcon,
24
- ChevronRightIcon,
25
23
  clsx,
26
24
  ContextMenu,
27
- DropdownMenu,
28
- FilledTriangleIcon,
25
+ ControlledDropdownMenu,
29
26
  MenuContent,
30
27
  MenuContentItem,
31
28
  TimesIcon,
@@ -65,6 +62,7 @@ import { observer } from 'mobx-react-lite';
65
62
  import React, {
66
63
  forwardRef,
67
64
  useCallback,
65
+ useEffect,
68
66
  useMemo,
69
67
  useRef,
70
68
  useState,
@@ -96,8 +94,8 @@ import { QUERY_BUILDER_GROUP_OPERATION } from '../../stores/QueryBuilderGroupOpe
96
94
  import { QueryBuilderTDSState } from '../../stores/fetch-structure/tds/QueryBuilderTDSState.js';
97
95
  import {
98
96
  type QueryBuilderVariableDragSource,
99
- BasicValueSpecificationEditor,
100
97
  QUERY_BUILDER_VARIABLE_DND_TYPE,
98
+ EditableBasicValueSpecificationEditor,
101
99
  } from '../shared/BasicValueSpecificationEditor.js';
102
100
  import {
103
101
  QueryBuilderColumnInfoTooltip,
@@ -114,6 +112,8 @@ import {
114
112
  QueryBuilderPostFilterOperator_In,
115
113
  QueryBuilderPostFilterOperator_NotIn,
116
114
  } from '../../stores/fetch-structure/tds/post-filter/operators/QueryBuilderPostFilterOperator_In.js';
115
+ import { QueryBuilderPropertyNameDisplay } from '../QueryBuilderPropertyExpressionEditor.js';
116
+ import { convertTextToPrimitiveInstanceValue } from '../../stores/shared/ValueSpecificationEditorHelper.js';
117
117
 
118
118
  const QueryBuilderPostFilterConditionContextMenu = observer(
119
119
  forwardRef<
@@ -185,28 +185,21 @@ const QueryBuilderPostFilterGroupConditionEditor = observer(
185
185
  node.groupOperation === QUERY_BUILDER_GROUP_OPERATION.AND ? 'AND' : 'OR';
186
186
 
187
187
  return (
188
- <div className="query-builder-post-filter-tree__node__label__content dnd__entry__container">
188
+ <div className="dnd__entry__container">
189
189
  <PanelEntryDropZonePlaceholder
190
190
  isDragOver={isDragOver}
191
191
  isDroppable={isDroppable}
192
- label={`Add to Logical Group '${operationName}'`}
192
+ label={operationName}
193
+ className="query-builder-post-filter-tree__group-node__drop-zone"
193
194
  >
194
195
  <div
195
- className={clsx('query-builder-post-filter-tree__group-node', {
196
- 'query-builder-post-filter-tree__group-node--and':
197
- node.groupOperation === QUERY_BUILDER_GROUP_OPERATION.AND,
198
- 'query-builder-post-filter-tree__group-node--or':
199
- node.groupOperation === QUERY_BUILDER_GROUP_OPERATION.OR,
200
- })}
196
+ className="query-builder-post-filter-tree__group-node"
201
197
  title="Switch Operation"
202
198
  onClick={switchOperation}
203
199
  >
204
- <div className="query-builder-post-filter-tree__group-node__label">
200
+ <div className="query-builder-post-filter-tree__group-node__label editable-value">
205
201
  {node.groupOperation}
206
202
  </div>
207
- <button className="query-builder-post-filter-tree__group-node__action">
208
- <FilledTriangleIcon />
209
- </button>
210
203
  </div>
211
204
  </PanelEntryDropZonePlaceholder>
212
205
  </div>
@@ -217,10 +210,10 @@ const QueryBuilderPostFilterGroupConditionEditor = observer(
217
210
  export const QueryBuilderColumnBadge = observer(
218
211
  (props: {
219
212
  colState: QueryBuilderTDSColumnState;
213
+ removeColumn: () => void;
220
214
  onColumnChange?:
221
215
  | ((columnState: QueryBuilderProjectionColumnState) => Promise<void>)
222
216
  | undefined;
223
- removeColumn?: () => void;
224
217
  }) => {
225
218
  const { colState, onColumnChange, removeColumn } = props;
226
219
  const applicationStore = useApplicationStore();
@@ -283,16 +276,14 @@ export const QueryBuilderColumnBadge = observer(
283
276
  <InfoCircleIcon />
284
277
  </div>
285
278
  </QueryBuilderColumnInfoTooltip>
286
- {removeColumn && (
287
- <button
288
- className="query-builder-column-badge__action"
289
- name="Reset"
290
- title="Reset"
291
- onClick={removeColumn}
292
- >
293
- <RefreshIcon />
294
- </button>
295
- )}
279
+ <button
280
+ className="query-builder-column-badge__action"
281
+ name="Reset"
282
+ title="Reset"
283
+ onClick={removeColumn}
284
+ >
285
+ <RefreshIcon />
286
+ </button>
296
287
  </div>
297
288
  );
298
289
 
@@ -419,11 +410,17 @@ const QueryBuilderPostFilterConditionEditor = observer(
419
410
  };
420
411
  const debouncedTypeaheadSearch = useMemo(
421
412
  () =>
422
- debounce(
423
- (inputVal: string) => node.condition.handleTypeaheadSearch(),
424
- 1000,
425
- ),
426
- [node],
413
+ debounce((inputValue: string) => {
414
+ const inputValueSpec = convertTextToPrimitiveInstanceValue(
415
+ PrimitiveType.STRING,
416
+ inputValue,
417
+ queryBuilderState.observerContext,
418
+ );
419
+ return node.condition.handleTypeaheadSearch(
420
+ inputValueSpec ?? undefined,
421
+ );
422
+ }, 1000),
423
+ [node, queryBuilderState.observerContext],
427
424
  );
428
425
  const cleanUpReloadValues = (): void => {
429
426
  node.condition.typeaheadSearchState.complete();
@@ -458,7 +455,7 @@ const QueryBuilderPostFilterConditionEditor = observer(
458
455
  isDroppable={isFilterValueDroppable}
459
456
  label="Change Filter Value"
460
457
  >
461
- <BasicValueSpecificationEditor
458
+ <EditableBasicValueSpecificationEditor
462
459
  valueSpecification={rightConditionValue.value}
463
460
  setValueSpecification={changeValueSpecification}
464
461
  graph={graph}
@@ -473,6 +470,7 @@ const QueryBuilderPostFilterConditionEditor = observer(
473
470
  isConstant={queryBuilderState.constantState.isValueSpecConstant(
474
471
  rightConditionValue.value,
475
472
  )}
473
+ initializeAsEditable={node.isNewlyAdded}
476
474
  />
477
475
  </PanelEntryDropZonePlaceholder>
478
476
  </div>
@@ -496,15 +494,13 @@ const QueryBuilderPostFilterConditionEditor = observer(
496
494
  isDroppable={isFilterValueDroppable}
497
495
  label="Change Filter Value"
498
496
  >
499
- <div className="query-builder-post-filter-tree__condition-node__property">
500
- <QueryBuilderColumnBadge
501
- colState={rightConditionValue.tdsColumn}
502
- onColumnChange={
503
- isFilterValueDroppable ? changeRightCol : undefined
504
- }
505
- removeColumn={removeTDSColumnValue}
506
- />
507
- </div>
497
+ <QueryBuilderColumnBadge
498
+ colState={rightConditionValue.tdsColumn}
499
+ onColumnChange={
500
+ isFilterValueDroppable ? changeRightCol : undefined
501
+ }
502
+ removeColumn={removeTDSColumnValue}
503
+ />
508
504
  </PanelEntryDropZonePlaceholder>
509
505
  </div>
510
506
  );
@@ -512,8 +508,12 @@ const QueryBuilderPostFilterConditionEditor = observer(
512
508
  return null;
513
509
  };
514
510
 
511
+ useEffect(() => {
512
+ node.setIsNewlyAdded(false);
513
+ }, [node]);
514
+
515
515
  return (
516
- <div className="query-builder-post-filter-tree__node__label__content dnd__entry__container">
516
+ <div className="dnd__entry__container">
517
517
  <PanelEntryDropZonePlaceholder
518
518
  isDragOver={isDragOver && !isFilterValueDragOver}
519
519
  alwaysShowChildren={true}
@@ -521,11 +521,12 @@ const QueryBuilderPostFilterConditionEditor = observer(
521
521
  >
522
522
  <div className="query-builder-post-filter-tree__condition-node">
523
523
  <div className="query-builder-post-filter-tree__condition-node__property">
524
- <QueryBuilderColumnBadge
525
- colState={node.condition.leftConditionValue}
524
+ <QueryBuilderPropertyNameDisplay
525
+ columnName={node.condition.leftConditionValue.columnName}
526
+ title={node.condition.leftConditionValue.columnName}
526
527
  />
527
528
  </div>
528
- <DropdownMenu
529
+ <ControlledDropdownMenu
529
530
  className="query-builder-post-filter-tree__condition-node__operator"
530
531
  title="Choose Operator..."
531
532
  content={
@@ -553,7 +554,7 @@ const QueryBuilderPostFilterConditionEditor = observer(
553
554
  <div className="query-builder-post-filter-tree__condition-node__operator__dropdown__trigger">
554
555
  <CaretDownIcon />
555
556
  </div>
556
- </DropdownMenu>
557
+ </ControlledDropdownMenu>
557
558
  {renderRightVal()}
558
559
  </div>
559
560
  </PanelEntryDropZonePlaceholder>
@@ -594,19 +595,18 @@ const QueryBuilderPostFilterTreeNodeContainer = observer(
594
595
  }
595
596
  >,
596
597
  ) => {
597
- const { node, level, stepPaddingInRem, onNodeSelect, innerProps } = props;
598
+ const { node, onNodeSelect, innerProps } = props;
598
599
  const { tdsState } = innerProps;
599
- const ref = useRef<HTMLDivElement>(null);
600
+ const dragRef = useRef<HTMLDivElement>(null);
600
601
  const [isSelectedFromContextMenu, setIsSelectedFromContextMenu] =
601
602
  useState(false);
602
603
  const applicationStore = useApplicationStore();
603
604
  const postFilterState = tdsState.postFilterState;
604
- const isExpandable =
605
- node instanceof QueryBuilderPostFilterTreeGroupNodeData;
606
605
  const selectNode = (): void => onNodeSelect?.(node);
607
- const toggleExpandNode = (): void => node.setIsOpen(!node.isOpen);
608
606
  const removeNode = (): void =>
609
607
  postFilterState.removeNodeAndPruneBranch(node);
608
+
609
+ // Drag and Drop
610
610
  const handleDrop = useCallback(
611
611
  (item: QueryBuilderPostFilterDropTarget, type: string): void => {
612
612
  if (QUERY_BUILDER_POST_FILTER_DND_TYPE.CONDITION === type) {
@@ -622,6 +622,7 @@ const QueryBuilderPostFilterTreeNodeContainer = observer(
622
622
  nodeBeingDragged.id,
623
623
  ) as QueryBuilderPostFilterTreeConditionNodeData
624
624
  ).condition,
625
+ true,
625
626
  );
626
627
 
627
628
  if (node instanceof QueryBuilderPostFilterTreeConditionNodeData) {
@@ -667,6 +668,7 @@ const QueryBuilderPostFilterTreeNodeContainer = observer(
667
668
  new QueryBuilderPostFilterTreeConditionNodeData(
668
669
  undefined,
669
670
  conditionState,
671
+ true,
670
672
  ),
671
673
  node,
672
674
  );
@@ -730,16 +732,15 @@ const QueryBuilderPostFilterTreeNodeContainer = observer(
730
732
  : QUERY_BUILDER_POST_FILTER_DND_TYPE.BLANK_CONDITION,
731
733
  item: () => ({ node }),
732
734
  end: (): void => postFilterState.setRearrangingConditions(false),
735
+ canDrag: () =>
736
+ node instanceof QueryBuilderPostFilterTreeConditionNodeData ||
737
+ node instanceof QueryBuilderPostFilterTreeBlankConditionNodeData,
733
738
  }),
734
739
  [node, postFilterState],
735
740
  );
736
- dragConnector(dropConnector(ref));
741
+ dragConnector(dropConnector(dragRef));
737
742
  useDragPreviewLayer(dragPreviewConnector);
738
743
 
739
- // context menu
740
- const onContextMenuOpen = (): void => setIsSelectedFromContextMenu(true);
741
- const onContextMenuClose = (): void => setIsSelectedFromContextMenu(false);
742
-
743
744
  const { isDroppable } = useDragLayer((monitor) => ({
744
745
  isDroppable:
745
746
  monitor.isDragging() &&
@@ -747,95 +748,94 @@ const QueryBuilderPostFilterTreeNodeContainer = observer(
747
748
  monitor.getItemType() === QUERY_BUILDER_WINDOW_COLUMN_DND_TYPE),
748
749
  }));
749
750
 
751
+ // context menu
752
+ const onContextMenuOpen = (): void => setIsSelectedFromContextMenu(true);
753
+ const onContextMenuClose = (): void => setIsSelectedFromContextMenu(false);
754
+
755
+ const showRemoveButton =
756
+ node instanceof QueryBuilderPostFilterTreeConditionNodeData ||
757
+ node instanceof QueryBuilderPostFilterTreeBlankConditionNodeData;
758
+
750
759
  return (
751
- <ContextMenu
752
- content={
753
- <QueryBuilderPostFilterConditionContextMenu
754
- tdsState={tdsState}
755
- node={node}
756
- />
760
+ <div
761
+ data-testid={
762
+ QUERY_BUILDER_TEST_ID.QUERY_BUILDER_POST_FILTER_TREE_NODE_CONTAINER
763
+ }
764
+ onClick={
765
+ node instanceof QueryBuilderPostFilterTreeConditionNodeData ||
766
+ node instanceof QueryBuilderPostFilterTreeBlankConditionNodeData
767
+ ? selectNode
768
+ : undefined
757
769
  }
758
- menuProps={{ elevation: 7 }}
759
- onOpen={onContextMenuOpen}
760
- onClose={onContextMenuClose}
770
+ className={clsx('query-builder-post-filter-tree__node__container', {
771
+ 'query-builder-post-filter-tree__node__container--group':
772
+ node instanceof QueryBuilderPostFilterTreeGroupNodeData,
773
+ 'query-builder-post-filter-tree__node__container--condition':
774
+ node instanceof QueryBuilderPostFilterTreeConditionNodeData ||
775
+ node instanceof QueryBuilderPostFilterTreeBlankConditionNodeData,
776
+ 'query-builder-post-filter-tree__node__container--no-hover':
777
+ postFilterState.isRearrangingConditions,
778
+ 'query-builder-post-filter-tree__node__container--selected':
779
+ node === postFilterState.selectedNode,
780
+ 'query-builder-post-filter-tree__node__container--selected-from-context-menu':
781
+ isSelectedFromContextMenu,
782
+ })}
761
783
  >
762
- <div
763
- ref={ref}
764
- data-testid={
765
- QUERY_BUILDER_TEST_ID.QUERY_BUILDER_POST_FILTER_TREE_NODE_CONTENT
784
+ <ContextMenu
785
+ content={
786
+ <QueryBuilderPostFilterConditionContextMenu
787
+ tdsState={tdsState}
788
+ node={node}
789
+ />
766
790
  }
767
- className={clsx(
768
- 'tree-view__node__container query-builder-post-filter-tree__node__container',
769
- {
770
- 'query-builder-post-filter-tree__node__container--no-hover':
771
- postFilterState.isRearrangingConditions,
772
- 'query-builder-post-filter-tree__node__container--selected':
773
- node === postFilterState.selectedNode,
774
- 'query-builder-post-filter-tree__node__container--selected-from-context-menu':
775
- isSelectedFromContextMenu,
776
- },
777
- )}
791
+ menuProps={{ elevation: 7 }}
792
+ onOpen={onContextMenuOpen}
793
+ onClose={onContextMenuClose}
794
+ className="query-builder-post-filter-tree__node__context-menu"
778
795
  >
779
796
  <div
797
+ data-testid={
798
+ QUERY_BUILDER_TEST_ID.QUERY_BUILDER_POST_FILTER_TREE_NODE_CONTENT
799
+ }
780
800
  className="query-builder-post-filter-tree__node__content"
781
- style={{
782
- paddingLeft: `${(level - 1) * (stepPaddingInRem ?? 2) + 1.5}rem`,
783
- display: 'flex',
784
- }}
785
- onClick={selectNode}
801
+ ref={dragRef}
786
802
  >
787
- {isExpandable && (
788
- <div
789
- className="query-builder-post-filter-tree__expand-icon"
790
- onClick={toggleExpandNode}
791
- >
792
- {node.isOpen ? <ChevronDownIcon /> : <ChevronRightIcon />}
793
- </div>
803
+ {node instanceof QueryBuilderPostFilterTreeGroupNodeData && (
804
+ <QueryBuilderPostFilterGroupConditionEditor
805
+ node={node}
806
+ isDroppable={isDroppable}
807
+ isDragOver={isDragOver}
808
+ />
809
+ )}
810
+ {node instanceof QueryBuilderPostFilterTreeConditionNodeData && (
811
+ <QueryBuilderPostFilterConditionEditor
812
+ node={node}
813
+ isDragOver={deepIsDragOver}
814
+ />
815
+ )}
816
+ {node instanceof
817
+ QueryBuilderPostFilterTreeBlankConditionNodeData && (
818
+ <QueryBuilderPostFilterBlankConditionEditor
819
+ node={node}
820
+ isDragOver={isDragOver}
821
+ isDroppable={isDroppable}
822
+ />
794
823
  )}
795
- <div
796
- className={clsx(
797
- 'tree-view__node__label query-builder-post-filter-tree__node__label',
798
- {
799
- 'query-builder-post-filter-tree__node__label--expandable':
800
- isExpandable,
801
- },
802
- )}
803
- >
804
- {node instanceof QueryBuilderPostFilterTreeGroupNodeData && (
805
- <QueryBuilderPostFilterGroupConditionEditor
806
- node={node}
807
- isDroppable={isDroppable}
808
- isDragOver={isDragOver}
809
- />
810
- )}
811
- {node instanceof QueryBuilderPostFilterTreeConditionNodeData && (
812
- <QueryBuilderPostFilterConditionEditor
813
- node={node}
814
- isDragOver={deepIsDragOver}
815
- />
816
- )}
817
- {node instanceof
818
- QueryBuilderPostFilterTreeBlankConditionNodeData && (
819
- <QueryBuilderPostFilterBlankConditionEditor
820
- node={node}
821
- isDragOver={isDragOver}
822
- isDroppable={isDroppable}
823
- />
824
- )}
825
- </div>
826
- </div>
827
- <div className="query-builder-post-filter-tree__node__actions">
828
- <button
829
- className="query-builder-post-filter-tree__node__action"
830
- tabIndex={-1}
831
- title="Remove"
832
- onClick={removeNode}
833
- >
834
- <TimesIcon />
835
- </button>
836
824
  </div>
837
- </div>
838
- </ContextMenu>
825
+ {showRemoveButton && (
826
+ <div className="query-builder-post-filter-tree__node__actions">
827
+ <button
828
+ className="query-builder-post-filter-tree__node__action"
829
+ tabIndex={-1}
830
+ title="Remove"
831
+ onClick={removeNode}
832
+ >
833
+ <TimesIcon />
834
+ </button>
835
+ </div>
836
+ )}
837
+ </ContextMenu>
838
+ </div>
839
839
  );
840
840
  },
841
841
  );
@@ -858,7 +858,15 @@ const QueryBuilderPostFilterTreeNodeView = observer(
858
858
  innerProps,
859
859
  } = props;
860
860
  return (
861
- <div className="tree-view__node__block">
861
+ <div
862
+ data-testid={
863
+ QUERY_BUILDER_TEST_ID.QUERY_BUILDER_POST_FILTER_TREE_NODE_BLOCK
864
+ }
865
+ className={clsx('query-builder-post-filter-tree__node__block', {
866
+ 'query-builder-post-filter-tree__node__block--group':
867
+ node instanceof QueryBuilderPostFilterTreeGroupNodeData,
868
+ })}
869
+ >
862
870
  <QueryBuilderPostFilterTreeNodeContainer
863
871
  node={node}
864
872
  level={level + 1}
@@ -866,17 +874,25 @@ const QueryBuilderPostFilterTreeNodeView = observer(
866
874
  onNodeSelect={onNodeSelect}
867
875
  innerProps={innerProps}
868
876
  />
869
- {node.isOpen &&
870
- getChildNodes(node).map((childNode) => (
871
- <QueryBuilderPostFilterTreeNodeView
872
- key={childNode.id}
873
- node={childNode}
874
- level={level + 1}
875
- onNodeSelect={onNodeSelect}
876
- getChildNodes={getChildNodes}
877
- innerProps={innerProps}
878
- />
879
- ))}
877
+ {node.isOpen && getChildNodes(node).length > 0 && (
878
+ <div
879
+ data-testid={
880
+ QUERY_BUILDER_TEST_ID.QUERY_BUILDER_POST_FILTER_TREE_NODE_CHILDREN
881
+ }
882
+ className="query-builder-post-filter-tree__node__children"
883
+ >
884
+ {getChildNodes(node).map((childNode) => (
885
+ <QueryBuilderPostFilterTreeNodeView
886
+ key={childNode.id}
887
+ node={childNode}
888
+ level={level + 1}
889
+ onNodeSelect={onNodeSelect}
890
+ getChildNodes={getChildNodes}
891
+ innerProps={innerProps}
892
+ />
893
+ ))}
894
+ </div>
895
+ )}
880
896
  </div>
881
897
  );
882
898
  },
@@ -900,7 +916,10 @@ const QueryBuilderPostFilterTree = observer(
900
916
  ? node.childrenIds.map((id) => postFilterState.getNode(id))
901
917
  : [];
902
918
  return (
903
- <div className="tree-view__node__root query-builder-post-filter-tree__root">
919
+ <div
920
+ data-testid={QUERY_BUILDER_TEST_ID.QUERY_BUILDER_POST_FILTER_TREE}
921
+ className="tree-view__node__root query-builder-post-filter-tree__root"
922
+ >
904
923
  {rootNodes.map((node) => (
905
924
  <QueryBuilderPostFilterTreeNodeView
906
925
  key={node.id}
@@ -990,6 +1009,14 @@ const QueryBuilderPostFilterPanelContent = observer(
990
1009
  );
991
1010
  }
992
1011
  };
1012
+
1013
+ const { isDroppable } = useDragLayer((monitor) => ({
1014
+ isDroppable:
1015
+ monitor.isDragging() &&
1016
+ (monitor.getItemType() === QUERY_BUILDER_PROJECTION_COLUMN_DND_TYPE ||
1017
+ monitor.getItemType() === QUERY_BUILDER_WINDOW_COLUMN_DND_TYPE),
1018
+ }));
1019
+
993
1020
  // Drag and Drop
994
1021
  const handleDrop = useCallback(
995
1022
  async (item: QueryBuilderPostFilterDropTarget): Promise<void> => {
@@ -1031,6 +1058,7 @@ const QueryBuilderPostFilterPanelContent = observer(
1031
1058
  new QueryBuilderPostFilterTreeConditionNodeData(
1032
1059
  undefined,
1033
1060
  postFilterConditionState,
1061
+ true,
1034
1062
  ),
1035
1063
  undefined,
1036
1064
  );
@@ -1062,13 +1090,6 @@ const QueryBuilderPostFilterPanelContent = observer(
1062
1090
  const addPostFilterRef = useRef<HTMLInputElement>(null);
1063
1091
  dropTargetConnector(addPostFilterRef);
1064
1092
 
1065
- const { isDroppable } = useDragLayer((monitor) => ({
1066
- isDroppable:
1067
- monitor.isDragging() &&
1068
- (monitor.getItemType() === QUERY_BUILDER_PROJECTION_COLUMN_DND_TYPE ||
1069
- monitor.getItemType() === QUERY_BUILDER_WINDOW_COLUMN_DND_TYPE),
1070
- }));
1071
-
1072
1093
  return (
1073
1094
  <>
1074
1095
  <div className="panel__header">
@@ -1081,7 +1102,7 @@ const QueryBuilderPostFilterPanelContent = observer(
1081
1102
  )}
1082
1103
  </div>
1083
1104
  <div className="panel__header__actions">
1084
- <DropdownMenu
1105
+ <ControlledDropdownMenu
1085
1106
  className="panel__header__action"
1086
1107
  title="Show Post-Filter Options Menu..."
1087
1108
  content={
@@ -1154,7 +1175,7 @@ const QueryBuilderPostFilterPanelContent = observer(
1154
1175
  }
1155
1176
  >
1156
1177
  <MoreVerticalIcon className="query-builder__icon__more-options" />
1157
- </DropdownMenu>
1178
+ </ControlledDropdownMenu>
1158
1179
  </div>
1159
1180
  </div>
1160
1181
  <PanelContent>
@@ -1196,7 +1217,7 @@ const QueryBuilderPostFilterPanelContent = observer(
1196
1217
  className="query-builder-post-filter-tree__free-drop-zone"
1197
1218
  label="Add post-filter to main group"
1198
1219
  >
1199
- <></>{' '}
1220
+ <></>
1200
1221
  </PanelEntryDropZonePlaceholder>
1201
1222
  </div>
1202
1223
  )}
@@ -21,7 +21,7 @@ import {
21
21
  BlankPanelPlaceholder,
22
22
  CalculatorIcon,
23
23
  TimesIcon,
24
- DropdownMenu,
24
+ ControlledDropdownMenu,
25
25
  MenuContent,
26
26
  MenuContentItem,
27
27
  CaretDownIcon,
@@ -1044,7 +1044,7 @@ const QueryBuilderProjectionColumnEditor = observer(
1044
1044
  <CalendarClockIcon />
1045
1045
  </div>
1046
1046
  )}
1047
- <DropdownMenu
1047
+ <ControlledDropdownMenu
1048
1048
  className="query-builder__projection__column__aggregate__operator__dropdown"
1049
1049
  title="Choose Aggregate Operator..."
1050
1050
  disabled={!aggreateOperators.length}
@@ -1089,7 +1089,7 @@ const QueryBuilderProjectionColumnEditor = observer(
1089
1089
  <div className="query-builder__projection__column__aggregate__operator__dropdown__trigger">
1090
1090
  <CaretDownIcon />
1091
1091
  </div>
1092
- </DropdownMenu>
1092
+ </ControlledDropdownMenu>
1093
1093
  </div>
1094
1094
  </div>
1095
1095
  <div className="query-builder__projection__column__actions">
@@ -20,7 +20,7 @@ import {
20
20
  PanelContent,
21
21
  PanelDropZone,
22
22
  BlankPanelPlaceholder,
23
- DropdownMenu,
23
+ ControlledDropdownMenu,
24
24
  InputWithInlineValidation,
25
25
  MenuContent,
26
26
  MenuContentItem,
@@ -439,7 +439,7 @@ const QueryBuilderWindowColumnModalEditor = observer(
439
439
  >
440
440
  {selectedOperatorState.operator.getLabel()}
441
441
  </div>
442
- <DropdownMenu
442
+ <ControlledDropdownMenu
443
443
  className="query-builder__olap__column__operation__operator__dropdown"
444
444
  title="Choose Window Function Operator..."
445
445
  disabled={!operators.length}
@@ -474,7 +474,7 @@ const QueryBuilderWindowColumnModalEditor = observer(
474
474
  <div className="query-builder__olap__column__operation__operator__dropdown__trigger">
475
475
  <CaretDownIcon />
476
476
  </div>
477
- </DropdownMenu>
477
+ </ControlledDropdownMenu>
478
478
  </div>
479
479
  </div>
480
480
  </div>
@@ -553,7 +553,7 @@ const QueryBuilderWindowColumnModalEditor = observer(
553
553
  {selectedSortBy.sortType.toLowerCase()}
554
554
  </div>
555
555
  )}
556
- <DropdownMenu
556
+ <ControlledDropdownMenu
557
557
  className="query-builder__olap__column__sortby__operator__dropdown"
558
558
  title="Choose Window Function SortBy Operator..."
559
559
  content={
@@ -601,7 +601,7 @@ const QueryBuilderWindowColumnModalEditor = observer(
601
601
  >
602
602
  <CaretDownIcon />
603
603
  </div>
604
- </DropdownMenu>
604
+ </ControlledDropdownMenu>
605
605
  </div>
606
606
  </div>
607
607
  </PanelFormSection>
@@ -1007,7 +1007,7 @@ const QueryBuilderWindowColumnEditor = observer(
1007
1007
  >
1008
1008
  {operationState.operator.getLabel()}
1009
1009
  </div>
1010
- <DropdownMenu
1010
+ <ControlledDropdownMenu
1011
1011
  className="query-builder__olap__column__operation__operator__dropdown"
1012
1012
  disabled={!operators.length}
1013
1013
  title="Choose Window Function Operator..."
@@ -1036,7 +1036,7 @@ const QueryBuilderWindowColumnEditor = observer(
1036
1036
  <div className="query-builder__olap__column__operation__operator__dropdown__trigger">
1037
1037
  <CaretDownIcon />
1038
1038
  </div>
1039
- </DropdownMenu>
1039
+ </ControlledDropdownMenu>
1040
1040
  </div>
1041
1041
  </div>
1042
1042
  <div className="query-builder__olap__column__window">
@@ -1132,7 +1132,7 @@ const QueryBuilderWindowColumnEditor = observer(
1132
1132
  {sortByState.sortType.toLowerCase()}
1133
1133
  </div>
1134
1134
  )}
1135
- <DropdownMenu
1135
+ <ControlledDropdownMenu
1136
1136
  className="query-builder__olap__column__sortby__operator__dropdown"
1137
1137
  title="Choose Window Function SortBy Operator..."
1138
1138
  content={
@@ -1176,7 +1176,7 @@ const QueryBuilderWindowColumnEditor = observer(
1176
1176
  <div className="query-builder__olap__column__sortby__operator__dropdown__trigger">
1177
1177
  <CaretDownIcon />
1178
1178
  </div>
1179
- </DropdownMenu>
1179
+ </ControlledDropdownMenu>
1180
1180
  </div>
1181
1181
  </div>
1182
1182
  <div className="query-builder__olap__column__name">