@finos/legend-query-builder 3.1.0 → 3.2.1

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