@elite.framework/ng.core 2.0.18 → 2.0.19

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.
@@ -114,6 +114,11 @@ class BaseService {
114
114
  url: this.buildUrl(id),
115
115
  body: input,
116
116
  }, { apiName: this.apiName });
117
+ patch = (id, input) => this.restService.request({
118
+ method: 'PATCH',
119
+ url: this.buildUrl(id),
120
+ body: input,
121
+ }, { apiName: this.apiName });
117
122
  delete = (id) => this.restService.request({
118
123
  method: 'DELETE',
119
124
  url: this.buildUrl(id),
@@ -834,7 +839,9 @@ class QueryParser {
834
839
  const params = {
835
840
  filters: [],
836
841
  orderBy: [],
837
- groupBy: []
842
+ groupBy: [],
843
+ select: [],
844
+ aggregates: []
838
845
  };
839
846
  if (!queryString)
840
847
  return params;
@@ -847,7 +854,13 @@ class QueryParser {
847
854
  this.parseOrderBy(part.substring('$orderby='.length), params);
848
855
  }
849
856
  else if (part.startsWith('$groupby=')) {
850
- params.groupBy = part.substring('$groupby='.length).split(',').map(f => f.trim());
857
+ this.parseGroupBy(part.substring('$groupby='.length), params);
858
+ }
859
+ else if (part.startsWith('$select=')) {
860
+ this.parseSelect(part.substring('$select='.length), params);
861
+ }
862
+ else if (part.startsWith('$aggregate=')) {
863
+ this.parseAggregate(part.substring('$aggregate='.length), params);
851
864
  }
852
865
  else if (part.startsWith('$top=')) {
853
866
  params.top = Number(part.substring('$top='.length));
@@ -858,6 +871,9 @@ class QueryParser {
858
871
  else if (part.startsWith('$expand=')) {
859
872
  params.expand = part.substring('$expand='.length).split(',').map(f => f.trim());
860
873
  }
874
+ else if (part.startsWith('$count=')) {
875
+ params.includeCount = part.substring('$count='.length).toLowerCase() === 'true';
876
+ }
861
877
  }
862
878
  return params;
863
879
  }
@@ -872,7 +888,16 @@ class QueryParser {
872
888
  parts.push(`$orderby=${encodeURIComponent(orderByStr)}`);
873
889
  }
874
890
  if (params.groupBy?.length) {
875
- parts.push(`$groupby=${encodeURIComponent(params.groupBy.join(','))}`);
891
+ const groupByStr = this.buildGroupByString(params.groupBy);
892
+ parts.push(`$groupby=${encodeURIComponent(groupByStr)}`);
893
+ }
894
+ if (params.select?.length) {
895
+ const selectStr = this.buildSelectString(params.select);
896
+ parts.push(`$select=${encodeURIComponent(selectStr)}`);
897
+ }
898
+ if (params.aggregates?.length) {
899
+ const aggregateStr = this.buildAggregateString(params.aggregates);
900
+ parts.push(`$aggregate=${encodeURIComponent(aggregateStr)}`);
876
901
  }
877
902
  if (params.top !== undefined) {
878
903
  parts.push(`$top=${params.top}`);
@@ -883,6 +908,9 @@ class QueryParser {
883
908
  if (params.expand?.length) {
884
909
  parts.push(`$expand=${encodeURIComponent(params.expand.join(','))}`);
885
910
  }
911
+ if (params.includeCount !== undefined) {
912
+ parts.push(`$count=${params.includeCount}`);
913
+ }
886
914
  return parts.join('&');
887
915
  }
888
916
  static buildFilterString(filters) {
@@ -909,9 +937,71 @@ class QueryParser {
909
937
  }
910
938
  }).join(' ');
911
939
  }
940
+ static buildGroupByString(groupBy) {
941
+ return groupBy
942
+ .sort((a, b) => (a.order || 0) - (b.order || 0))
943
+ .map(group => {
944
+ let groupStr = group.propertyName;
945
+ // Add display name if provided
946
+ if (group.displayName) {
947
+ groupStr += ` as ${group.displayName}`;
948
+ }
949
+ // Add metadata for UI
950
+ const metadata = [];
951
+ if (group.showTotal !== undefined) {
952
+ metadata.push(`showTotal:${group.showTotal}`);
953
+ }
954
+ if (group.totalBackground) {
955
+ metadata.push(`totalBg:${group.totalBackground}`);
956
+ }
957
+ if (group.groupHeaderBackground) {
958
+ metadata.push(`headerBg:${group.groupHeaderBackground}`);
959
+ }
960
+ if (metadata.length > 0) {
961
+ groupStr += `(${metadata.join(';')})`;
962
+ }
963
+ return groupStr;
964
+ })
965
+ .join(',');
966
+ }
967
+ static buildSelectString(select) {
968
+ return select
969
+ .sort((a, b) => (a.order || 0) - (b.order || 0))
970
+ .map(item => {
971
+ let selectStr = item.propertyName;
972
+ // Add display name if provided
973
+ if (item.displayName) {
974
+ selectStr += ` as ${item.displayName}`;
975
+ }
976
+ // Add aggregate function if provided
977
+ if (item.aggregateFunction) {
978
+ selectStr += ` with ${item.aggregateFunction}`;
979
+ }
980
+ // Add visibility metadata
981
+ const metadata = [];
982
+ if (item.isVisible !== undefined) {
983
+ metadata.push(`visible:${item.isVisible}`);
984
+ }
985
+ if (metadata.length > 0) {
986
+ selectStr += `(${metadata.join(';')})`;
987
+ }
988
+ return selectStr;
989
+ })
990
+ .join(',');
991
+ }
992
+ static buildAggregateString(aggregates) {
993
+ return aggregates
994
+ .map(agg => {
995
+ let aggStr = `${agg.aggregateFunction}(${agg.propertyName})`;
996
+ if (agg.alias) {
997
+ aggStr += ` as ${agg.alias}`;
998
+ }
999
+ return aggStr;
1000
+ })
1001
+ .join(',');
1002
+ }
912
1003
  static parseFilter(filterString, params) {
913
1004
  const decodedString = decodeURIComponent(filterString);
914
- // Handle individual conditions and groups
915
1005
  const groups = [];
916
1006
  // Split by top-level AND/OR operators
917
1007
  const topLevelPattern = /(?:^|\s)(and|or)\s+(?=(?:[^()]*\([^()]*\))*[^()]*$)/gi;
@@ -945,19 +1035,18 @@ class QueryParser {
945
1035
  }
946
1036
  if (conditions.length > 0) {
947
1037
  const group = {
948
- logicalOperator: 'and', // Default for conditions within group
1038
+ logicalOperator: 'and',
949
1039
  conditions: conditions
950
1040
  };
951
1041
  // For groups after the first, set the groupLogicalOperator
952
1042
  if (index > 0) {
953
1043
  group.groupLogicalOperator = segment.operator?.toLowerCase() || 'and';
954
1044
  }
955
- // If it's a single condition group, we can detect OR operators within
1045
+ // Detect the logical operator used within the group
956
1046
  if (conditions.length === 1 && segment.condition.includes(' or ')) {
957
1047
  group.logicalOperator = 'or';
958
1048
  }
959
1049
  else if (conditions.length > 1) {
960
- // Detect the logical operator used within the group
961
1050
  group.logicalOperator = segment.condition.toLowerCase().includes(' or ') ? 'or' : 'and';
962
1051
  }
963
1052
  groups.push(group);
@@ -967,8 +1056,8 @@ class QueryParser {
967
1056
  }
968
1057
  static parseConditions(conditionString) {
969
1058
  const conditions = [];
970
- // Split by AND/OR but be careful not to split within quotes or parentheses
971
- const pattern = /(\w+)\s+(eq|ne|gt|ge|lt|le|contains|startswith|endswith)\s+('[^']*'|\d+|true|false)/gi;
1059
+ // Enhanced pattern to handle more operators and complex values
1060
+ const pattern = /(\w+(?:\.\w+)*)\s+(eq|ne|gt|ge|lt|le|contains|startswith|endswith|in|notin)\s+('[^']*'|\[[^\]]*\]|\d+|true|false|null)/gi;
972
1061
  let match;
973
1062
  while ((match = pattern.exec(conditionString)) !== null) {
974
1063
  const [, field, operator, rawValue] = match;
@@ -976,6 +1065,13 @@ class QueryParser {
976
1065
  if (rawValue.startsWith("'") && rawValue.endsWith("'")) {
977
1066
  value = rawValue.slice(1, -1).replace(/''/g, "'");
978
1067
  }
1068
+ else if (rawValue.startsWith('[') && rawValue.endsWith(']')) {
1069
+ // Handle array values for IN/NOT IN operators
1070
+ value = rawValue.slice(1, -1).split(',').map(v => {
1071
+ const trimmed = v.trim().replace(/^'|'$/g, '');
1072
+ return isNaN(Number(trimmed)) ? trimmed : Number(trimmed);
1073
+ });
1074
+ }
979
1075
  else if (!isNaN(Number(rawValue))) {
980
1076
  value = Number(rawValue);
981
1077
  }
@@ -985,6 +1081,9 @@ class QueryParser {
985
1081
  else if (rawValue.toLowerCase() === 'false') {
986
1082
  value = false;
987
1083
  }
1084
+ else if (rawValue.toLowerCase() === 'null') {
1085
+ value = null;
1086
+ }
988
1087
  conditions.push({
989
1088
  field: field.trim(),
990
1089
  operator: operator.trim().toLowerCase(),
@@ -1006,6 +1105,173 @@ class QueryParser {
1006
1105
  });
1007
1106
  }
1008
1107
  }
1108
+ static parseGroupBy(groupByString, params) {
1109
+ const decodedString = decodeURIComponent(groupByString);
1110
+ const groups = decodedString.split(',');
1111
+ params.groupBy = groups.map((groupStr, index) => {
1112
+ const group = {
1113
+ propertyName: '',
1114
+ order: index
1115
+ };
1116
+ // Check for metadata in parentheses
1117
+ const metadataMatch = groupStr.match(/^(.*?)\((.*)\)$/);
1118
+ let baseStr = groupStr;
1119
+ let metadataStr = '';
1120
+ if (metadataMatch) {
1121
+ baseStr = metadataMatch[1];
1122
+ metadataStr = metadataMatch[2];
1123
+ }
1124
+ // Check for display name alias
1125
+ const aliasMatch = baseStr.match(/^(.*?)\s+as\s+(.*)$/);
1126
+ if (aliasMatch) {
1127
+ group.propertyName = aliasMatch[1].trim();
1128
+ group.displayName = aliasMatch[2].trim();
1129
+ }
1130
+ else {
1131
+ group.propertyName = baseStr.trim();
1132
+ }
1133
+ // Parse metadata
1134
+ if (metadataStr) {
1135
+ const metadataParts = metadataStr.split(';');
1136
+ metadataParts.forEach(part => {
1137
+ const [key, value] = part.split(':');
1138
+ switch (key) {
1139
+ case 'showTotal':
1140
+ group.showTotal = value.toLowerCase() === 'true';
1141
+ break;
1142
+ case 'totalBg':
1143
+ group.totalBackground = value;
1144
+ break;
1145
+ case 'headerBg':
1146
+ group.groupHeaderBackground = value;
1147
+ break;
1148
+ }
1149
+ });
1150
+ }
1151
+ return group;
1152
+ });
1153
+ }
1154
+ static parseSelect(selectString, params) {
1155
+ const decodedString = decodeURIComponent(selectString);
1156
+ const items = decodedString.split(',');
1157
+ params.select = items.map((itemStr, index) => {
1158
+ const item = {
1159
+ propertyName: '',
1160
+ isVisible: true,
1161
+ order: index
1162
+ };
1163
+ // Check for metadata in parentheses
1164
+ const metadataMatch = itemStr.match(/^(.*?)\((.*)\)$/);
1165
+ let baseStr = itemStr;
1166
+ let metadataStr = '';
1167
+ if (metadataMatch) {
1168
+ baseStr = metadataMatch[1];
1169
+ metadataStr = metadataMatch[2];
1170
+ }
1171
+ // Check for display name alias and aggregate function
1172
+ const aliasMatch = baseStr.match(/^(.*?)(?:\s+as\s+(.*?))?(?:\s+with\s+(\w+))?$/);
1173
+ if (aliasMatch) {
1174
+ item.propertyName = aliasMatch[1].trim();
1175
+ if (aliasMatch[2]) {
1176
+ item.displayName = aliasMatch[2].trim();
1177
+ }
1178
+ if (aliasMatch[3]) {
1179
+ item.aggregateFunction = aliasMatch[3].trim();
1180
+ }
1181
+ }
1182
+ else {
1183
+ item.propertyName = baseStr.trim();
1184
+ }
1185
+ // Parse metadata
1186
+ if (metadataStr) {
1187
+ const metadataParts = metadataStr.split(';');
1188
+ metadataParts.forEach(part => {
1189
+ const [key, value] = part.split(':');
1190
+ if (key === 'visible') {
1191
+ item.isVisible = value.toLowerCase() === 'true';
1192
+ }
1193
+ });
1194
+ }
1195
+ return item;
1196
+ });
1197
+ }
1198
+ static parseAggregate(aggregateString, params) {
1199
+ const decodedString = decodeURIComponent(aggregateString);
1200
+ const aggregates = decodedString.split(',');
1201
+ params.aggregates = aggregates.map(aggStr => {
1202
+ const agg = {
1203
+ propertyName: '',
1204
+ aggregateFunction: ''
1205
+ };
1206
+ // Match pattern: function(property) as alias
1207
+ const match = aggStr.match(/^(\w+)\((\w+)\)(?:\s+as\s+(\w+))?$/);
1208
+ if (match) {
1209
+ agg.aggregateFunction = match[1];
1210
+ agg.propertyName = match[2];
1211
+ if (match[3]) {
1212
+ agg.alias = match[3];
1213
+ }
1214
+ }
1215
+ return agg;
1216
+ });
1217
+ }
1218
+ // Helper method to convert QueryParameters to ODataQueryParameters (C# equivalent)
1219
+ static toODataQueryParameters(params) {
1220
+ return {
1221
+ Filters: params.filters.map(group => ({
1222
+ LogicalOperator: group.logicalOperator,
1223
+ GroupLogicalOperator: group.groupLogicalOperator,
1224
+ Conditions: group.conditions.map(cond => ({
1225
+ Field: cond.field,
1226
+ Operator: cond.operator,
1227
+ Value: cond.value
1228
+ }))
1229
+ })),
1230
+ OrderBy: params.orderBy?.map(order => ({
1231
+ Field: order.field,
1232
+ Direction: order.direction
1233
+ })) || [],
1234
+ GroupBy: params.groupBy?.map(group => ({
1235
+ PropertyName: group.propertyName,
1236
+ DisplayName: group.displayName,
1237
+ ShowTotal: group.showTotal ?? true,
1238
+ TotalBackground: group.totalBackground,
1239
+ GroupHeaderBackground: group.groupHeaderBackground,
1240
+ Order: group.order
1241
+ })) || [],
1242
+ Select: params.select?.map(sel => ({
1243
+ PropertyName: sel.propertyName,
1244
+ DisplayName: sel.displayName,
1245
+ AggregateFunction: sel.aggregateFunction,
1246
+ IsVisible: sel.isVisible ?? true,
1247
+ Order: sel.order
1248
+ })) || [],
1249
+ Aggregates: params.aggregates?.map(agg => ({
1250
+ PropertyName: agg.propertyName,
1251
+ AggregateFunction: agg.aggregateFunction,
1252
+ Alias: agg.alias
1253
+ })) || [],
1254
+ Top: params.top,
1255
+ Skip: params.skip,
1256
+ Expand: params.expand,
1257
+ IncludeCount: params.includeCount
1258
+ };
1259
+ }
1260
+ // Helper method to create query parameters from UI model
1261
+ static fromUIModel(model) {
1262
+ const params = {
1263
+ filters: model.advancedFilters || [],
1264
+ orderBy: model.sorting || [],
1265
+ groupBy: model.grouping || [],
1266
+ select: model.columns || [],
1267
+ aggregates: model.aggregates || [],
1268
+ top: model.pagination?.top,
1269
+ skip: model.pagination?.skip,
1270
+ expand: model.expand,
1271
+ includeCount: model.pagination?.includeCount
1272
+ };
1273
+ return params;
1274
+ }
1009
1275
  }
1010
1276
 
1011
1277
  /**