@finos/legend-query-builder 4.9.4 → 4.10.1

Sign up to get free protection for your applications and to get access to all the features.
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 {