@finos/legend-data-cube 0.1.6 → 0.1.8
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/core/DataCubeExecutionErrorAlert.d.ts +23 -0
- package/lib/components/core/DataCubeExecutionErrorAlert.d.ts.map +1 -0
- package/lib/components/core/DataCubeExecutionErrorAlert.js +40 -0
- package/lib/components/core/DataCubeExecutionErrorAlert.js.map +1 -0
- package/lib/components/core/DataCubeFormUtils.d.ts +6 -0
- package/lib/components/core/DataCubeFormUtils.d.ts.map +1 -1
- package/lib/components/core/DataCubeFormUtils.js +6 -1
- package/lib/components/core/DataCubeFormUtils.js.map +1 -1
- package/lib/components/view/editor/DataCubeEditorColumnPropertiesPanel.js +1 -1
- package/lib/components/view/editor/DataCubeEditorColumnPropertiesPanel.js.map +1 -1
- package/lib/components/view/editor/DataCubeEditorDimensionsPanel.d.ts +32 -0
- package/lib/components/view/editor/DataCubeEditorDimensionsPanel.d.ts.map +1 -0
- package/lib/components/view/editor/DataCubeEditorDimensionsPanel.js +311 -0
- package/lib/components/view/editor/DataCubeEditorDimensionsPanel.js.map +1 -0
- package/lib/components/view/grid/DataCubeMultidimensionalGrid.d.ts +22 -0
- package/lib/components/view/grid/DataCubeMultidimensionalGrid.d.ts.map +1 -0
- package/lib/components/view/grid/DataCubeMultidimensionalGrid.js +20 -0
- package/lib/components/view/grid/DataCubeMultidimensionalGrid.js.map +1 -0
- package/lib/index.css +1 -1
- package/lib/package.json +4 -4
- package/lib/stores/core/DataCubeEngine.d.ts +5 -1
- package/lib/stores/core/DataCubeEngine.d.ts.map +1 -1
- package/lib/stores/core/DataCubeEngine.js +7 -1
- package/lib/stores/core/DataCubeEngine.js.map +1 -1
- package/lib/stores/core/model/AdhocQueryDataCubeSource.d.ts +3 -2
- package/lib/stores/core/model/AdhocQueryDataCubeSource.d.ts.map +1 -1
- package/lib/stores/core/model/AdhocQueryDataCubeSource.js.map +1 -1
- package/lib/stores/core/model/CachedDataCubeSource.d.ts +2 -2
- package/lib/stores/core/model/CachedDataCubeSource.d.ts.map +1 -1
- package/lib/stores/core/model/CachedDataCubeSource.js +0 -1
- package/lib/stores/core/model/CachedDataCubeSource.js.map +1 -1
- package/lib/stores/services/DataCubeAlertService.d.ts +2 -0
- package/lib/stores/services/DataCubeAlertService.d.ts.map +1 -1
- package/lib/stores/services/DataCubeAlertService.js +13 -0
- package/lib/stores/services/DataCubeAlertService.js.map +1 -1
- package/lib/stores/view/DataCubeViewState.d.ts.map +1 -1
- package/lib/stores/view/DataCubeViewState.js +2 -1
- package/lib/stores/view/DataCubeViewState.js.map +1 -1
- package/lib/stores/view/editor/DataCubeEditorDimensionsPanelState.d.ts +34 -0
- package/lib/stores/view/editor/DataCubeEditorDimensionsPanelState.d.ts.map +1 -0
- package/lib/stores/view/editor/DataCubeEditorDimensionsPanelState.js +86 -0
- package/lib/stores/view/editor/DataCubeEditorDimensionsPanelState.js.map +1 -0
- package/lib/stores/view/grid/DataCubeGridClientEngine.d.ts.map +1 -1
- package/lib/stores/view/grid/DataCubeGridClientEngine.js +23 -6
- package/lib/stores/view/grid/DataCubeGridClientEngine.js.map +1 -1
- package/package.json +9 -9
- package/src/components/core/DataCubeExecutionErrorAlert.tsx +134 -0
- package/src/components/core/DataCubeFormUtils.tsx +37 -2
- package/src/components/view/editor/DataCubeEditorColumnPropertiesPanel.tsx +1 -1
- package/src/components/view/editor/DataCubeEditorDimensionsPanel.tsx +606 -0
- package/src/components/view/grid/DataCubeMultidimensionalGrid.tsx +24 -0
- package/src/stores/core/DataCubeEngine.tsx +9 -1
- package/src/stores/core/model/AdhocQueryDataCubeSource.ts +3 -2
- package/src/stores/core/model/CachedDataCubeSource.ts +2 -3
- package/src/stores/services/DataCubeAlertService.tsx +27 -0
- package/src/stores/view/DataCubeViewState.ts +1 -0
- package/src/stores/view/editor/DataCubeEditorDimensionsPanelState.ts +134 -0
- package/src/stores/view/grid/DataCubeGridClientEngine.ts +24 -7
- package/tsconfig.json +4 -0
|
@@ -284,7 +284,7 @@ export const DataCubeEditorColumnPropertiesPanel = observer(
|
|
|
284
284
|
)}
|
|
285
285
|
</div>
|
|
286
286
|
|
|
287
|
-
<div className="-
|
|
287
|
+
<div className="mb-2 ml-2 mt-3 h-[1px] w-[calc(100%_-_16px)] bg-neutral-200" />
|
|
288
288
|
|
|
289
289
|
{selectedColumn && (
|
|
290
290
|
<>
|
|
@@ -0,0 +1,606 @@
|
|
|
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 { observer } from 'mobx-react-lite';
|
|
18
|
+
import { DataCubeIcon } from '@finos/legend-art';
|
|
19
|
+
import {
|
|
20
|
+
type ColDef,
|
|
21
|
+
type ColDefField,
|
|
22
|
+
type GridApi,
|
|
23
|
+
type ModelUpdatedEvent,
|
|
24
|
+
type RowDragEndEvent,
|
|
25
|
+
type SelectionChangedEvent,
|
|
26
|
+
AllCommunityModule,
|
|
27
|
+
} from 'ag-grid-community';
|
|
28
|
+
import React, { useCallback, useEffect, useRef, useState } from 'react';
|
|
29
|
+
import {
|
|
30
|
+
AgGridReact,
|
|
31
|
+
type AgGridReactProps,
|
|
32
|
+
type CustomCellRendererProps,
|
|
33
|
+
type CustomNoRowsOverlayProps,
|
|
34
|
+
} from 'ag-grid-react';
|
|
35
|
+
import {
|
|
36
|
+
type DataCubeEditorColumnSelectorColumnState,
|
|
37
|
+
type DataCubeEditorColumnSelectorState,
|
|
38
|
+
} from '../../../stores/view/editor/DataCubeEditorColumnSelectorState.js';
|
|
39
|
+
import { isNonNullable } from '@finos/legend-shared';
|
|
40
|
+
import {
|
|
41
|
+
getDataForAllFilteredNodes,
|
|
42
|
+
getDataForAllNodes,
|
|
43
|
+
} from '../../../stores/view/grid/DataCubeGridClientEngine.js';
|
|
44
|
+
|
|
45
|
+
function getBaseGridProps<
|
|
46
|
+
T extends DataCubeEditorColumnSelectorColumnState,
|
|
47
|
+
>(): AgGridReactProps<T> {
|
|
48
|
+
return {
|
|
49
|
+
modules: [AllCommunityModule],
|
|
50
|
+
className: 'ag-theme-quartz',
|
|
51
|
+
animateRows: false,
|
|
52
|
+
getRowId: (params) => params.data.name,
|
|
53
|
+
editType: 'fullRow',
|
|
54
|
+
rowDragMultiRow: true,
|
|
55
|
+
rowDragEntireRow: true,
|
|
56
|
+
rowSelection: {
|
|
57
|
+
mode: 'multiRow',
|
|
58
|
+
checkboxes: true,
|
|
59
|
+
headerCheckbox: true,
|
|
60
|
+
enableClickSelection: true,
|
|
61
|
+
},
|
|
62
|
+
selectionColumnDef: {
|
|
63
|
+
width: 40,
|
|
64
|
+
headerClass: '!pl-[23px]',
|
|
65
|
+
cellClass: '!pl-1.5',
|
|
66
|
+
rowDrag: true,
|
|
67
|
+
rowDragText: (params, dragItemCount) => {
|
|
68
|
+
if (dragItemCount > 1) {
|
|
69
|
+
return `${dragItemCount} columns`;
|
|
70
|
+
}
|
|
71
|
+
return (params.rowNode?.data as T).name;
|
|
72
|
+
},
|
|
73
|
+
sortable: false,
|
|
74
|
+
resizable: false,
|
|
75
|
+
suppressHeaderMenuButton: true,
|
|
76
|
+
},
|
|
77
|
+
suppressMoveWhenRowDragging: true,
|
|
78
|
+
rowHeight: 20,
|
|
79
|
+
headerHeight: 20,
|
|
80
|
+
suppressRowHoverHighlight: false,
|
|
81
|
+
noRowsOverlayComponent: (
|
|
82
|
+
params: CustomNoRowsOverlayProps<T> & {
|
|
83
|
+
noColumnsSelectedRenderer?: (() => React.ReactNode) | undefined;
|
|
84
|
+
},
|
|
85
|
+
) => {
|
|
86
|
+
if (params.api.getQuickFilter()) {
|
|
87
|
+
return (
|
|
88
|
+
<div className="flex items-center border-[1.5px] border-neutral-300 p-2 font-semibold text-neutral-400">
|
|
89
|
+
<div>
|
|
90
|
+
<DataCubeIcon.WarningCircle className="mr-1 text-lg" />
|
|
91
|
+
</div>
|
|
92
|
+
No match found
|
|
93
|
+
</div>
|
|
94
|
+
);
|
|
95
|
+
}
|
|
96
|
+
if (params.noColumnsSelectedRenderer) {
|
|
97
|
+
return params.noColumnsSelectedRenderer();
|
|
98
|
+
}
|
|
99
|
+
return <div />;
|
|
100
|
+
},
|
|
101
|
+
// Show no rows overlay when there are no search results
|
|
102
|
+
// See https://stackoverflow.com/a/72637410
|
|
103
|
+
onModelUpdated: (event: ModelUpdatedEvent<T>) => {
|
|
104
|
+
if (event.api.getDisplayedRowCount() === 0) {
|
|
105
|
+
event.api.showNoRowsOverlay();
|
|
106
|
+
} else {
|
|
107
|
+
event.api.hideOverlay();
|
|
108
|
+
}
|
|
109
|
+
},
|
|
110
|
+
};
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
function getBaseColumnDef<
|
|
114
|
+
T extends DataCubeEditorColumnSelectorColumnState,
|
|
115
|
+
>(): ColDef<T> {
|
|
116
|
+
return {
|
|
117
|
+
field: 'name' as ColDefField<T>,
|
|
118
|
+
colId: 'name',
|
|
119
|
+
flex: 1,
|
|
120
|
+
minWidth: 100,
|
|
121
|
+
filter: true,
|
|
122
|
+
sortable: false,
|
|
123
|
+
resizable: false,
|
|
124
|
+
suppressHeaderMenuButton: true,
|
|
125
|
+
getQuickFilterText: (params) => params.value,
|
|
126
|
+
};
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
/**
|
|
130
|
+
* Move this display to a separate component to avoid re-rendering the header too frequently
|
|
131
|
+
*/
|
|
132
|
+
const ColumnsSearchResultCountBadge = observer(
|
|
133
|
+
function ColumnsSearchResultCountBadge<
|
|
134
|
+
T extends DataCubeEditorColumnSelectorColumnState,
|
|
135
|
+
>(props: {
|
|
136
|
+
selector: DataCubeEditorColumnSelectorState<T>;
|
|
137
|
+
gridApi: GridApi<T>;
|
|
138
|
+
scope: 'available' | 'selected';
|
|
139
|
+
}) {
|
|
140
|
+
const { selector, gridApi, scope } = props;
|
|
141
|
+
return (
|
|
142
|
+
<div className="flex items-center justify-center rounded-lg bg-neutral-500 px-1 py-0.5 font-mono text-xs font-bold text-white">
|
|
143
|
+
{`${getDataForAllFilteredNodes(gridApi).length}/${scope === 'available' ? selector.availableColumnsForDisplay.length : selector.selectedColumnsForDisplay.length}`}
|
|
144
|
+
<span className="hidden">
|
|
145
|
+
{scope === 'available'
|
|
146
|
+
? // subscribing to the search text to trigger re-render as it changes
|
|
147
|
+
selector.availableColumnsSearchText
|
|
148
|
+
: selector.selectedColumnsSearchText}
|
|
149
|
+
</span>
|
|
150
|
+
</div>
|
|
151
|
+
);
|
|
152
|
+
},
|
|
153
|
+
);
|
|
154
|
+
|
|
155
|
+
export const DataCubeEditorColumnSelector = observer(
|
|
156
|
+
function DataCubeEditorColumnSelector<
|
|
157
|
+
T extends DataCubeEditorColumnSelectorColumnState,
|
|
158
|
+
>(props: {
|
|
159
|
+
selector: DataCubeEditorColumnSelectorState<T>;
|
|
160
|
+
columnLabelRenderer?:
|
|
161
|
+
| ((p: {
|
|
162
|
+
selector: DataCubeEditorColumnSelectorState<T>;
|
|
163
|
+
column: T;
|
|
164
|
+
}) => React.ReactNode)
|
|
165
|
+
| undefined;
|
|
166
|
+
columnActionRenderer?:
|
|
167
|
+
| ((p: {
|
|
168
|
+
selector: DataCubeEditorColumnSelectorState<T>;
|
|
169
|
+
column: T;
|
|
170
|
+
}) => React.ReactNode)
|
|
171
|
+
| undefined;
|
|
172
|
+
noColumnsSelectedRenderer?: (() => React.ReactNode) | undefined;
|
|
173
|
+
}) {
|
|
174
|
+
const {
|
|
175
|
+
selector,
|
|
176
|
+
columnLabelRenderer,
|
|
177
|
+
columnActionRenderer,
|
|
178
|
+
noColumnsSelectedRenderer,
|
|
179
|
+
} = props;
|
|
180
|
+
const [selectedAvailableColumns, setSelectedAvailableColumns] = useState<
|
|
181
|
+
T[]
|
|
182
|
+
>([]);
|
|
183
|
+
const [selectedSelectedColumns, setSelectedSelectedColumns] = useState<T[]>(
|
|
184
|
+
[],
|
|
185
|
+
);
|
|
186
|
+
const [availableColumnsGridApi, setAvailableColumnsGridApi] =
|
|
187
|
+
useState<GridApi | null>(null);
|
|
188
|
+
const [selectedColumnsGridApi, setSelectedColumnsGridApi] =
|
|
189
|
+
useState<GridApi | null>(null);
|
|
190
|
+
const searchAvailableColumnsInputRef = useRef<HTMLInputElement | null>(
|
|
191
|
+
null,
|
|
192
|
+
);
|
|
193
|
+
const searchSelectedColumnsInputRef = useRef<HTMLInputElement | null>(null);
|
|
194
|
+
|
|
195
|
+
/**
|
|
196
|
+
* Since we use managed row dragging for selected columns,
|
|
197
|
+
* we just need to sync the row data with the state.
|
|
198
|
+
* Dragging (multiple) rows to specific position have been
|
|
199
|
+
* handled by ag-grid.
|
|
200
|
+
*/
|
|
201
|
+
const onSelectedColumnsDragStop = useCallback(
|
|
202
|
+
(params: RowDragEndEvent<T>) => {
|
|
203
|
+
selector.setSelectedColumns(getDataForAllNodes(params.api));
|
|
204
|
+
},
|
|
205
|
+
[selector],
|
|
206
|
+
);
|
|
207
|
+
|
|
208
|
+
const onAvailableColumnsDragStop = useCallback(
|
|
209
|
+
(params: RowDragEndEvent<T>) => {
|
|
210
|
+
const nodes = params.nodes;
|
|
211
|
+
const columnsToMove = nodes
|
|
212
|
+
.map((node) => node.data)
|
|
213
|
+
.filter(isNonNullable);
|
|
214
|
+
selector.setSelectedColumns(
|
|
215
|
+
selector.selectedColumns.filter(
|
|
216
|
+
(column) => !columnsToMove.includes(column),
|
|
217
|
+
),
|
|
218
|
+
);
|
|
219
|
+
},
|
|
220
|
+
[selector],
|
|
221
|
+
);
|
|
222
|
+
|
|
223
|
+
/**
|
|
224
|
+
* Since we use managed row dragging for selected columns,
|
|
225
|
+
* we just need to sync the row data with the state
|
|
226
|
+
* Dragging (multiple) rows to specific position have been
|
|
227
|
+
* handled by ag-grid.
|
|
228
|
+
*/
|
|
229
|
+
const onSelectedColumnsDragEnd = useCallback(
|
|
230
|
+
(event: RowDragEndEvent) => {
|
|
231
|
+
if (event.overIndex === -1) {
|
|
232
|
+
return;
|
|
233
|
+
}
|
|
234
|
+
selector.setSelectedColumns(getDataForAllNodes(event.api));
|
|
235
|
+
},
|
|
236
|
+
[selector],
|
|
237
|
+
);
|
|
238
|
+
|
|
239
|
+
/**
|
|
240
|
+
* Setup row drop zones for each grid to be the other
|
|
241
|
+
* See https://www.ag-grid.com/react-data-grid/row-dragging-to-grid/
|
|
242
|
+
*/
|
|
243
|
+
useEffect(() => {
|
|
244
|
+
if (!availableColumnsGridApi || !selectedColumnsGridApi) {
|
|
245
|
+
return;
|
|
246
|
+
}
|
|
247
|
+
const selectedColumnsDropZoneParams =
|
|
248
|
+
selectedColumnsGridApi.getRowDropZoneParams({
|
|
249
|
+
onDragStop: (event) => {
|
|
250
|
+
onSelectedColumnsDragStop(event);
|
|
251
|
+
availableColumnsGridApi.clearFocusedCell();
|
|
252
|
+
},
|
|
253
|
+
});
|
|
254
|
+
if (selectedColumnsDropZoneParams) {
|
|
255
|
+
availableColumnsGridApi.removeRowDropZone(
|
|
256
|
+
selectedColumnsDropZoneParams,
|
|
257
|
+
);
|
|
258
|
+
availableColumnsGridApi.addRowDropZone(selectedColumnsDropZoneParams);
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
const availableColumnsDropZoneParams =
|
|
262
|
+
availableColumnsGridApi.getRowDropZoneParams({
|
|
263
|
+
onDragStop: (event) => {
|
|
264
|
+
onAvailableColumnsDragStop(event);
|
|
265
|
+
selectedColumnsGridApi.clearFocusedCell();
|
|
266
|
+
},
|
|
267
|
+
});
|
|
268
|
+
if (availableColumnsDropZoneParams) {
|
|
269
|
+
selectedColumnsGridApi.removeRowDropZone(
|
|
270
|
+
availableColumnsDropZoneParams,
|
|
271
|
+
);
|
|
272
|
+
selectedColumnsGridApi.addRowDropZone(availableColumnsDropZoneParams);
|
|
273
|
+
}
|
|
274
|
+
}, [
|
|
275
|
+
availableColumnsGridApi,
|
|
276
|
+
selectedColumnsGridApi,
|
|
277
|
+
onSelectedColumnsDragStop,
|
|
278
|
+
onAvailableColumnsDragStop,
|
|
279
|
+
]);
|
|
280
|
+
|
|
281
|
+
return (
|
|
282
|
+
<div className="data-cube-column-selector flex h-full w-full">
|
|
283
|
+
<div className="h-full w-[calc(50%_-_20px)]">
|
|
284
|
+
<div className="flex h-5 items-center text-sm">
|
|
285
|
+
Available columns:
|
|
286
|
+
</div>
|
|
287
|
+
<div className="h-[calc(100%_-_20px)] rounded-sm border border-neutral-200">
|
|
288
|
+
<div className="relative h-6 border-b border-neutral-200">
|
|
289
|
+
<input
|
|
290
|
+
className="h-full w-full pl-10 pr-6 placeholder-neutral-400"
|
|
291
|
+
ref={searchAvailableColumnsInputRef}
|
|
292
|
+
placeholder="Click here to search..."
|
|
293
|
+
value={selector.availableColumnsSearchText}
|
|
294
|
+
onChange={(event) =>
|
|
295
|
+
selector.setAvailableColumnsSearchText(event.target.value)
|
|
296
|
+
}
|
|
297
|
+
onKeyDown={(event) => {
|
|
298
|
+
if (event.code === 'Escape') {
|
|
299
|
+
event.stopPropagation();
|
|
300
|
+
searchAvailableColumnsInputRef.current?.select();
|
|
301
|
+
selector.setAvailableColumnsSearchText('');
|
|
302
|
+
}
|
|
303
|
+
}}
|
|
304
|
+
/>
|
|
305
|
+
<div className="absolute left-0 top-0 flex h-6 w-10 items-center justify-center">
|
|
306
|
+
<DataCubeIcon.Search className="stroke-[3px] text-lg text-neutral-500" />
|
|
307
|
+
</div>
|
|
308
|
+
<button
|
|
309
|
+
className="absolute right-0 top-0 flex h-6 w-6 items-center justify-center text-neutral-500 disabled:text-neutral-300"
|
|
310
|
+
disabled={!selector.availableColumnsSearchText}
|
|
311
|
+
title="Clear search [Esc]"
|
|
312
|
+
onClick={() => {
|
|
313
|
+
selector.setAvailableColumnsSearchText('');
|
|
314
|
+
searchAvailableColumnsInputRef.current?.focus();
|
|
315
|
+
}}
|
|
316
|
+
>
|
|
317
|
+
<DataCubeIcon.X className="text-lg" />
|
|
318
|
+
</button>
|
|
319
|
+
</div>
|
|
320
|
+
<div className="h-[calc(100%_-_24px)]">
|
|
321
|
+
<AgGridReact
|
|
322
|
+
theme="legacy"
|
|
323
|
+
{...getBaseGridProps<T>()}
|
|
324
|
+
// Disable managed row-dragging to disallow changing the order of columns
|
|
325
|
+
// and to make sure the row data and the available columns state are in sync
|
|
326
|
+
rowDragManaged={false}
|
|
327
|
+
onGridReady={(params) => setAvailableColumnsGridApi(params.api)}
|
|
328
|
+
onSelectionChanged={(event: SelectionChangedEvent<T>) => {
|
|
329
|
+
setSelectedAvailableColumns(
|
|
330
|
+
event.api
|
|
331
|
+
.getSelectedNodes()
|
|
332
|
+
.map((node) => node.data)
|
|
333
|
+
.filter(isNonNullable),
|
|
334
|
+
);
|
|
335
|
+
}}
|
|
336
|
+
// Using ag-grid quick filter is a cheap way to implement search
|
|
337
|
+
quickFilterText={selector.availableColumnsSearchText}
|
|
338
|
+
rowData={selector.availableColumnsForDisplay}
|
|
339
|
+
columnDefs={[
|
|
340
|
+
{
|
|
341
|
+
...getBaseColumnDef<T>(),
|
|
342
|
+
/**
|
|
343
|
+
* Support double-click to add all (filtered by search) columns
|
|
344
|
+
*/
|
|
345
|
+
headerComponent: (params: CustomCellRendererProps<T>) => (
|
|
346
|
+
<button
|
|
347
|
+
title="Double-click to add all columns"
|
|
348
|
+
className="flex h-full w-full items-center justify-between pl-0.5"
|
|
349
|
+
onDoubleClick={() => {
|
|
350
|
+
// The columns being moved are scoped by the current search
|
|
351
|
+
const filteredData = getDataForAllFilteredNodes(
|
|
352
|
+
params.api,
|
|
353
|
+
);
|
|
354
|
+
selector.setSelectedColumns([
|
|
355
|
+
...selector.selectedColumns,
|
|
356
|
+
...filteredData,
|
|
357
|
+
]);
|
|
358
|
+
params.api.clearFocusedCell();
|
|
359
|
+
}}
|
|
360
|
+
>
|
|
361
|
+
<div>{`[All Columns]`}</div>
|
|
362
|
+
<ColumnsSearchResultCountBadge
|
|
363
|
+
selector={selector}
|
|
364
|
+
gridApi={params.api}
|
|
365
|
+
scope="available"
|
|
366
|
+
/>
|
|
367
|
+
</button>
|
|
368
|
+
),
|
|
369
|
+
cellRenderer: (params: CustomCellRendererProps<T>) => {
|
|
370
|
+
const data = params.data;
|
|
371
|
+
if (!data) {
|
|
372
|
+
return null;
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
return (
|
|
376
|
+
<div
|
|
377
|
+
className="flex h-full w-full cursor-pointer items-center"
|
|
378
|
+
title={`[${data.name}]\nDouble-click to add column`}
|
|
379
|
+
onDoubleClick={() => {
|
|
380
|
+
selector.setSelectedColumns([
|
|
381
|
+
...selector.selectedColumns,
|
|
382
|
+
data,
|
|
383
|
+
]);
|
|
384
|
+
params.api.clearFocusedCell();
|
|
385
|
+
}}
|
|
386
|
+
>
|
|
387
|
+
{columnLabelRenderer?.({
|
|
388
|
+
selector,
|
|
389
|
+
column: data,
|
|
390
|
+
}) ?? (
|
|
391
|
+
<div className="h-full flex-1 items-center overflow-hidden overflow-ellipsis whitespace-nowrap pl-2">
|
|
392
|
+
{data.name}
|
|
393
|
+
</div>
|
|
394
|
+
)}
|
|
395
|
+
<div className="flex h-full">
|
|
396
|
+
{columnActionRenderer?.({
|
|
397
|
+
selector,
|
|
398
|
+
column: data,
|
|
399
|
+
}) ?? null}
|
|
400
|
+
</div>
|
|
401
|
+
</div>
|
|
402
|
+
);
|
|
403
|
+
},
|
|
404
|
+
},
|
|
405
|
+
]}
|
|
406
|
+
/>
|
|
407
|
+
</div>
|
|
408
|
+
</div>
|
|
409
|
+
</div>
|
|
410
|
+
<div className="flex h-full w-10 items-center justify-center">
|
|
411
|
+
<div className="flex flex-col">
|
|
412
|
+
<button
|
|
413
|
+
className="flex items-center justify-center rounded-sm border border-neutral-300 bg-neutral-100 text-neutral-500 hover:bg-neutral-200 disabled:bg-neutral-200 disabled:text-neutral-400"
|
|
414
|
+
title="Add selected columns"
|
|
415
|
+
/**
|
|
416
|
+
* Support add selected (filtered by search) columns
|
|
417
|
+
* We reset the selection after this operation
|
|
418
|
+
*/
|
|
419
|
+
onClick={() => {
|
|
420
|
+
if (!availableColumnsGridApi) {
|
|
421
|
+
return;
|
|
422
|
+
}
|
|
423
|
+
// The columns being moved are scoped by the current search
|
|
424
|
+
const filteredData = getDataForAllFilteredNodes(
|
|
425
|
+
availableColumnsGridApi,
|
|
426
|
+
);
|
|
427
|
+
const columnsToMove = selectedAvailableColumns.filter(
|
|
428
|
+
(column) => filteredData.includes(column),
|
|
429
|
+
);
|
|
430
|
+
selector.setSelectedColumns([
|
|
431
|
+
...selector.selectedColumns,
|
|
432
|
+
...columnsToMove,
|
|
433
|
+
]);
|
|
434
|
+
availableColumnsGridApi.clearFocusedCell();
|
|
435
|
+
}}
|
|
436
|
+
disabled={selectedAvailableColumns.length === 0}
|
|
437
|
+
>
|
|
438
|
+
<DataCubeIcon.ChevronRight className="text-2xl" />
|
|
439
|
+
</button>
|
|
440
|
+
<button
|
|
441
|
+
className="mt-2 flex items-center justify-center rounded-sm border border-neutral-300 bg-neutral-100 text-neutral-500 hover:bg-neutral-200 disabled:bg-neutral-200 disabled:text-neutral-400"
|
|
442
|
+
title="Remove selected columns"
|
|
443
|
+
/**
|
|
444
|
+
* Support remove selected (filtered by search) columns
|
|
445
|
+
* We reset the selection after this operation
|
|
446
|
+
*/
|
|
447
|
+
onClick={() => {
|
|
448
|
+
if (!selectedColumnsGridApi) {
|
|
449
|
+
return;
|
|
450
|
+
}
|
|
451
|
+
// The columns being moved are scoped by the current search
|
|
452
|
+
const filteredData = getDataForAllFilteredNodes(
|
|
453
|
+
selectedColumnsGridApi,
|
|
454
|
+
);
|
|
455
|
+
const columnsToMove = selectedSelectedColumns.filter((column) =>
|
|
456
|
+
filteredData.includes(column),
|
|
457
|
+
);
|
|
458
|
+
selector.setSelectedColumns(
|
|
459
|
+
selector.selectedColumns.filter(
|
|
460
|
+
(column) => !columnsToMove.includes(column),
|
|
461
|
+
),
|
|
462
|
+
);
|
|
463
|
+
selectedColumnsGridApi.clearFocusedCell();
|
|
464
|
+
}}
|
|
465
|
+
disabled={selectedSelectedColumns.length === 0}
|
|
466
|
+
>
|
|
467
|
+
<DataCubeIcon.ChevronLeft className="text-2xl" />
|
|
468
|
+
</button>
|
|
469
|
+
</div>
|
|
470
|
+
</div>
|
|
471
|
+
<div className="h-full w-[calc(50%_-_20px)]">
|
|
472
|
+
<div className="flex h-5 items-center text-sm">Selected columns:</div>
|
|
473
|
+
<div className="h-[calc(100%_-_20px)] rounded-sm border border-neutral-200">
|
|
474
|
+
<div className="relative h-6 border-b border-neutral-200">
|
|
475
|
+
<input
|
|
476
|
+
className="h-full w-full pl-10 placeholder-neutral-400"
|
|
477
|
+
ref={searchSelectedColumnsInputRef}
|
|
478
|
+
placeholder="Click here to search..."
|
|
479
|
+
value={selector.selectedColumnsSearchText}
|
|
480
|
+
onChange={(event) =>
|
|
481
|
+
selector.setSelectedColumnsSearchText(event.target.value)
|
|
482
|
+
}
|
|
483
|
+
onKeyDown={(event) => {
|
|
484
|
+
if (event.code === 'Escape') {
|
|
485
|
+
event.stopPropagation();
|
|
486
|
+
selector.setSelectedColumnsSearchText('');
|
|
487
|
+
}
|
|
488
|
+
}}
|
|
489
|
+
/>
|
|
490
|
+
<div className="absolute left-0 top-0 flex h-6 w-10 items-center justify-center">
|
|
491
|
+
<DataCubeIcon.Search className="stroke-[3px] text-lg text-neutral-500" />
|
|
492
|
+
</div>
|
|
493
|
+
<button
|
|
494
|
+
className="absolute right-0 top-0 flex h-6 w-6 items-center justify-center text-neutral-500 disabled:text-neutral-300"
|
|
495
|
+
disabled={!selector.selectedColumnsSearchText}
|
|
496
|
+
title="Clear search [Esc]"
|
|
497
|
+
onClick={() => {
|
|
498
|
+
selector.setSelectedColumnsSearchText('');
|
|
499
|
+
searchSelectedColumnsInputRef.current?.focus();
|
|
500
|
+
}}
|
|
501
|
+
>
|
|
502
|
+
<DataCubeIcon.X className="text-lg" />
|
|
503
|
+
</button>
|
|
504
|
+
</div>
|
|
505
|
+
<div className="h-[calc(100%_-_24px)]">
|
|
506
|
+
<AgGridReact
|
|
507
|
+
theme="legacy"
|
|
508
|
+
{...getBaseGridProps<T>()}
|
|
509
|
+
// NOTE: technically, we don't want to enable managed row-dragging here
|
|
510
|
+
// but enabling this gives us free row moving management and interaction
|
|
511
|
+
// comes out of the box from ag-grid, we will just sync the state with
|
|
512
|
+
// grid row data afterwards to ensure consistency
|
|
513
|
+
rowDragManaged={true}
|
|
514
|
+
onRowDragEnd={onSelectedColumnsDragEnd}
|
|
515
|
+
onGridReady={(params) => setSelectedColumnsGridApi(params.api)}
|
|
516
|
+
onSelectionChanged={(event: SelectionChangedEvent<T>) => {
|
|
517
|
+
setSelectedSelectedColumns(
|
|
518
|
+
event.api
|
|
519
|
+
.getSelectedNodes()
|
|
520
|
+
.map((node) => node.data)
|
|
521
|
+
.filter(isNonNullable),
|
|
522
|
+
);
|
|
523
|
+
}}
|
|
524
|
+
// Using ag-grid quick filter is a cheap way to implement search
|
|
525
|
+
quickFilterText={selector.selectedColumnsSearchText}
|
|
526
|
+
noRowsOverlayComponentParams={{
|
|
527
|
+
noColumnsSelectedRenderer,
|
|
528
|
+
}}
|
|
529
|
+
rowData={selector.selectedColumnsForDisplay}
|
|
530
|
+
columnDefs={[
|
|
531
|
+
{
|
|
532
|
+
...getBaseColumnDef<T>(),
|
|
533
|
+
/**
|
|
534
|
+
* Support double-click to remove all (filtered by search) columns
|
|
535
|
+
*/
|
|
536
|
+
headerComponent: (params: CustomCellRendererProps<T>) => (
|
|
537
|
+
<button
|
|
538
|
+
title="Double-click to remove all columns"
|
|
539
|
+
className="flex h-full w-full items-center justify-between pl-0.5"
|
|
540
|
+
onDoubleClick={() => {
|
|
541
|
+
// The columns being moved are scoped by the current search
|
|
542
|
+
const filteredData = getDataForAllFilteredNodes(
|
|
543
|
+
params.api,
|
|
544
|
+
);
|
|
545
|
+
selector.setSelectedColumns(
|
|
546
|
+
selector.selectedColumns.filter(
|
|
547
|
+
(column) => !filteredData.includes(column),
|
|
548
|
+
),
|
|
549
|
+
);
|
|
550
|
+
params.api.clearFocusedCell();
|
|
551
|
+
}}
|
|
552
|
+
>
|
|
553
|
+
<div>{`[All Columns]`}</div>
|
|
554
|
+
<ColumnsSearchResultCountBadge
|
|
555
|
+
selector={selector}
|
|
556
|
+
gridApi={params.api}
|
|
557
|
+
scope="selected"
|
|
558
|
+
/>
|
|
559
|
+
</button>
|
|
560
|
+
),
|
|
561
|
+
cellRenderer: (params: CustomCellRendererProps<T>) => {
|
|
562
|
+
const data = params.data;
|
|
563
|
+
if (!data) {
|
|
564
|
+
return null;
|
|
565
|
+
}
|
|
566
|
+
|
|
567
|
+
return (
|
|
568
|
+
<div
|
|
569
|
+
className="flex h-full w-full cursor-pointer items-center"
|
|
570
|
+
title={`[${data.name}]\nDouble-click to add column`}
|
|
571
|
+
onDoubleClick={() => {
|
|
572
|
+
selector.setSelectedColumns(
|
|
573
|
+
selector.selectedColumns.filter(
|
|
574
|
+
(column) => column !== data,
|
|
575
|
+
),
|
|
576
|
+
);
|
|
577
|
+
params.api.clearFocusedCell();
|
|
578
|
+
}}
|
|
579
|
+
>
|
|
580
|
+
{columnLabelRenderer?.({
|
|
581
|
+
selector,
|
|
582
|
+
column: data,
|
|
583
|
+
}) ?? (
|
|
584
|
+
<div className="h-full flex-1 items-center overflow-hidden overflow-ellipsis whitespace-nowrap pl-2">
|
|
585
|
+
{data.name}
|
|
586
|
+
</div>
|
|
587
|
+
)}
|
|
588
|
+
<div className="flex h-full">
|
|
589
|
+
{columnActionRenderer?.({
|
|
590
|
+
selector,
|
|
591
|
+
column: data,
|
|
592
|
+
}) ?? null}
|
|
593
|
+
</div>
|
|
594
|
+
</div>
|
|
595
|
+
);
|
|
596
|
+
},
|
|
597
|
+
},
|
|
598
|
+
]}
|
|
599
|
+
/>
|
|
600
|
+
</div>
|
|
601
|
+
</div>
|
|
602
|
+
</div>
|
|
603
|
+
</div>
|
|
604
|
+
);
|
|
605
|
+
},
|
|
606
|
+
);
|
|
@@ -0,0 +1,24 @@
|
|
|
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 { observer } from 'mobx-react-lite';
|
|
18
|
+
import type { DataCubeViewState } from '../../../stores/view/DataCubeViewState.js';
|
|
19
|
+
|
|
20
|
+
export const DataCubeMultidimensionalGrid = observer(
|
|
21
|
+
(props: { view: DataCubeViewState }) => {
|
|
22
|
+
return null;
|
|
23
|
+
},
|
|
24
|
+
);
|
|
@@ -22,6 +22,7 @@ import {
|
|
|
22
22
|
PRIMITIVE_TYPE,
|
|
23
23
|
V1_deserializeValueSpecification,
|
|
24
24
|
V1_serializeValueSpecification,
|
|
25
|
+
type V1_ExecuteInput,
|
|
25
26
|
} from '@finos/legend-graph';
|
|
26
27
|
import {
|
|
27
28
|
getFilterOperation,
|
|
@@ -113,6 +114,11 @@ export type DataCubeExecutionResult = {
|
|
|
113
114
|
executionTime: number;
|
|
114
115
|
};
|
|
115
116
|
|
|
117
|
+
export class DataCubeExecutionError extends Error {
|
|
118
|
+
executeInput?: PlainObject<V1_ExecuteInput> | undefined;
|
|
119
|
+
queryCode?: string | undefined;
|
|
120
|
+
}
|
|
121
|
+
|
|
116
122
|
/**
|
|
117
123
|
* This is the base engine of DataCube, it provides capabilities that DataCube cannot itself
|
|
118
124
|
* handle, such as query execution, compilation, etc.
|
|
@@ -279,7 +285,9 @@ export abstract class DataCubeEngine {
|
|
|
279
285
|
return undefined;
|
|
280
286
|
}
|
|
281
287
|
|
|
282
|
-
async disposeCache(source: CachedDataCubeSource) {
|
|
288
|
+
async disposeCache(source: CachedDataCubeSource) {
|
|
289
|
+
// do nothing
|
|
290
|
+
}
|
|
283
291
|
|
|
284
292
|
// ---------------------------------- APPLICATION ----------------------------------
|
|
285
293
|
|
|
@@ -21,10 +21,11 @@ import {
|
|
|
21
21
|
} from '@finos/legend-shared';
|
|
22
22
|
import { DataCubeSource } from './DataCubeSource.js';
|
|
23
23
|
import { createModelSchema, primitive, raw } from 'serializr';
|
|
24
|
+
import type { V1_PureModelContext } from '@finos/legend-graph';
|
|
24
25
|
|
|
25
26
|
export class AdhocQueryDataCubeSource extends DataCubeSource {
|
|
26
27
|
runtime!: string;
|
|
27
|
-
model!: PlainObject
|
|
28
|
+
model!: PlainObject<V1_PureModelContext>;
|
|
28
29
|
}
|
|
29
30
|
|
|
30
31
|
export const ADHOC_QUERY_DATA_CUBE_SOURCE_TYPE = 'adhocQuery';
|
|
@@ -32,7 +33,7 @@ export const ADHOC_QUERY_DATA_CUBE_SOURCE_TYPE = 'adhocQuery';
|
|
|
32
33
|
export class RawAdhocQueryDataCubeSource {
|
|
33
34
|
query!: string;
|
|
34
35
|
runtime!: string;
|
|
35
|
-
model!: PlainObject
|
|
36
|
+
model!: PlainObject<V1_PureModelContext>;
|
|
36
37
|
|
|
37
38
|
static readonly serialization = new SerializationFactory(
|
|
38
39
|
createModelSchema(RawAdhocQueryDataCubeSource, {
|