@finos/legend-query-builder 4.14.44 → 4.14.46

Sign up to get free protection for your applications and to get access to all the features.
Files changed (71) hide show
  1. package/lib/__lib__/QueryBuilderTelemetryHelper.d.ts +3 -3
  2. package/lib/__lib__/QueryBuilderTelemetryHelper.d.ts.map +1 -1
  3. package/lib/__lib__/QueryBuilderTesting.d.ts +1 -0
  4. package/lib/__lib__/QueryBuilderTesting.d.ts.map +1 -1
  5. package/lib/__lib__/QueryBuilderTesting.js +1 -0
  6. package/lib/__lib__/QueryBuilderTesting.js.map +1 -1
  7. package/lib/components/QueryBuilder.d.ts.map +1 -1
  8. package/lib/components/QueryBuilder.js +5 -2
  9. package/lib/components/QueryBuilder.js.map +1 -1
  10. package/lib/components/fetch-structure/QueryBuilderFetchStructurePanel.d.ts.map +1 -1
  11. package/lib/components/fetch-structure/QueryBuilderFetchStructurePanel.js.map +1 -1
  12. package/lib/components/fetch-structure/QueryBuilderTDSPanel.d.ts.map +1 -1
  13. package/lib/components/fetch-structure/QueryBuilderTDSPanel.js +93 -2
  14. package/lib/components/fetch-structure/QueryBuilderTDSPanel.js.map +1 -1
  15. package/lib/graph/QueryBuilderMetaModelConst.d.ts +1 -0
  16. package/lib/graph/QueryBuilderMetaModelConst.d.ts.map +1 -1
  17. package/lib/graph/QueryBuilderMetaModelConst.js +1 -0
  18. package/lib/graph/QueryBuilderMetaModelConst.js.map +1 -1
  19. package/lib/index.css +2 -2
  20. package/lib/index.css.map +1 -1
  21. package/lib/index.d.ts +1 -1
  22. package/lib/index.d.ts.map +1 -1
  23. package/lib/package.json +5 -4
  24. package/lib/stores/QueryBuilderState.d.ts +5 -5
  25. package/lib/stores/QueryBuilderState.d.ts.map +1 -1
  26. package/lib/stores/QueryBuilderState.js.map +1 -1
  27. package/lib/stores/QueryBuilderStateHashUtils.d.ts +1 -0
  28. package/lib/stores/QueryBuilderStateHashUtils.d.ts.map +1 -1
  29. package/lib/stores/QueryBuilderStateHashUtils.js +1 -0
  30. package/lib/stores/QueryBuilderStateHashUtils.js.map +1 -1
  31. package/lib/stores/fetch-structure/tds/QueryBuilderTDSState.d.ts.map +1 -1
  32. package/lib/stores/fetch-structure/tds/QueryBuilderTDSState.js +2 -0
  33. package/lib/stores/fetch-structure/tds/QueryBuilderTDSState.js.map +1 -1
  34. package/lib/stores/fetch-structure/tds/aggregation/QueryBuilderAggregateOperator.d.ts +2 -0
  35. package/lib/stores/fetch-structure/tds/aggregation/QueryBuilderAggregateOperator.d.ts.map +1 -1
  36. package/lib/stores/fetch-structure/tds/aggregation/QueryBuilderAggregateOperator.js +8 -0
  37. package/lib/stores/fetch-structure/tds/aggregation/QueryBuilderAggregateOperator.js.map +1 -1
  38. package/lib/stores/fetch-structure/tds/aggregation/QueryBuilderAggregateOperatorLoader.d.ts.map +1 -1
  39. package/lib/stores/fetch-structure/tds/aggregation/QueryBuilderAggregateOperatorLoader.js +2 -0
  40. package/lib/stores/fetch-structure/tds/aggregation/QueryBuilderAggregateOperatorLoader.js.map +1 -1
  41. package/lib/stores/fetch-structure/tds/aggregation/QueryBuilderAggregationState.d.ts +1 -0
  42. package/lib/stores/fetch-structure/tds/aggregation/QueryBuilderAggregationState.d.ts.map +1 -1
  43. package/lib/stores/fetch-structure/tds/aggregation/QueryBuilderAggregationState.js +7 -3
  44. package/lib/stores/fetch-structure/tds/aggregation/QueryBuilderAggregationState.js.map +1 -1
  45. package/lib/stores/fetch-structure/tds/aggregation/operators/QueryBuilderAggregateOperatorValueSpecificationBuilder.d.ts.map +1 -1
  46. package/lib/stores/fetch-structure/tds/aggregation/operators/QueryBuilderAggregateOperatorValueSpecificationBuilder.js.map +1 -1
  47. package/lib/stores/fetch-structure/tds/aggregation/operators/QueryBuilderAggregateOperator_Percentile.d.ts +37 -0
  48. package/lib/stores/fetch-structure/tds/aggregation/operators/QueryBuilderAggregateOperator_Percentile.d.ts.map +1 -0
  49. package/lib/stores/fetch-structure/tds/aggregation/operators/QueryBuilderAggregateOperator_Percentile.js +123 -0
  50. package/lib/stores/fetch-structure/tds/aggregation/operators/QueryBuilderAggregateOperator_Percentile.js.map +1 -0
  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 +3 -7
  53. package/lib/stores/fetch-structure/tds/post-filter/QueryBuilderPostFilterState.js.map +1 -1
  54. package/package.json +13 -12
  55. package/src/__lib__/QueryBuilderTelemetryHelper.ts +3 -3
  56. package/src/__lib__/QueryBuilderTesting.ts +1 -0
  57. package/src/components/QueryBuilder.tsx +5 -2
  58. package/src/components/fetch-structure/QueryBuilderFetchStructurePanel.tsx +0 -1
  59. package/src/components/fetch-structure/QueryBuilderTDSPanel.tsx +208 -4
  60. package/src/graph/QueryBuilderMetaModelConst.ts +1 -0
  61. package/src/index.ts +1 -1
  62. package/src/stores/QueryBuilderState.ts +5 -5
  63. package/src/stores/QueryBuilderStateHashUtils.ts +1 -0
  64. package/src/stores/fetch-structure/tds/QueryBuilderTDSState.ts +5 -0
  65. package/src/stores/fetch-structure/tds/aggregation/QueryBuilderAggregateOperator.ts +10 -0
  66. package/src/stores/fetch-structure/tds/aggregation/QueryBuilderAggregateOperatorLoader.ts +2 -0
  67. package/src/stores/fetch-structure/tds/aggregation/QueryBuilderAggregationState.ts +9 -3
  68. package/src/stores/fetch-structure/tds/aggregation/operators/QueryBuilderAggregateOperatorValueSpecificationBuilder.ts +0 -1
  69. package/src/stores/fetch-structure/tds/aggregation/operators/QueryBuilderAggregateOperator_Percentile.ts +241 -0
  70. package/src/stores/fetch-structure/tds/post-filter/QueryBuilderPostFilterState.ts +3 -7
  71. package/tsconfig.json +1 -0
@@ -41,6 +41,8 @@ import {
41
41
  FunctionIcon,
42
42
  CogIcon,
43
43
  InfoCircleIcon,
44
+ BasePopover,
45
+ InputWithInlineValidation,
44
46
  } from '@finos/legend-art';
45
47
  import {
46
48
  type QueryBuilderExplorerTreeDragSource,
@@ -116,6 +118,7 @@ import {
116
118
  QueryBuilderPropertyInfoTooltip,
117
119
  } from '../shared/QueryBuilderPropertyInfoTooltip.js';
118
120
  import { getNameOfValueSpecification } from '../shared/QueryBuilderVariableSelector.js';
121
+ import { QueryBuilderAggregateOperator_Percentile } from '../../stores/fetch-structure/tds/aggregation/operators/QueryBuilderAggregateOperator_Percentile.js';
119
122
 
120
123
  const QueryBuilderProjectionColumnContextMenu = observer(
121
124
  forwardRef<
@@ -689,6 +692,52 @@ const QueryBuilderProjectionColumnEditor = observer(
689
692
  }),
690
693
  [handleDrop],
691
694
  );
695
+ const percentileButtonRef = useRef<HTMLButtonElement>(null);
696
+ const percentileInputRef = useRef<HTMLInputElement>(null);
697
+ const [isPercentileOpen, setIsPercentileOpen] = useState(false);
698
+ const [percentileValue, setPercentileValue] = useState(
699
+ aggregateColumnState &&
700
+ aggregateColumnState.operator instanceof
701
+ QueryBuilderAggregateOperator_Percentile &&
702
+ aggregateColumnState.operator.percentile !== undefined
703
+ ? (aggregateColumnState.operator.percentile * 100).toString()
704
+ : '',
705
+ );
706
+ const [acending, setAcending] = useState(
707
+ aggregateColumnState &&
708
+ aggregateColumnState.operator instanceof
709
+ QueryBuilderAggregateOperator_Percentile
710
+ ? aggregateColumnState.operator.acending
711
+ : undefined,
712
+ );
713
+ const [continuous, setContinuous] = useState(
714
+ aggregateColumnState &&
715
+ aggregateColumnState.operator instanceof
716
+ QueryBuilderAggregateOperator_Percentile
717
+ ? aggregateColumnState.operator.continuous
718
+ : undefined,
719
+ );
720
+ const percentileOptions = ['true', 'false'].map((op) => ({
721
+ label: op,
722
+ value: op,
723
+ }));
724
+ const getPercentileDisplayValue = (): string => {
725
+ if (percentileValue === '') {
726
+ return '...';
727
+ }
728
+ if (acending === undefined || continuous === undefined) {
729
+ return `${Number(percentileValue)}`;
730
+ }
731
+ return `${Number(percentileValue)}, ${acending}, ${continuous}`;
732
+ };
733
+ const setPercentileArguments = (): void => {
734
+ setIsPercentileOpen(!isPercentileOpen);
735
+ };
736
+ const onPercentileValueChange: React.ChangeEventHandler<
737
+ HTMLInputElement
738
+ > = (event) => {
739
+ setPercentileValue(event.target.value);
740
+ };
692
741
 
693
742
  return (
694
743
  <PanelDnDEntry
@@ -817,12 +866,167 @@ const QueryBuilderProjectionColumnEditor = observer(
817
866
  <div className="query-builder__projection__column__aggregate">
818
867
  <div className="query-builder__projection__column__aggregate__operator">
819
868
  {aggregateColumnState && (
820
- <div className="query-builder__projection__column__aggregate__operator__label">
821
- {aggregateColumnState.operator.getLabel(
822
- projectionColumnState,
823
- )}
869
+ <div className="query-builder__projection__column__aggregate__operator">
870
+ <div className="query-builder__projection__column__aggregate__operator__label">
871
+ {aggregateColumnState.operator.getLabel(
872
+ projectionColumnState,
873
+ )}
874
+ {aggregateColumnState.operator instanceof
875
+ QueryBuilderAggregateOperator_Percentile && (
876
+ <button
877
+ className="query-builder__projection__column__aggregate__operator__percentile__badge"
878
+ ref={percentileButtonRef}
879
+ onClick={setPercentileArguments}
880
+ title="Set Percentile Argument(s)..."
881
+ >
882
+ {getPercentileDisplayValue()}
883
+ </button>
884
+ )}
885
+ </div>
824
886
  </div>
825
887
  )}
888
+ {aggregateColumnState && isPercentileOpen && (
889
+ <BasePopover
890
+ open={isPercentileOpen}
891
+ PaperProps={{
892
+ classes: {
893
+ root: 'query-builder__projection__column__aggregate__operator__percentile__container__root',
894
+ },
895
+ }}
896
+ className={clsx(
897
+ 'query-builder__projection__column__aggregate__operator__percentile__container',
898
+ )}
899
+ anchorEl={percentileButtonRef.current}
900
+ onClose={() => {
901
+ const percentileOperator =
902
+ aggregateColumnState.operator as QueryBuilderAggregateOperator_Percentile;
903
+ if (percentileValue === '') {
904
+ percentileOperator.setPercentile(undefined);
905
+ } else {
906
+ percentileOperator.setPercentile(
907
+ Number(percentileValue) / 100,
908
+ );
909
+ }
910
+ if (acending !== undefined && continuous !== undefined) {
911
+ percentileOperator.setAcending(acending);
912
+ percentileOperator.setContinuous(continuous);
913
+ }
914
+ setIsPercentileOpen(false);
915
+ }}
916
+ anchorOrigin={{
917
+ vertical: 'bottom',
918
+ horizontal: 'left',
919
+ }}
920
+ transformOrigin={{
921
+ vertical: 'top',
922
+ horizontal: 'center',
923
+ }}
924
+ >
925
+ <div
926
+ data-testid={
927
+ QUERY_BUILDER_TEST_ID.QUERY_BUILDER_PERCENTILE_PANEL
928
+ }
929
+ className=""
930
+ >
931
+ <div className="query-builder__projection__column__aggregate__operator__percentile__argument-combo">
932
+ <div className="query-builder__projection__column__aggregate__operator__percentile__argument-combo__label">
933
+ Percentile
934
+ <br />
935
+ (0-100)
936
+ </div>
937
+ <div
938
+ className={clsx(
939
+ 'query-builder__projection__column__aggregate__operator__percentile__argument-combo__value',
940
+ )}
941
+ >
942
+ <InputWithInlineValidation
943
+ ref={percentileInputRef}
944
+ className={clsx(
945
+ 'query-builder__projection__column__aggregate__operator__percentile__input input--dark',
946
+ )}
947
+ type="text"
948
+ inputMode="numeric"
949
+ onChange={onPercentileValueChange}
950
+ value={percentileValue}
951
+ error={
952
+ percentileValue && Number(percentileValue) > 100
953
+ ? `Invalid aggregation agruement for ${aggregateColumnState.columnName}`
954
+ : undefined
955
+ }
956
+ />
957
+ </div>
958
+ </div>
959
+ <div className="query-builder__projection__column__aggregate__operator__percentile__argument-combo">
960
+ <div className="query-builder__projection__column__aggregate__operator__percentile__argument-combo__label">
961
+ Acending
962
+ </div>
963
+ <CustomSelectorInput
964
+ className="query-loader__results__sort-by__selector query-builder__projection__column__aggregate__operator__percentile__argument-combo__value"
965
+ options={percentileOptions}
966
+ onChange={(option: {
967
+ label: string;
968
+ value: string;
969
+ }) => {
970
+ setAcending(option.value === 'true' ? true : false);
971
+ }}
972
+ value={{
973
+ label:
974
+ acending === undefined
975
+ ? ''
976
+ : acending
977
+ ? 'true'
978
+ : 'false',
979
+ value:
980
+ acending === undefined
981
+ ? ''
982
+ : acending
983
+ ? 'true'
984
+ : 'false',
985
+ }}
986
+ darkMode={
987
+ !applicationStore.layoutService
988
+ .TEMPORARY__isLightColorThemeEnabled
989
+ }
990
+ />
991
+ </div>
992
+ <div className="query-builder__projection__column__aggregate__operator__percentile__argument-combo">
993
+ <div className="query-builder__projection__column__aggregate__operator__percentile__argument-combo__label">
994
+ Continous
995
+ </div>
996
+ <CustomSelectorInput
997
+ className="query-loader__results__sort-by__selector query-builder__projection__column__aggregate__operator__percentile__argument-combo__value"
998
+ options={percentileOptions}
999
+ onChange={(option: {
1000
+ label: string;
1001
+ value: string;
1002
+ }) =>
1003
+ setContinuous(
1004
+ option.value === 'true' ? true : false,
1005
+ )
1006
+ }
1007
+ value={{
1008
+ label:
1009
+ continuous === undefined
1010
+ ? ''
1011
+ : continuous
1012
+ ? 'true'
1013
+ : 'false',
1014
+ value:
1015
+ continuous === undefined
1016
+ ? ''
1017
+ : continuous
1018
+ ? 'true'
1019
+ : 'false',
1020
+ }}
1021
+ darkMode={
1022
+ !applicationStore.layoutService
1023
+ .TEMPORARY__isLightColorThemeEnabled
1024
+ }
1025
+ />
1026
+ </div>
1027
+ </div>
1028
+ </BasePopover>
1029
+ )}
826
1030
  {isCalendarEnabled &&
827
1031
  aggregateColumnState &&
828
1032
  aggregateCalendarFunctions.length > 0 && (
@@ -163,6 +163,7 @@ export enum QUERY_BUILDER_SUPPORTED_FUNCTIONS {
163
163
  FROM = 'meta::pure::mapping::from',
164
164
  CHECKED = 'meta::pure::dataQuality::checked',
165
165
  MERGERUNTIMES = 'meta::pure::runtime::mergeRuntimes',
166
+ PERCENTILE = 'meta::pure::functions::math::percentile',
166
167
 
167
168
  // TOTDS
168
169
  TABLE_TO_TDS = 'meta::pure::tds::tableToTDS',
package/src/index.ts CHANGED
@@ -30,7 +30,7 @@ export { QueryBuilderNavigationBlocker } from './components/QueryBuilderNavigati
30
30
  export { QueryBuilder } from './components/QueryBuilder.js';
31
31
  export { QUERY_BUILDER_COMPONENT_ELEMENT_ID } from './components/QueryBuilderComponentElement.js';
32
32
  export {
33
- type QuerySDLC,
33
+ type QueryableSourceInfo as QuerySDLC,
34
34
  QueryBuilderState,
35
35
  } from './stores/QueryBuilderState.js';
36
36
  export {
@@ -111,9 +111,9 @@ import { QUERY_BUILDER_EVENT } from '../__lib__/QueryBuilderEvent.js';
111
111
  import { QueryBuilderChangeHistoryState } from './QueryBuilderChangeHistoryState.js';
112
112
  import { type QueryBuilderWorkflowState } from './query-workflow/QueryBuilderWorkFlowState.js';
113
113
 
114
- export interface QuerySDLC {}
114
+ export interface QueryableSourceInfo {}
115
115
 
116
- export type QueryStateInfo = QuerySDLC & {
116
+ export type QueryableClassMappingRuntimeInfo = QueryableSourceInfo & {
117
117
  class: string;
118
118
  mapping: string;
119
119
  runtime: string;
@@ -169,7 +169,7 @@ export abstract class QueryBuilderState implements CommandRegistrar {
169
169
  // NOTE: This property contains information about workflow used
170
170
  // to create this state. This should only be used to add additional
171
171
  // information to query builder analytics.
172
- sourceInfo?: QuerySDLC | undefined;
172
+ sourceInfo?: QueryableSourceInfo | undefined;
173
173
 
174
174
  // NOTE: this makes it so that we need to import components in stores code,
175
175
  // we probably want to refactor to an extension mechanism
@@ -180,7 +180,7 @@ export abstract class QueryBuilderState implements CommandRegistrar {
180
180
  graphManagerState: GraphManagerState,
181
181
  workflowState: QueryBuilderWorkflowState,
182
182
  config: QueryBuilderConfig | undefined,
183
- sourceInfo?: QuerySDLC | undefined,
183
+ sourceInfo?: QueryableSourceInfo | undefined,
184
184
  ) {
185
185
  makeObservable(this, {
186
186
  explorerState: observable,
@@ -341,7 +341,7 @@ export abstract class QueryBuilderState implements CommandRegistrar {
341
341
  * Gets information about the current queryBuilderState.
342
342
  * This information can be used as a part of analytics
343
343
  */
344
- getStateInfo(): QueryStateInfo | undefined {
344
+ getStateInfo(): QueryableClassMappingRuntimeInfo | undefined {
345
345
  if (this.sourceInfo) {
346
346
  const classPath = this.class?.path;
347
347
  const mappingPath = this.executionContextState.mapping?.path;
@@ -32,6 +32,7 @@ export enum QUERY_BUILDER_STATE_HASH_STRUCTURE {
32
32
  AGGREGATE_OPERATOR_STD_DEV_SAMPLE = 'AGGREGATE_OPERATOR_STD_DEV_SAMPLE',
33
33
  AGGREGATE_OPERATOR_SUM = 'AGGREGATE_OPERATOR_SUM',
34
34
  AGGREGATE_COLUMN_STATE = 'AGGREGATE_COLUMN_STATE',
35
+ AGGREGATE_OPERATOR_PERCENTILE = 'AGGREGATE_OPERATOR_PERCENTILE',
35
36
  AGGREGATION_STATE = 'AGGREGATION_STATE',
36
37
  SIMPLE_PROJECTION_COLUMN_STATE = 'SIMPLE_PROJECTION_COLUMN_STATE',
37
38
  PROPERTY_EXPRESSION_STATE = 'PROPERTY_EXPRESSION_STATE',
@@ -139,6 +139,7 @@ export class QueryBuilderTDSState
139
139
  super(queryBuilderState, fetchStructureState);
140
140
 
141
141
  makeObservable(this, {
142
+ aggregationState: observable,
142
143
  projectionColumns: observable,
143
144
  isConvertDerivationProjectionObjects: observable,
144
145
  showPostFilterPanel: observable,
@@ -343,6 +344,10 @@ export class QueryBuilderTDSState
343
344
  }
344
345
  });
345
346
 
347
+ this.aggregationState.allValidationIssues.forEach((issue) =>
348
+ validationIssues.push(issue),
349
+ );
350
+
346
351
  return validationIssues;
347
352
  }
348
353
 
@@ -35,6 +35,8 @@ export abstract class QueryBuilderAggregateOperator implements Hashable {
35
35
 
36
36
  constructor() {
37
37
  makeObservable(this, {
38
+ getOperator: computed,
39
+ allValidationIssues: computed,
38
40
  hashCode: computed,
39
41
  });
40
42
  }
@@ -84,5 +86,13 @@ export abstract class QueryBuilderAggregateOperator implements Hashable {
84
86
  return aggregateColumnState.projectionColumnState.getColumnType();
85
87
  }
86
88
 
89
+ get getOperator(): QueryBuilderAggregateOperator {
90
+ return this;
91
+ }
92
+
93
+ get allValidationIssues(): string[] {
94
+ return [];
95
+ }
96
+
87
97
  abstract get hashCode(): string;
88
98
  }
@@ -25,6 +25,7 @@ import { QueryBuilderAggregateOperator_Min } from './operators/QueryBuilderAggre
25
25
  import { QueryBuilderAggregateOperator_Max } from './operators/QueryBuilderAggregateOperator_Max.js';
26
26
  import { QueryBuilderAggregateOperator_JoinString } from './operators/QueryBuilderAggregateOperator_JoinString.js';
27
27
  import type { QueryBuilderAggregateOperator } from './QueryBuilderAggregateOperator.js';
28
+ import { QueryBuilderAggregateOperator_Percentile } from './operators/QueryBuilderAggregateOperator_Percentile.js';
28
29
 
29
30
  export const getQueryBuilderCoreAggregrationOperators =
30
31
  (): QueryBuilderAggregateOperator[] => [
@@ -35,6 +36,7 @@ export const getQueryBuilderCoreAggregrationOperators =
35
36
  new QueryBuilderAggregateOperator_Average(),
36
37
  new QueryBuilderAggregateOperator_Min(),
37
38
  new QueryBuilderAggregateOperator_Max(),
39
+ new QueryBuilderAggregateOperator_Percentile(),
38
40
  new QueryBuilderAggregateOperator_StdDev_Population(),
39
41
  new QueryBuilderAggregateOperator_StdDev_Sample(),
40
42
  new QueryBuilderAggregateOperator_JoinString(),
@@ -136,6 +136,8 @@ export class QueryBuilderAggregationState implements Hashable {
136
136
  addColumn: action,
137
137
  changeColumnAggregateOperator: action,
138
138
  disableCalendar: action,
139
+
140
+ allValidationIssues: computed,
139
141
  hashCode: computed,
140
142
  });
141
143
 
@@ -179,7 +181,7 @@ export class QueryBuilderAggregationState implements Hashable {
179
181
  `${colName} (${val.getLabel(projectionColumnState)})`,
180
182
  );
181
183
  }
182
- aggregateColumnState.setOperator(val);
184
+ aggregateColumnState.setOperator(val.getOperator);
183
185
  } else {
184
186
  if (!hideOperatorInColumnName) {
185
187
  projectionColumnState.setColumnName(
@@ -189,9 +191,9 @@ export class QueryBuilderAggregationState implements Hashable {
189
191
  const newAggregateColumnState = new QueryBuilderAggregateColumnState(
190
192
  this,
191
193
  projectionColumnState,
192
- val,
194
+ val.getOperator,
193
195
  );
194
- newAggregateColumnState.setOperator(val);
196
+ newAggregateColumnState.setOperator(val.getOperator);
195
197
  this.addColumn(newAggregateColumnState);
196
198
 
197
199
  // automatically move the column to the end
@@ -233,6 +235,10 @@ export class QueryBuilderAggregationState implements Hashable {
233
235
  });
234
236
  }
235
237
 
238
+ get allValidationIssues(): string[] {
239
+ return this.columns.map((col) => col.operator.allValidationIssues).flat();
240
+ }
241
+
236
242
  get hashCode(): string {
237
243
  return hashArray([
238
244
  QUERY_BUILDER_STATE_HASH_STRUCTURE.AGGREGATION_STATE,
@@ -97,7 +97,6 @@ export const buildAggregateColumnState = (
97
97
  )}() expression: property is not compatible with operator`,
98
98
  );
99
99
  aggregateColumnState.setOperator(operator);
100
-
101
100
  return aggregateColumnState;
102
101
  }
103
102
 
@@ -0,0 +1,241 @@
1
+ /**
2
+ * Copyright (c) 2020-present, Goldman Sachs
3
+ *
4
+ * Licensed under the Apache License, Version 2.0 (the "License");
5
+ * you may not use this file except in compliance with the License.
6
+ * You may obtain a copy of the License at
7
+ *
8
+ * http://www.apache.org/licenses/LICENSE-2.0
9
+ *
10
+ * Unless required by applicable law or agreed to in writing, software
11
+ * distributed under the License is distributed on an "AS IS" BASIS,
12
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ * See the License for the specific language governing permissions and
14
+ * limitations under the License.
15
+ */
16
+
17
+ import {
18
+ matchFunctionName,
19
+ PRIMITIVE_TYPE,
20
+ type ValueSpecification,
21
+ SimpleFunctionExpression,
22
+ VariableExpression,
23
+ type PureModel,
24
+ type AbstractPropertyExpression,
25
+ GenericType,
26
+ GenericTypeExplicitReference,
27
+ Multiplicity,
28
+ PrimitiveInstanceValue,
29
+ PrimitiveType,
30
+ extractElementNameFromPath,
31
+ } from '@finos/legend-graph';
32
+ import { QueryBuilderAggregateColumnState } from '../QueryBuilderAggregationState.js';
33
+ import { QueryBuilderAggregateOperator } from '../QueryBuilderAggregateOperator.js';
34
+ import {
35
+ type QueryBuilderProjectionColumnState,
36
+ QueryBuilderSimpleProjectionColumnState,
37
+ } from '../../projection/QueryBuilderProjectionColumnState.js';
38
+ import { QUERY_BUILDER_SUPPORTED_FUNCTIONS } from '../../../../../graph/QueryBuilderMetaModelConst.js';
39
+ import {
40
+ type Hashable,
41
+ hashArray,
42
+ assertTrue,
43
+ guaranteeType,
44
+ } from '@finos/legend-shared';
45
+ import { QUERY_BUILDER_STATE_HASH_STRUCTURE } from '../../../../QueryBuilderStateHashUtils.js';
46
+ import { action, makeObservable, observable } from 'mobx';
47
+
48
+ export class QueryBuilderAggregateOperator_Percentile
49
+ extends QueryBuilderAggregateOperator
50
+ implements Hashable
51
+ {
52
+ percentile: number | undefined;
53
+ acending: boolean | undefined;
54
+ continuous: boolean | undefined;
55
+
56
+ constructor() {
57
+ super();
58
+ makeObservable(this, {
59
+ percentile: observable,
60
+ acending: observable,
61
+ continuous: observable,
62
+ setPercentile: action,
63
+ setAcending: action,
64
+ setContinuous: action,
65
+ });
66
+ }
67
+
68
+ setPercentile(val: number | undefined) {
69
+ this.percentile = val;
70
+ }
71
+
72
+ setAcending(val: boolean | undefined) {
73
+ this.acending = val;
74
+ }
75
+
76
+ setContinuous(val: boolean | undefined) {
77
+ this.continuous = val;
78
+ }
79
+
80
+ getLabel(projectionColumnState: QueryBuilderProjectionColumnState): string {
81
+ return 'percentile';
82
+ }
83
+
84
+ isCompatibleWithColumn(
85
+ projectionColumnState: QueryBuilderProjectionColumnState,
86
+ ): boolean {
87
+ if (
88
+ projectionColumnState instanceof QueryBuilderSimpleProjectionColumnState
89
+ ) {
90
+ const propertyType =
91
+ projectionColumnState.propertyExpressionState.propertyExpression.func
92
+ .value.genericType.value.rawType;
93
+ return (
94
+ [
95
+ PRIMITIVE_TYPE.NUMBER,
96
+ PRIMITIVE_TYPE.INTEGER,
97
+ PRIMITIVE_TYPE.DECIMAL,
98
+ PRIMITIVE_TYPE.FLOAT,
99
+ ] as string[]
100
+ ).includes(propertyType.path);
101
+ }
102
+ return true;
103
+ }
104
+
105
+ buildAggregateExpression(
106
+ propertyExpression: AbstractPropertyExpression | undefined,
107
+ variableName: string,
108
+ graph: PureModel,
109
+ ): ValueSpecification {
110
+ const percentileValue = this.percentile ?? 0;
111
+ const expression = new SimpleFunctionExpression(
112
+ extractElementNameFromPath(QUERY_BUILDER_SUPPORTED_FUNCTIONS.PERCENTILE),
113
+ );
114
+ const percentile = new PrimitiveInstanceValue(
115
+ GenericTypeExplicitReference.create(
116
+ new GenericType(PrimitiveType.NUMBER),
117
+ ),
118
+ );
119
+ percentile.values = [percentileValue];
120
+ if (
121
+ this.acending === undefined ||
122
+ this.continuous === undefined ||
123
+ (this.acending && this.continuous)
124
+ ) {
125
+ expression.parametersValues.push(
126
+ new VariableExpression(variableName, Multiplicity.ONE),
127
+ percentile,
128
+ );
129
+ } else {
130
+ const acending = new PrimitiveInstanceValue(
131
+ GenericTypeExplicitReference.create(
132
+ new GenericType(PrimitiveType.BOOLEAN),
133
+ ),
134
+ );
135
+ acending.values = [this.acending];
136
+ const continuous = new PrimitiveInstanceValue(
137
+ GenericTypeExplicitReference.create(
138
+ new GenericType(PrimitiveType.BOOLEAN),
139
+ ),
140
+ );
141
+ continuous.values = [this.continuous];
142
+ expression.parametersValues.push(
143
+ new VariableExpression(variableName, Multiplicity.ONE),
144
+ percentile,
145
+ acending,
146
+ continuous,
147
+ );
148
+ }
149
+ return expression;
150
+ }
151
+
152
+ buildAggregateColumnState(
153
+ expression: SimpleFunctionExpression,
154
+ lambdaParam: VariableExpression,
155
+ projectionColumnState: QueryBuilderProjectionColumnState,
156
+ ): QueryBuilderAggregateColumnState | undefined {
157
+ if (
158
+ matchFunctionName(
159
+ expression.functionName,
160
+ QUERY_BUILDER_SUPPORTED_FUNCTIONS.PERCENTILE,
161
+ )
162
+ ) {
163
+ const aggregateColumnState = new QueryBuilderAggregateColumnState(
164
+ projectionColumnState.tdsState.aggregationState,
165
+ projectionColumnState,
166
+ new QueryBuilderAggregateOperator_Percentile(),
167
+ );
168
+
169
+ const currentOperator = guaranteeType(
170
+ aggregateColumnState.operator,
171
+ QueryBuilderAggregateOperator_Percentile,
172
+ );
173
+
174
+ aggregateColumnState.setLambdaParameterName(lambdaParam.name);
175
+
176
+ assertTrue(
177
+ [2, 4].includes(expression.parametersValues.length),
178
+ `Can't process percentile() expression: percentile() expects 2 or 4 argument`,
179
+ );
180
+
181
+ // variable
182
+ const variableExpression = guaranteeType(
183
+ expression.parametersValues[0],
184
+ VariableExpression,
185
+ `Can't process percentile() expression: only support percentile() immediately following a variable expression`,
186
+ );
187
+ assertTrue(
188
+ aggregateColumnState.lambdaParameterName === variableExpression.name,
189
+ `Can't process percentile() expression: expects variable used in lambda body '${variableExpression.name}' to match lambda parameter '${aggregateColumnState.lambdaParameterName}'`,
190
+ );
191
+ const percentile = guaranteeType(
192
+ expression.parametersValues[1],
193
+ PrimitiveInstanceValue,
194
+ `Can't process percentile() expression: percentile() expects arugment #2 to be a primitive instance value`,
195
+ );
196
+ currentOperator.percentile = percentile.values[0] as number;
197
+
198
+ if (expression.parametersValues.length === 4) {
199
+ const acending = guaranteeType(
200
+ expression.parametersValues[2],
201
+ PrimitiveInstanceValue,
202
+ `Can't process percentile() expression: percentile() expects arugment #3 to be a primitive instance value`,
203
+ );
204
+ currentOperator.acending = acending.values[0] as boolean;
205
+ const continuous = guaranteeType(
206
+ expression.parametersValues[3],
207
+ PrimitiveInstanceValue,
208
+ `Can't process percentile() expression: percentile() expects arugment #4 to be a primitive instance value`,
209
+ );
210
+ currentOperator.continuous = continuous.values[0] as boolean;
211
+ }
212
+ // operator
213
+ assertTrue(
214
+ this.isCompatibleWithColumn(aggregateColumnState.projectionColumnState),
215
+ `Can't process percentile() expression: property is not compatible with operator`,
216
+ );
217
+ aggregateColumnState.setOperator(currentOperator);
218
+ return aggregateColumnState;
219
+ }
220
+
221
+ return undefined;
222
+ }
223
+
224
+ override get getOperator(): QueryBuilderAggregateOperator {
225
+ return new QueryBuilderAggregateOperator_Percentile();
226
+ }
227
+
228
+ override get allValidationIssues(): string[] {
229
+ const validationIssues: string[] = [];
230
+ if (this.percentile === undefined || this.percentile > 100) {
231
+ validationIssues.push('Invalid Aggregation Argument for Percentile');
232
+ }
233
+ return validationIssues;
234
+ }
235
+
236
+ get hashCode(): string {
237
+ return hashArray([
238
+ QUERY_BUILDER_STATE_HASH_STRUCTURE.AGGREGATE_OPERATOR_PERCENTILE,
239
+ ]);
240
+ }
241
+ }
@@ -1000,12 +1000,10 @@ export class QueryBuilderPostFilterState
1000
1000
  node.condition.rightConditionValue instanceof
1001
1001
  PostFilterValueSpecConditionValueState &&
1002
1002
  node.condition.rightConditionValue.value instanceof InstanceValue &&
1003
- !isValidInstanceValue(node.condition.rightConditionValue.value) &&
1004
- node.condition.leftConditionValue instanceof
1005
- QueryBuilderSimpleProjectionColumnState
1003
+ !isValidInstanceValue(node.condition.rightConditionValue.value)
1006
1004
  ) {
1007
1005
  validationIssues.push(
1008
- `Filter value for ${node.condition.leftConditionValue.propertyExpressionState.title} is missing or invalid`,
1006
+ `Filter value for ${node.condition.leftConditionValue.columnName} is missing or invalid`,
1009
1007
  );
1010
1008
  }
1011
1009
  if (
@@ -1029,9 +1027,7 @@ export class QueryBuilderPostFilterState
1029
1027
  node.condition.rightConditionValue instanceof
1030
1028
  PostFilterValueSpecConditionValueState &&
1031
1029
  node.condition.rightConditionValue.value instanceof InstanceValue &&
1032
- !isValidInstanceValue(node.condition.rightConditionValue.value) &&
1033
- node.condition.leftConditionValue instanceof
1034
- QueryBuilderSimpleProjectionColumnState,
1030
+ !isValidInstanceValue(node.condition.rightConditionValue.value),
1035
1031
  );
1036
1032
  }
1037
1033