@finos/legend-query-builder 4.14.10 → 4.14.12

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (26) hide show
  1. package/lib/__lib__/QueryBuilderTesting.d.ts +3 -1
  2. package/lib/__lib__/QueryBuilderTesting.d.ts.map +1 -1
  3. package/lib/__lib__/QueryBuilderTesting.js +3 -0
  4. package/lib/__lib__/QueryBuilderTesting.js.map +1 -1
  5. package/lib/components/fetch-structure/QueryBuilderResultModifierPanel.d.ts.map +1 -1
  6. package/lib/components/fetch-structure/QueryBuilderResultModifierPanel.js +91 -44
  7. package/lib/components/fetch-structure/QueryBuilderResultModifierPanel.js.map +1 -1
  8. package/lib/components/shared/LambdaEditor.d.ts.map +1 -1
  9. package/lib/components/shared/LambdaEditor.js +10 -5
  10. package/lib/components/shared/LambdaEditor.js.map +1 -1
  11. package/lib/components/shared/QueryBuilderPropertyInfoTooltip.d.ts.map +1 -1
  12. package/lib/components/shared/QueryBuilderPropertyInfoTooltip.js +12 -11
  13. package/lib/components/shared/QueryBuilderPropertyInfoTooltip.js.map +1 -1
  14. package/lib/index.css +2 -2
  15. package/lib/index.css.map +1 -1
  16. package/lib/package.json +1 -1
  17. package/lib/stores/fetch-structure/tds/QueryResultSetModifierState.d.ts +2 -2
  18. package/lib/stores/fetch-structure/tds/QueryResultSetModifierState.d.ts.map +1 -1
  19. package/lib/stores/fetch-structure/tds/QueryResultSetModifierState.js +7 -7
  20. package/lib/stores/fetch-structure/tds/QueryResultSetModifierState.js.map +1 -1
  21. package/package.json +5 -5
  22. package/src/__lib__/QueryBuilderTesting.ts +3 -0
  23. package/src/components/fetch-structure/QueryBuilderResultModifierPanel.tsx +185 -94
  24. package/src/components/shared/LambdaEditor.tsx +10 -8
  25. package/src/components/shared/QueryBuilderPropertyInfoTooltip.tsx +81 -65
  26. package/src/stores/fetch-structure/tds/QueryResultSetModifierState.ts +7 -12
@@ -31,20 +31,32 @@ import {
31
31
  MenuContent,
32
32
  MenuContentItem,
33
33
  ModalFooterButton,
34
+ InputWithInlineValidation,
34
35
  } from '@finos/legend-art';
35
36
  import { SortColumnState } from '../../stores/fetch-structure/tds/QueryResultSetModifierState.js';
36
- import { guaranteeNonNullable } from '@finos/legend-shared';
37
+ import {
38
+ addUniqueEntry,
39
+ deleteEntry,
40
+ guaranteeNonNullable,
41
+ } from '@finos/legend-shared';
37
42
  import { useApplicationStore } from '@finos/legend-application';
38
43
  import type { QueryBuilderTDSState } from '../../stores/fetch-structure/tds/QueryBuilderTDSState.js';
39
44
  import type { QueryBuilderTDSColumnState } from '../../stores/fetch-structure/tds/QueryBuilderTDSColumnState.js';
40
45
  import { COLUMN_SORT_TYPE } from '../../graph/QueryBuilderMetaModelConst.js';
46
+ import { useEffect, useState } from 'react';
47
+ import type { QueryBuilderProjectionColumnState } from '../../stores/fetch-structure/tds/projection/QueryBuilderProjectionColumnState.js';
48
+ import { QUERY_BUILDER_TEST_ID } from '../../__lib__/QueryBuilderTesting.js';
41
49
 
42
50
  const ColumnSortEditor = observer(
43
- (props: { tdsState: QueryBuilderTDSState; sortState: SortColumnState }) => {
44
- const { tdsState, sortState } = props;
51
+ (props: {
52
+ sortColumns: SortColumnState[];
53
+ setSortColumns: (sortColumns: SortColumnState[]) => void;
54
+ sortState: SortColumnState;
55
+ tdsColumns: QueryBuilderTDSColumnState[];
56
+ }) => {
57
+ const { sortColumns, setSortColumns, sortState, tdsColumns } = props;
45
58
  const applicationStore = useApplicationStore();
46
- const sortColumns = tdsState.resultSetModifierState.sortColumns;
47
- const projectionOptions = tdsState.tdsColumns
59
+ const projectionOptions = tdsColumns
48
60
  .filter(
49
61
  (projectionCol) =>
50
62
  projectionCol === sortState.columnState ||
@@ -58,6 +70,8 @@ const ColumnSortEditor = observer(
58
70
  label: sortState.columnState.columnName,
59
71
  value: sortState,
60
72
  };
73
+ const sortType = sortState.sortType;
74
+
61
75
  const onChange = (
62
76
  val: { label: string; value: QueryBuilderTDSColumnState } | null,
63
77
  ): void => {
@@ -65,13 +79,17 @@ const ColumnSortEditor = observer(
65
79
  sortState.setColumnState(val.value);
66
80
  }
67
81
  };
68
- const sortType = sortState.sortType;
69
82
 
70
- const deleteColumnSort = (): void =>
71
- tdsState.resultSetModifierState.deleteSortColumn(sortState);
83
+ const deleteColumnSort = (): void => {
84
+ const newSortColumns = [...sortColumns];
85
+ deleteEntry(newSortColumns, sortState);
86
+ setSortColumns(newSortColumns);
87
+ };
88
+
72
89
  const changeSortBy = (sortOp: COLUMN_SORT_TYPE) => (): void => {
73
90
  sortState.setSortType(sortOp);
74
91
  };
92
+
75
93
  return (
76
94
  <div className="panel__content__form__section__list__item query-builder__projection__options__sort">
77
95
  <CustomSelectorInput
@@ -120,6 +138,9 @@ const ColumnSortEditor = observer(
120
138
  onClick={deleteColumnSort}
121
139
  tabIndex={-1}
122
140
  title="Remove"
141
+ data-testid={
142
+ QUERY_BUILDER_TEST_ID.QUERY_BUILDER_RESULT_MODIFIER_PANEL_SORT_REMOVE_BTN
143
+ }
123
144
  >
124
145
  <TimesIcon />
125
146
  </button>
@@ -129,11 +150,15 @@ const ColumnSortEditor = observer(
129
150
  );
130
151
 
131
152
  const ColumnsSortEditor = observer(
132
- (props: { tdsState: QueryBuilderTDSState }) => {
133
- const { tdsState } = props;
134
- const resultSetModifierState = tdsState.resultSetModifierState;
135
- const sortColumns = resultSetModifierState.sortColumns;
136
- const projectionOptions = tdsState.projectionColumns
153
+ (props: {
154
+ projectionColumns: QueryBuilderProjectionColumnState[];
155
+ sortColumns: SortColumnState[];
156
+ setSortColumns: (sortColumns: SortColumnState[]) => void;
157
+ tdsColumns: QueryBuilderTDSColumnState[];
158
+ }) => {
159
+ const { projectionColumns, sortColumns, setSortColumns, tdsColumns } =
160
+ props;
161
+ const projectionOptions = projectionColumns
137
162
  .filter(
138
163
  (projectionCol) =>
139
164
  !sortColumns.some((sortCol) => sortCol.columnState === projectionCol),
@@ -147,7 +172,9 @@ const ColumnsSortEditor = observer(
147
172
  const sortColumn = new SortColumnState(
148
173
  guaranteeNonNullable(projectionOptions[0]).value,
149
174
  );
150
- resultSetModifierState.addSortColumn(sortColumn);
175
+ const newSortColumns = [...sortColumns];
176
+ addUniqueEntry(newSortColumns, sortColumn);
177
+ setSortColumns(newSortColumns);
151
178
  }
152
179
  };
153
180
 
@@ -166,8 +193,10 @@ const ColumnsSortEditor = observer(
166
193
  {sortColumns.map((value) => (
167
194
  <ColumnSortEditor
168
195
  key={value.columnState.uuid}
169
- tdsState={tdsState}
196
+ sortColumns={sortColumns}
197
+ setSortColumns={setSortColumns}
170
198
  sortState={value}
199
+ tdsColumns={tdsColumns}
171
200
  />
172
201
  ))}
173
202
  </div>
@@ -187,77 +216,140 @@ const ColumnsSortEditor = observer(
187
216
  },
188
217
  );
189
218
 
219
+ const cloneSortColumnStateArray = (
220
+ sortColumns: SortColumnState[],
221
+ ): SortColumnState[] =>
222
+ sortColumns.map((sortColumn) => {
223
+ const newSortColumn = new SortColumnState(sortColumn.columnState);
224
+ newSortColumn.setSortType(sortColumn.sortType);
225
+ return newSortColumn;
226
+ });
227
+
190
228
  export const QueryResultModifierModal = observer(
191
229
  (props: { tdsState: QueryBuilderTDSState }) => {
192
- const { tdsState: tdsState } = props;
230
+ // Read current state
231
+ const { tdsState } = props;
193
232
  const resultSetModifierState = tdsState.resultSetModifierState;
194
- const limitResults = resultSetModifierState.limit;
195
- const distinct = resultSetModifierState.distinct;
196
- const close = (): void => resultSetModifierState.setShowModal(false);
197
- const toggleDistinct = (): void => resultSetModifierState.toggleDistinct();
198
- const changeValue: React.ChangeEventHandler<HTMLInputElement> = (event) => {
199
- const val = event.target.value;
200
- resultSetModifierState.setLimit(
201
- val === '' ? undefined : parseInt(val, 10),
202
- );
203
- };
233
+ const stateSortColumns = resultSetModifierState.sortColumns;
234
+ const stateDistinct = resultSetModifierState.distinct;
235
+ const stateLimitResults = resultSetModifierState.limit;
236
+ const stateSlice = resultSetModifierState.slice;
237
+
238
+ // Set up temp state for modal lifecycle
239
+ const [sortColumns, setSortColumns] = useState(
240
+ cloneSortColumnStateArray(stateSortColumns),
241
+ );
242
+ const [distinct, setDistinct] = useState(stateDistinct);
243
+ const [limitResults, setLimitResults] = useState(stateLimitResults);
244
+ const [slice, setSlice] = useState<
245
+ [number | undefined, number | undefined]
246
+ >(stateSlice ?? [undefined, undefined]);
247
+
248
+ // Sync temp state with tdsState when modal is opened/closed
249
+ useEffect(() => {
250
+ setSortColumns(cloneSortColumnStateArray(stateSortColumns));
251
+ setDistinct(stateDistinct);
252
+ setLimitResults(stateLimitResults);
253
+ setSlice(stateSlice ?? [undefined, undefined]);
254
+ }, [
255
+ resultSetModifierState.showModal,
256
+ stateSortColumns,
257
+ stateDistinct,
258
+ stateLimitResults,
259
+ stateSlice,
260
+ ]);
204
261
 
205
- const handleSliceStartChange = (start: number, end: number): void => {
206
- const slice: [number, number] = [start, end];
207
- resultSetModifierState.setSlice(slice);
262
+ // Handle user actions
263
+ const closeModal = (): void => resultSetModifierState.setShowModal(false);
264
+ const applyChanges = (): void => {
265
+ resultSetModifierState.setSortColumns(sortColumns);
266
+ resultSetModifierState.setDistinct(distinct);
267
+ resultSetModifierState.setLimit(limitResults);
268
+ if (slice[0] !== undefined && slice[1] !== undefined) {
269
+ resultSetModifierState.setSlice([slice[0], slice[1]]);
270
+ } else {
271
+ resultSetModifierState.setSlice(undefined);
272
+ }
273
+ resultSetModifierState.setShowModal(false);
208
274
  };
209
275
 
210
- const clearSlice = (): void => {
211
- resultSetModifierState.setSlice(undefined);
276
+ const handleLimitResultsChange: React.ChangeEventHandler<
277
+ HTMLInputElement
278
+ > = (event) => {
279
+ const val = event.target.value.replace(/[^0-9]/g, '');
280
+ setLimitResults(val === '' ? undefined : parseInt(val, 10));
212
281
  };
213
282
 
214
- const addSlice = (): void => {
215
- resultSetModifierState.setSlice([0, 1]);
283
+ const handleSliceChange = (
284
+ start: number | undefined,
285
+ end: number | undefined,
286
+ ): void => {
287
+ const newSlice: [number | undefined, number | undefined] = [start, end];
288
+ setSlice(newSlice);
216
289
  };
217
290
 
218
291
  const changeSliceStart: React.ChangeEventHandler<HTMLInputElement> = (
219
292
  event,
220
293
  ) => {
221
- const currentSlice = resultSetModifierState.slice;
222
- const val = event.target.value;
223
- const start = typeof val === 'number' ? val : parseInt(val, 10);
224
- if (currentSlice) {
225
- handleSliceStartChange(start, currentSlice[1]);
294
+ const val = event.target.value.replace(/[^0-9]/g, '');
295
+ if (val === '') {
296
+ handleSliceChange(undefined, slice[1]);
297
+ } else {
298
+ const start = typeof val === 'number' ? val : parseInt(val, 10);
299
+ handleSliceChange(start, slice[1]);
226
300
  }
227
301
  };
228
302
  const changeSliceEnd: React.ChangeEventHandler<HTMLInputElement> = (
229
303
  event,
230
304
  ) => {
231
- const currentSlice = resultSetModifierState.slice;
232
- const val = event.target.value;
233
- const end = typeof val === 'number' ? val : parseInt(val, 10);
234
- if (currentSlice) {
235
- handleSliceStartChange(currentSlice[0], end);
305
+ const val = event.target.value.replace(/[^0-9]/g, '');
306
+ if (val === '') {
307
+ handleSliceChange(slice[0], undefined);
308
+ } else {
309
+ const end = typeof val === 'number' ? val : parseInt(val, 10);
310
+ handleSliceChange(slice[0], end);
236
311
  }
237
312
  };
238
313
 
314
+ // Error states
315
+ const isInvalidSlice =
316
+ (slice[0] === undefined && slice[1] !== undefined) ||
317
+ (slice[0] !== undefined && slice[1] === undefined) ||
318
+ (slice[0] !== undefined &&
319
+ slice[1] !== undefined &&
320
+ slice[0] >= slice[1]);
321
+
239
322
  return (
240
323
  <Dialog
241
324
  open={Boolean(resultSetModifierState.showModal)}
242
- onClose={close}
325
+ onClose={closeModal}
243
326
  classes={{
244
327
  root: 'editor-modal__root-container',
245
328
  container: 'editor-modal__container',
246
329
  paper: 'editor-modal__content',
247
330
  }}
331
+ data-testid={QUERY_BUILDER_TEST_ID.QUERY_BUILDER_RESULT_MODIFIER_PANEL}
248
332
  >
249
- <Modal darkMode={true} className="editor-modal">
333
+ <Modal
334
+ darkMode={true}
335
+ className="editor-modal query-builder__projection__modal"
336
+ >
250
337
  <ModalHeader title="Result Set Modifier" />
251
338
  <ModalBody className="query-builder__projection__modal__body">
252
339
  <div className="query-builder__projection__options">
253
- <ColumnsSortEditor tdsState={tdsState} />
340
+ <ColumnsSortEditor
341
+ projectionColumns={tdsState.projectionColumns}
342
+ sortColumns={sortColumns}
343
+ setSortColumns={setSortColumns}
344
+ tdsColumns={tdsState.tdsColumns}
345
+ />
254
346
  <div className="panel__content__form__section">
255
347
  <div className="panel__content__form__section__header__label">
256
348
  Eliminate Duplicate Rows
257
349
  </div>
258
350
  <div
259
351
  className="panel__content__form__section__toggler"
260
- onClick={toggleDistinct}
352
+ onClick={() => setDistinct(!distinct)}
261
353
  >
262
354
  <button
263
355
  className={clsx(
@@ -277,75 +369,74 @@ export const QueryResultModifierModal = observer(
277
369
  </div>
278
370
  </div>
279
371
  <div className="panel__content__form__section">
280
- <div className="panel__content__form__section__header__label">
372
+ <label
373
+ htmlFor="query-builder__projection__modal__limit-results-input"
374
+ className="panel__content__form__section__header__label"
375
+ >
281
376
  Limit Results
282
- </div>
377
+ </label>
283
378
  <div className="panel__content__form__section__header__prompt">
284
379
  Specify the maximum total number of rows the output will
285
380
  produce
286
381
  </div>
287
382
  <input
383
+ id="query-builder__projection__modal__limit-results-input"
288
384
  className="panel__content__form__section__input panel__content__form__section__number-input"
289
385
  spellCheck={false}
290
- type="number"
386
+ type="text"
291
387
  value={limitResults ?? ''}
292
- onChange={changeValue}
388
+ onChange={handleLimitResultsChange}
293
389
  />
294
390
  </div>
295
391
  <div className="panel__content__form__section">
296
- <div className="panel__content__form__section__header__label">
392
+ <label
393
+ htmlFor="query-builder__projection__modal__slice-start-input"
394
+ className="panel__content__form__section__header__label"
395
+ >
297
396
  Slice
298
- </div>
397
+ </label>
299
398
  <div className="panel__content__form__section__header__prompt">
300
399
  Reduce the number of rows in the provided TDS, selecting the
301
400
  set of rows in the specified range between start and stop
302
401
  </div>
303
- {resultSetModifierState.slice ? (
304
- <>
305
- <div className="query-builder__result__slice">
306
- <input
307
- className="input--dark query-builder__result__slice__input"
308
- spellCheck={false}
309
- value={resultSetModifierState.slice[0]}
310
- onChange={changeSliceStart}
311
- type="number"
312
- />
313
- <div className="query-builder__result__slice__range">
314
- ..
315
- </div>
316
- <input
317
- className="input--dark query-builder__result__slice__input"
318
- spellCheck={false}
319
- value={resultSetModifierState.slice[1]}
320
- onChange={changeSliceEnd}
321
- type="number"
322
- />
323
- <button
324
- className="query-builder__projection__options__sort__remove-btn btn--dark btn--caution"
325
- onClick={clearSlice}
326
- tabIndex={-1}
327
- title="Remove"
328
- >
329
- <TimesIcon />
330
- </button>
331
- </div>
332
- </>
333
- ) : (
334
- <div className="panel__content__form__section__list__new-item__add">
335
- <button
336
- className="panel__content__form__section__list__new-item__add-btn btn btn--dark"
337
- onClick={addSlice}
338
- tabIndex={-1}
339
- >
340
- Add Slice
341
- </button>
402
+ <div className="query-builder__result__slice">
403
+ <div className="query-builder__result__slice__input__wrapper">
404
+ <InputWithInlineValidation
405
+ id="query-builder__projection__modal__slice-start-input"
406
+ className="input--dark query-builder__result__slice__input"
407
+ spellCheck={false}
408
+ value={slice[0] ?? ''}
409
+ onChange={changeSliceStart}
410
+ type="text"
411
+ error={isInvalidSlice ? 'Invalid slice' : undefined}
412
+ />
413
+ </div>
414
+ <div className="query-builder__result__slice__range">..</div>
415
+ <div className="query-builder__result__slice__input__wrapper">
416
+ <InputWithInlineValidation
417
+ className="input--dark query-builder__result__slice__input"
418
+ spellCheck={false}
419
+ value={slice[1] ?? ''}
420
+ onChange={changeSliceEnd}
421
+ type="text"
422
+ error={isInvalidSlice ? 'Invalid slice' : undefined}
423
+ />
342
424
  </div>
343
- )}
425
+ </div>
344
426
  </div>
345
427
  </div>
346
428
  </ModalBody>
347
429
  <ModalFooter>
348
- <ModalFooterButton onClick={close} text="Close" />
430
+ <ModalFooterButton
431
+ onClick={applyChanges}
432
+ text="Apply"
433
+ disabled={isInvalidSlice}
434
+ />
435
+ <ModalFooterButton
436
+ onClick={closeModal}
437
+ text="Cancel"
438
+ type="secondary"
439
+ />
349
440
  </ModalFooter>
350
441
  </Modal>
351
442
  </Dialog>
@@ -159,6 +159,8 @@ const LambdaEditor_Inner = observer(
159
159
  applicationStore.alertUnhandledError,
160
160
  );
161
161
  setExpanded(!isExpanded);
162
+ } else if (!forceExpansion && parserError) {
163
+ setExpanded(!isExpanded);
162
164
  }
163
165
  };
164
166
 
@@ -399,7 +401,6 @@ const LambdaEditor_Inner = observer(
399
401
  <button
400
402
  className="lambda-editor__editor__expand-btn"
401
403
  onClick={toggleExpandedMode}
402
- disabled={Boolean(parserError)}
403
404
  tabIndex={-1}
404
405
  title="Toggle Expand"
405
406
  >
@@ -410,7 +411,6 @@ const LambdaEditor_Inner = observer(
410
411
  <button
411
412
  className="lambda-editor__action"
412
413
  onClick={openInPopUp}
413
- disabled={Boolean(parserError)}
414
414
  tabIndex={-1}
415
415
  title="Open in a popup..."
416
416
  >
@@ -569,12 +569,14 @@ const LambdaEditor_PopUp = observer(
569
569
  }
570
570
 
571
571
  useEffect(() => {
572
- flowResult(
573
- lambdaEditorState.convertLambdaObjectToGrammarString({
574
- pretty: true,
575
- preserveCompilationError: true,
576
- }),
577
- ).catch(applicationStore.alertUnhandledError);
572
+ if (!lambdaEditorState.parserError) {
573
+ flowResult(
574
+ lambdaEditorState.convertLambdaObjectToGrammarString({
575
+ pretty: true,
576
+ preserveCompilationError: true,
577
+ }),
578
+ ).catch(applicationStore.alertUnhandledError);
579
+ }
578
580
  }, [applicationStore, lambdaEditorState]);
579
581
 
580
582
  // dispose editor
@@ -18,6 +18,7 @@ import {
18
18
  ShareBoxIcon,
19
19
  type TooltipPlacement,
20
20
  Tooltip,
21
+ ClickAwayListener,
21
22
  } from '@finos/legend-art';
22
23
  import {
23
24
  type AbstractProperty,
@@ -137,73 +138,88 @@ export const QueryBuilderPropertyInfoTooltip: React.FC<{
137
138
  explorerState,
138
139
  } = props;
139
140
 
141
+ const [open, setIsOpen] = useState(false);
142
+
140
143
  return (
141
- <Tooltip
142
- arrow={true}
143
- {...(placement !== undefined ? { placement } : {})}
144
- classes={{
145
- tooltip: 'query-builder__tooltip',
146
- arrow: 'query-builder__tooltip__arrow',
147
- tooltipPlacementRight: 'query-builder__tooltip--right',
148
- }}
149
- TransitionProps={{
150
- // disable transition
151
- // NOTE: somehow, this is the only workaround we have, if for example
152
- // we set `appear = true`, the tooltip will jump out of position
153
- timeout: 0,
154
- }}
155
- title={
156
- <div className="query-builder__tooltip__content">
157
- <div className="query-builder__tooltip__header">{title}</div>
158
- <div className="query-builder__tooltip__item">
159
- <div className="query-builder__tooltip__item__label">Type</div>
160
- <div className="query-builder__tooltip__item__value">
161
- {type?.path ?? property.genericType.value.rawType.path}
162
- </div>
163
- </div>
164
- <div className="query-builder__tooltip__item">
165
- <div className="query-builder__tooltip__item__label">Path</div>
166
- <div className="query-builder__tooltip__item__value">{path}</div>
167
- {explorerState && (
168
- <div className="query-builder__tooltip__item__action">
169
- <button
170
- onClick={() => explorerState.highlightTreeNode(path)}
171
- title="Show in tree"
172
- >
173
- <ShareBoxIcon color="white" />
174
- </button>
144
+ <ClickAwayListener onClickAway={() => setIsOpen(false)}>
145
+ <div>
146
+ <Tooltip
147
+ arrow={true}
148
+ {...(placement !== undefined ? { placement } : {})}
149
+ classes={{
150
+ tooltip: 'query-builder__tooltip',
151
+ arrow: 'query-builder__tooltip__arrow',
152
+ tooltipPlacementRight: 'query-builder__tooltip--right',
153
+ }}
154
+ open={open}
155
+ onClose={() => setIsOpen(false)}
156
+ TransitionProps={{
157
+ // disable transition
158
+ // NOTE: somehow, this is the only workaround we have, if for example
159
+ // we set `appear = true`, the tooltip will jump out of position
160
+ timeout: 0,
161
+ }}
162
+ disableFocusListener={true}
163
+ disableHoverListener={true}
164
+ disableTouchListener={true}
165
+ title={
166
+ <div className="query-builder__tooltip__content">
167
+ <div className="query-builder__tooltip__header">{title}</div>
168
+ <div className="query-builder__tooltip__item">
169
+ <div className="query-builder__tooltip__item__label">Type</div>
170
+ <div className="query-builder__tooltip__item__value">
171
+ {type?.path ?? property.genericType.value.rawType.path}
172
+ </div>
175
173
  </div>
176
- )}
177
- </div>
178
- <div className="query-builder__tooltip__item">
179
- <div className="query-builder__tooltip__item__label">
180
- Multiplicity
181
- </div>
182
- <div className="query-builder__tooltip__item__value">
183
- {getMultiplicityDescription(property.multiplicity)}
184
- </div>
185
- </div>
186
- <div className="query-builder__tooltip__item">
187
- <div className="query-builder__tooltip__item__label">
188
- Derived Property
189
- </div>
190
- <div className="query-builder__tooltip__item__value">
191
- {property instanceof DerivedProperty ? 'Yes' : 'No'}
192
- </div>
193
- </div>
194
- <div className="query-builder__tooltip__item">
195
- <div className="query-builder__tooltip__item__label">Mapped</div>
196
- <div className="query-builder__tooltip__item__value">
197
- {isMapped ? 'Yes' : 'No'}
174
+ <div className="query-builder__tooltip__item">
175
+ <div className="query-builder__tooltip__item__label">Path</div>
176
+ <div className="query-builder__tooltip__item__value">
177
+ {path}
178
+ </div>
179
+ {explorerState && (
180
+ <div className="query-builder__tooltip__item__action">
181
+ <button
182
+ onClick={() => explorerState.highlightTreeNode(path)}
183
+ title="Show in tree"
184
+ >
185
+ <ShareBoxIcon color="white" />
186
+ </button>
187
+ </div>
188
+ )}
189
+ </div>
190
+ <div className="query-builder__tooltip__item">
191
+ <div className="query-builder__tooltip__item__label">
192
+ Multiplicity
193
+ </div>
194
+ <div className="query-builder__tooltip__item__value">
195
+ {getMultiplicityDescription(property.multiplicity)}
196
+ </div>
197
+ </div>
198
+ <div className="query-builder__tooltip__item">
199
+ <div className="query-builder__tooltip__item__label">
200
+ Derived Property
201
+ </div>
202
+ <div className="query-builder__tooltip__item__value">
203
+ {property instanceof DerivedProperty ? 'Yes' : 'No'}
204
+ </div>
205
+ </div>
206
+ <div className="query-builder__tooltip__item">
207
+ <div className="query-builder__tooltip__item__label">
208
+ Mapped
209
+ </div>
210
+ <div className="query-builder__tooltip__item__value">
211
+ {isMapped ? 'Yes' : 'No'}
212
+ </div>
213
+ </div>
214
+ <QueryBuilderTaggedValueInfoTooltip
215
+ taggedValues={property.taggedValues}
216
+ />
198
217
  </div>
199
- </div>
200
- <QueryBuilderTaggedValueInfoTooltip
201
- taggedValues={property.taggedValues}
202
- />
203
- </div>
204
- }
205
- >
206
- {children}
207
- </Tooltip>
218
+ }
219
+ >
220
+ <div onClick={() => setIsOpen(true)}>{children}</div>
221
+ </Tooltip>
222
+ </div>
223
+ </ClickAwayListener>
208
224
  );
209
225
  };
@@ -16,12 +16,7 @@
16
16
 
17
17
  import { action, computed, makeObservable, observable } from 'mobx';
18
18
  import type { QueryBuilderTDSState } from './QueryBuilderTDSState.js';
19
- import {
20
- addUniqueEntry,
21
- deleteEntry,
22
- type Hashable,
23
- hashArray,
24
- } from '@finos/legend-shared';
19
+ import { addUniqueEntry, type Hashable, hashArray } from '@finos/legend-shared';
25
20
  import { QUERY_BUILDER_STATE_HASH_STRUCTURE } from '../../QueryBuilderStateHashUtils.js';
26
21
  import type { QueryBuilderTDSColumnState } from './QueryBuilderTDSColumnState.js';
27
22
  import { COLUMN_SORT_TYPE } from '../../../graph/QueryBuilderMetaModelConst.js';
@@ -76,8 +71,8 @@ export class QueryResultSetModifierState implements Hashable {
76
71
  slice: observable.ref,
77
72
  setShowModal: action,
78
73
  setLimit: action,
79
- toggleDistinct: action,
80
- deleteSortColumn: action,
74
+ setDistinct: action,
75
+ setSortColumns: action,
81
76
  addSortColumn: action,
82
77
  updateSortColumns: action,
83
78
  setSlice: action,
@@ -96,12 +91,12 @@ export class QueryResultSetModifierState implements Hashable {
96
91
  this.limit = val === undefined || val <= 0 ? undefined : val;
97
92
  }
98
93
 
99
- toggleDistinct(): void {
100
- this.distinct = !this.distinct;
94
+ setDistinct(val: boolean): void {
95
+ this.distinct = val;
101
96
  }
102
97
 
103
- deleteSortColumn(val: SortColumnState): void {
104
- deleteEntry(this.sortColumns, val);
98
+ setSortColumns(val: SortColumnState[]): void {
99
+ this.sortColumns = val;
105
100
  }
106
101
 
107
102
  addSortColumn(val: SortColumnState): void {