@finos/legend-query-builder 4.9.4 → 4.10.1

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 (118) hide show
  1. package/lib/__lib__/QueryBuilderTesting.d.ts +4 -1
  2. package/lib/__lib__/QueryBuilderTesting.d.ts.map +1 -1
  3. package/lib/__lib__/QueryBuilderTesting.js +5 -1
  4. package/lib/__lib__/QueryBuilderTesting.js.map +1 -1
  5. package/lib/components/QueryBuilderResultPanel.d.ts.map +1 -1
  6. package/lib/components/QueryBuilderResultPanel.js +80 -90
  7. package/lib/components/QueryBuilderResultPanel.js.map +1 -1
  8. package/lib/components/__test-utils__/QueryBuilderComponentTestUtils.d.ts +1 -0
  9. package/lib/components/__test-utils__/QueryBuilderComponentTestUtils.d.ts.map +1 -1
  10. package/lib/components/__test-utils__/QueryBuilderComponentTestUtils.js +13 -1
  11. package/lib/components/__test-utils__/QueryBuilderComponentTestUtils.js.map +1 -1
  12. package/lib/components/fetch-structure/QueryBuilderPostFilterPanel.d.ts +2 -2
  13. package/lib/components/fetch-structure/QueryBuilderPostFilterPanel.d.ts.map +1 -1
  14. package/lib/components/fetch-structure/QueryBuilderPostFilterPanel.js +42 -24
  15. package/lib/components/fetch-structure/QueryBuilderPostFilterPanel.js.map +1 -1
  16. package/lib/components/fetch-structure/QueryBuilderTDSPanel.d.ts.map +1 -1
  17. package/lib/components/fetch-structure/QueryBuilderTDSPanel.js +1 -1
  18. package/lib/components/fetch-structure/QueryBuilderTDSPanel.js.map +1 -1
  19. package/lib/components/filter/QueryBuilderFilterPanel.d.ts +3 -2
  20. package/lib/components/filter/QueryBuilderFilterPanel.d.ts.map +1 -1
  21. package/lib/components/filter/QueryBuilderFilterPanel.js +145 -8
  22. package/lib/components/filter/QueryBuilderFilterPanel.js.map +1 -1
  23. package/lib/index.css +16 -0
  24. package/lib/package.json +1 -1
  25. package/lib/stores/QueryBuilderConstantsState.d.ts +5 -1
  26. package/lib/stores/QueryBuilderConstantsState.d.ts.map +1 -1
  27. package/lib/stores/QueryBuilderConstantsState.js +14 -1
  28. package/lib/stores/QueryBuilderConstantsState.js.map +1 -1
  29. package/lib/stores/QueryBuilderResultState.d.ts +3 -0
  30. package/lib/stores/QueryBuilderResultState.d.ts.map +1 -1
  31. package/lib/stores/QueryBuilderResultState.js +31 -2
  32. package/lib/stores/QueryBuilderResultState.js.map +1 -1
  33. package/lib/stores/QueryBuilderStateHashUtils.d.ts +2 -0
  34. package/lib/stores/QueryBuilderStateHashUtils.d.ts.map +1 -1
  35. package/lib/stores/QueryBuilderStateHashUtils.js +2 -0
  36. package/lib/stores/QueryBuilderStateHashUtils.js.map +1 -1
  37. package/lib/stores/QueryBuilderValueSpecificationBuilder.d.ts.map +1 -1
  38. package/lib/stores/QueryBuilderValueSpecificationBuilder.js +3 -23
  39. package/lib/stores/QueryBuilderValueSpecificationBuilder.js.map +1 -1
  40. package/lib/stores/fetch-structure/tds/post-filter/QueryBuilderPostFilterOperator.js +1 -1
  41. package/lib/stores/fetch-structure/tds/post-filter/QueryBuilderPostFilterOperator.js.map +1 -1
  42. package/lib/stores/fetch-structure/tds/post-filter/QueryBuilderPostFilterState.d.ts +31 -5
  43. package/lib/stores/fetch-structure/tds/post-filter/QueryBuilderPostFilterState.d.ts.map +1 -1
  44. package/lib/stores/fetch-structure/tds/post-filter/QueryBuilderPostFilterState.js +125 -30
  45. package/lib/stores/fetch-structure/tds/post-filter/QueryBuilderPostFilterState.js.map +1 -1
  46. package/lib/stores/fetch-structure/tds/post-filter/QueryBuilderPostFilterStateBuilder.d.ts.map +1 -1
  47. package/lib/stores/fetch-structure/tds/post-filter/QueryBuilderPostFilterStateBuilder.js +21 -8
  48. package/lib/stores/fetch-structure/tds/post-filter/QueryBuilderPostFilterStateBuilder.js.map +1 -1
  49. package/lib/stores/fetch-structure/tds/post-filter/operators/QueryBuilderPostFilterOperatorValueSpecificationBuilder.d.ts +3 -1
  50. package/lib/stores/fetch-structure/tds/post-filter/operators/QueryBuilderPostFilterOperatorValueSpecificationBuilder.d.ts.map +1 -1
  51. package/lib/stores/fetch-structure/tds/post-filter/operators/QueryBuilderPostFilterOperatorValueSpecificationBuilder.js +20 -17
  52. package/lib/stores/fetch-structure/tds/post-filter/operators/QueryBuilderPostFilterOperatorValueSpecificationBuilder.js.map +1 -1
  53. package/lib/stores/fetch-structure/tds/post-filter/operators/QueryBuilderPostFilterOperator_Contain.d.ts.map +1 -1
  54. package/lib/stores/fetch-structure/tds/post-filter/operators/QueryBuilderPostFilterOperator_Contain.js +4 -6
  55. package/lib/stores/fetch-structure/tds/post-filter/operators/QueryBuilderPostFilterOperator_Contain.js.map +1 -1
  56. package/lib/stores/fetch-structure/tds/post-filter/operators/QueryBuilderPostFilterOperator_EndWith.d.ts.map +1 -1
  57. package/lib/stores/fetch-structure/tds/post-filter/operators/QueryBuilderPostFilterOperator_EndWith.js +4 -6
  58. package/lib/stores/fetch-structure/tds/post-filter/operators/QueryBuilderPostFilterOperator_EndWith.js.map +1 -1
  59. package/lib/stores/fetch-structure/tds/post-filter/operators/QueryBuilderPostFilterOperator_Equal.d.ts +1 -1
  60. package/lib/stores/fetch-structure/tds/post-filter/operators/QueryBuilderPostFilterOperator_Equal.d.ts.map +1 -1
  61. package/lib/stores/fetch-structure/tds/post-filter/operators/QueryBuilderPostFilterOperator_Equal.js +10 -10
  62. package/lib/stores/fetch-structure/tds/post-filter/operators/QueryBuilderPostFilterOperator_Equal.js.map +1 -1
  63. package/lib/stores/fetch-structure/tds/post-filter/operators/QueryBuilderPostFilterOperator_GreaterThan.d.ts.map +1 -1
  64. package/lib/stores/fetch-structure/tds/post-filter/operators/QueryBuilderPostFilterOperator_GreaterThan.js +10 -10
  65. package/lib/stores/fetch-structure/tds/post-filter/operators/QueryBuilderPostFilterOperator_GreaterThan.js.map +1 -1
  66. package/lib/stores/fetch-structure/tds/post-filter/operators/QueryBuilderPostFilterOperator_GreaterThanEqual.d.ts.map +1 -1
  67. package/lib/stores/fetch-structure/tds/post-filter/operators/QueryBuilderPostFilterOperator_GreaterThanEqual.js +5 -5
  68. package/lib/stores/fetch-structure/tds/post-filter/operators/QueryBuilderPostFilterOperator_GreaterThanEqual.js.map +1 -1
  69. package/lib/stores/fetch-structure/tds/post-filter/operators/QueryBuilderPostFilterOperator_In.d.ts +1 -1
  70. package/lib/stores/fetch-structure/tds/post-filter/operators/QueryBuilderPostFilterOperator_In.d.ts.map +1 -1
  71. package/lib/stores/fetch-structure/tds/post-filter/operators/QueryBuilderPostFilterOperator_In.js +32 -27
  72. package/lib/stores/fetch-structure/tds/post-filter/operators/QueryBuilderPostFilterOperator_In.js.map +1 -1
  73. package/lib/stores/fetch-structure/tds/post-filter/operators/QueryBuilderPostFilterOperator_IsEmpty.d.ts.map +1 -1
  74. package/lib/stores/fetch-structure/tds/post-filter/operators/QueryBuilderPostFilterOperator_IsEmpty.js +10 -5
  75. package/lib/stores/fetch-structure/tds/post-filter/operators/QueryBuilderPostFilterOperator_IsEmpty.js.map +1 -1
  76. package/lib/stores/fetch-structure/tds/post-filter/operators/QueryBuilderPostFilterOperator_LessThan.d.ts.map +1 -1
  77. package/lib/stores/fetch-structure/tds/post-filter/operators/QueryBuilderPostFilterOperator_LessThan.js +10 -10
  78. package/lib/stores/fetch-structure/tds/post-filter/operators/QueryBuilderPostFilterOperator_LessThan.js.map +1 -1
  79. package/lib/stores/fetch-structure/tds/post-filter/operators/QueryBuilderPostFilterOperator_LessThanEqual.d.ts.map +1 -1
  80. package/lib/stores/fetch-structure/tds/post-filter/operators/QueryBuilderPostFilterOperator_LessThanEqual.js +5 -5
  81. package/lib/stores/fetch-structure/tds/post-filter/operators/QueryBuilderPostFilterOperator_LessThanEqual.js.map +1 -1
  82. package/lib/stores/fetch-structure/tds/post-filter/operators/QueryBuilderPostFilterOperator_StartWith.d.ts.map +1 -1
  83. package/lib/stores/fetch-structure/tds/post-filter/operators/QueryBuilderPostFilterOperator_StartWith.js +4 -6
  84. package/lib/stores/fetch-structure/tds/post-filter/operators/QueryBuilderPostFilterOperator_StartWith.js.map +1 -1
  85. package/lib/stores/fetch-structure/tds/projection/QueryBuilderProjectionColumnState.d.ts.map +1 -1
  86. package/lib/stores/fetch-structure/tds/projection/QueryBuilderProjectionColumnState.js +17 -3
  87. package/lib/stores/fetch-structure/tds/projection/QueryBuilderProjectionColumnState.js.map +1 -1
  88. package/lib/stores/filter/QueryBuilderFilterState.d.ts +7 -1
  89. package/lib/stores/filter/QueryBuilderFilterState.d.ts.map +1 -1
  90. package/lib/stores/filter/QueryBuilderFilterState.js +27 -0
  91. package/lib/stores/filter/QueryBuilderFilterState.js.map +1 -1
  92. package/package.json +5 -5
  93. package/src/__lib__/QueryBuilderTesting.ts +5 -1
  94. package/src/components/QueryBuilderResultPanel.tsx +127 -135
  95. package/src/components/__test-utils__/QueryBuilderComponentTestUtils.tsx +25 -1
  96. package/src/components/fetch-structure/QueryBuilderPostFilterPanel.tsx +102 -49
  97. package/src/components/fetch-structure/QueryBuilderTDSPanel.tsx +6 -2
  98. package/src/components/filter/QueryBuilderFilterPanel.tsx +239 -21
  99. package/src/stores/QueryBuilderConstantsState.ts +30 -0
  100. package/src/stores/QueryBuilderResultState.ts +36 -1
  101. package/src/stores/QueryBuilderStateHashUtils.ts +2 -0
  102. package/src/stores/QueryBuilderValueSpecificationBuilder.ts +4 -50
  103. package/src/stores/fetch-structure/tds/post-filter/QueryBuilderPostFilterOperator.ts +1 -1
  104. package/src/stores/fetch-structure/tds/post-filter/QueryBuilderPostFilterState.ts +180 -34
  105. package/src/stores/fetch-structure/tds/post-filter/QueryBuilderPostFilterStateBuilder.ts +38 -9
  106. package/src/stores/fetch-structure/tds/post-filter/operators/QueryBuilderPostFilterOperatorValueSpecificationBuilder.ts +36 -20
  107. package/src/stores/fetch-structure/tds/post-filter/operators/QueryBuilderPostFilterOperator_Contain.ts +5 -6
  108. package/src/stores/fetch-structure/tds/post-filter/operators/QueryBuilderPostFilterOperator_EndWith.ts +6 -6
  109. package/src/stores/fetch-structure/tds/post-filter/operators/QueryBuilderPostFilterOperator_Equal.ts +12 -13
  110. package/src/stores/fetch-structure/tds/post-filter/operators/QueryBuilderPostFilterOperator_GreaterThan.ts +10 -9
  111. package/src/stores/fetch-structure/tds/post-filter/operators/QueryBuilderPostFilterOperator_GreaterThanEqual.ts +5 -4
  112. package/src/stores/fetch-structure/tds/post-filter/operators/QueryBuilderPostFilterOperator_In.ts +48 -43
  113. package/src/stores/fetch-structure/tds/post-filter/operators/QueryBuilderPostFilterOperator_IsEmpty.ts +12 -4
  114. package/src/stores/fetch-structure/tds/post-filter/operators/QueryBuilderPostFilterOperator_LessThan.ts +10 -9
  115. package/src/stores/fetch-structure/tds/post-filter/operators/QueryBuilderPostFilterOperator_LessThanEqual.ts +5 -4
  116. package/src/stores/fetch-structure/tds/post-filter/operators/QueryBuilderPostFilterOperator_StartWith.ts +6 -6
  117. package/src/stores/fetch-structure/tds/projection/QueryBuilderProjectionColumnState.ts +26 -4
  118. package/src/stores/filter/QueryBuilderFilterState.ts +40 -1
@@ -61,7 +61,13 @@ import {
61
61
  } from '@finos/legend-shared';
62
62
  import { flowResult } from 'mobx';
63
63
  import { observer } from 'mobx-react-lite';
64
- import { forwardRef, useCallback, useMemo, useRef, useState } from 'react';
64
+ import React, {
65
+ forwardRef,
66
+ useCallback,
67
+ useMemo,
68
+ useRef,
69
+ useState,
70
+ } from 'react';
65
71
  import { useDrop, useDrag, useDragLayer } from 'react-dnd';
66
72
  import { QueryBuilderAggregateColumnState } from '../../stores/fetch-structure/tds/aggregation/QueryBuilderAggregationState.js';
67
73
  import type { QueryBuilderPostFilterOperator } from '../../stores/fetch-structure/tds/post-filter/QueryBuilderPostFilterOperator.js';
@@ -74,6 +80,8 @@ import {
74
80
  QueryBuilderPostFilterTreeGroupNodeData,
75
81
  QueryBuilderPostFilterTreeBlankConditionNodeData,
76
82
  QUERY_BUILDER_POST_FILTER_DND_TYPE,
83
+ PostFilterValueSpecConditionValueState,
84
+ PostFilterTDSColumnValueConditionValueState,
77
85
  } from '../../stores/fetch-structure/tds/post-filter/QueryBuilderPostFilterState.js';
78
86
  import {
79
87
  type QueryBuilderProjectionColumnState,
@@ -203,14 +211,14 @@ const QueryBuilderPostFilterGroupConditionEditor = observer(
203
211
 
204
212
  export const QueryBuilderColumnBadge = observer(
205
213
  (props: {
206
- postFilterConditionState: PostFilterConditionState;
214
+ colState: QueryBuilderTDSColumnState;
207
215
  onColumnChange: (
208
216
  columnState: QueryBuilderProjectionColumnState,
209
217
  ) => Promise<void>;
210
218
  }) => {
211
- const { postFilterConditionState, onColumnChange } = props;
219
+ const { colState, onColumnChange } = props;
212
220
  const applicationStore = useApplicationStore();
213
- const type = postFilterConditionState.columnState.getColumnType();
221
+ const type = colState.getColumnType();
214
222
  const handleDrop = useCallback(
215
223
  (item: QueryBuilderProjectionColumnDragSource): Promise<void> =>
216
224
  onColumnChange(item.columnState),
@@ -261,12 +269,12 @@ export const QueryBuilderColumnBadge = observer(
261
269
  )}
262
270
  <div
263
271
  className="query-builder-column-badge__property"
264
- title={`${postFilterConditionState.columnName}`}
272
+ title={`${colState.columnName}`}
265
273
  >
266
- {postFilterConditionState.columnName}
274
+ {colState.columnName}
267
275
  </div>
268
276
  <QueryBuilderColumnInfoTooltip
269
- columnState={postFilterConditionState.columnState}
277
+ columnState={colState}
270
278
  placement="bottom-end"
271
279
  >
272
280
  <div className="query-builder-column-badge__property__info">
@@ -291,13 +299,15 @@ const QueryBuilderPostFilterConditionEditor = observer(
291
299
  const applicationStore = useApplicationStore();
292
300
  const changeOperator = (val: QueryBuilderPostFilterOperator) => (): void =>
293
301
  node.condition.changeOperator(val);
302
+ const rightConditionValue = node.condition.rightConditionValue;
294
303
  const changeColumn = async (
295
304
  columnState: QueryBuilderProjectionColumnState,
296
305
  ): Promise<void> => {
297
306
  const currentColState =
298
- node.condition.columnState instanceof QueryBuilderAggregateColumnState
299
- ? node.condition.columnState.projectionColumnState
300
- : node.condition.columnState;
307
+ node.condition.leftConditionValue instanceof
308
+ QueryBuilderAggregateColumnState
309
+ ? node.condition.leftConditionValue.projectionColumnState
310
+ : node.condition.leftConditionValue;
301
311
  if (currentColState !== columnState) {
302
312
  await flowResult(node.condition.changeColumn(columnState));
303
313
  }
@@ -306,12 +316,13 @@ const QueryBuilderPostFilterConditionEditor = observer(
306
316
  const handleDrop = useCallback(
307
317
  (item: QueryBuilderVariableDragSource): void => {
308
318
  const parameterType = item.variable.genericType?.value.rawType;
309
- const conditionValueType = node.condition.columnState.getColumnType();
319
+ const conditionValueType =
320
+ node.condition.leftConditionValue.getColumnType();
310
321
  if (
311
322
  conditionValueType &&
312
323
  isTypeCompatibleForAssignment(parameterType, conditionValueType)
313
324
  ) {
314
- node.condition.setValue(item.variable);
325
+ node.condition.buildFromValueSpec(item.variable);
315
326
  } else {
316
327
  applicationStore.notificationService.notifyWarning(
317
328
  `Incompatible parameter type ${parameterType?.name}. ${parameterType?.name} is not compatible with type ${conditionValueType?.name}.`,
@@ -339,7 +350,7 @@ const QueryBuilderPostFilterConditionEditor = observer(
339
350
  [handleDrop],
340
351
  );
341
352
  const resetNode = (): void => {
342
- node.condition.setValue(
353
+ node.condition.buildFromValueSpec(
343
354
  node.condition.operator.getDefaultFilterConditionValue(node.condition),
344
355
  );
345
356
  };
@@ -355,7 +366,7 @@ const QueryBuilderPostFilterConditionEditor = observer(
355
366
  node.condition.typeaheadSearchState.complete();
356
367
  };
357
368
  const changeValueSpecification = (val: ValueSpecification): void => {
358
- node.condition.setValue(val);
369
+ node.condition.buildFromValueSpec(val);
359
370
  };
360
371
  const selectorConfig = {
361
372
  values: node.condition.typeaheadSearchResults,
@@ -371,6 +382,70 @@ const QueryBuilderPostFilterConditionEditor = observer(
371
382
  monitor.getItemType() === QUERY_BUILDER_WINDOW_COLUMN_DND_TYPE),
372
383
  }));
373
384
 
385
+ const renderRightVal = (): React.ReactNode => {
386
+ if (
387
+ rightConditionValue instanceof PostFilterValueSpecConditionValueState &&
388
+ rightConditionValue.value
389
+ ) {
390
+ return (
391
+ <div
392
+ ref={dropConnector}
393
+ className="query-builder-post-filter-tree__condition-node__value"
394
+ >
395
+ <PanelEntryDropZonePlaceholder
396
+ isDragOver={isFilterValueDragOver}
397
+ label="Change Filter Value"
398
+ >
399
+ <BasicValueSpecificationEditor
400
+ valueSpecification={rightConditionValue.value}
401
+ setValueSpecification={changeValueSpecification}
402
+ graph={graph}
403
+ obseverContext={queryBuilderState.observerContext}
404
+ typeCheckOption={{
405
+ expectedType: guaranteeNonNullable(
406
+ node.condition.leftConditionValue.getColumnType(),
407
+ ),
408
+ }}
409
+ resetValue={resetNode}
410
+ selectorConfig={selectorConfig}
411
+ isConstant={queryBuilderState.constantState.isValueSpecConstant(
412
+ rightConditionValue.value,
413
+ )}
414
+ />
415
+ </PanelEntryDropZonePlaceholder>
416
+ </div>
417
+ );
418
+ } else if (
419
+ rightConditionValue instanceof
420
+ PostFilterTDSColumnValueConditionValueState
421
+ ) {
422
+ const changeRightCol = async (
423
+ columnState: QueryBuilderProjectionColumnState,
424
+ ): Promise<void> => {
425
+ rightConditionValue.changeCol(columnState);
426
+ };
427
+ return (
428
+ <div
429
+ ref={dropConnector}
430
+ className="query-builder-post-filter-tree__condition-node__value"
431
+ >
432
+ <PanelEntryDropZonePlaceholder
433
+ isDragOver={isFilterValueDragOver}
434
+ label="Change Filter Value"
435
+ >
436
+ <div className="query-builder-post-filter-tree__condition-node__property">
437
+ <QueryBuilderColumnBadge
438
+ colState={rightConditionValue.tdsColumn}
439
+ onColumnChange={changeRightCol}
440
+ />
441
+ </div>
442
+ </PanelEntryDropZonePlaceholder>
443
+ </div>
444
+ );
445
+ }
446
+ return null;
447
+ };
448
+
374
449
  return (
375
450
  <div className="query-builder-post-filter-tree__node__label__content dnd__entry__container">
376
451
  <PanelEntryDropZonePlaceholder
@@ -381,7 +456,7 @@ const QueryBuilderPostFilterConditionEditor = observer(
381
456
  <div className="query-builder-post-filter-tree__condition-node">
382
457
  <div className="query-builder-post-filter-tree__condition-node__property">
383
458
  <QueryBuilderColumnBadge
384
- postFilterConditionState={node.condition}
459
+ colState={node.condition.leftConditionValue}
385
460
  onColumnChange={changeColumn}
386
461
  />
387
462
  </div>
@@ -414,34 +489,7 @@ const QueryBuilderPostFilterConditionEditor = observer(
414
489
  <CaretDownIcon />
415
490
  </div>
416
491
  </DropdownMenu>
417
- {node.condition.value && (
418
- <div
419
- ref={dropConnector}
420
- className="query-builder-post-filter-tree__condition-node__value"
421
- >
422
- <PanelEntryDropZonePlaceholder
423
- isDragOver={isFilterValueDragOver}
424
- label="Change Filter Value"
425
- >
426
- <BasicValueSpecificationEditor
427
- valueSpecification={node.condition.value}
428
- setValueSpecification={changeValueSpecification}
429
- graph={graph}
430
- obseverContext={queryBuilderState.observerContext}
431
- typeCheckOption={{
432
- expectedType: guaranteeNonNullable(
433
- node.condition.columnState.getColumnType(),
434
- ),
435
- }}
436
- resetValue={resetNode}
437
- selectorConfig={selectorConfig}
438
- isConstant={queryBuilderState.constantState.isValueSpecConstant(
439
- node.condition.value,
440
- )}
441
- />
442
- </PanelEntryDropZonePlaceholder>
443
- </div>
444
- )}
492
+ {renderRightVal()}
445
493
  </div>
446
494
  </PanelEntryDropZonePlaceholder>
447
495
  </div>
@@ -538,9 +586,8 @@ const QueryBuilderPostFilterTreeNodeContainer = observer(
538
586
  postFilterState,
539
587
  columnState,
540
588
  undefined,
541
- undefined,
542
589
  );
543
- conditionState.setValue(
590
+ conditionState.buildFromValueSpec(
544
591
  conditionState.operator.getDefaultFilterConditionValue(
545
592
  conditionState,
546
593
  ),
@@ -648,6 +695,9 @@ const QueryBuilderPostFilterTreeNodeContainer = observer(
648
695
  >
649
696
  <div
650
697
  ref={ref}
698
+ data-testid={
699
+ QUERY_BUILDER_TEST_ID.QUERY_BUILDER_POST_FILTER_TREE_NODE_CONTENT
700
+ }
651
701
  className={clsx(
652
702
  'tree-view__node__container query-builder-post-filter-tree__node__container',
653
703
  {
@@ -679,6 +729,10 @@ const QueryBuilderPostFilterTreeNodeContainer = observer(
679
729
  <div
680
730
  className={clsx(
681
731
  'tree-view__node__label query-builder-post-filter-tree__node__label',
732
+ {
733
+ 'query-builder-post-filter-tree__node__label--expandable':
734
+ isExpandable,
735
+ },
682
736
  )}
683
737
  >
684
738
  {node instanceof QueryBuilderPostFilterTreeGroupNodeData && (
@@ -894,9 +948,8 @@ const QueryBuilderPostFilterPanelContent = observer(
894
948
  postFilterState,
895
949
  aggregateColumnState ?? columnState,
896
950
  undefined,
897
- undefined,
898
951
  );
899
- postFilterConditionState.setValue(
952
+ postFilterConditionState.buildFromValueSpec(
900
953
  postFilterConditionState.operator.getDefaultFilterConditionValue(
901
954
  postFilterConditionState,
902
955
  ),
@@ -959,7 +1012,7 @@ const QueryBuilderPostFilterPanelContent = observer(
959
1012
  <div className="panel__header__actions">
960
1013
  <DropdownMenu
961
1014
  className="panel__header__action"
962
- title="Show Filter Options Menu..."
1015
+ title="Show Post-Filter Options Menu..."
963
1016
  content={
964
1017
  <MenuContent>
965
1018
  <MenuContentItem onClick={createCondition}>
@@ -1091,7 +1144,7 @@ export const QueryBuilderPostFilterPanel = observer(
1091
1144
 
1092
1145
  return (
1093
1146
  <div
1094
- data-testid={QUERY_BUILDER_TEST_ID.QUERY_BUILDER_POST_FILTER}
1147
+ data-testid={QUERY_BUILDER_TEST_ID.QUERY_BUILDER_POST_FILTER_PANEL}
1095
1148
  className="panel"
1096
1149
  >
1097
1150
  {fetchStructureImplementation instanceof QueryBuilderTDSState && (
@@ -688,7 +688,6 @@ const QueryBuilderProjectionColumnEditor = observer(
688
688
  }),
689
689
  [handleDrop],
690
690
  );
691
-
692
691
  return (
693
692
  <PanelDnDEntry
694
693
  ref={ref}
@@ -732,7 +731,12 @@ const QueryBuilderProjectionColumnEditor = observer(
732
731
  onOpen={onContextMenuOpen}
733
732
  onClose={onContextMenuClose}
734
733
  >
735
- <div className="query-builder__projection__column__container">
734
+ <div
735
+ data-testid={
736
+ QUERY_BUILDER_TEST_ID.QUERY_BUILDER_TDS_PROJECTION_COLUMN
737
+ }
738
+ className="query-builder__projection__column__container"
739
+ >
736
740
  <PanelEntryDragHandle
737
741
  isDragging={false}
738
742
  dragSourceConnector={dragHandleRef}
@@ -113,6 +113,7 @@ import {
113
113
  import { QueryBuilderTelemetryHelper } from '../../__lib__/QueryBuilderTelemetryHelper.js';
114
114
  import { getPropertyChainName } from '../../stores/QueryBuilderPropertyEditorState.js';
115
115
  import { QUERY_BUILDER_SUPPORTED_FUNCTIONS } from '../../graph/QueryBuilderMetaModelConst.js';
116
+ import { buildPropertyExpressionChain } from '../../stores/QueryBuilderValueSpecificationBuilderHelper.js';
116
117
 
117
118
  const isCollectionProperty = (
118
119
  propertyExpression: AbstractPropertyExpression,
@@ -151,7 +152,7 @@ const isCollectionProperty = (
151
152
  export const buildFilterTreeWithExists = (
152
153
  propertyExpression: AbstractPropertyExpression,
153
154
  filterState: QueryBuilderFilterState,
154
- targetDropNode?: QueryBuilderFilterTreeOperationNodeData,
155
+ targetDropNode?: QueryBuilderFilterTreeNodeData,
155
156
  ): void => {
156
157
  // 1. Decompose property expression
157
158
  const expressions: (AbstractPropertyExpression | SimpleFunctionExpression)[] =
@@ -294,7 +295,21 @@ export const buildFilterTreeWithExists = (
294
295
  .value.path,
295
296
  );
296
297
  if (parentPropertyChainIndex >= 0) {
297
- parentNode = targetDropNode;
298
+ // Here we choose parentNode based on what the type of targetDropNode
299
+ // 1. QueryBuilderFilterTreeConditionNodeData: In this case we would want to
300
+ // add a new group condition from the targetDropNode and add new exists node
301
+ // getting created as it's child so the parent would be the new group node
302
+ // 2. QueryBuilderFilterTreeGroupNodeData: Parent node would be same as
303
+ // targetDropNode
304
+ // 3. QueryBuilderFilterTreeBlankConditionNodeData: parentNode would be the
305
+ // parent of the targetDropNode. At the end we would deelete this blank node
306
+ // that got created.
307
+ parentNode =
308
+ targetDropNode instanceof QueryBuilderFilterTreeConditionNodeData
309
+ ? filterState.newGroupConditionFromNode(targetDropNode)
310
+ : targetDropNode instanceof QueryBuilderFilterTreeGroupNodeData
311
+ ? targetDropNode
312
+ : filterState.getParentNode(targetDropNode);
298
313
  existsLambdaPropertyChains = existsLambdaPropertyChains.slice(
299
314
  parentPropertyChainIndex + 1,
300
315
  );
@@ -303,7 +318,12 @@ export const buildFilterTreeWithExists = (
303
318
  );
304
319
  }
305
320
  } else if (!parentId) {
306
- parentNode = targetDropNode;
321
+ parentNode =
322
+ targetDropNode instanceof QueryBuilderFilterTreeConditionNodeData
323
+ ? filterState.newGroupConditionFromNode(targetDropNode)
324
+ : targetDropNode instanceof QueryBuilderFilterTreeGroupNodeData
325
+ ? targetDropNode
326
+ : filterState.getParentNode(targetDropNode);
307
327
  }
308
328
  }
309
329
  }
@@ -332,6 +352,127 @@ export const buildFilterTreeWithExists = (
332
352
  filterConditionState,
333
353
  );
334
354
  filterState.addNodeFromNode(treeNode, parentNode);
355
+ if (targetDropNode instanceof QueryBuilderFilterTreeBlankConditionNodeData) {
356
+ filterState.removeNodeAndPruneBranch(targetDropNode);
357
+ }
358
+ };
359
+
360
+ export const buildPropertyExpressionFromExistsNode = (
361
+ filterState: QueryBuilderFilterState,
362
+ existsNode: QueryBuilderFilterTreeExistsNodeData,
363
+ node: QueryBuilderFilterTreeConditionNodeData,
364
+ ): AbstractPropertyExpression => {
365
+ let nodeParent = filterState.getParentNode(node);
366
+ let existsLambdaParameterNames: string[] = [];
367
+ let existsLambdaExpressions: AbstractPropertyExpression[] = [];
368
+ existsLambdaExpressions.push(
369
+ node.condition.propertyExpressionState.propertyExpression,
370
+ );
371
+ existsLambdaParameterNames.push(
372
+ nodeParent?.lambdaParameterName ?? filterState.lambdaParameterName,
373
+ );
374
+ while (nodeParent && nodeParent.id !== existsNode.id) {
375
+ if (nodeParent instanceof QueryBuilderFilterTreeExistsNodeData) {
376
+ existsLambdaExpressions.push(
377
+ nodeParent.propertyExpressionState.propertyExpression,
378
+ );
379
+ existsLambdaParameterNames.push(
380
+ nodeParent.lambdaParameterName ?? filterState.lambdaParameterName,
381
+ );
382
+ }
383
+ nodeParent = filterState.getParentNode(nodeParent);
384
+ }
385
+ if (nodeParent?.id === existsNode.id) {
386
+ existsLambdaExpressions.push(
387
+ existsNode.propertyExpressionState.propertyExpression,
388
+ );
389
+ existsLambdaParameterNames.push(
390
+ existsNode.lambdaParameterName ?? filterState.lambdaParameterName,
391
+ );
392
+ }
393
+ existsLambdaParameterNames = existsLambdaParameterNames.reverse();
394
+ existsLambdaExpressions = existsLambdaExpressions.reverse();
395
+
396
+ const initialPropertyExpression = guaranteeNonNullable(
397
+ existsLambdaExpressions[0],
398
+ );
399
+ existsLambdaParameterNames = existsLambdaParameterNames.slice(1);
400
+ existsLambdaExpressions = existsLambdaExpressions.slice(1);
401
+
402
+ let flattenedPropertyExpressionChain = new AbstractPropertyExpression('');
403
+ flattenedPropertyExpressionChain.func = initialPropertyExpression.func;
404
+ flattenedPropertyExpressionChain.parametersValues = [
405
+ ...initialPropertyExpression.parametersValues,
406
+ ];
407
+ for (const exp of existsLambdaExpressions) {
408
+ // when rebuilding the property expression chain, disregard the initial variable that starts the chain
409
+ const expressions: (
410
+ | AbstractPropertyExpression
411
+ | SimpleFunctionExpression
412
+ )[] = [];
413
+ let currentExpression: ValueSpecification = exp;
414
+ while (
415
+ currentExpression instanceof AbstractPropertyExpression ||
416
+ (currentExpression instanceof SimpleFunctionExpression &&
417
+ matchFunctionName(
418
+ currentExpression.functionName,
419
+ QUERY_BUILDER_SUPPORTED_FUNCTIONS.SUBTYPE,
420
+ ))
421
+ ) {
422
+ if (currentExpression instanceof SimpleFunctionExpression) {
423
+ const functionExpression = new SimpleFunctionExpression(
424
+ extractElementNameFromPath(QUERY_BUILDER_SUPPORTED_FUNCTIONS.SUBTYPE),
425
+ );
426
+ functionExpression.parametersValues.unshift(
427
+ guaranteeNonNullable(currentExpression.parametersValues[1]),
428
+ );
429
+ expressions.push(functionExpression);
430
+ } else if (currentExpression instanceof AbstractPropertyExpression) {
431
+ const propertyExp = new AbstractPropertyExpression('');
432
+ propertyExp.func = currentExpression.func;
433
+ // NOTE: we must retain the rest of the parameters as those are derived property parameters
434
+ propertyExp.parametersValues =
435
+ currentExpression.parametersValues.length > 1
436
+ ? currentExpression.parametersValues.slice(1)
437
+ : [];
438
+ expressions.push(propertyExp);
439
+ }
440
+ currentExpression = guaranteeNonNullable(
441
+ currentExpression.parametersValues[0],
442
+ );
443
+ }
444
+ assertTrue(
445
+ expressions.length > 0,
446
+ `Can't process exists() expression: exists() usage with non-chain property expression is not supported`,
447
+ );
448
+ for (let i = 0; i < expressions.length - 1; ++i) {
449
+ (
450
+ expressions[i] as AbstractPropertyExpression | SimpleFunctionExpression
451
+ ).parametersValues.unshift(
452
+ expressions[i + 1] as
453
+ | AbstractPropertyExpression
454
+ | SimpleFunctionExpression,
455
+ );
456
+ }
457
+ (
458
+ expressions[expressions.length - 1] as
459
+ | AbstractPropertyExpression
460
+ | SimpleFunctionExpression
461
+ ).parametersValues.unshift(flattenedPropertyExpressionChain);
462
+ flattenedPropertyExpressionChain = guaranteeType(
463
+ expressions[0],
464
+ AbstractPropertyExpression,
465
+ `Can't process exists() expression: can't flatten to a property expression`,
466
+ );
467
+ }
468
+ return guaranteeType(
469
+ buildPropertyExpressionChain(
470
+ flattenedPropertyExpressionChain,
471
+ filterState.queryBuilderState,
472
+ existsNode.lambdaParameterName ?? filterState.lambdaParameterName,
473
+ ),
474
+ AbstractPropertyExpression,
475
+ );
335
476
  };
336
477
 
337
478
  /**
@@ -340,7 +481,7 @@ export const buildFilterTreeWithExists = (
340
481
  const buildFilterTree = (
341
482
  propertyExpression: AbstractPropertyExpression,
342
483
  filterState: QueryBuilderFilterState,
343
- targetDropNode?: QueryBuilderFilterTreeOperationNodeData | undefined,
484
+ targetDropNode?: QueryBuilderFilterTreeNodeData | undefined,
344
485
  ): void => {
345
486
  if (isCollectionProperty(propertyExpression)) {
346
487
  const propertyChainName = getPropertyChainName(
@@ -383,12 +524,88 @@ const buildFilterTree = (
383
524
  undefined,
384
525
  filterConditionState,
385
526
  );
386
- filterState.addNodeFromNode(
387
- treeNode,
388
- targetDropNode instanceof QueryBuilderFilterTreeGroupNodeData
389
- ? targetDropNode
390
- : undefined,
391
- );
527
+ // Check if there are any exists node present in the parent nodes of the target.
528
+ // This would change the way we build the filter tree
529
+ let cn: QueryBuilderFilterTreeNodeData | undefined = targetDropNode;
530
+ let existsNode: QueryBuilderFilterTreeExistsNodeData | undefined =
531
+ undefined;
532
+ let parentId = targetDropNode?.parentId;
533
+ while (parentId) {
534
+ cn = filterState.nodes.get(parentId);
535
+ parentId = cn?.parentId;
536
+ if (cn instanceof QueryBuilderFilterTreeExistsNodeData) {
537
+ existsNode = cn;
538
+ }
539
+ }
540
+ if (cn instanceof QueryBuilderFilterTreeExistsNodeData) {
541
+ existsNode = cn;
542
+ }
543
+ if (targetDropNode instanceof QueryBuilderFilterTreeGroupNodeData) {
544
+ if (existsNode) {
545
+ filterState.newGroupConditionFromNode(
546
+ existsNode,
547
+ treeNode,
548
+ targetDropNode.groupOperation,
549
+ );
550
+ } else {
551
+ filterState.addNodeFromNode(treeNode, targetDropNode);
552
+ }
553
+ } else if (
554
+ targetDropNode instanceof QueryBuilderFilterTreeBlankConditionNodeData
555
+ ) {
556
+ if (existsNode) {
557
+ filterState.queryBuilderState.applicationStore.notificationService.notifyError(
558
+ `Can't drag and drop here: property expression of target and source doesn't match`,
559
+ );
560
+ } else {
561
+ filterState.replaceBlankNodeWithNode(treeNode, targetDropNode);
562
+ }
563
+ } else if (
564
+ existsNode &&
565
+ targetDropNode instanceof QueryBuilderFilterTreeExistsNodeData
566
+ ) {
567
+ filterState.newGroupConditionFromNode(existsNode, treeNode);
568
+ } else if (
569
+ targetDropNode instanceof QueryBuilderFilterTreeConditionNodeData
570
+ ) {
571
+ const parentNode = filterState.getParentNode(targetDropNode);
572
+ if (
573
+ existsNode &&
574
+ parentNode instanceof QueryBuilderFilterTreeExistsNodeData &&
575
+ parentNode.childrenIds.length === 1
576
+ ) {
577
+ filterState.newGroupConditionFromNode(
578
+ existsNode,
579
+ treeNode,
580
+ QUERY_BUILDER_GROUP_OPERATION.AND,
581
+ );
582
+ } else if (
583
+ existsNode &&
584
+ parentNode instanceof QueryBuilderFilterTreeGroupNodeData
585
+ ) {
586
+ const propertyExpression1 = buildPropertyExpressionFromExistsNode(
587
+ filterState,
588
+ existsNode,
589
+ targetDropNode,
590
+ );
591
+ filterState.removeNodeAndPruneBranch(targetDropNode);
592
+ filterState.newGroupConditionFromNode(
593
+ existsNode,
594
+ treeNode,
595
+ parentNode.groupOperation,
596
+ );
597
+ const newParentNode = filterState.getParentNode(treeNode);
598
+ buildFilterTreeWithExists(
599
+ propertyExpression1,
600
+ filterState,
601
+ newParentNode,
602
+ );
603
+ } else {
604
+ filterState.newGroupWithConditionFromNode(treeNode, targetDropNode);
605
+ }
606
+ } else {
607
+ filterState.addNodeFromNode(treeNode, undefined);
608
+ }
392
609
  }
393
610
  };
394
611
 
@@ -604,7 +821,12 @@ const QueryBuilderFilterConditionEditor = observer(
604
821
  };
605
822
 
606
823
  return (
607
- <div className="query-builder-filter-tree__node__label__content dnd__entry__container">
824
+ <div
825
+ className="query-builder-filter-tree__node__label__content dnd__entry__container"
826
+ data-testid={
827
+ QUERY_BUILDER_TEST_ID.QUERY_BUILDER_FILTER_TREE_CONDITION_NODE_CONTENT
828
+ }
829
+ >
608
830
  <PanelEntryDropZonePlaceholder
609
831
  isDragOver={isDragOver}
610
832
  label="Add New Logical Group"
@@ -845,21 +1067,17 @@ const QueryBuilderFilterTreeNodeContainer = observer(
845
1067
  node,
846
1068
  );
847
1069
  } else if (node instanceof QueryBuilderFilterTreeConditionNodeData) {
848
- filterState.newGroupWithConditionFromNode(
849
- new QueryBuilderFilterTreeConditionNodeData(
850
- undefined,
851
- filterConditionState,
852
- ),
1070
+ buildFilterTree(
1071
+ filterConditionState.propertyExpressionState.propertyExpression,
1072
+ filterState,
853
1073
  node,
854
1074
  );
855
1075
  } else if (
856
1076
  node instanceof QueryBuilderFilterTreeBlankConditionNodeData
857
1077
  ) {
858
- filterState.replaceBlankNodeWithNode(
859
- new QueryBuilderFilterTreeConditionNodeData(
860
- undefined,
861
- filterConditionState,
862
- ),
1078
+ buildFilterTree(
1079
+ filterConditionState.propertyExpressionState.propertyExpression,
1080
+ filterState,
863
1081
  node,
864
1082
  );
865
1083
  }
@@ -24,6 +24,12 @@ import {
24
24
  buildSourceInformationSourceId,
25
25
  ParserError,
26
26
  GRAPH_MANAGER_EVENT,
27
+ SimpleFunctionExpression,
28
+ PrimitiveInstanceValue,
29
+ PrimitiveType,
30
+ extractElementNameFromPath,
31
+ SUPPORTED_FUNCTIONS,
32
+ INTERNAL__UnknownValueSpecification,
27
33
  } from '@finos/legend-graph';
28
34
  import {
29
35
  type Hashable,
@@ -70,6 +76,22 @@ export abstract class QueryBuilderConstantExpressionState implements Hashable {
70
76
  this.variable.name,
71
77
  ]);
72
78
  }
79
+
80
+ buildLetExpression(): SimpleFunctionExpression {
81
+ const leftSide = new PrimitiveInstanceValue(
82
+ GenericTypeExplicitReference.create(
83
+ new GenericType(PrimitiveType.STRING),
84
+ ),
85
+ );
86
+ leftSide.values = [this.variable.name];
87
+ const letFunc = new SimpleFunctionExpression(
88
+ extractElementNameFromPath(SUPPORTED_FUNCTIONS.LET),
89
+ );
90
+ letFunc.parametersValues = [leftSide, this.buildLetAssignmentValue()];
91
+ return letFunc;
92
+ }
93
+
94
+ abstract buildLetAssignmentValue(): ValueSpecification;
73
95
  }
74
96
 
75
97
  export class QueryBuilderSimpleConstantExpressionState
@@ -141,6 +163,10 @@ export class QueryBuilderSimpleConstantExpressionState
141
163
  }
142
164
  }
143
165
 
166
+ override buildLetAssignmentValue(): ValueSpecification {
167
+ return this.value;
168
+ }
169
+
144
170
  override get hashCode(): string {
145
171
  return hashArray([
146
172
  QUERY_BUILDER_STATE_HASH_STRUCTURE.CONSTANT_EXPRESSION_STATE,
@@ -272,6 +298,10 @@ export class QueryBuilderCalculatedConstantExpressionState
272
298
  setValue(val: PlainObject): void {
273
299
  this.value = val;
274
300
  }
301
+
302
+ override buildLetAssignmentValue(): ValueSpecification {
303
+ return new INTERNAL__UnknownValueSpecification(this.value);
304
+ }
275
305
  }
276
306
 
277
307
  export class QueryBuilderConstantsState implements Hashable {