@finos/legend-query-builder 3.1.0 → 3.2.0
Sign up to get free protection for your applications and to get access to all the features.
- package/lib/__lib__/QueryBuilderTelemetryHelper.d.ts +20 -20
- package/lib/__lib__/QueryBuilderTelemetryHelper.d.ts.map +1 -1
- package/lib/__lib__/QueryBuilderTelemetryHelper.js +40 -40
- package/lib/__lib__/QueryBuilderTelemetryHelper.js.map +1 -1
- package/lib/components/QueryLoader.d.ts +30 -0
- package/lib/components/QueryLoader.d.ts.map +1 -0
- package/lib/components/QueryLoader.js +160 -0
- package/lib/components/QueryLoader.js.map +1 -0
- package/lib/components/shared/QueryBuilderPanelIssueCountBadge.d.ts.map +1 -1
- package/lib/components/shared/QueryBuilderPanelIssueCountBadge.js +2 -1
- package/lib/components/shared/QueryBuilderPanelIssueCountBadge.js.map +1 -1
- package/lib/index.css +2 -2
- package/lib/index.css.map +1 -1
- package/lib/index.d.ts +3 -0
- package/lib/index.d.ts.map +1 -1
- package/lib/index.js +3 -0
- package/lib/index.js.map +1 -1
- package/lib/package.json +1 -1
- package/lib/stores/QueryBuilder_LegendApplicationPlugin_Extension.d.ts +30 -0
- package/lib/stores/QueryBuilder_LegendApplicationPlugin_Extension.d.ts.map +1 -0
- package/lib/stores/QueryBuilder_LegendApplicationPlugin_Extension.js +17 -0
- package/lib/stores/QueryBuilder_LegendApplicationPlugin_Extension.js.map +1 -0
- package/lib/stores/QueryLoaderState.d.ts +67 -0
- package/lib/stores/QueryLoaderState.d.ts.map +1 -0
- package/lib/stores/QueryLoaderState.js +205 -0
- package/lib/stores/QueryLoaderState.js.map +1 -0
- package/lib/stores/shared/ValueSpecificationEditorHelper.d.ts +3 -1
- package/lib/stores/shared/ValueSpecificationEditorHelper.d.ts.map +1 -1
- package/lib/stores/shared/ValueSpecificationEditorHelper.js +7 -1
- package/lib/stores/shared/ValueSpecificationEditorHelper.js.map +1 -1
- package/package.json +8 -8
- package/src/__lib__/QueryBuilderTelemetryHelper.ts +40 -59
- package/src/components/QueryLoader.tsx +501 -0
- package/src/components/shared/QueryBuilderPanelIssueCountBadge.tsx +2 -1
- package/src/index.ts +3 -0
- package/src/stores/QueryBuilder_LegendApplicationPlugin_Extension.ts +36 -0
- package/src/stores/QueryLoaderState.ts +298 -0
- package/src/stores/shared/ValueSpecificationEditorHelper.ts +39 -2
- package/tsconfig.json +3 -0
@@ -0,0 +1,501 @@
|
|
1
|
+
/**
|
2
|
+
* Copyright (c) 2020-present, Goldman Sachs
|
3
|
+
*
|
4
|
+
* Licensed under the Apache License, Version 2.0 (the "License");
|
5
|
+
* you may not use this file except in compliance with the License.
|
6
|
+
* You may obtain a copy of the License at
|
7
|
+
*
|
8
|
+
* http://www.apache.org/licenses/LICENSE-2.0
|
9
|
+
*
|
10
|
+
* Unless required by applicable law or agreed to in writing, software
|
11
|
+
* distributed under the License is distributed on an "AS IS" BASIS,
|
12
|
+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
13
|
+
* See the License for the specific language governing permissions and
|
14
|
+
* limitations under the License.
|
15
|
+
*/
|
16
|
+
|
17
|
+
import { useApplicationStore } from '@finos/legend-application';
|
18
|
+
import {
|
19
|
+
CODE_EDITOR_LANGUAGE,
|
20
|
+
CodeEditor,
|
21
|
+
} from '@finos/legend-lego/code-editor';
|
22
|
+
import {
|
23
|
+
Dialog,
|
24
|
+
Modal,
|
25
|
+
ModalTitle,
|
26
|
+
clsx,
|
27
|
+
SearchIcon,
|
28
|
+
TimesIcon,
|
29
|
+
DropdownMenu,
|
30
|
+
MenuContent,
|
31
|
+
MenuContentItem,
|
32
|
+
BlankPanelContent,
|
33
|
+
PanelLoadingIndicator,
|
34
|
+
ModalHeader,
|
35
|
+
ModalBody,
|
36
|
+
ModalFooter,
|
37
|
+
ModalFooterButton,
|
38
|
+
UserIcon,
|
39
|
+
LastModifiedIcon,
|
40
|
+
MoreVerticalIcon,
|
41
|
+
ThinChevronRightIcon,
|
42
|
+
InfoCircleIcon,
|
43
|
+
} from '@finos/legend-art';
|
44
|
+
import type { LightQuery } from '@finos/legend-graph';
|
45
|
+
import {
|
46
|
+
debounce,
|
47
|
+
formatDistanceToNow,
|
48
|
+
guaranteeNonNullable,
|
49
|
+
quantifyList,
|
50
|
+
} from '@finos/legend-shared';
|
51
|
+
import { flowResult } from 'mobx';
|
52
|
+
import { observer } from 'mobx-react-lite';
|
53
|
+
import { useRef, useState, useMemo, useEffect } from 'react';
|
54
|
+
import {
|
55
|
+
QUERY_LOADER_TYPEAHEAD_SEARCH_LIMIT,
|
56
|
+
type QueryLoaderState,
|
57
|
+
} from '../stores/QueryLoaderState.js';
|
58
|
+
|
59
|
+
const QueryPreviewViewer = observer(
|
60
|
+
(props: { queryLoaderState: QueryLoaderState }) => {
|
61
|
+
const { queryLoaderState } = props;
|
62
|
+
const close = (): void => {
|
63
|
+
queryLoaderState.setShowPreviewViewer(false);
|
64
|
+
};
|
65
|
+
return (
|
66
|
+
<Dialog
|
67
|
+
open={queryLoaderState.showPreviewViewer}
|
68
|
+
onClose={close}
|
69
|
+
classes={{
|
70
|
+
root: 'editor-modal__root-container',
|
71
|
+
container: 'editor-modal__container',
|
72
|
+
paper: 'editor-modal__content',
|
73
|
+
}}
|
74
|
+
>
|
75
|
+
<Modal className="editor-modal" darkMode={true}>
|
76
|
+
<ModalHeader
|
77
|
+
title={
|
78
|
+
guaranteeNonNullable(queryLoaderState.queryPreviewContent).name
|
79
|
+
}
|
80
|
+
/>
|
81
|
+
<ModalBody>
|
82
|
+
<CodeEditor
|
83
|
+
inputValue={
|
84
|
+
guaranteeNonNullable(queryLoaderState.queryPreviewContent)
|
85
|
+
.content
|
86
|
+
}
|
87
|
+
isReadOnly={true}
|
88
|
+
language={CODE_EDITOR_LANGUAGE.PURE}
|
89
|
+
showMiniMap={true}
|
90
|
+
/>
|
91
|
+
</ModalBody>
|
92
|
+
<ModalFooter>
|
93
|
+
<ModalFooterButton onClick={close} text="Close" />
|
94
|
+
</ModalFooter>
|
95
|
+
</Modal>
|
96
|
+
</Dialog>
|
97
|
+
);
|
98
|
+
},
|
99
|
+
);
|
100
|
+
|
101
|
+
export const QueryLoader = observer(
|
102
|
+
(props: { queryLoaderState: QueryLoaderState; loadActionLabel: string }) => {
|
103
|
+
const { queryLoaderState, loadActionLabel } = props;
|
104
|
+
const applicationStore = useApplicationStore();
|
105
|
+
const searchInputRef = useRef<HTMLInputElement>(null);
|
106
|
+
const queryRenameInputRef = useRef<HTMLInputElement>(null);
|
107
|
+
const results = queryLoaderState.queries;
|
108
|
+
const [isMineOnly, setIsMineOnly] = useState(false);
|
109
|
+
const [showQueryNameEditInput, setShowQueryNameEditInput] = useState<
|
110
|
+
number | undefined
|
111
|
+
>();
|
112
|
+
useEffect(() => {
|
113
|
+
queryRenameInputRef.current?.focus();
|
114
|
+
queryRenameInputRef.current?.select();
|
115
|
+
}, [showQueryNameEditInput]);
|
116
|
+
const [queryNameInputValue, setQueryNameInputValue] = useState<string>('');
|
117
|
+
const showEditQueryNameInput =
|
118
|
+
(value: string, idx: number): (() => void) =>
|
119
|
+
(): void => {
|
120
|
+
setQueryNameInputValue(value);
|
121
|
+
setShowQueryNameEditInput(idx);
|
122
|
+
};
|
123
|
+
const hideEditQueryNameInput = (): void => {
|
124
|
+
setShowQueryNameEditInput(undefined);
|
125
|
+
setQueryNameInputValue('');
|
126
|
+
};
|
127
|
+
const changeQueryNameInputValue: React.ChangeEventHandler<
|
128
|
+
HTMLInputElement
|
129
|
+
> = (event) => setQueryNameInputValue(event.target.value);
|
130
|
+
|
131
|
+
// search text
|
132
|
+
const debouncedLoadQueries = useMemo(
|
133
|
+
() =>
|
134
|
+
debounce((input: string): void => {
|
135
|
+
flowResult(queryLoaderState.searchQueries(input)).catch(
|
136
|
+
applicationStore.alertUnhandledError,
|
137
|
+
);
|
138
|
+
}, 500),
|
139
|
+
[applicationStore.alertUnhandledError, queryLoaderState],
|
140
|
+
);
|
141
|
+
const onSearchTextChange: React.ChangeEventHandler<HTMLInputElement> = (
|
142
|
+
event,
|
143
|
+
) => {
|
144
|
+
if (event.target.value !== queryLoaderState.searchText) {
|
145
|
+
queryLoaderState.setSearchText(event.target.value);
|
146
|
+
debouncedLoadQueries.cancel();
|
147
|
+
debouncedLoadQueries(event.target.value);
|
148
|
+
}
|
149
|
+
};
|
150
|
+
const clearQuerySearching = (): void => {
|
151
|
+
queryLoaderState.setSearchText('');
|
152
|
+
debouncedLoadQueries.cancel();
|
153
|
+
debouncedLoadQueries('');
|
154
|
+
};
|
155
|
+
const toggleShowCurrentUserQueriesOnly = (): void => {
|
156
|
+
queryLoaderState.setShowCurrentUserQueriesOnly(
|
157
|
+
!queryLoaderState.showCurrentUserQueriesOnly,
|
158
|
+
);
|
159
|
+
setIsMineOnly(!isMineOnly);
|
160
|
+
debouncedLoadQueries.cancel();
|
161
|
+
debouncedLoadQueries(queryLoaderState.searchText);
|
162
|
+
};
|
163
|
+
const toggleExtraFilters = (key: string): void => {
|
164
|
+
queryLoaderState.extraFilters.set(
|
165
|
+
key,
|
166
|
+
!queryLoaderState.extraFilters.get(key),
|
167
|
+
);
|
168
|
+
debouncedLoadQueries.cancel();
|
169
|
+
debouncedLoadQueries(queryLoaderState.searchText);
|
170
|
+
};
|
171
|
+
|
172
|
+
useEffect(() => {
|
173
|
+
flowResult(queryLoaderState.searchQueries('')).catch(
|
174
|
+
applicationStore.alertUnhandledError,
|
175
|
+
);
|
176
|
+
}, [applicationStore, queryLoaderState]);
|
177
|
+
|
178
|
+
useEffect(() => {
|
179
|
+
searchInputRef.current?.focus();
|
180
|
+
}, [queryLoaderState]);
|
181
|
+
|
182
|
+
// actions
|
183
|
+
const renameQuery =
|
184
|
+
(query: LightQuery): (() => void) =>
|
185
|
+
(): void => {
|
186
|
+
if (!queryLoaderState.isReadOnly) {
|
187
|
+
flowResult(
|
188
|
+
queryLoaderState.renameQuery(query.id, queryNameInputValue),
|
189
|
+
)
|
190
|
+
.catch(applicationStore.alertUnhandledError)
|
191
|
+
.finally(() => hideEditQueryNameInput());
|
192
|
+
}
|
193
|
+
};
|
194
|
+
|
195
|
+
const deleteQuery =
|
196
|
+
(query: LightQuery): (() => void) =>
|
197
|
+
(): void => {
|
198
|
+
if (!queryLoaderState.isReadOnly) {
|
199
|
+
flowResult(queryLoaderState.deleteQuery(query.id)).catch(
|
200
|
+
applicationStore.alertUnhandledError,
|
201
|
+
);
|
202
|
+
}
|
203
|
+
};
|
204
|
+
|
205
|
+
const showPreview = (queryId: string): void => {
|
206
|
+
flowResult(queryLoaderState.getPreviewQueryContent(queryId)).catch(
|
207
|
+
applicationStore.alertUnhandledError,
|
208
|
+
);
|
209
|
+
queryLoaderState.setShowPreviewViewer(true);
|
210
|
+
};
|
211
|
+
|
212
|
+
return (
|
213
|
+
<div className="query-loader">
|
214
|
+
<div className="query-loader__header">
|
215
|
+
<div className="query-loader__search">
|
216
|
+
<div className="query-loader__search__input__container">
|
217
|
+
<input
|
218
|
+
ref={searchInputRef}
|
219
|
+
className={clsx('query-loader__search__input input--dark', {
|
220
|
+
'query-loader__search__input--searching':
|
221
|
+
queryLoaderState.searchText,
|
222
|
+
})}
|
223
|
+
onChange={onSearchTextChange}
|
224
|
+
value={queryLoaderState.searchText}
|
225
|
+
placeholder="Search for queries by name or ID"
|
226
|
+
/>
|
227
|
+
{!queryLoaderState.searchText ? (
|
228
|
+
<div className="query-loader__search__input__search__icon">
|
229
|
+
<SearchIcon />
|
230
|
+
</div>
|
231
|
+
) : (
|
232
|
+
<>
|
233
|
+
<button
|
234
|
+
className="query-loader__search__input__clear-btn"
|
235
|
+
tabIndex={-1}
|
236
|
+
onClick={clearQuerySearching}
|
237
|
+
title="Clear"
|
238
|
+
>
|
239
|
+
<TimesIcon />
|
240
|
+
</button>
|
241
|
+
</>
|
242
|
+
)}
|
243
|
+
</div>
|
244
|
+
</div>
|
245
|
+
<div className="query-loader__filter">
|
246
|
+
<div className="query-loader__filter__toggler">
|
247
|
+
<button
|
248
|
+
className={clsx('query-loader__filter__toggler__btn', {
|
249
|
+
'query-loader__filter__toggler__btn--toggled': isMineOnly,
|
250
|
+
})}
|
251
|
+
onClick={toggleShowCurrentUserQueriesOnly}
|
252
|
+
tabIndex={-1}
|
253
|
+
>
|
254
|
+
Mine Only
|
255
|
+
</button>
|
256
|
+
{queryLoaderState.extraFilterOptions.length > 0 && (
|
257
|
+
<div className="query-loader__filter__extra__filters">
|
258
|
+
{Array.from(queryLoaderState.extraFilters.entries()).map(
|
259
|
+
([key, value]) => (
|
260
|
+
<button
|
261
|
+
key={key}
|
262
|
+
className={clsx('query-loader__filter__toggler__btn', {
|
263
|
+
'query-loader__filter__toggler__btn--toggled': value,
|
264
|
+
})}
|
265
|
+
onClick={(): void => toggleExtraFilters(key)}
|
266
|
+
tabIndex={-1}
|
267
|
+
>
|
268
|
+
{key}
|
269
|
+
</button>
|
270
|
+
),
|
271
|
+
)}
|
272
|
+
</div>
|
273
|
+
)}
|
274
|
+
</div>
|
275
|
+
</div>
|
276
|
+
</div>
|
277
|
+
<div className="query-loader__content">
|
278
|
+
<PanelLoadingIndicator
|
279
|
+
isLoading={
|
280
|
+
queryLoaderState.searchQueriesState.isInProgress ||
|
281
|
+
queryLoaderState.renameQueryState.isInProgress ||
|
282
|
+
queryLoaderState.deleteQueryState.isInProgress ||
|
283
|
+
queryLoaderState.previewQueryState.isInProgress
|
284
|
+
}
|
285
|
+
/>
|
286
|
+
|
287
|
+
<div className="query-loader__results">
|
288
|
+
{queryLoaderState.searchQueriesState.hasCompleted && (
|
289
|
+
<>
|
290
|
+
<div className="query-loader__results__summary">
|
291
|
+
{queryLoaderState.showingDefaultQueries ? (
|
292
|
+
queryLoaderState.generateDefaultQueriesSummaryText?.(
|
293
|
+
results,
|
294
|
+
) ?? 'Refine your search to get better matches'
|
295
|
+
) : results.length >= QUERY_LOADER_TYPEAHEAD_SEARCH_LIMIT ? (
|
296
|
+
<>
|
297
|
+
{`Found ${QUERY_LOADER_TYPEAHEAD_SEARCH_LIMIT}+ matches`}{' '}
|
298
|
+
<InfoCircleIcon
|
299
|
+
title="Some queries are not listed, refine your search to get better matches"
|
300
|
+
className="query-loader__results__summary__info"
|
301
|
+
/>
|
302
|
+
</>
|
303
|
+
) : (
|
304
|
+
`Found ${quantifyList(results, 'match', 'matches')}`
|
305
|
+
)}
|
306
|
+
</div>
|
307
|
+
{results
|
308
|
+
.slice(0, QUERY_LOADER_TYPEAHEAD_SEARCH_LIMIT)
|
309
|
+
.map((query, idx) => (
|
310
|
+
<div
|
311
|
+
className="query-loader__result"
|
312
|
+
title={`Click to ${loadActionLabel}...`}
|
313
|
+
key={query.id}
|
314
|
+
onClick={() => queryLoaderState.loadQuery(query)}
|
315
|
+
>
|
316
|
+
<div className="query-loader__result__content">
|
317
|
+
{showQueryNameEditInput === idx ? (
|
318
|
+
<div className="query-loader__result__title__editor">
|
319
|
+
<input
|
320
|
+
className="query-loader__result__title__editor__input input--dark"
|
321
|
+
spellCheck={false}
|
322
|
+
ref={queryRenameInputRef}
|
323
|
+
value={queryNameInputValue}
|
324
|
+
onChange={changeQueryNameInputValue}
|
325
|
+
onKeyDown={(event) => {
|
326
|
+
if (event.code === 'Enter') {
|
327
|
+
renameQuery(query)();
|
328
|
+
} else if (event.code === 'Escape') {
|
329
|
+
hideEditQueryNameInput();
|
330
|
+
}
|
331
|
+
}}
|
332
|
+
onBlur={() => hideEditQueryNameInput()}
|
333
|
+
/>
|
334
|
+
</div>
|
335
|
+
) : (
|
336
|
+
<div
|
337
|
+
className="query-loader__result__title"
|
338
|
+
title={query.name}
|
339
|
+
>
|
340
|
+
{query.name}
|
341
|
+
</div>
|
342
|
+
)}
|
343
|
+
<div className="query-loader__result__description">
|
344
|
+
<div className="query-loader__result__description__date__icon">
|
345
|
+
<LastModifiedIcon />
|
346
|
+
</div>
|
347
|
+
<div className="query-loader__result__description__date">
|
348
|
+
{query.lastUpdatedAt
|
349
|
+
? formatDistanceToNow(
|
350
|
+
new Date(query.lastUpdatedAt),
|
351
|
+
{
|
352
|
+
includeSeconds: true,
|
353
|
+
addSuffix: true,
|
354
|
+
},
|
355
|
+
)
|
356
|
+
: '(unknown)'}
|
357
|
+
</div>
|
358
|
+
<div
|
359
|
+
className={clsx(
|
360
|
+
'query-loader__result__description__author__icon',
|
361
|
+
{
|
362
|
+
'query-loader__result__description__author__icon--owner':
|
363
|
+
query.isCurrentUserQuery,
|
364
|
+
},
|
365
|
+
)}
|
366
|
+
>
|
367
|
+
<UserIcon />
|
368
|
+
</div>
|
369
|
+
<div className="query-loader__result__description__author__name">
|
370
|
+
{query.isCurrentUserQuery ? (
|
371
|
+
<div
|
372
|
+
title={query.owner}
|
373
|
+
className="query-loader__result__description__owner"
|
374
|
+
>
|
375
|
+
Me
|
376
|
+
</div>
|
377
|
+
) : (
|
378
|
+
query.owner
|
379
|
+
)}
|
380
|
+
</div>
|
381
|
+
</div>
|
382
|
+
</div>
|
383
|
+
<DropdownMenu
|
384
|
+
className="query-loader__result__actions-menu"
|
385
|
+
title="More Actions..."
|
386
|
+
content={
|
387
|
+
<MenuContent>
|
388
|
+
<MenuContentItem
|
389
|
+
onClick={(): void => showPreview(query.id)}
|
390
|
+
>
|
391
|
+
Show Query Preview
|
392
|
+
</MenuContentItem>
|
393
|
+
{!queryLoaderState.isReadOnly && (
|
394
|
+
<MenuContentItem
|
395
|
+
disabled={!query.isCurrentUserQuery}
|
396
|
+
onClick={deleteQuery(query)}
|
397
|
+
>
|
398
|
+
Delete
|
399
|
+
</MenuContentItem>
|
400
|
+
)}
|
401
|
+
{!queryLoaderState.isReadOnly && (
|
402
|
+
<MenuContentItem
|
403
|
+
disabled={!query.isCurrentUserQuery}
|
404
|
+
onClick={showEditQueryNameInput(
|
405
|
+
query.name,
|
406
|
+
idx,
|
407
|
+
)}
|
408
|
+
>
|
409
|
+
Rename
|
410
|
+
</MenuContentItem>
|
411
|
+
)}
|
412
|
+
</MenuContent>
|
413
|
+
}
|
414
|
+
menuProps={{
|
415
|
+
anchorOrigin: {
|
416
|
+
vertical: 'bottom',
|
417
|
+
horizontal: 'left',
|
418
|
+
},
|
419
|
+
transformOrigin: {
|
420
|
+
vertical: 'top',
|
421
|
+
horizontal: 'left',
|
422
|
+
},
|
423
|
+
elevation: 7,
|
424
|
+
}}
|
425
|
+
>
|
426
|
+
<MoreVerticalIcon />
|
427
|
+
</DropdownMenu>
|
428
|
+
<div className="query-loader__result__arrow">
|
429
|
+
<ThinChevronRightIcon />
|
430
|
+
</div>
|
431
|
+
</div>
|
432
|
+
))}
|
433
|
+
</>
|
434
|
+
)}
|
435
|
+
{!queryLoaderState.searchQueriesState.hasCompleted && (
|
436
|
+
<BlankPanelContent>Loading queries...</BlankPanelContent>
|
437
|
+
)}
|
438
|
+
</div>
|
439
|
+
{queryLoaderState.showPreviewViewer &&
|
440
|
+
queryLoaderState.queryPreviewContent && (
|
441
|
+
<QueryPreviewViewer queryLoaderState={queryLoaderState} />
|
442
|
+
)}
|
443
|
+
</div>
|
444
|
+
</div>
|
445
|
+
);
|
446
|
+
},
|
447
|
+
);
|
448
|
+
|
449
|
+
export const QueryLoaderDialog = observer(
|
450
|
+
(props: {
|
451
|
+
queryLoaderState: QueryLoaderState;
|
452
|
+
title: string;
|
453
|
+
loadActionLabel?: string | undefined;
|
454
|
+
}) => {
|
455
|
+
const { queryLoaderState, title, loadActionLabel } = props;
|
456
|
+
|
457
|
+
const close = (): void => {
|
458
|
+
queryLoaderState.setQueryLoaderDialogOpen(false);
|
459
|
+
queryLoaderState.reset();
|
460
|
+
};
|
461
|
+
|
462
|
+
return (
|
463
|
+
<Dialog
|
464
|
+
open={queryLoaderState.isQueryLoaderDialogOpen}
|
465
|
+
onClose={close}
|
466
|
+
classes={{
|
467
|
+
root: 'query-loader__dialog',
|
468
|
+
container: 'query-loader__dialog__container',
|
469
|
+
}}
|
470
|
+
PaperProps={{
|
471
|
+
classes: { root: 'query-loader__dialog__body' },
|
472
|
+
}}
|
473
|
+
>
|
474
|
+
<Modal
|
475
|
+
darkMode={true}
|
476
|
+
className="modal query-loader__dialog__body__content"
|
477
|
+
>
|
478
|
+
<div className="modal query-loader__dialog__header">
|
479
|
+
<ModalTitle
|
480
|
+
className="query-loader__dialog__header__title"
|
481
|
+
title={title}
|
482
|
+
/>
|
483
|
+
<button
|
484
|
+
className="query-loader__dialog__header__close-btn"
|
485
|
+
title="Close"
|
486
|
+
onClick={close}
|
487
|
+
>
|
488
|
+
<TimesIcon />
|
489
|
+
</button>
|
490
|
+
</div>
|
491
|
+
<div className="modal query-loader__dialog__content">
|
492
|
+
<QueryLoader
|
493
|
+
queryLoaderState={queryLoaderState}
|
494
|
+
loadActionLabel={loadActionLabel ?? title.toLowerCase()}
|
495
|
+
/>
|
496
|
+
</div>
|
497
|
+
</Modal>
|
498
|
+
</Dialog>
|
499
|
+
);
|
500
|
+
},
|
501
|
+
);
|
@@ -15,6 +15,7 @@
|
|
15
15
|
*/
|
16
16
|
|
17
17
|
import { TimesCircleIcon } from '@finos/legend-art';
|
18
|
+
import { quantifyList } from '@finos/legend-shared';
|
18
19
|
|
19
20
|
export const QueryBuilderPanelIssueCountBadge: React.FC<{
|
20
21
|
issues: string[] | undefined;
|
@@ -23,7 +24,7 @@ export const QueryBuilderPanelIssueCountBadge: React.FC<{
|
|
23
24
|
if (!issues) {
|
24
25
|
return null;
|
25
26
|
}
|
26
|
-
const labelText =
|
27
|
+
const labelText = quantifyList(issues, 'issue');
|
27
28
|
return (
|
28
29
|
<div
|
29
30
|
className="query-builder-panel-issue-count-badge"
|
package/src/index.ts
CHANGED
@@ -70,3 +70,6 @@ export * from './stores/shared/ValueSpecificationEditorHelper.js';
|
|
70
70
|
|
71
71
|
export * from './components/execution-plan/ExecutionPlanViewer.js';
|
72
72
|
export * from './stores/execution-plan/ExecutionPlanState.js';
|
73
|
+
export * from './components/QueryLoader.js';
|
74
|
+
export * from './stores/QueryLoaderState.js';
|
75
|
+
export * from './stores/QueryBuilder_LegendApplicationPlugin_Extension.js';
|
@@ -0,0 +1,36 @@
|
|
1
|
+
/**
|
2
|
+
* Copyright (c) 2020-present, Goldman Sachs
|
3
|
+
*
|
4
|
+
* Licensed under the Apache License, Version 2.0 (the "License");
|
5
|
+
* you may not use this file except in compliance with the License.
|
6
|
+
* You may obtain a copy of the License at
|
7
|
+
*
|
8
|
+
* http://www.apache.org/licenses/LICENSE-2.0
|
9
|
+
*
|
10
|
+
* Unless required by applicable law or agreed to in writing, software
|
11
|
+
* distributed under the License is distributed on an "AS IS" BASIS,
|
12
|
+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
13
|
+
* See the License for the specific language governing permissions and
|
14
|
+
* limitations under the License.
|
15
|
+
*/
|
16
|
+
|
17
|
+
import type { LegendApplicationPlugin } from '@finos/legend-application';
|
18
|
+
import type { QueryBuilderState } from './QueryBuilderState.js';
|
19
|
+
import type { QuerySearchSpecification } from '@finos/legend-graph';
|
20
|
+
|
21
|
+
export type LoadQueryFilterOption = {
|
22
|
+
key: string;
|
23
|
+
label: (queryBuilderState: QueryBuilderState) => string | undefined;
|
24
|
+
filterFunction: (
|
25
|
+
searchSpecification: QuerySearchSpecification,
|
26
|
+
queryBuilderState: QueryBuilderState,
|
27
|
+
) => QuerySearchSpecification;
|
28
|
+
};
|
29
|
+
|
30
|
+
export interface QueryBuilder_LegendApplicationPlugin_Extension
|
31
|
+
extends LegendApplicationPlugin {
|
32
|
+
/**
|
33
|
+
* Get the list of filter options for query loader.
|
34
|
+
*/
|
35
|
+
getExtraLoadQueryFilterOptions?(): LoadQueryFilterOption[];
|
36
|
+
}
|