@finos/legend-query-builder 4.14.10 → 4.14.11

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 +86 -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 +174 -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>
@@ -189,75 +218,127 @@ const ColumnsSortEditor = observer(
189
218
 
190
219
  export const QueryResultModifierModal = observer(
191
220
  (props: { tdsState: QueryBuilderTDSState }) => {
192
- const { tdsState: tdsState } = props;
221
+ // Read current state
222
+ const { tdsState } = props;
193
223
  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
- };
224
+ const stateSortColumns = resultSetModifierState.sortColumns;
225
+ const stateDistinct = resultSetModifierState.distinct;
226
+ const stateLimitResults = resultSetModifierState.limit;
227
+ const stateSlice = resultSetModifierState.slice;
228
+
229
+ // Set up temp state for modal lifecycle
230
+ const [sortColumns, setSortColumns] = useState([...stateSortColumns]);
231
+ const [distinct, setDistinct] = useState(stateDistinct);
232
+ const [limitResults, setLimitResults] = useState(stateLimitResults);
233
+ const [slice, setSlice] = useState<
234
+ [number | undefined, number | undefined]
235
+ >(stateSlice ?? [undefined, undefined]);
204
236
 
205
- const handleSliceStartChange = (start: number, end: number): void => {
206
- const slice: [number, number] = [start, end];
207
- resultSetModifierState.setSlice(slice);
237
+ // Sync temp state with tdsState when modal is opened/closed
238
+ useEffect(() => {
239
+ setSortColumns([...stateSortColumns]);
240
+ setDistinct(stateDistinct);
241
+ setLimitResults(stateLimitResults);
242
+ setSlice(stateSlice ?? [undefined, undefined]);
243
+ }, [
244
+ resultSetModifierState.showModal,
245
+ stateSortColumns,
246
+ stateDistinct,
247
+ stateLimitResults,
248
+ stateSlice,
249
+ ]);
250
+
251
+ // Handle user actions
252
+ const closeModal = (): void => resultSetModifierState.setShowModal(false);
253
+ const applyChanges = (): void => {
254
+ resultSetModifierState.setSortColumns(sortColumns);
255
+ resultSetModifierState.setDistinct(distinct);
256
+ resultSetModifierState.setLimit(limitResults);
257
+ if (slice[0] !== undefined && slice[1] !== undefined) {
258
+ resultSetModifierState.setSlice([slice[0], slice[1]]);
259
+ } else {
260
+ resultSetModifierState.setSlice(undefined);
261
+ }
262
+ resultSetModifierState.setShowModal(false);
208
263
  };
209
264
 
210
- const clearSlice = (): void => {
211
- resultSetModifierState.setSlice(undefined);
265
+ const handleLimitResultsChange: React.ChangeEventHandler<
266
+ HTMLInputElement
267
+ > = (event) => {
268
+ const val = event.target.value.replace(/[^0-9]/g, '');
269
+ setLimitResults(val === '' ? undefined : parseInt(val, 10));
212
270
  };
213
271
 
214
- const addSlice = (): void => {
215
- resultSetModifierState.setSlice([0, 1]);
272
+ const handleSliceChange = (
273
+ start: number | undefined,
274
+ end: number | undefined,
275
+ ): void => {
276
+ const newSlice: [number | undefined, number | undefined] = [start, end];
277
+ setSlice(newSlice);
216
278
  };
217
279
 
218
280
  const changeSliceStart: React.ChangeEventHandler<HTMLInputElement> = (
219
281
  event,
220
282
  ) => {
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]);
283
+ const val = event.target.value.replace(/[^0-9]/g, '');
284
+ if (val === '') {
285
+ handleSliceChange(undefined, slice[1]);
286
+ } else {
287
+ const start = typeof val === 'number' ? val : parseInt(val, 10);
288
+ handleSliceChange(start, slice[1]);
226
289
  }
227
290
  };
228
291
  const changeSliceEnd: React.ChangeEventHandler<HTMLInputElement> = (
229
292
  event,
230
293
  ) => {
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);
294
+ const val = event.target.value.replace(/[^0-9]/g, '');
295
+ if (val === '') {
296
+ handleSliceChange(slice[0], undefined);
297
+ } else {
298
+ const end = typeof val === 'number' ? val : parseInt(val, 10);
299
+ handleSliceChange(slice[0], end);
236
300
  }
237
301
  };
238
302
 
303
+ // Error states
304
+ const isInvalidSlice =
305
+ (slice[0] === undefined && slice[1] !== undefined) ||
306
+ (slice[0] !== undefined && slice[1] === undefined) ||
307
+ (slice[0] !== undefined &&
308
+ slice[1] !== undefined &&
309
+ slice[0] >= slice[1]);
310
+
239
311
  return (
240
312
  <Dialog
241
313
  open={Boolean(resultSetModifierState.showModal)}
242
- onClose={close}
314
+ onClose={closeModal}
243
315
  classes={{
244
316
  root: 'editor-modal__root-container',
245
317
  container: 'editor-modal__container',
246
318
  paper: 'editor-modal__content',
247
319
  }}
320
+ data-testid={QUERY_BUILDER_TEST_ID.QUERY_BUILDER_RESULT_MODIFIER_PANEL}
248
321
  >
249
- <Modal darkMode={true} className="editor-modal">
322
+ <Modal
323
+ darkMode={true}
324
+ className="editor-modal query-builder__projection__modal"
325
+ >
250
326
  <ModalHeader title="Result Set Modifier" />
251
327
  <ModalBody className="query-builder__projection__modal__body">
252
328
  <div className="query-builder__projection__options">
253
- <ColumnsSortEditor tdsState={tdsState} />
329
+ <ColumnsSortEditor
330
+ projectionColumns={tdsState.projectionColumns}
331
+ sortColumns={sortColumns}
332
+ setSortColumns={setSortColumns}
333
+ tdsColumns={tdsState.tdsColumns}
334
+ />
254
335
  <div className="panel__content__form__section">
255
336
  <div className="panel__content__form__section__header__label">
256
337
  Eliminate Duplicate Rows
257
338
  </div>
258
339
  <div
259
340
  className="panel__content__form__section__toggler"
260
- onClick={toggleDistinct}
341
+ onClick={() => setDistinct(!distinct)}
261
342
  >
262
343
  <button
263
344
  className={clsx(
@@ -277,75 +358,74 @@ export const QueryResultModifierModal = observer(
277
358
  </div>
278
359
  </div>
279
360
  <div className="panel__content__form__section">
280
- <div className="panel__content__form__section__header__label">
361
+ <label
362
+ htmlFor="query-builder__projection__modal__limit-results-input"
363
+ className="panel__content__form__section__header__label"
364
+ >
281
365
  Limit Results
282
- </div>
366
+ </label>
283
367
  <div className="panel__content__form__section__header__prompt">
284
368
  Specify the maximum total number of rows the output will
285
369
  produce
286
370
  </div>
287
371
  <input
372
+ id="query-builder__projection__modal__limit-results-input"
288
373
  className="panel__content__form__section__input panel__content__form__section__number-input"
289
374
  spellCheck={false}
290
- type="number"
375
+ type="text"
291
376
  value={limitResults ?? ''}
292
- onChange={changeValue}
377
+ onChange={handleLimitResultsChange}
293
378
  />
294
379
  </div>
295
380
  <div className="panel__content__form__section">
296
- <div className="panel__content__form__section__header__label">
381
+ <label
382
+ htmlFor="query-builder__projection__modal__slice-start-input"
383
+ className="panel__content__form__section__header__label"
384
+ >
297
385
  Slice
298
- </div>
386
+ </label>
299
387
  <div className="panel__content__form__section__header__prompt">
300
388
  Reduce the number of rows in the provided TDS, selecting the
301
389
  set of rows in the specified range between start and stop
302
390
  </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>
391
+ <div className="query-builder__result__slice">
392
+ <div className="query-builder__result__slice__input__wrapper">
393
+ <InputWithInlineValidation
394
+ id="query-builder__projection__modal__slice-start-input"
395
+ className="input--dark query-builder__result__slice__input"
396
+ spellCheck={false}
397
+ value={slice[0] ?? ''}
398
+ onChange={changeSliceStart}
399
+ type="text"
400
+ error={isInvalidSlice ? 'Invalid slice' : undefined}
401
+ />
402
+ </div>
403
+ <div className="query-builder__result__slice__range">..</div>
404
+ <div className="query-builder__result__slice__input__wrapper">
405
+ <InputWithInlineValidation
406
+ className="input--dark query-builder__result__slice__input"
407
+ spellCheck={false}
408
+ value={slice[1] ?? ''}
409
+ onChange={changeSliceEnd}
410
+ type="text"
411
+ error={isInvalidSlice ? 'Invalid slice' : undefined}
412
+ />
342
413
  </div>
343
- )}
414
+ </div>
344
415
  </div>
345
416
  </div>
346
417
  </ModalBody>
347
418
  <ModalFooter>
348
- <ModalFooterButton onClick={close} text="Close" />
419
+ <ModalFooterButton
420
+ onClick={applyChanges}
421
+ text="Apply"
422
+ disabled={isInvalidSlice}
423
+ />
424
+ <ModalFooterButton
425
+ onClick={closeModal}
426
+ text="Cancel"
427
+ type="secondary"
428
+ />
349
429
  </ModalFooter>
350
430
  </Modal>
351
431
  </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 {