@finos/legend-query-builder 4.14.55 → 4.14.57

Sign up to get free protection for your applications and to get access to all the features.
Files changed (81) 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.d.ts.map +1 -1
  12. package/lib/components/QueryLoader.js +51 -11
  13. package/lib/components/QueryLoader.js.map +1 -1
  14. package/lib/components/execution-plan/ExecutionPlanViewer.js +2 -2
  15. package/lib/components/execution-plan/ExecutionPlanViewer.js.map +1 -1
  16. package/lib/components/explorer/QueryBuilderExplorerPanel.d.ts +12 -2
  17. package/lib/components/explorer/QueryBuilderExplorerPanel.d.ts.map +1 -1
  18. package/lib/components/explorer/QueryBuilderExplorerPanel.js +4 -4
  19. package/lib/components/explorer/QueryBuilderExplorerPanel.js.map +1 -1
  20. package/lib/components/explorer/QueryBuilderFunctionsExplorerPanel.js +2 -2
  21. package/lib/components/explorer/QueryBuilderFunctionsExplorerPanel.js.map +1 -1
  22. package/lib/components/fetch-structure/QueryBuilderPostFilterPanel.d.ts +1 -1
  23. package/lib/components/fetch-structure/QueryBuilderPostFilterPanel.d.ts.map +1 -1
  24. package/lib/components/fetch-structure/QueryBuilderPostFilterPanel.js +55 -45
  25. package/lib/components/fetch-structure/QueryBuilderPostFilterPanel.js.map +1 -1
  26. package/lib/components/fetch-structure/QueryBuilderTDSPanel.js +2 -2
  27. package/lib/components/fetch-structure/QueryBuilderTDSPanel.js.map +1 -1
  28. package/lib/components/fetch-structure/QueryBuilderTDSWindowPanel.js +5 -5
  29. package/lib/components/fetch-structure/QueryBuilderTDSWindowPanel.js.map +1 -1
  30. package/lib/components/filter/QueryBuilderFilterPanel.js +3 -3
  31. package/lib/components/filter/QueryBuilderFilterPanel.js.map +1 -1
  32. package/lib/components/result/QueryBuilderResultPanel.js +3 -3
  33. package/lib/components/result/QueryBuilderResultPanel.js.map +1 -1
  34. package/lib/components/result/tds/QueryBuilderTDSGridResult.js +2 -2
  35. package/lib/components/result/tds/QueryBuilderTDSGridResult.js.map +1 -1
  36. package/lib/components/result/tds/QueryBuilderTDSSimpleGridResult.d.ts.map +1 -1
  37. package/lib/components/result/tds/QueryBuilderTDSSimpleGridResult.js +7 -6
  38. package/lib/components/result/tds/QueryBuilderTDSSimpleGridResult.js.map +1 -1
  39. package/lib/index.css +2 -2
  40. package/lib/index.css.map +1 -1
  41. package/lib/index.d.ts +12 -0
  42. package/lib/index.d.ts.map +1 -1
  43. package/lib/index.js +12 -0
  44. package/lib/index.js.map +1 -1
  45. package/lib/package.json +6 -6
  46. package/lib/stores/QueryLoaderState.d.ts +11 -0
  47. package/lib/stores/QueryLoaderState.d.ts.map +1 -1
  48. package/lib/stores/QueryLoaderState.js +15 -3
  49. package/lib/stores/QueryLoaderState.js.map +1 -1
  50. package/lib/stores/fetch-structure/tds/post-filter/QueryBuilderPostFilterState.d.ts +4 -2
  51. package/lib/stores/fetch-structure/tds/post-filter/QueryBuilderPostFilterState.d.ts.map +1 -1
  52. package/lib/stores/fetch-structure/tds/post-filter/QueryBuilderPostFilterState.js +12 -4
  53. package/lib/stores/fetch-structure/tds/post-filter/QueryBuilderPostFilterState.js.map +1 -1
  54. package/lib/stores/filter/QueryBuilderFilterStateBuilder.d.ts +2 -1
  55. package/lib/stores/filter/QueryBuilderFilterStateBuilder.d.ts.map +1 -1
  56. package/lib/stores/filter/QueryBuilderFilterStateBuilder.js +15 -0
  57. package/lib/stores/filter/QueryBuilderFilterStateBuilder.js.map +1 -1
  58. package/lib/stores/filter/QueryBuilderFilterValueSpecificationBuilder.d.ts +3 -2
  59. package/lib/stores/filter/QueryBuilderFilterValueSpecificationBuilder.d.ts.map +1 -1
  60. package/lib/stores/filter/QueryBuilderFilterValueSpecificationBuilder.js +1 -1
  61. package/lib/stores/filter/QueryBuilderFilterValueSpecificationBuilder.js.map +1 -1
  62. package/package.json +14 -14
  63. package/src/__lib__/QueryBuilderTesting.ts +10 -1
  64. package/src/components/QueryBuilder.tsx +5 -5
  65. package/src/components/QueryBuilderPropertyExpressionEditor.tsx +35 -16
  66. package/src/components/QueryLoader.tsx +69 -9
  67. package/src/components/execution-plan/ExecutionPlanViewer.tsx +3 -3
  68. package/src/components/explorer/QueryBuilderExplorerPanel.tsx +5 -5
  69. package/src/components/explorer/QueryBuilderFunctionsExplorerPanel.tsx +3 -3
  70. package/src/components/fetch-structure/QueryBuilderPostFilterPanel.tsx +181 -160
  71. package/src/components/fetch-structure/QueryBuilderTDSPanel.tsx +3 -3
  72. package/src/components/fetch-structure/QueryBuilderTDSWindowPanel.tsx +9 -9
  73. package/src/components/filter/QueryBuilderFilterPanel.tsx +5 -5
  74. package/src/components/result/QueryBuilderResultPanel.tsx +5 -5
  75. package/src/components/result/tds/QueryBuilderTDSGridResult.tsx +2 -2
  76. package/src/components/result/tds/QueryBuilderTDSSimpleGridResult.tsx +7 -5
  77. package/src/index.ts +24 -2
  78. package/src/stores/QueryLoaderState.ts +24 -5
  79. package/src/stores/fetch-structure/tds/post-filter/QueryBuilderPostFilterState.ts +15 -3
  80. package/src/stores/filter/QueryBuilderFilterStateBuilder.ts +39 -0
  81. package/src/stores/filter/QueryBuilderFilterValueSpecificationBuilder.ts +1 -1
@@ -26,7 +26,7 @@ import {
26
26
  Dialog,
27
27
  TreeView,
28
28
  BlankPanelContent,
29
- DropdownMenu,
29
+ ControlledDropdownMenu,
30
30
  ContextMenu,
31
31
  MenuContent,
32
32
  MenuContentItem,
@@ -106,7 +106,7 @@ import { getClassPropertyIcon } from '@finos/legend-lego/graph-editor';
106
106
  import { QueryBuilderRootClassInfoTooltip } from '../shared/QueryBuilderRootClassInfoTooltip.js';
107
107
  import { QueryBuilderTelemetryHelper } from '../../__lib__/QueryBuilderTelemetryHelper.js';
108
108
 
109
- const checkForDeprecatedNode = (
109
+ export const checkForDeprecatedNode = (
110
110
  node: QueryBuilderExplorerTreeNodeData,
111
111
  graph: PureModel,
112
112
  treeData: TreeData<QueryBuilderExplorerTreeNodeData>,
@@ -754,7 +754,7 @@ const QueryBuilderExplorerTreeNodeView = observer(
754
754
  * 4. Class Subtypes
755
755
  * Note: Derived property nodes will be ranked lower for each relevant return type
756
756
  */
757
- const getQueryBuilderExplorerTreeNodeSortRank = (
757
+ export const getQueryBuilderExplorerTreeNodeSortRank = (
758
758
  node: QueryBuilderExplorerTreeNodeData,
759
759
  ): number => {
760
760
  if (node instanceof QueryBuilderExplorerTreeSubTypeNodeData) {
@@ -928,7 +928,7 @@ export const QueryBuilderExplorerPanel = observer(
928
928
  <PanelHeaderActionItem onClick={collapseTree} title="Collapse Tree">
929
929
  <CompressIcon />
930
930
  </PanelHeaderActionItem>
931
- <DropdownMenu
931
+ <ControlledDropdownMenu
932
932
  className="panel__header__action"
933
933
  title="Show Options Menu..."
934
934
  content={
@@ -972,7 +972,7 @@ export const QueryBuilderExplorerPanel = observer(
972
972
  }}
973
973
  >
974
974
  <MoreVerticalIcon className="query-builder__icon__more-options" />
975
- </DropdownMenu>
975
+ </ControlledDropdownMenu>
976
976
  </PanelHeaderActions>
977
977
  {propertySearchPanelState.isSearchPanelOpen && (
978
978
  <QueryBuilderPropertySearchPanel
@@ -32,7 +32,7 @@ import {
32
32
  TreeView,
33
33
  BlankPanelContent,
34
34
  MenuContentItem,
35
- DropdownMenu,
35
+ ControlledDropdownMenu,
36
36
  MenuContent,
37
37
  CheckIcon,
38
38
  MenuContentItemIcon,
@@ -454,7 +454,7 @@ export const QueryBuilderFunctionsExplorerPanel = observer(
454
454
  />
455
455
  )}
456
456
  </div>
457
- <DropdownMenu
457
+ <ControlledDropdownMenu
458
458
  className="panel__header__action"
459
459
  title="Show Options Menu..."
460
460
  content={
@@ -476,7 +476,7 @@ export const QueryBuilderFunctionsExplorerPanel = observer(
476
476
  }}
477
477
  >
478
478
  <MoreVerticalIcon className="query-builder__icon__more-options" />
479
- </DropdownMenu>
479
+ </ControlledDropdownMenu>
480
480
  </div>
481
481
  </div>
482
482
  <div className="panel__content query-builder__functions-explorer__content">
@@ -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">