@finos/legend-query-builder 4.14.10 → 4.14.12

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