@finos/legend-query-builder 3.2.0 → 3.2.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (74) 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/components/QueryBuilder.d.ts.map +1 -1
  6. package/lib/components/QueryBuilder.js +24 -34
  7. package/lib/components/QueryBuilder.js.map +1 -1
  8. package/lib/components/QueryBuilder_LegendApplicationPlugin.d.ts +0 -1
  9. package/lib/components/QueryBuilder_LegendApplicationPlugin.d.ts.map +1 -1
  10. package/lib/components/QueryBuilder_LegendApplicationPlugin.js +3 -4
  11. package/lib/components/QueryBuilder_LegendApplicationPlugin.js.map +1 -1
  12. package/lib/components/QueryLoader.d.ts.map +1 -1
  13. package/lib/components/QueryLoader.js +6 -2
  14. package/lib/components/QueryLoader.js.map +1 -1
  15. package/lib/components/data-access/DataAccessOverview.d.ts +23 -0
  16. package/lib/components/data-access/DataAccessOverview.d.ts.map +1 -0
  17. package/lib/components/data-access/DataAccessOverview.js +146 -0
  18. package/lib/components/data-access/DataAccessOverview.js.map +1 -0
  19. package/lib/components/execution-plan/ExecutionPlanViewer.d.ts.map +1 -1
  20. package/lib/components/execution-plan/ExecutionPlanViewer.js +3 -3
  21. package/lib/components/execution-plan/ExecutionPlanViewer.js.map +1 -1
  22. package/lib/components/execution-plan/SQLExecutionNodeViewer.js +1 -1
  23. package/lib/components/execution-plan/SQLExecutionNodeViewer.js.map +1 -1
  24. package/lib/components/explorer/QueryBuilderPropertySearchPanel.js +1 -1
  25. package/lib/components/explorer/QueryBuilderPropertySearchPanel.js.map +1 -1
  26. package/lib/components/fetch-structure/QueryBuilderResultModifierPanel.d.ts.map +1 -1
  27. package/lib/components/fetch-structure/QueryBuilderResultModifierPanel.js +2 -2
  28. package/lib/components/fetch-structure/QueryBuilderResultModifierPanel.js.map +1 -1
  29. package/lib/components/fetch-structure/QueryBuilderTDSPanel.d.ts +0 -18
  30. package/lib/components/fetch-structure/QueryBuilderTDSPanel.d.ts.map +1 -1
  31. package/lib/components/fetch-structure/QueryBuilderTDSPanel.js +34 -36
  32. package/lib/components/fetch-structure/QueryBuilderTDSPanel.js.map +1 -1
  33. package/lib/components/shared/LambdaEditor.d.ts.map +1 -1
  34. package/lib/components/shared/LambdaEditor.js +7 -19
  35. package/lib/components/shared/LambdaEditor.js.map +1 -1
  36. package/lib/index.css +2 -2
  37. package/lib/index.css.map +1 -1
  38. package/lib/index.d.ts +2 -0
  39. package/lib/index.d.ts.map +1 -1
  40. package/lib/index.js +2 -0
  41. package/lib/index.js.map +1 -1
  42. package/lib/package.json +6 -4
  43. package/lib/stores/QueryBuilderState.d.ts +3 -3
  44. package/lib/stores/QueryBuilderState.d.ts.map +1 -1
  45. package/lib/stores/QueryBuilderState.js +34 -18
  46. package/lib/stores/QueryBuilderState.js.map +1 -1
  47. package/lib/stores/QueryBuilder_LegendApplicationPlugin_Extension.d.ts +9 -0
  48. package/lib/stores/QueryBuilder_LegendApplicationPlugin_Extension.d.ts.map +1 -1
  49. package/lib/stores/data-access/DataAccessState.d.ts +56 -0
  50. package/lib/stores/data-access/DataAccessState.d.ts.map +1 -0
  51. package/lib/stores/data-access/DataAccessState.js +212 -0
  52. package/lib/stores/data-access/DataAccessState.js.map +1 -0
  53. package/lib/stores/entitlements/QueryBuilderCheckEntitlementsState.d.ts +4 -2
  54. package/lib/stores/entitlements/QueryBuilderCheckEntitlementsState.d.ts.map +1 -1
  55. package/lib/stores/entitlements/QueryBuilderCheckEntitlementsState.js +20 -6
  56. package/lib/stores/entitlements/QueryBuilderCheckEntitlementsState.js.map +1 -1
  57. package/package.json +14 -12
  58. package/src/__lib__/QueryBuilderColorTheme.ts +23 -0
  59. package/src/components/QueryBuilder.tsx +85 -96
  60. package/src/components/QueryBuilder_LegendApplicationPlugin.ts +4 -5
  61. package/src/components/QueryLoader.tsx +4 -1
  62. package/src/components/data-access/DataAccessOverview.tsx +308 -0
  63. package/src/components/execution-plan/ExecutionPlanViewer.tsx +1 -3
  64. package/src/components/execution-plan/SQLExecutionNodeViewer.tsx +1 -1
  65. package/src/components/explorer/QueryBuilderPropertySearchPanel.tsx +1 -1
  66. package/src/components/fetch-structure/QueryBuilderResultModifierPanel.tsx +2 -3
  67. package/src/components/fetch-structure/QueryBuilderTDSPanel.tsx +99 -102
  68. package/src/components/shared/LambdaEditor.tsx +4 -21
  69. package/src/index.ts +4 -0
  70. package/src/stores/QueryBuilderState.ts +65 -19
  71. package/src/stores/QueryBuilder_LegendApplicationPlugin_Extension.ts +10 -0
  72. package/src/stores/data-access/DataAccessState.ts +322 -0
  73. package/src/stores/entitlements/QueryBuilderCheckEntitlementsState.ts +53 -6
  74. package/tsconfig.json +3 -0
@@ -0,0 +1,308 @@
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 { useEffect } from 'react';
18
+ import { observer } from 'mobx-react-lite';
19
+ import { useApplicationStore } from '@finos/legend-application';
20
+ import type {
21
+ DataAccessState,
22
+ DatasetAccessInfo,
23
+ } from '../../stores/data-access/DataAccessState.js';
24
+ import {
25
+ CheckCircleIcon,
26
+ ExclamationCircleIcon,
27
+ MinusCircleIcon,
28
+ PanelLoadingIndicator,
29
+ RefreshIcon,
30
+ TimesCircleIcon,
31
+ clsx,
32
+ } from '@finos/legend-art';
33
+ import {
34
+ DataGrid,
35
+ type DataGridCellRendererParams,
36
+ } from '@finos/legend-lego/data-grid';
37
+ import {
38
+ DatasetEntitlementAccessApprovedReport,
39
+ DatasetEntitlementAccessGrantedReport,
40
+ DatasetEntitlementAccessNotGrantedReport,
41
+ DatasetEntitlementAccessRequestedReport,
42
+ DatasetEntitlementUnsupportedReport,
43
+ } from '@finos/legend-graph';
44
+ import { Doughnut } from 'react-chartjs-2';
45
+ import { getNullableFirstEntry } from '@finos/legend-shared';
46
+ import type { QueryBuilder_LegendApplicationPlugin_Extension } from '../../stores/QueryBuilder_LegendApplicationPlugin_Extension.js';
47
+
48
+ const DataAccessOverviewChart = observer(
49
+ (props: { dataAccessState: DataAccessState }) => {
50
+ const { dataAccessState } = props;
51
+ const applicationStore = useApplicationStore();
52
+ const entitlementCheckInfo = dataAccessState.entitlementCheckInfo;
53
+ const total = entitlementCheckInfo.total;
54
+ const accessGrantedCount =
55
+ getNullableFirstEntry(entitlementCheckInfo.data)?.count ?? 0;
56
+ const accessGrantedPercentage =
57
+ getNullableFirstEntry(entitlementCheckInfo.data)?.percentage ?? 0;
58
+
59
+ return (
60
+ <div className="data-access-overview__chart">
61
+ <div className="data-access-overview__chart__actions">
62
+ <button
63
+ className="data-access-overview__chart__actions__refresh-btn btn--dark"
64
+ tabIndex={-1}
65
+ title="Refresh"
66
+ onClick={() => {
67
+ dataAccessState
68
+ .refresh()
69
+ .catch(applicationStore.alertUnhandledError);
70
+ }}
71
+ >
72
+ <RefreshIcon />
73
+ </button>
74
+ </div>
75
+ <div className="data-access-overview__chart__container">
76
+ <Doughnut
77
+ data={{
78
+ labels: entitlementCheckInfo.data.map((item) => item.label),
79
+ datasets: [
80
+ {
81
+ data: entitlementCheckInfo.data.map((item) => item.count),
82
+ backgroundColor: entitlementCheckInfo.data.map(
83
+ (item) => item.color,
84
+ ),
85
+ hoverBorderWidth: 0,
86
+ borderWidth: 0,
87
+ },
88
+ ],
89
+ }}
90
+ options={{
91
+ responsive: true,
92
+ resizeDelay: 0,
93
+ maintainAspectRatio: false,
94
+ cutout: '75%',
95
+ plugins: {
96
+ tooltip: {
97
+ enabled: total !== 0,
98
+ usePointStyle: false,
99
+ boxPadding: 5,
100
+ callbacks: {
101
+ labelPointStyle: () => ({
102
+ pointStyle: 'rectRounded',
103
+ rotation: 0,
104
+ }),
105
+ },
106
+ },
107
+ },
108
+ }}
109
+ />
110
+ <div className="data-access-overview__chart__stats">
111
+ <div className="data-access-overview__chart__stats__percentage">
112
+ {accessGrantedPercentage}%
113
+ </div>
114
+ <div className="data-access-overview__chart__stats__tally">
115
+ {total === 0 ? 0 : accessGrantedCount}/{total}
116
+ </div>
117
+ </div>
118
+ </div>
119
+ </div>
120
+ );
121
+ },
122
+ );
123
+
124
+ const AccessStatusCellRenderer = observer(
125
+ (
126
+ params: DataGridCellRendererParams<DatasetAccessInfo> & {
127
+ dataAccessState: DataAccessState;
128
+ },
129
+ ) => {
130
+ const { data } = params;
131
+ const applicationStore = useApplicationStore();
132
+
133
+ if (!data) {
134
+ return null;
135
+ }
136
+
137
+ if (
138
+ data.entitlementReport instanceof DatasetEntitlementAccessGrantedReport
139
+ ) {
140
+ return (
141
+ <div className="data-access-overview__grid__access-status-cell">
142
+ <div className="data-access-overview__grid__access-status-cell__content">
143
+ <div className="data-access-overview__grid__access-status-cell__icon data-access-overview__grid__access-status-cell__icon--access-granted">
144
+ <CheckCircleIcon />
145
+ </div>
146
+ <div className="data-access-overview__grid__access-status-cell__text">
147
+ Access Granted
148
+ </div>
149
+ </div>
150
+ </div>
151
+ );
152
+ } else if (
153
+ data.entitlementReport instanceof DatasetEntitlementAccessApprovedReport
154
+ ) {
155
+ return (
156
+ <div className="data-access-overview__grid__access-status-cell">
157
+ <div className="data-access-overview__grid__access-status-cell__content">
158
+ <div className="data-access-overview__grid__access-status-cell__icon data-access-overview__grid__access-status-cell__icon--access-approved">
159
+ <MinusCircleIcon />
160
+ </div>
161
+ <div className="data-access-overview__grid__access-status-cell__text">
162
+ Access Approved
163
+ </div>
164
+ </div>
165
+ </div>
166
+ );
167
+ } else if (
168
+ data.entitlementReport instanceof DatasetEntitlementAccessRequestedReport
169
+ ) {
170
+ return (
171
+ <div className="data-access-overview__grid__access-status-cell">
172
+ <div className="data-access-overview__grid__access-status-cell__content">
173
+ <div className="data-access-overview__grid__access-status-cell__icon data-access-overview__grid__access-status-cell__icon--access-requested">
174
+ <ExclamationCircleIcon />
175
+ </div>
176
+ <div className="data-access-overview__grid__access-status-cell__text">
177
+ Access Requested
178
+ </div>
179
+ </div>
180
+ </div>
181
+ );
182
+ } else if (
183
+ data.entitlementReport instanceof DatasetEntitlementAccessNotGrantedReport
184
+ ) {
185
+ const plugins = applicationStore.pluginManager
186
+ .getApplicationPlugins()
187
+ .flatMap(
188
+ (plugin) =>
189
+ (
190
+ plugin as QueryBuilder_LegendApplicationPlugin_Extension
191
+ ).getExtraDatasetEntitlementAccessNotGrantedReportActionConfigurations?.() ??
192
+ [],
193
+ );
194
+ let action: React.ReactNode | undefined;
195
+ for (const plugin of plugins) {
196
+ action = plugin.renderer(data);
197
+ if (action) {
198
+ break;
199
+ }
200
+ }
201
+ return (
202
+ <div className="data-access-overview__grid__access-status-cell">
203
+ <div className="data-access-overview__grid__access-status-cell__content">
204
+ <div className="data-access-overview__grid__access-status-cell__icon data-access-overview__grid__access-status-cell__icon--access-not-granted">
205
+ <TimesCircleIcon />
206
+ </div>
207
+ <div className="data-access-overview__grid__access-status-cell__text">
208
+ Access Not Granted
209
+ </div>
210
+ </div>
211
+ {action && (
212
+ <div className="data-access-overview__grid__access-status-cell__action">
213
+ {action}
214
+ </div>
215
+ )}
216
+ </div>
217
+ );
218
+ } else if (
219
+ data.entitlementReport instanceof DatasetEntitlementUnsupportedReport
220
+ ) {
221
+ return (
222
+ <div className="data-access-overview__grid__empty-cell">
223
+ (unsupported)
224
+ </div>
225
+ );
226
+ }
227
+
228
+ return null;
229
+ },
230
+ );
231
+
232
+ const DataAccessOverviewGrid = observer(
233
+ (props: { dataAccessState: DataAccessState }) => {
234
+ const { dataAccessState } = props;
235
+
236
+ return (
237
+ <div className="data-access-overview__grid ag-theme-balham-dark">
238
+ <DataGrid
239
+ rowData={dataAccessState.datasets}
240
+ gridOptions={{
241
+ suppressScrollOnNewData: true,
242
+ getRowId: (rowData) => rowData.data.uuid,
243
+ }}
244
+ columnDefs={[
245
+ {
246
+ minWidth: 50,
247
+ sortable: true,
248
+ resizable: true,
249
+ field: 'specification.name',
250
+ headerName: 'Dataset',
251
+ flex: 1,
252
+ },
253
+ {
254
+ minWidth: 50,
255
+ sortable: true,
256
+ resizable: true,
257
+ field: 'specification.type',
258
+ headerName: 'Type',
259
+ flex: 1,
260
+ },
261
+ {
262
+ minWidth: 50,
263
+ sortable: true,
264
+ resizable: true,
265
+ headerName: 'Access Status',
266
+ cellRendererParams: {
267
+ dataAccessState,
268
+ },
269
+ cellRenderer: AccessStatusCellRenderer,
270
+ flex: 1,
271
+ },
272
+ ]}
273
+ />
274
+ </div>
275
+ );
276
+ },
277
+ );
278
+
279
+ export const DataAccessOverview = observer(
280
+ (props: {
281
+ dataAccessState: DataAccessState;
282
+ compact?: boolean | undefined;
283
+ }) => {
284
+ const { dataAccessState, compact } = props;
285
+ const applicationStore = useApplicationStore();
286
+
287
+ useEffect(() => {
288
+ dataAccessState.intialize().catch(applicationStore.alertUnhandledError);
289
+ }, [applicationStore, dataAccessState]);
290
+
291
+ return (
292
+ <div
293
+ className={clsx('data-access-overview', {
294
+ 'data-access-overview--compact': Boolean(compact),
295
+ })}
296
+ >
297
+ <PanelLoadingIndicator
298
+ isLoading={
299
+ dataAccessState.surveyDatasetsState.isInProgress ||
300
+ dataAccessState.checkEntitlementsState.isInProgress
301
+ }
302
+ />
303
+ <DataAccessOverviewChart dataAccessState={dataAccessState} />
304
+ <DataAccessOverviewGrid dataAccessState={dataAccessState} />
305
+ </div>
306
+ );
307
+ },
308
+ );
@@ -380,7 +380,7 @@ const ExecutionPlanViewPanel = observer(
380
380
  inputValue={displayData}
381
381
  isReadOnly={true}
382
382
  language={CODE_EDITOR_LANGUAGE.JSON}
383
- showMiniMap={false}
383
+ hideMinimap={true}
384
384
  />
385
385
  )}
386
386
  {executionPlanState.viewMode ===
@@ -462,7 +462,6 @@ const ExecutionPlanViewerContent = observer(
462
462
  inputValue={JSON.stringify(rawPlan, undefined, DEFAULT_TAB_SIZE)}
463
463
  isReadOnly={true}
464
464
  language={CODE_EDITOR_LANGUAGE.JSON}
465
- showMiniMap={true}
466
465
  />
467
466
  )}
468
467
  </div>
@@ -523,7 +522,6 @@ export const ExecutionPlanViewer = observer(
523
522
  inputValue={executionPlanState.debugText}
524
523
  isReadOnly={true}
525
524
  language={CODE_EDITOR_LANGUAGE.TEXT}
526
- showMiniMap={true}
527
525
  />
528
526
  </PanelContent>
529
527
  </div>
@@ -42,7 +42,7 @@ export const SQLExecutionNodeViewer: React.FC<{
42
42
  inputValue={formatSQL(query)}
43
43
  isReadOnly={true}
44
44
  language={CODE_EDITOR_LANGUAGE.SQL}
45
- showMiniMap={false}
45
+ hideMinimap={true}
46
46
  />
47
47
  );
48
48
  });
@@ -14,7 +14,7 @@
14
14
  * limitations under the License.
15
15
  */
16
16
 
17
- import React, { useMemo, useRef, useState } from 'react';
17
+ import { useMemo, useRef, useState } from 'react';
18
18
  import {
19
19
  clsx,
20
20
  CheckSquareIcon,
@@ -30,6 +30,7 @@ import {
30
30
  DropdownMenu,
31
31
  MenuContent,
32
32
  MenuContentItem,
33
+ ModalFooterButton,
33
34
  } from '@finos/legend-art';
34
35
  import {
35
36
  COLUMN_SORT_TYPE,
@@ -262,9 +263,7 @@ export const QueryResultModifierModal = observer(
262
263
  </div>
263
264
  </ModalBody>
264
265
  <ModalFooter>
265
- <button className="btn modal__footer__close-btn" onClick={close}>
266
- Close
267
- </button>
266
+ <ModalFooterButton onClick={close} text="Close" />
268
267
  </ModalFooter>
269
268
  </Modal>
270
269
  </Dialog>
@@ -39,8 +39,8 @@ import {
39
39
  CalendarIcon,
40
40
  CalendarClockIcon,
41
41
  CustomSelectorInput,
42
- PURE_FunctionIcon,
43
42
  PanelEntryDropZonePlaceholder,
43
+ FunctionIcon,
44
44
  } from '@finos/legend-art';
45
45
  import {
46
46
  type QueryBuilderExplorerTreeDragSource,
@@ -102,62 +102,6 @@ import { getPropertyChainName } from '../../stores/QueryBuilderPropertyEditorSta
102
102
  import { generateDefaultValueForPrimitiveType } from '../../stores/QueryBuilderValueSpecificationHelper.js';
103
103
  import { QUERY_BUILDER_CALENDAR_TYPE } from '../../graph-manager/QueryBuilderConst.js';
104
104
 
105
- export type CalendarFunctionOption = {
106
- label: string | React.ReactNode;
107
- value: QueryBuilderAggregateCalendarFunction;
108
- };
109
-
110
- export const buildCalendarFunctionOption = (
111
- calendarFunction: QueryBuilderAggregateCalendarFunction,
112
- ): CalendarFunctionOption => ({
113
- label: (
114
- <div
115
- className="query-builder__projection__calendar__function__label"
116
- title={calendarFunction.getLabel()}
117
- >
118
- <PURE_FunctionIcon />
119
- <div className="query-builder__projection__calendar__function__label__title">
120
- {calendarFunction.getLabel()}
121
- </div>
122
- </div>
123
- ),
124
- value: calendarFunction,
125
- });
126
-
127
- export type CalendarFunctionDateColumnOption = {
128
- label: string;
129
- value: AbstractPropertyExpression;
130
- };
131
-
132
- export const buildCalendarFunctionDateColumnOption = (
133
- dateColumn: Property | AbstractPropertyExpression,
134
- parameter: VariableExpression,
135
- humanizeLabel: boolean,
136
- ): CalendarFunctionDateColumnOption => {
137
- if (dateColumn instanceof Property) {
138
- const propertyExpression = new AbstractPropertyExpression('');
139
- propertyExpression_setFunc(
140
- propertyExpression,
141
- PropertyExplicitReference.create(guaranteeNonNullable(dateColumn)),
142
- );
143
- propertyExpression.parametersValues = [parameter];
144
- return {
145
- label: getPropertyChainName(propertyExpression, humanizeLabel),
146
- value: propertyExpression,
147
- };
148
- } else {
149
- return {
150
- label: getPropertyChainName(dateColumn, humanizeLabel),
151
- value: dateColumn,
152
- };
153
- }
154
- };
155
-
156
- export type CalendarTypeOption = {
157
- label: string;
158
- value: QUERY_BUILDER_CALENDAR_TYPE;
159
- };
160
-
161
105
  const QueryBuilderProjectionColumnContextMenu = observer(
162
106
  forwardRef<
163
107
  HTMLDivElement,
@@ -304,6 +248,76 @@ const QueryBuilderDerivationProjectionColumnEditor = observer(
304
248
  },
305
249
  );
306
250
 
251
+ type CalendarFunctionOption = {
252
+ label: string | React.ReactNode;
253
+ value: QueryBuilderAggregateCalendarFunction;
254
+ };
255
+
256
+ const buildCalendarFunctionOption = (
257
+ calendarFunction: QueryBuilderAggregateCalendarFunction,
258
+ ): CalendarFunctionOption => ({
259
+ label: (
260
+ <div
261
+ className="query-builder__projection__calendar__function__label"
262
+ title={calendarFunction.getLabel()}
263
+ >
264
+ <FunctionIcon className="query-builder__projection__calendar__function__label__icon" />
265
+ <div className="query-builder__projection__calendar__function__label__title">
266
+ {calendarFunction.getLabel()}
267
+ </div>
268
+ </div>
269
+ ),
270
+ value: calendarFunction,
271
+ });
272
+
273
+ type CalendarFunctionDateColumnOption = {
274
+ label: React.ReactNode;
275
+ value: AbstractPropertyExpression;
276
+ };
277
+
278
+ const buildCalendarFunctionDateColumnOption = (
279
+ dateColumn: Property | AbstractPropertyExpression,
280
+ parameter: VariableExpression,
281
+ humanizeLabel: boolean,
282
+ ): CalendarFunctionDateColumnOption => {
283
+ if (dateColumn instanceof Property) {
284
+ const propertyExpression = new AbstractPropertyExpression('');
285
+ propertyExpression_setFunc(
286
+ propertyExpression,
287
+ PropertyExplicitReference.create(guaranteeNonNullable(dateColumn)),
288
+ );
289
+ propertyExpression.parametersValues = [parameter];
290
+ return {
291
+ label: getPropertyChainName(propertyExpression, humanizeLabel),
292
+ value: propertyExpression,
293
+ };
294
+ } else {
295
+ return {
296
+ label: getPropertyChainName(dateColumn, humanizeLabel),
297
+ value: dateColumn,
298
+ };
299
+ }
300
+ };
301
+
302
+ type CalendarTypeOption = {
303
+ label: React.ReactNode;
304
+ value: QUERY_BUILDER_CALENDAR_TYPE;
305
+ };
306
+
307
+ const buildCalendarTypeOption = (
308
+ val: QUERY_BUILDER_CALENDAR_TYPE,
309
+ ): CalendarTypeOption => ({
310
+ label: (
311
+ <div className="query-builder__projection__calendar__type__option">
312
+ <CalendarIcon className="query-builder__projection__calendar__type__option__icon" />
313
+ <div className="query-builder__projection__calendar__type__option__title">
314
+ {val}
315
+ </div>
316
+ </div>
317
+ ),
318
+ value: val,
319
+ });
320
+
307
321
  const QueryBuilderProjectionColumnEditor = observer(
308
322
  (props: { projectionColumnState: QueryBuilderProjectionColumnState }) => {
309
323
  const handleRef = useRef<HTMLDivElement>(null);
@@ -369,31 +383,13 @@ const QueryBuilderProjectionColumnEditor = observer(
369
383
  ),
370
384
  );
371
385
  const calendarTypeOptions = Object.values(QUERY_BUILDER_CALENDAR_TYPE).map(
372
- (ct) => ({
373
- label: (
374
- <div className="query-builder__projection__calendar__type__option">
375
- <CalendarIcon />
376
- <div className="query-builder__projection__calendar__type__option__title">
377
- {ct}
378
- </div>
379
- </div>
380
- ),
381
- value: ct,
382
- }),
386
+ buildCalendarTypeOption,
383
387
  );
384
388
  const selectedCalendarTypeOption = aggregateColumnState?.calendarFunction
385
389
  ?.calendarType
386
- ? {
387
- label: (
388
- <div className="query-builder__projection__calendar__type__option">
389
- <CalendarIcon />
390
- <div className="query-builder__projection__calendar__type__option__title">
391
- {aggregateColumnState.calendarFunction.calendarType}
392
- </div>
393
- </div>
394
- ),
395
- value: aggregateColumnState.calendarFunction.calendarType,
396
- }
390
+ ? buildCalendarTypeOption(
391
+ aggregateColumnState.calendarFunction.calendarType,
392
+ )
397
393
  : null;
398
394
  const onCalendarTypeOptionChange = (option: CalendarTypeOption): void => {
399
395
  if (
@@ -661,13 +657,15 @@ const QueryBuilderProjectionColumnEditor = observer(
661
657
  aggregateColumnState &&
662
658
  aggregateCalendarFunctions.length > 0 && (
663
659
  <div
664
- className={
665
- aggregateColumnState.hideCalendarColumnState
666
- ? 'query-builder__projection__column__aggregate__calendar--clock--icon__hidden'
667
- : 'query-builder__projection__column__aggregate__calendar--clock--icon'
668
- }
660
+ className={clsx(
661
+ 'query-builder__projection__column__aggregate__calendar__toggler',
662
+ {
663
+ 'query-builder__projection__column__aggregate__calendar__toggler--active':
664
+ !aggregateColumnState.hideCalendarColumnState,
665
+ },
666
+ )}
669
667
  onClick={toggleHideCalendarColumnState}
670
- title="Click to select calendar function"
668
+ title="Toggle calendar aggregation"
671
669
  >
672
670
  <CalendarClockIcon />
673
671
  </div>
@@ -749,21 +747,19 @@ const QueryBuilderProjectionColumnEditor = observer(
749
747
  },
750
748
  )}
751
749
  >
752
- <div data-testid="test">
753
- <CustomSelectorInput
754
- className="query-builder__projection__calendar__function"
755
- options={calendarFunctionOptions}
756
- onChange={onCalendarFunctionOptionChange}
757
- value={selectedCalendarFunctionOption}
758
- placeholder={'Select Calendar Function'}
759
- isClearable={true}
760
- escapeClearsValue={true}
761
- darkMode={
762
- !applicationStore.layoutService
763
- .TEMPORARY__isLightColorThemeEnabled
764
- }
765
- />
766
- </div>
750
+ <CustomSelectorInput
751
+ className="query-builder__projection__calendar__function"
752
+ options={calendarFunctionOptions}
753
+ onChange={onCalendarFunctionOptionChange}
754
+ value={selectedCalendarFunctionOption}
755
+ placeholder="Select Calendar Function"
756
+ isClearable={true}
757
+ escapeClearsValue={true}
758
+ darkMode={
759
+ !applicationStore.layoutService
760
+ .TEMPORARY__isLightColorThemeEnabled
761
+ }
762
+ />
767
763
  <div className="query-builder__projection__calendar__value">
768
764
  <BasicValueSpecificationEditor
769
765
  valueSpecification={
@@ -807,7 +803,7 @@ const QueryBuilderProjectionColumnEditor = observer(
807
803
  </div>
808
804
  </div>
809
805
  ) : (
810
- <div className="query-builder__projection__calendar__date__column__dnd__placeholder">
806
+ <div className="query-builder__projection__calendar__date__column__placeholder">
811
807
  Drag and drop date column here
812
808
  </div>
813
809
  )}
@@ -818,7 +814,8 @@ const QueryBuilderProjectionColumnEditor = observer(
818
814
  options={calendarTypeOptions}
819
815
  onChange={onCalendarTypeOptionChange}
820
816
  value={selectedCalendarTypeOption ?? calendarTypeOptions[0]}
821
- placeholder={'Select calendar type'}
817
+ placeholder="Select calendar type"
818
+ disabled={!aggregateColumnState.calendarFunction}
822
819
  darkMode={
823
820
  !applicationStore.layoutService
824
821
  .TEMPORARY__isLightColorThemeEnabled