@finos/legend-query-builder 4.14.19 → 4.14.20
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.
- package/lib/components/QueryLoader.d.ts.map +1 -1
- package/lib/components/QueryLoader.js +85 -36
- package/lib/components/QueryLoader.js.map +1 -1
- package/lib/index.css +2 -2
- package/lib/index.css.map +1 -1
- package/lib/package.json +1 -1
- package/lib/stores/QueryBuilder_LegendApplicationPlugin_Extension.d.ts +19 -1
- package/lib/stores/QueryBuilder_LegendApplicationPlugin_Extension.d.ts.map +1 -1
- package/lib/stores/QueryLoaderState.d.ts +14 -4
- package/lib/stores/QueryLoaderState.d.ts.map +1 -1
- package/lib/stores/QueryLoaderState.js +35 -5
- package/lib/stores/QueryLoaderState.js.map +1 -1
- package/package.json +1 -1
- package/src/components/QueryLoader.tsx +279 -127
- package/src/stores/QueryBuilder_LegendApplicationPlugin_Extension.ts +30 -1
- package/src/stores/QueryLoaderState.ts +64 -12
@@ -41,11 +41,12 @@ import {
|
|
41
41
|
ThinChevronRightIcon,
|
42
42
|
InfoCircleIcon,
|
43
43
|
} from '@finos/legend-art';
|
44
|
-
import type { LightQuery } from '@finos/legend-graph';
|
44
|
+
import type { LightQuery, RawLambda } from '@finos/legend-graph';
|
45
45
|
import {
|
46
46
|
debounce,
|
47
47
|
formatDistanceToNow,
|
48
48
|
guaranteeNonNullable,
|
49
|
+
isNonNullable,
|
49
50
|
quantifyList,
|
50
51
|
} from '@finos/legend-shared';
|
51
52
|
import { flowResult } from 'mobx';
|
@@ -110,6 +111,21 @@ export const QueryLoader = observer(
|
|
110
111
|
const searchInputRef = useRef<HTMLInputElement>(null);
|
111
112
|
const queryRenameInputRef = useRef<HTMLInputElement>(null);
|
112
113
|
const results = queryLoaderState.queries;
|
114
|
+
const curatedTemplateQueries =
|
115
|
+
queryLoaderState.curatedTemplateQuerySepcifications
|
116
|
+
.map((s) =>
|
117
|
+
queryLoaderState.queryBuilderState
|
118
|
+
? s.getCuratedTemplateQueries(queryLoaderState.queryBuilderState)
|
119
|
+
: [],
|
120
|
+
)
|
121
|
+
.flat();
|
122
|
+
const loadCuratedTemplateQuery =
|
123
|
+
queryLoaderState.curatedTemplateQuerySepcifications
|
124
|
+
// already using an arrow function suggested by @typescript-eslint/unbound-method
|
125
|
+
// eslint-disable-next-line
|
126
|
+
.map((s) => () => s.loadCuratedTemplateQuery)
|
127
|
+
.filter(isNonNullable)[0];
|
128
|
+
|
113
129
|
const [isMineOnly, setIsMineOnly] = useState(false);
|
114
130
|
const [showQueryNameEditInput, setShowQueryNameEditInput] = useState<
|
115
131
|
number | undefined
|
@@ -173,7 +189,25 @@ export const QueryLoader = observer(
|
|
173
189
|
debouncedLoadQueries.cancel();
|
174
190
|
debouncedLoadQueries(queryLoaderState.searchText);
|
175
191
|
};
|
176
|
-
|
192
|
+
const toggleCuratedTemplate = (): void => {
|
193
|
+
Array.from(queryLoaderState.extraFilters).map(([key, value]) =>
|
194
|
+
queryLoaderState.extraFilters.set(key, false),
|
195
|
+
);
|
196
|
+
queryLoaderState.setShowCurrentUserQueriesOnly(false);
|
197
|
+
setIsMineOnly(false);
|
198
|
+
queryLoaderState.extraQueryFilterOptionsRelatedToTemplateQuery.forEach(
|
199
|
+
(op) =>
|
200
|
+
queryLoaderState.extraFilters.set(
|
201
|
+
op,
|
202
|
+
!queryLoaderState.isCuratedTemplateToggled,
|
203
|
+
),
|
204
|
+
);
|
205
|
+
queryLoaderState.showingDefaultQueries =
|
206
|
+
queryLoaderState.isCuratedTemplateToggled;
|
207
|
+
queryLoaderState.setIsCuratedTemplateToggled(
|
208
|
+
!queryLoaderState.isCuratedTemplateToggled,
|
209
|
+
);
|
210
|
+
};
|
177
211
|
useEffect(() => {
|
178
212
|
flowResult(queryLoaderState.searchQueries('')).catch(
|
179
213
|
applicationStore.alertUnhandledError,
|
@@ -207,10 +241,16 @@ export const QueryLoader = observer(
|
|
207
241
|
}
|
208
242
|
};
|
209
243
|
|
210
|
-
const showPreview = (
|
211
|
-
|
212
|
-
|
213
|
-
|
244
|
+
const showPreview = (
|
245
|
+
queryId: string | undefined,
|
246
|
+
template?: {
|
247
|
+
queryName: string;
|
248
|
+
queryContent: RawLambda;
|
249
|
+
},
|
250
|
+
): void => {
|
251
|
+
flowResult(
|
252
|
+
queryLoaderState.getPreviewQueryContent(queryId, template),
|
253
|
+
).catch(applicationStore.alertUnhandledError);
|
214
254
|
queryLoaderState.setShowPreviewViewer(true);
|
215
255
|
};
|
216
256
|
|
@@ -254,6 +294,12 @@ export const QueryLoader = observer(
|
|
254
294
|
'query-loader__filter__toggler__btn--toggled': isMineOnly,
|
255
295
|
})}
|
256
296
|
onClick={toggleShowCurrentUserQueriesOnly}
|
297
|
+
disabled={queryLoaderState.isCuratedTemplateToggled}
|
298
|
+
title={
|
299
|
+
queryLoaderState.isCuratedTemplateToggled
|
300
|
+
? 'current fitler is disabled when `Curated Template Query` is on'
|
301
|
+
: 'click to add filter'
|
302
|
+
}
|
257
303
|
tabIndex={-1}
|
258
304
|
>
|
259
305
|
Mine Only
|
@@ -267,6 +313,12 @@ export const QueryLoader = observer(
|
|
267
313
|
className={clsx('query-loader__filter__toggler__btn', {
|
268
314
|
'query-loader__filter__toggler__btn--toggled': value,
|
269
315
|
})}
|
316
|
+
disabled={queryLoaderState.isCuratedTemplateToggled}
|
317
|
+
title={
|
318
|
+
queryLoaderState.isCuratedTemplateToggled
|
319
|
+
? 'current fitler is disabled when `Curated Template Query` is on'
|
320
|
+
: 'click to add filter'
|
321
|
+
}
|
270
322
|
onClick={(): void => toggleExtraFilters(key)}
|
271
323
|
tabIndex={-1}
|
272
324
|
>
|
@@ -276,6 +328,21 @@ export const QueryLoader = observer(
|
|
276
328
|
)}
|
277
329
|
</div>
|
278
330
|
)}
|
331
|
+
{queryLoaderState.extraQueryFilterOptionsRelatedToTemplateQuery
|
332
|
+
.length > 0 && (
|
333
|
+
<div className="query-loader__filter__extra__filters">
|
334
|
+
<button
|
335
|
+
className={clsx('query-loader__filter__toggler__btn', {
|
336
|
+
'query-loader__filter__toggler__btn--toggled':
|
337
|
+
queryLoaderState.isCuratedTemplateToggled,
|
338
|
+
})}
|
339
|
+
onClick={toggleCuratedTemplate}
|
340
|
+
tabIndex={-1}
|
341
|
+
>
|
342
|
+
Curated Template Query
|
343
|
+
</button>
|
344
|
+
</div>
|
345
|
+
)}
|
279
346
|
</div>
|
280
347
|
</div>
|
281
348
|
</div>
|
@@ -297,7 +364,20 @@ export const QueryLoader = observer(
|
|
297
364
|
queryLoaderState.generateDefaultQueriesSummaryText?.(
|
298
365
|
results,
|
299
366
|
) ?? 'Refine your search to get better matches'
|
300
|
-
) :
|
367
|
+
) : !queryLoaderState.isCuratedTemplateToggled ? (
|
368
|
+
results.length >= QUERY_LOADER_TYPEAHEAD_SEARCH_LIMIT ? (
|
369
|
+
<>
|
370
|
+
{`Found ${QUERY_LOADER_TYPEAHEAD_SEARCH_LIMIT}+ matches`}{' '}
|
371
|
+
<InfoCircleIcon
|
372
|
+
title="Some queries are not listed, refine your search to get better matches"
|
373
|
+
className="query-loader__results__summary__info"
|
374
|
+
/>
|
375
|
+
</>
|
376
|
+
) : (
|
377
|
+
`Found ${quantifyList(results, 'match', 'matches')}`
|
378
|
+
)
|
379
|
+
) : curatedTemplateQueries.length >=
|
380
|
+
QUERY_LOADER_TYPEAHEAD_SEARCH_LIMIT ? (
|
301
381
|
<>
|
302
382
|
{`Found ${QUERY_LOADER_TYPEAHEAD_SEARCH_LIMIT}+ matches`}{' '}
|
303
383
|
<InfoCircleIcon
|
@@ -306,139 +386,211 @@ export const QueryLoader = observer(
|
|
306
386
|
/>
|
307
387
|
</>
|
308
388
|
) : (
|
309
|
-
`Found ${quantifyList(
|
389
|
+
`Found ${quantifyList(
|
390
|
+
curatedTemplateQueries,
|
391
|
+
'match',
|
392
|
+
'matches',
|
393
|
+
)}`
|
310
394
|
)}
|
311
395
|
</div>
|
312
|
-
{
|
313
|
-
|
314
|
-
|
315
|
-
|
316
|
-
|
317
|
-
|
318
|
-
|
319
|
-
|
320
|
-
|
321
|
-
|
322
|
-
|
323
|
-
|
324
|
-
<
|
325
|
-
|
326
|
-
|
327
|
-
|
328
|
-
|
329
|
-
|
330
|
-
|
331
|
-
|
332
|
-
event.
|
333
|
-
|
334
|
-
|
335
|
-
event.
|
336
|
-
|
337
|
-
|
338
|
-
|
339
|
-
|
340
|
-
|
341
|
-
|
342
|
-
|
396
|
+
{!queryLoaderState.isCuratedTemplateToggled &&
|
397
|
+
results
|
398
|
+
.slice(0, QUERY_LOADER_TYPEAHEAD_SEARCH_LIMIT)
|
399
|
+
.map((query, idx) => (
|
400
|
+
<div
|
401
|
+
className="query-loader__result"
|
402
|
+
title={`Click to ${loadActionLabel}...`}
|
403
|
+
key={query.id}
|
404
|
+
onClick={() => queryLoaderState.loadQuery(query)}
|
405
|
+
>
|
406
|
+
<div className="query-loader__result__content">
|
407
|
+
{showQueryNameEditInput === idx ? (
|
408
|
+
<div className="query-loader__result__title__editor">
|
409
|
+
<input
|
410
|
+
className="query-loader__result__title__editor__input input--dark"
|
411
|
+
spellCheck={false}
|
412
|
+
ref={queryRenameInputRef}
|
413
|
+
value={queryNameInputValue}
|
414
|
+
onChange={changeQueryNameInputValue}
|
415
|
+
onKeyDown={(event) => {
|
416
|
+
if (event.code === 'Enter') {
|
417
|
+
event.stopPropagation();
|
418
|
+
renameQuery(query)();
|
419
|
+
} else if (event.code === 'Escape') {
|
420
|
+
event.stopPropagation();
|
421
|
+
hideEditQueryNameInput();
|
422
|
+
}
|
423
|
+
}}
|
424
|
+
onBlur={() => hideEditQueryNameInput()}
|
425
|
+
// avoid clicking on the input causing the call to load query
|
426
|
+
onClick={(event) => event.stopPropagation()}
|
427
|
+
/>
|
428
|
+
</div>
|
429
|
+
) : (
|
430
|
+
<div
|
431
|
+
className="query-loader__result__title"
|
432
|
+
title={query.name}
|
433
|
+
>
|
434
|
+
{query.name}
|
435
|
+
</div>
|
436
|
+
)}
|
437
|
+
<div className="query-loader__result__description">
|
438
|
+
<div className="query-loader__result__description__date__icon">
|
439
|
+
<LastModifiedIcon />
|
440
|
+
</div>
|
441
|
+
<div className="query-loader__result__description__date">
|
442
|
+
{query.lastUpdatedAt
|
443
|
+
? formatDistanceToNow(
|
444
|
+
new Date(query.lastUpdatedAt),
|
445
|
+
{
|
446
|
+
includeSeconds: true,
|
447
|
+
addSuffix: true,
|
448
|
+
},
|
449
|
+
)
|
450
|
+
: '(unknown)'}
|
451
|
+
</div>
|
452
|
+
<div
|
453
|
+
className={clsx(
|
454
|
+
'query-loader__result__description__author__icon',
|
455
|
+
{
|
456
|
+
'query-loader__result__description__author__icon--owner':
|
457
|
+
query.isCurrentUserQuery,
|
458
|
+
},
|
459
|
+
)}
|
460
|
+
>
|
461
|
+
<UserIcon />
|
462
|
+
</div>
|
463
|
+
<div className="query-loader__result__description__author__name">
|
464
|
+
{query.isCurrentUserQuery ? (
|
465
|
+
<div
|
466
|
+
title={query.owner}
|
467
|
+
className="query-loader__result__description__owner"
|
468
|
+
>
|
469
|
+
Me
|
470
|
+
</div>
|
471
|
+
) : (
|
472
|
+
query.owner
|
473
|
+
)}
|
474
|
+
</div>
|
343
475
|
</div>
|
344
|
-
|
476
|
+
</div>
|
477
|
+
<DropdownMenu
|
478
|
+
className="query-loader__result__actions-menu"
|
479
|
+
title="More Actions..."
|
480
|
+
content={
|
481
|
+
<MenuContent>
|
482
|
+
<MenuContentItem
|
483
|
+
onClick={(): void => showPreview(query.id)}
|
484
|
+
>
|
485
|
+
Show Query Preview
|
486
|
+
</MenuContentItem>
|
487
|
+
{!queryLoaderState.isReadOnly && (
|
488
|
+
<MenuContentItem
|
489
|
+
disabled={!query.isCurrentUserQuery}
|
490
|
+
onClick={deleteQuery(query)}
|
491
|
+
>
|
492
|
+
Delete
|
493
|
+
</MenuContentItem>
|
494
|
+
)}
|
495
|
+
{!queryLoaderState.isReadOnly && (
|
496
|
+
<MenuContentItem
|
497
|
+
disabled={!query.isCurrentUserQuery}
|
498
|
+
onClick={showEditQueryNameInput(
|
499
|
+
query.name,
|
500
|
+
idx,
|
501
|
+
)}
|
502
|
+
>
|
503
|
+
Rename
|
504
|
+
</MenuContentItem>
|
505
|
+
)}
|
506
|
+
</MenuContent>
|
507
|
+
}
|
508
|
+
menuProps={{
|
509
|
+
anchorOrigin: {
|
510
|
+
vertical: 'bottom',
|
511
|
+
horizontal: 'left',
|
512
|
+
},
|
513
|
+
transformOrigin: {
|
514
|
+
vertical: 'top',
|
515
|
+
horizontal: 'left',
|
516
|
+
},
|
517
|
+
elevation: 7,
|
518
|
+
}}
|
519
|
+
>
|
520
|
+
<MoreVerticalIcon />
|
521
|
+
</DropdownMenu>
|
522
|
+
<div className="query-loader__result__arrow">
|
523
|
+
<ThinChevronRightIcon />
|
524
|
+
</div>
|
525
|
+
</div>
|
526
|
+
))}
|
527
|
+
{queryLoaderState.queryBuilderState &&
|
528
|
+
queryLoaderState.isCuratedTemplateToggled &&
|
529
|
+
loadCuratedTemplateQuery &&
|
530
|
+
curatedTemplateQueries
|
531
|
+
.slice(0, QUERY_LOADER_TYPEAHEAD_SEARCH_LIMIT)
|
532
|
+
.map((templateQuery, idx) => (
|
533
|
+
<div
|
534
|
+
className="query-loader__result"
|
535
|
+
title={`Click to ${loadActionLabel}...`}
|
536
|
+
key={templateQuery.title}
|
537
|
+
onClick={() => {
|
538
|
+
loadCuratedTemplateQuery()(
|
539
|
+
templateQuery,
|
540
|
+
guaranteeNonNullable(
|
541
|
+
queryLoaderState.queryBuilderState,
|
542
|
+
),
|
543
|
+
);
|
544
|
+
queryLoaderState.setQueryLoaderDialogOpen(false);
|
545
|
+
}}
|
546
|
+
>
|
547
|
+
<div className="query-loader__result__content">
|
345
548
|
<div
|
346
549
|
className="query-loader__result__title"
|
347
|
-
title={
|
348
|
-
>
|
349
|
-
{query.name}
|
350
|
-
</div>
|
351
|
-
)}
|
352
|
-
<div className="query-loader__result__description">
|
353
|
-
<div className="query-loader__result__description__date__icon">
|
354
|
-
<LastModifiedIcon />
|
355
|
-
</div>
|
356
|
-
<div className="query-loader__result__description__date">
|
357
|
-
{query.lastUpdatedAt
|
358
|
-
? formatDistanceToNow(
|
359
|
-
new Date(query.lastUpdatedAt),
|
360
|
-
{
|
361
|
-
includeSeconds: true,
|
362
|
-
addSuffix: true,
|
363
|
-
},
|
364
|
-
)
|
365
|
-
: '(unknown)'}
|
366
|
-
</div>
|
367
|
-
<div
|
368
|
-
className={clsx(
|
369
|
-
'query-loader__result__description__author__icon',
|
370
|
-
{
|
371
|
-
'query-loader__result__description__author__icon--owner':
|
372
|
-
query.isCurrentUserQuery,
|
373
|
-
},
|
374
|
-
)}
|
550
|
+
title={templateQuery.title}
|
375
551
|
>
|
376
|
-
|
552
|
+
{templateQuery.title}
|
377
553
|
</div>
|
378
|
-
<div className="query-
|
379
|
-
{
|
380
|
-
<div
|
381
|
-
title={query.owner}
|
382
|
-
className="query-loader__result__description__owner"
|
383
|
-
>
|
384
|
-
Me
|
385
|
-
</div>
|
386
|
-
) : (
|
387
|
-
query.owner
|
388
|
-
)}
|
554
|
+
<div className="query-loader__result__description">
|
555
|
+
{templateQuery.description}
|
389
556
|
</div>
|
390
557
|
</div>
|
391
|
-
|
392
|
-
|
393
|
-
|
394
|
-
|
395
|
-
|
396
|
-
<MenuContent>
|
397
|
-
<MenuContentItem
|
398
|
-
onClick={(): void => showPreview(query.id)}
|
399
|
-
>
|
400
|
-
Show Query Preview
|
401
|
-
</MenuContentItem>
|
402
|
-
{!queryLoaderState.isReadOnly && (
|
403
|
-
<MenuContentItem
|
404
|
-
disabled={!query.isCurrentUserQuery}
|
405
|
-
onClick={deleteQuery(query)}
|
406
|
-
>
|
407
|
-
Delete
|
408
|
-
</MenuContentItem>
|
409
|
-
)}
|
410
|
-
{!queryLoaderState.isReadOnly && (
|
558
|
+
<DropdownMenu
|
559
|
+
className="query-loader__result__actions-menu"
|
560
|
+
title="More Actions..."
|
561
|
+
content={
|
562
|
+
<MenuContent>
|
411
563
|
<MenuContentItem
|
412
|
-
|
413
|
-
|
414
|
-
|
415
|
-
|
416
|
-
|
564
|
+
onClick={(): void =>
|
565
|
+
showPreview(undefined, {
|
566
|
+
queryContent: templateQuery.query,
|
567
|
+
queryName: templateQuery.title,
|
568
|
+
})
|
569
|
+
}
|
417
570
|
>
|
418
|
-
|
571
|
+
Show Query Preview
|
419
572
|
</MenuContentItem>
|
420
|
-
|
421
|
-
|
422
|
-
|
423
|
-
|
424
|
-
|
425
|
-
|
426
|
-
|
427
|
-
|
428
|
-
|
429
|
-
|
430
|
-
|
431
|
-
|
432
|
-
|
433
|
-
|
434
|
-
|
435
|
-
|
436
|
-
|
437
|
-
|
438
|
-
|
573
|
+
</MenuContent>
|
574
|
+
}
|
575
|
+
menuProps={{
|
576
|
+
anchorOrigin: {
|
577
|
+
vertical: 'bottom',
|
578
|
+
horizontal: 'left',
|
579
|
+
},
|
580
|
+
transformOrigin: {
|
581
|
+
vertical: 'top',
|
582
|
+
horizontal: 'left',
|
583
|
+
},
|
584
|
+
elevation: 7,
|
585
|
+
}}
|
586
|
+
>
|
587
|
+
<MoreVerticalIcon />
|
588
|
+
</DropdownMenu>
|
589
|
+
<div className="query-loader__result__arrow">
|
590
|
+
<ThinChevronRightIcon />
|
591
|
+
</div>
|
439
592
|
</div>
|
440
|
-
|
441
|
-
))}
|
593
|
+
))}
|
442
594
|
</>
|
443
595
|
)}
|
444
596
|
{!queryLoaderState.searchQueriesState.hasCompleted && (
|
@@ -16,12 +16,29 @@
|
|
16
16
|
|
17
17
|
import type { LegendApplicationPlugin } from '@finos/legend-application';
|
18
18
|
import type { QueryBuilderState } from './QueryBuilderState.js';
|
19
|
-
import type { QuerySearchSpecification } from '@finos/legend-graph';
|
19
|
+
import type { QuerySearchSpecification, RawLambda } from '@finos/legend-graph';
|
20
20
|
import type {
|
21
21
|
DataAccessState,
|
22
22
|
DatasetAccessInfo,
|
23
23
|
} from './data-access/DataAccessState.js';
|
24
24
|
|
25
|
+
export type CuratedTemplateQuery = {
|
26
|
+
title: string;
|
27
|
+
description: string | undefined;
|
28
|
+
query: RawLambda;
|
29
|
+
executionContextKey: string;
|
30
|
+
};
|
31
|
+
|
32
|
+
export type CuratedTemplateQuerySpecification = {
|
33
|
+
getCuratedTemplateQueries(
|
34
|
+
queryBuilderState: QueryBuilderState,
|
35
|
+
): CuratedTemplateQuery[];
|
36
|
+
loadCuratedTemplateQuery(
|
37
|
+
templateQuery: CuratedTemplateQuery,
|
38
|
+
queryBuilderState: QueryBuilderState,
|
39
|
+
): void;
|
40
|
+
};
|
41
|
+
|
25
42
|
export type LoadQueryFilterOption = {
|
26
43
|
key: string;
|
27
44
|
label: (queryBuilderState: QueryBuilderState) => string | undefined;
|
@@ -59,11 +76,23 @@ export type TemplateQueryPanelContentRenderer = (
|
|
59
76
|
|
60
77
|
export interface QueryBuilder_LegendApplicationPlugin_Extension
|
61
78
|
extends LegendApplicationPlugin {
|
79
|
+
/**
|
80
|
+
* Get the list of template query specifications
|
81
|
+
*/
|
82
|
+
getCuratedTemplateQuerySpecifications?(): CuratedTemplateQuerySpecification[];
|
83
|
+
|
62
84
|
/**
|
63
85
|
* Get the list of filter options for query loader.
|
64
86
|
*/
|
65
87
|
getExtraLoadQueryFilterOptions?(): LoadQueryFilterOption[];
|
66
88
|
|
89
|
+
/**
|
90
|
+
* Get the list of filter options related to template query
|
91
|
+
*/
|
92
|
+
getQueryFilterOptionsRelatedToTemplateQuery?(): (
|
93
|
+
queryBuilderState: QueryBuilderState,
|
94
|
+
) => string[];
|
95
|
+
|
67
96
|
/**
|
68
97
|
* Get the list of warehouse entitlement configurations
|
69
98
|
*/
|