@finos/legend-query-builder 4.14.61 → 4.14.62

Sign up to get free protection for your applications and to get access to all the features.
Files changed (37) hide show
  1. package/lib/components/QueryBuilder.d.ts.map +1 -1
  2. package/lib/components/QueryBuilder.js +1 -2
  3. package/lib/components/QueryBuilder.js.map +1 -1
  4. package/lib/components/QueryBuilderParametersPanel.d.ts.map +1 -1
  5. package/lib/components/QueryBuilderParametersPanel.js +4 -3
  6. package/lib/components/QueryBuilderParametersPanel.js.map +1 -1
  7. package/lib/components/shared/BasicValueSpecificationEditor.d.ts.map +1 -1
  8. package/lib/components/shared/BasicValueSpecificationEditor.js +107 -14
  9. package/lib/components/shared/BasicValueSpecificationEditor.js.map +1 -1
  10. package/lib/index.css +2 -2
  11. package/lib/index.css.map +1 -1
  12. package/lib/package.json +1 -1
  13. package/lib/stores/QueryBuilderState.d.ts.map +1 -1
  14. package/lib/stores/QueryBuilderState.js +1 -1
  15. package/lib/stores/QueryBuilderState.js.map +1 -1
  16. package/lib/stores/milestoning/QueryBuilderBitemporalMilestoningImplementation.d.ts.map +1 -1
  17. package/lib/stores/milestoning/QueryBuilderBitemporalMilestoningImplementation.js +0 -2
  18. package/lib/stores/milestoning/QueryBuilderBitemporalMilestoningImplementation.js.map +1 -1
  19. package/lib/stores/milestoning/QueryBuilderBusinessTemporalMilestoningImplementation.d.ts.map +1 -1
  20. package/lib/stores/milestoning/QueryBuilderBusinessTemporalMilestoningImplementation.js +0 -2
  21. package/lib/stores/milestoning/QueryBuilderBusinessTemporalMilestoningImplementation.js.map +1 -1
  22. package/lib/stores/milestoning/QueryBuilderProcessingTemporalMilestoningImplementation.d.ts.map +1 -1
  23. package/lib/stores/milestoning/QueryBuilderProcessingTemporalMilestoningImplementation.js +0 -2
  24. package/lib/stores/milestoning/QueryBuilderProcessingTemporalMilestoningImplementation.js.map +1 -1
  25. package/lib/stores/shared/ValueSpecificationEditorHelper.d.ts +2 -1
  26. package/lib/stores/shared/ValueSpecificationEditorHelper.d.ts.map +1 -1
  27. package/lib/stores/shared/ValueSpecificationEditorHelper.js +1 -0
  28. package/lib/stores/shared/ValueSpecificationEditorHelper.js.map +1 -1
  29. package/package.json +1 -1
  30. package/src/components/QueryBuilder.tsx +6 -2
  31. package/src/components/QueryBuilderParametersPanel.tsx +11 -3
  32. package/src/components/shared/BasicValueSpecificationEditor.tsx +167 -25
  33. package/src/stores/QueryBuilderState.ts +6 -1
  34. package/src/stores/milestoning/QueryBuilderBitemporalMilestoningImplementation.ts +0 -2
  35. package/src/stores/milestoning/QueryBuilderBusinessTemporalMilestoningImplementation.ts +0 -2
  36. package/src/stores/milestoning/QueryBuilderProcessingTemporalMilestoningImplementation.ts +0 -2
  37. package/src/stores/shared/ValueSpecificationEditorHelper.ts +6 -0
@@ -36,6 +36,7 @@ import {
36
36
  DragPreviewLayer,
37
37
  CalculateIcon,
38
38
  InputWithInlineValidation,
39
+ CopyIcon,
39
40
  } from '@finos/legend-art';
40
41
  import {
41
42
  type Enum,
@@ -94,6 +95,7 @@ import {
94
95
  import { evaluate } from 'mathjs';
95
96
  import { isUsedDateFunctionSupportedInFormMode } from '../../stores/QueryBuilderStateBuilder.js';
96
97
  import {
98
+ convertTextToEnum,
97
99
  convertTextToPrimitiveInstanceValue,
98
100
  getValueSpecificationStringValue,
99
101
  } from '../../stores/shared/ValueSpecificationEditorHelper.js';
@@ -775,6 +777,8 @@ const PrimitiveCollectionInstanceValueEditor = observer(
775
777
  : undefined;
776
778
  const noMatchMessage =
777
779
  isTypeaheadSearchEnabled && isLoading ? 'Loading...' : undefined;
780
+ const copyButtonName = `copy-${valueSpecification.hashCode}`;
781
+ const inputName = `input-${valueSpecification.hashCode}`;
778
782
 
779
783
  // helper functions
780
784
  const buildOptionForValueSpec = (
@@ -879,6 +883,11 @@ const PrimitiveCollectionInstanceValueEditor = observer(
879
883
  }
880
884
  };
881
885
 
886
+ const copyValueToClipboard = async () =>
887
+ navigator.clipboard.writeText(
888
+ selectedOptions.map((option) => option.value).join(','),
889
+ );
890
+
882
891
  const updateValueSpecAndSaveEdit = (): void => {
883
892
  const newValueSpec = convertInputValueToValueSpec();
884
893
  const finalSelectedOptions =
@@ -936,8 +945,19 @@ const PrimitiveCollectionInstanceValueEditor = observer(
936
945
  event.preventDefault();
937
946
  };
938
947
 
948
+ const onBlur = (
949
+ event: React.FocusEvent<HTMLInputElement, HTMLButtonElement>,
950
+ ): void => {
951
+ if (
952
+ event.relatedTarget?.name !== copyButtonName &&
953
+ event.relatedTarget?.name !== inputName
954
+ ) {
955
+ updateValueSpecAndSaveEdit();
956
+ }
957
+ };
958
+
939
959
  return (
940
- <>
960
+ <div className="value-spec-editor" onBlur={onBlur}>
941
961
  <CustomSelectorInput
942
962
  className={clsx('value-spec-editor__primitive-collection-selector', {
943
963
  'value-spec-editor__primitive-collection-selector--error':
@@ -954,7 +974,6 @@ const PrimitiveCollectionInstanceValueEditor = observer(
954
974
  inputRef={inputRef}
955
975
  onChange={changeValue}
956
976
  onInputChange={handleInputChange}
957
- onBlur={() => updateValueSpecAndSaveEdit()}
958
977
  onKeyDown={handleKeyDown}
959
978
  onPaste={handlePaste}
960
979
  value={selectedOptions}
@@ -968,14 +987,26 @@ const PrimitiveCollectionInstanceValueEditor = observer(
968
987
  components={{
969
988
  DropdownIndicator: null,
970
989
  }}
990
+ inputName={inputName}
971
991
  />
992
+ <button
993
+ className="value-spec-editor__list-editor__copy-button"
994
+ // eslint-disable-next-line no-void
995
+ onClick={() => void copyValueToClipboard()}
996
+ name={copyButtonName}
997
+ title="Copy values to clipboard"
998
+ >
999
+ <CopyIcon />
1000
+ </button>
972
1001
  <button
973
1002
  className="value-spec-editor__list-editor__save-button btn--dark"
1003
+ name="Save"
1004
+ title="Save"
974
1005
  onClick={updateValueSpecAndSaveEdit}
975
1006
  >
976
1007
  <SaveIcon />
977
1008
  </button>
978
- </>
1009
+ </div>
979
1010
  );
980
1011
  },
981
1012
  );
@@ -987,12 +1018,15 @@ const EnumCollectionInstanceValueEditor = observer(
987
1018
  saveEdit: () => void;
988
1019
  }) => {
989
1020
  const { valueSpecification, observerContext, saveEdit } = props;
1021
+
1022
+ // local state and variables
990
1023
  const applicationStore = useApplicationStore();
991
1024
  const enumType = guaranteeType(
992
1025
  valueSpecification.genericType?.value.rawType,
993
1026
  Enumeration,
994
1027
  );
995
-
1028
+ const [inputValue, setInputValue] = useState('');
1029
+ const [inputValueIsError, setInputValueIsError] = useState(false);
996
1030
  const [selectedOptions, setSelectedOptions] = useState<
997
1031
  { label: string; value: Enum }[]
998
1032
  >(
@@ -1016,12 +1050,107 @@ const EnumCollectionInstanceValueEditor = observer(
1016
1050
  value: value,
1017
1051
  }));
1018
1052
 
1053
+ const copyButtonName = `copy-${valueSpecification.hashCode}`;
1054
+ const inputName = `input-${valueSpecification.hashCode}`;
1055
+
1056
+ // helper functions
1057
+ const isValueAlreadySelected = (value: Enum): boolean =>
1058
+ selectedOptions.map((option) => option.value).includes(value);
1059
+
1060
+ /**
1061
+ * NOTE: We attempt to be less disruptive here by not throwing errors left and right, instead
1062
+ * we simply return null for values which are not valid or parsable. But perhaps, we can consider
1063
+ * passing in logger or notifier to give the users some idea of what went wrong instead of ignoring
1064
+ * their input.
1065
+ */
1066
+ const convertInputValueToEnum = (): Enum | null => {
1067
+ const trimmedInputValue = inputValue.trim();
1068
+
1069
+ if (trimmedInputValue.length) {
1070
+ const newEnum = convertTextToEnum(trimmedInputValue, enumType);
1071
+
1072
+ if (newEnum === undefined || isValueAlreadySelected(newEnum)) {
1073
+ return null;
1074
+ }
1075
+
1076
+ return newEnum;
1077
+ }
1078
+ return null;
1079
+ };
1080
+
1081
+ const addInputValueToSelectedOptions = (): void => {
1082
+ const newEnum = convertInputValueToEnum();
1083
+
1084
+ if (newEnum !== null) {
1085
+ setSelectedOptions([
1086
+ ...selectedOptions,
1087
+ {
1088
+ label: newEnum.name,
1089
+ value: newEnum,
1090
+ },
1091
+ ]);
1092
+ setInputValue('');
1093
+ } else if (inputValue.trim().length) {
1094
+ setInputValueIsError(true);
1095
+ }
1096
+ };
1097
+
1098
+ // event handlers
1019
1099
  const changeValue = (
1020
1100
  newSelectedOptions: { value: Enum; label: string }[],
1101
+ actionChange: SelectActionData<{ value: Enum; label: string }>,
1021
1102
  ): void => {
1022
1103
  setSelectedOptions(newSelectedOptions);
1104
+ if (actionChange.action === 'select-option') {
1105
+ setInputValue('');
1106
+ } else if (
1107
+ actionChange.action === 'remove-value' &&
1108
+ actionChange.removedValue.value.name === inputValue
1109
+ ) {
1110
+ setInputValueIsError(false);
1111
+ }
1112
+ };
1113
+
1114
+ const handleInputChange = (
1115
+ newInputValue: string,
1116
+ actionChange: InputActionData,
1117
+ ): void => {
1118
+ if (actionChange.action === 'input-change') {
1119
+ setInputValue(newInputValue);
1120
+ setInputValueIsError(false);
1121
+ }
1122
+ };
1123
+
1124
+ const handleKeyDown = (event: KeyboardEvent): void => {
1125
+ if ((event.key === 'Enter' || event.key === ',') && !event.shiftKey) {
1126
+ addInputValueToSelectedOptions();
1127
+ event.preventDefault();
1128
+ }
1129
+ };
1130
+
1131
+ const handlePaste = (event: React.ClipboardEvent<string>): void => {
1132
+ const pastedText = event.clipboardData.getData('text');
1133
+ const parsedData = parseCSVString(pastedText);
1134
+ if (!parsedData) {
1135
+ return;
1136
+ }
1137
+ const newValues = uniq(
1138
+ uniq(parsedData)
1139
+ .map((value) => convertTextToEnum(value, enumType))
1140
+ .filter(isNonNullable),
1141
+ ).filter((value) => !isValueAlreadySelected(value));
1142
+ setSelectedOptions([
1143
+ ...selectedOptions,
1144
+ ...newValues.map((value) => ({ label: value.name, value })),
1145
+ ]);
1146
+ event.preventDefault();
1023
1147
  };
1024
1148
 
1149
+ const copyValueToClipboard = async () =>
1150
+ navigator.clipboard.writeText(
1151
+ selectedOptions.map((option) => option.value.name).join(','),
1152
+ );
1153
+
1025
1154
  const updateValueSpecAndSaveEdit = (): void => {
1026
1155
  const result = selectedOptions
1027
1156
  .map((value) => {
@@ -1040,35 +1169,59 @@ const EnumCollectionInstanceValueEditor = observer(
1040
1169
  saveEdit();
1041
1170
  };
1042
1171
 
1172
+ const onBlur = (
1173
+ event: React.FocusEvent<HTMLInputElement, HTMLButtonElement>,
1174
+ ): void => {
1175
+ if (
1176
+ event.relatedTarget?.name !== copyButtonName &&
1177
+ event.relatedTarget?.name !== inputName
1178
+ ) {
1179
+ updateValueSpecAndSaveEdit();
1180
+ }
1181
+ };
1182
+
1043
1183
  return (
1044
- <>
1184
+ <div className="value-spec-editor" onBlur={onBlur}>
1045
1185
  <CustomSelectorInput
1046
- className="value-spec-editor__enum-collection-selector"
1186
+ className={clsx('value-spec-editor__enum-collection-selector', {
1187
+ 'value-spec-editor__enum-collection-selector--error':
1188
+ inputValueIsError,
1189
+ })}
1047
1190
  options={availableOptions}
1191
+ inputValue={inputValue}
1048
1192
  isMulti={true}
1193
+ autoFocus={true}
1049
1194
  onChange={changeValue}
1050
- onBlur={updateValueSpecAndSaveEdit}
1051
- onKeyDown={(event: KeyboardEvent): void => {
1052
- if (event.key === 'Enter' && !event.shiftKey) {
1053
- updateValueSpecAndSaveEdit();
1054
- }
1055
- }}
1195
+ onInputChange={handleInputChange}
1196
+ onKeyDown={handleKeyDown}
1197
+ onPaste={handlePaste}
1056
1198
  value={selectedOptions}
1057
1199
  darkMode={
1058
1200
  !applicationStore.layoutService.TEMPORARY__isLightColorThemeEnabled
1059
1201
  }
1060
1202
  placeholder={null}
1061
1203
  inputPlaceholder="Add"
1062
- autoFocus={true}
1063
1204
  menuIsOpen={true}
1205
+ inputName={inputName}
1064
1206
  />
1207
+ <button
1208
+ className="value-spec-editor__list-editor__copy-button"
1209
+ // eslint-disable-next-line no-void
1210
+ onClick={() => void copyValueToClipboard()}
1211
+ name={copyButtonName}
1212
+ title="Copy values to clipboard"
1213
+ >
1214
+ <CopyIcon />
1215
+ </button>
1065
1216
  <button
1066
1217
  className="value-spec-editor__list-editor__save-button btn--dark"
1218
+ name="Save"
1219
+ title="Save"
1067
1220
  onClick={updateValueSpecAndSaveEdit}
1068
1221
  >
1069
1222
  <SaveIcon />
1070
1223
  </button>
1071
- </>
1224
+ </div>
1072
1225
  );
1073
1226
  },
1074
1227
  );
@@ -1081,7 +1234,6 @@ const CollectionValueInstanceValueEditor = observer(
1081
1234
  graph: PureModel;
1082
1235
  expectedType: Type;
1083
1236
  className?: string | undefined;
1084
- resetValue: () => void;
1085
1237
  setValueSpecification: (val: ValueSpecification) => void;
1086
1238
  selectorConfig?: BasicValueSpecificationEditorSelectorConfig | undefined;
1087
1239
  observerContext: ObserverContext;
@@ -1090,7 +1242,6 @@ const CollectionValueInstanceValueEditor = observer(
1090
1242
  valueSpecification,
1091
1243
  expectedType,
1092
1244
  className,
1093
- resetValue,
1094
1245
  setValueSpecification,
1095
1246
  selectorConfig,
1096
1247
  observerContext,
@@ -1138,14 +1289,6 @@ const CollectionValueInstanceValueEditor = observer(
1138
1289
  observerContext={observerContext}
1139
1290
  />
1140
1291
  )}
1141
- <button
1142
- className="value-spec-editor__reset-btn"
1143
- name="Reset"
1144
- title="Reset"
1145
- onClick={resetValue}
1146
- >
1147
- <RefreshIcon />
1148
- </button>
1149
1292
  </div>
1150
1293
  </>
1151
1294
  );
@@ -1358,7 +1501,6 @@ export const BasicValueSpecificationEditor = forwardRef<
1358
1501
  graph={graph}
1359
1502
  expectedType={typeCheckOption.expectedType}
1360
1503
  className={className}
1361
- resetValue={resetValue}
1362
1504
  setValueSpecification={setValueSpecification}
1363
1505
  selectorConfig={selectorConfig}
1364
1506
  observerContext={observerContext}
@@ -697,7 +697,12 @@ export abstract class QueryBuilderState implements CommandRegistrar {
697
697
  },
698
698
  );
699
699
  }
700
- if (this.parametersState.parameterStates.length > 0) {
700
+ if (
701
+ this.parametersState.parameterStates.filter(
702
+ (paramState) =>
703
+ !this.milestoningState.isMilestoningParameter(paramState.parameter),
704
+ ).length > 0
705
+ ) {
701
706
  this.setShowParametersPanel(true);
702
707
  }
703
708
  this.fetchStructureState.initializeWithQuery();
@@ -69,8 +69,6 @@ export class QueryBuilderBitemporalMilestoningImplementation extends QueryBuilde
69
69
  ),
70
70
  );
71
71
  }
72
- // Show the parameter panel because we populate paramaters state with milestoning parameters
73
- this.milestoningState.queryBuilderState.setShowParametersPanel(true);
74
72
  }
75
73
 
76
74
  buildParameterStatesFromMilestoningParameters(): LambdaParameterState[] {
@@ -52,8 +52,6 @@ export class QueryBuilderBusinessTemporalMilestoningImplementation extends Query
52
52
  ),
53
53
  );
54
54
  }
55
- // Show the parameter panel because we populate paramaters state with milestoning parameters
56
- this.milestoningState.queryBuilderState.setShowParametersPanel(true);
57
55
  }
58
56
 
59
57
  buildParameterStatesFromMilestoningParameters(): LambdaParameterState[] {
@@ -51,8 +51,6 @@ export class QueryBuilderProcessingTemporalMilestoningImplementation extends Que
51
51
  ),
52
52
  );
53
53
  }
54
- // Show the parameter panel because we populate paramaters state with milestoning parameters
55
- this.milestoningState.queryBuilderState.setShowParametersPanel(true);
56
54
  }
57
55
 
58
56
  buildParameterStatesFromMilestoningParameters(): LambdaParameterState[] {
@@ -435,3 +435,9 @@ export const convertTextToPrimitiveInstanceValue = (
435
435
  }
436
436
  return result;
437
437
  };
438
+
439
+ export const convertTextToEnum = (
440
+ value: string,
441
+ enumType: Enumeration,
442
+ ): Enum | undefined =>
443
+ enumType.values.find((enumValue) => enumValue.name === value);