@finos/legend-application-data-cube 0.3.2 → 0.3.4
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/__lib__/LegendDataCubeNavigation.d.ts +2 -0
- package/lib/__lib__/LegendDataCubeNavigation.d.ts.map +1 -1
- package/lib/__lib__/LegendDataCubeNavigation.js +2 -0
- package/lib/__lib__/LegendDataCubeNavigation.js.map +1 -1
- package/lib/application/LegendDataCubeApplicationConfig.d.ts +4 -0
- package/lib/application/LegendDataCubeApplicationConfig.d.ts.map +1 -1
- package/lib/application/LegendDataCubeApplicationConfig.js +5 -0
- package/lib/application/LegendDataCubeApplicationConfig.js.map +1 -1
- package/lib/application/__test-utils__/LegendDataCubeApplicationTestUtils.d.ts +18 -0
- package/lib/application/__test-utils__/LegendDataCubeApplicationTestUtils.d.ts.map +1 -0
- package/lib/application/__test-utils__/LegendDataCubeApplicationTestUtils.js +48 -0
- package/lib/application/__test-utils__/LegendDataCubeApplicationTestUtils.js.map +1 -0
- package/lib/components/LegendDataCubeBlockingWindow.d.ts +2 -1
- package/lib/components/LegendDataCubeBlockingWindow.d.ts.map +1 -1
- package/lib/components/LegendDataCubeBlockingWindow.js +9 -3
- package/lib/components/LegendDataCubeBlockingWindow.js.map +1 -1
- package/lib/components/__test-utils__/LegendDataCubeStoreTestUtils.d.ts +42 -0
- package/lib/components/__test-utils__/LegendDataCubeStoreTestUtils.d.ts.map +1 -0
- package/lib/components/__test-utils__/LegendDataCubeStoreTestUtils.js +104 -0
- package/lib/components/__test-utils__/LegendDataCubeStoreTestUtils.js.map +1 -0
- package/lib/components/builder/LegendDataCubeBuilder.d.ts +5 -0
- package/lib/components/builder/LegendDataCubeBuilder.d.ts.map +1 -1
- package/lib/components/builder/LegendDataCubeBuilder.js +26 -5
- package/lib/components/builder/LegendDataCubeBuilder.js.map +1 -1
- package/lib/components/builder/LegendDataCubeBuilderStoreProvider.d.ts.map +1 -1
- package/lib/components/builder/LegendDataCubeBuilderStoreProvider.js +2 -1
- package/lib/components/builder/LegendDataCubeBuilderStoreProvider.js.map +1 -1
- package/lib/components/builder/LegendDataCubeSourceViewer.d.ts.map +1 -1
- package/lib/components/builder/LegendDataCubeSourceViewer.js +61 -1
- package/lib/components/builder/LegendDataCubeSourceViewer.js.map +1 -1
- package/lib/components/builder/source/LegendDataCubeSourceLoader.d.ts +25 -0
- package/lib/components/builder/source/LegendDataCubeSourceLoader.d.ts.map +1 -0
- package/lib/components/builder/source/LegendDataCubeSourceLoader.js +47 -0
- package/lib/components/builder/source/LegendDataCubeSourceLoader.js.map +1 -0
- package/lib/components/builder/source/LocalFileDataCubeSourceBuilder.d.ts.map +1 -1
- package/lib/components/builder/source/LocalFileDataCubeSourceBuilder.js +1 -2
- package/lib/components/builder/source/LocalFileDataCubeSourceBuilder.js.map +1 -1
- package/lib/components/builder/source/LocalFileDataCubeSourceLoader.d.ts +22 -0
- package/lib/components/builder/source/LocalFileDataCubeSourceLoader.d.ts.map +1 -0
- package/lib/components/builder/source/LocalFileDataCubeSourceLoader.js +28 -0
- package/lib/components/builder/source/LocalFileDataCubeSourceLoader.js.map +1 -0
- package/lib/components/builder/source/UserDefinedFunctionDataCubeSourceBuilder.d.ts.map +1 -1
- package/lib/index.css +2 -2
- package/lib/index.css.map +1 -1
- package/lib/package.json +7 -5
- package/lib/stores/LegendDataCubeBaseStore.d.ts +2 -1
- package/lib/stores/LegendDataCubeBaseStore.d.ts.map +1 -1
- package/lib/stores/LegendDataCubeBaseStore.js +6 -3
- package/lib/stores/LegendDataCubeBaseStore.js.map +1 -1
- package/lib/stores/LegendDataCubeDataCubeEngine.d.ts +8 -5
- package/lib/stores/LegendDataCubeDataCubeEngine.d.ts.map +1 -1
- package/lib/stores/LegendDataCubeDataCubeEngine.js +132 -114
- package/lib/stores/LegendDataCubeDataCubeEngine.js.map +1 -1
- package/lib/stores/LegendDataCubeDuckDBEngine.d.ts +11 -4
- package/lib/stores/LegendDataCubeDuckDBEngine.d.ts.map +1 -1
- package/lib/stores/LegendDataCubeDuckDBEngine.js +92 -20
- package/lib/stores/LegendDataCubeDuckDBEngine.js.map +1 -1
- package/lib/stores/builder/LegendDataCubeBuilderStore.d.ts +7 -0
- package/lib/stores/builder/LegendDataCubeBuilderStore.d.ts.map +1 -1
- package/lib/stores/builder/LegendDataCubeBuilderStore.js +68 -17
- package/lib/stores/builder/LegendDataCubeBuilderStore.js.map +1 -1
- package/lib/stores/builder/source/LegendDataCubeSourceLoaderState.d.ts +39 -0
- package/lib/stores/builder/source/LegendDataCubeSourceLoaderState.d.ts.map +1 -0
- package/lib/stores/builder/source/LegendDataCubeSourceLoaderState.js +66 -0
- package/lib/stores/builder/source/LegendDataCubeSourceLoaderState.js.map +1 -0
- package/lib/stores/builder/source/LocalFileDataCubeSourceBuilderState.d.ts +3 -2
- package/lib/stores/builder/source/LocalFileDataCubeSourceBuilderState.d.ts.map +1 -1
- package/lib/stores/builder/source/LocalFileDataCubeSourceBuilderState.js +7 -10
- package/lib/stores/builder/source/LocalFileDataCubeSourceBuilderState.js.map +1 -1
- package/lib/stores/builder/source/LocalFileDataCubeSourceLoaderState.d.ts +45 -0
- package/lib/stores/builder/source/LocalFileDataCubeSourceLoaderState.d.ts.map +1 -0
- package/lib/stores/builder/source/LocalFileDataCubeSourceLoaderState.js +142 -0
- package/lib/stores/builder/source/LocalFileDataCubeSourceLoaderState.js.map +1 -0
- package/lib/stores/model/LegendQueryDataCubeSource.d.ts +2 -1
- package/lib/stores/model/LegendQueryDataCubeSource.d.ts.map +1 -1
- package/lib/stores/model/LegendQueryDataCubeSource.js +1 -0
- package/lib/stores/model/LegendQueryDataCubeSource.js.map +1 -1
- package/lib/stores/model/LocalFileDataCubeSource.d.ts +3 -8
- package/lib/stores/model/LocalFileDataCubeSource.d.ts.map +1 -1
- package/lib/stores/model/LocalFileDataCubeSource.js +5 -15
- package/lib/stores/model/LocalFileDataCubeSource.js.map +1 -1
- package/package.json +17 -15
- package/src/__lib__/LegendDataCubeNavigation.ts +21 -0
- package/src/application/LegendDataCubeApplicationConfig.ts +10 -0
- package/src/application/__test-utils__/LegendDataCubeApplicationTestUtils.ts +52 -0
- package/src/components/LegendDataCubeBlockingWindow.tsx +9 -2
- package/src/components/__test-utils__/LegendDataCubeStoreTestUtils.tsx +231 -0
- package/src/components/builder/LegendDataCubeBuilder.tsx +51 -6
- package/src/components/builder/LegendDataCubeBuilderStoreProvider.tsx +2 -0
- package/src/components/builder/LegendDataCubeSourceViewer.tsx +171 -1
- package/src/components/builder/source/LegendDataCubeSourceLoader.tsx +111 -0
- package/src/components/builder/source/LocalFileDataCubeSourceBuilder.tsx +1 -2
- package/src/components/builder/source/LocalFileDataCubeSourceLoader.tsx +58 -0
- package/src/stores/LegendDataCubeBaseStore.ts +13 -6
- package/src/stores/LegendDataCubeDataCubeEngine.ts +167 -131
- package/src/stores/LegendDataCubeDuckDBEngine.ts +110 -20
- package/src/stores/builder/LegendDataCubeBuilderStore.tsx +114 -23
- package/src/stores/builder/source/LegendDataCubeSourceLoaderState.tsx +104 -0
- package/src/stores/builder/source/LocalFileDataCubeSourceBuilderState.ts +9 -14
- package/src/stores/builder/source/LocalFileDataCubeSourceLoaderState.ts +232 -0
- package/src/stores/model/LegendQueryDataCubeSource.ts +2 -0
- package/src/stores/model/LocalFileDataCubeSource.ts +6 -15
- package/tsconfig.json +7 -1
|
@@ -18,6 +18,8 @@ import * as duckdb from '@duckdb/duckdb-wasm';
|
|
|
18
18
|
import duckdb_wasm from '@duckdb/duckdb-wasm/dist/duckdb-mvp.wasm';
|
|
19
19
|
import duckdb_wasm_next from '@duckdb/duckdb-wasm/dist/duckdb-eh.wasm';
|
|
20
20
|
import {
|
|
21
|
+
DATE_FORMAT,
|
|
22
|
+
DATE_TIME_FORMAT,
|
|
21
23
|
INTERNAL__TDSColumn,
|
|
22
24
|
PRIMITIVE_TYPE,
|
|
23
25
|
TDSBuilder,
|
|
@@ -28,8 +30,11 @@ import {
|
|
|
28
30
|
import {
|
|
29
31
|
assertNonNullable,
|
|
30
32
|
csvStringify,
|
|
33
|
+
formatDate,
|
|
31
34
|
guaranteeNonNullable,
|
|
35
|
+
isNullable,
|
|
32
36
|
UnsupportedOperationError,
|
|
37
|
+
uuid,
|
|
33
38
|
} from '@finos/legend-shared';
|
|
34
39
|
import type { CachedDataCubeSource } from '@finos/legend-data-cube';
|
|
35
40
|
import { Type } from 'apache-arrow';
|
|
@@ -48,6 +53,7 @@ export class LegendDataCubeDuckDBEngine {
|
|
|
48
53
|
// Options for creating csv using papa parser: https://www.papaparse.com/docs#config
|
|
49
54
|
private static readonly ESCAPE_CHAR = `'`;
|
|
50
55
|
private static readonly QUOTE_CHAR = `'`;
|
|
56
|
+
private _catalog: Map<string, DuckDBCatalogTable> = new Map();
|
|
51
57
|
|
|
52
58
|
private _database?: duckdb.AsyncDuckDB | undefined;
|
|
53
59
|
|
|
@@ -58,6 +64,13 @@ export class LegendDataCubeDuckDBEngine {
|
|
|
58
64
|
);
|
|
59
65
|
}
|
|
60
66
|
|
|
67
|
+
retrieveCatalogTable(ref: string) {
|
|
68
|
+
return guaranteeNonNullable(
|
|
69
|
+
this._catalog.get(ref),
|
|
70
|
+
`Can't find reference ${ref}`,
|
|
71
|
+
);
|
|
72
|
+
}
|
|
73
|
+
|
|
61
74
|
async initialize() {
|
|
62
75
|
// Initialize DuckDB with WASM
|
|
63
76
|
// See: https://duckdb.org/docs/api/wasm/instantiation.html
|
|
@@ -99,35 +112,90 @@ export class LegendDataCubeDuckDBEngine {
|
|
|
99
112
|
const table = `${LegendDataCubeDuckDBEngine.CACHE_TABLE_NAME_PREFIX}${LegendDataCubeDuckDBEngine.cacheTableCounter}`;
|
|
100
113
|
const csvFileName = `${LegendDataCubeDuckDBEngine.CACHE_FILE_NAME}${LegendDataCubeDuckDBEngine.cacheTableCounter}.csv`;
|
|
101
114
|
|
|
115
|
+
const columns: string[] = [];
|
|
102
116
|
const columnNames: string[] = [];
|
|
103
|
-
result.builder.columns.forEach((col) =>
|
|
117
|
+
result.builder.columns.forEach((col) => {
|
|
118
|
+
let colType: string;
|
|
119
|
+
switch (col.type as string) {
|
|
120
|
+
case PRIMITIVE_TYPE.BINARY: {
|
|
121
|
+
colType = 'BIT';
|
|
122
|
+
break;
|
|
123
|
+
}
|
|
124
|
+
case PRIMITIVE_TYPE.BOOLEAN: {
|
|
125
|
+
colType = 'BOOLEAN';
|
|
126
|
+
break;
|
|
127
|
+
}
|
|
128
|
+
case PRIMITIVE_TYPE.NUMBER: {
|
|
129
|
+
colType = 'DOUBLE';
|
|
130
|
+
break;
|
|
131
|
+
}
|
|
132
|
+
case PRIMITIVE_TYPE.INTEGER: {
|
|
133
|
+
colType = 'INTEGER';
|
|
134
|
+
break;
|
|
135
|
+
}
|
|
136
|
+
// TODO: we need precision and scale
|
|
137
|
+
case PRIMITIVE_TYPE.DECIMAL: {
|
|
138
|
+
colType = 'DECIMAL';
|
|
139
|
+
break;
|
|
140
|
+
}
|
|
141
|
+
case PRIMITIVE_TYPE.FLOAT: {
|
|
142
|
+
colType = 'FLOAT';
|
|
143
|
+
break;
|
|
144
|
+
}
|
|
145
|
+
case PRIMITIVE_TYPE.STRICTDATE:
|
|
146
|
+
case PRIMITIVE_TYPE.DATE: {
|
|
147
|
+
colType = 'DATE';
|
|
148
|
+
break;
|
|
149
|
+
}
|
|
150
|
+
case PRIMITIVE_TYPE.DATETIME: {
|
|
151
|
+
colType = 'TIMESTAMP';
|
|
152
|
+
break;
|
|
153
|
+
}
|
|
154
|
+
case PRIMITIVE_TYPE.STRING: {
|
|
155
|
+
colType = 'VARCHAR';
|
|
156
|
+
break;
|
|
157
|
+
}
|
|
158
|
+
default: {
|
|
159
|
+
throw new UnsupportedOperationError(
|
|
160
|
+
`Can't initialize cache: failed to find matching DuckDB type for Pure type '${col.type}'`,
|
|
161
|
+
);
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
columns.push(`"${col.name}" ${colType}`);
|
|
165
|
+
columnNames.push(col.name);
|
|
166
|
+
});
|
|
104
167
|
|
|
105
168
|
const data = result.result.rows.map((row) => row.values);
|
|
106
169
|
|
|
107
|
-
const csvContent = csvStringify([columnNames, ...data]
|
|
108
|
-
escapeChar: LegendDataCubeDuckDBEngine.ESCAPE_CHAR,
|
|
109
|
-
quoteChar: LegendDataCubeDuckDBEngine.QUOTE_CHAR,
|
|
110
|
-
});
|
|
170
|
+
const csvContent = csvStringify([columnNames, ...data]);
|
|
111
171
|
await this.database.registerFileText(csvFileName, csvContent);
|
|
112
172
|
|
|
113
173
|
const connection = await this.database.connect();
|
|
174
|
+
|
|
175
|
+
// we create our own table schema becuase of date type conversions from arrow to duckDB data types
|
|
176
|
+
const CREATE_TABLE_SQL = `CREATE TABLE ${schema}.${table} (${columns.join(',')})`;
|
|
177
|
+
await connection.query(CREATE_TABLE_SQL);
|
|
178
|
+
|
|
114
179
|
await connection.insertCSVFromPath(csvFileName, {
|
|
115
180
|
schema: schema,
|
|
116
181
|
name: table,
|
|
117
|
-
create:
|
|
118
|
-
header: true,
|
|
182
|
+
create: false,
|
|
183
|
+
header: true, // we add header and get it to autodetect otherwise we would have to provide column details with arrow datatypes
|
|
119
184
|
detect: true,
|
|
120
|
-
dateFormat: 'YYYY-MM-DD',
|
|
121
|
-
timestampFormat: 'YYYY-MM-DD', // make sure Date is not auto-converted to timestamp
|
|
122
|
-
escape: LegendDataCubeDuckDBEngine.ESCAPE_CHAR,
|
|
123
|
-
quote: LegendDataCubeDuckDBEngine.QUOTE_CHAR,
|
|
124
185
|
});
|
|
125
186
|
await connection.close();
|
|
126
187
|
|
|
127
188
|
return { schema, table, rowCount: result.result.rows.length };
|
|
128
189
|
}
|
|
129
190
|
|
|
130
|
-
async ingestLocalFileData(data: string, format: string) {
|
|
191
|
+
async ingestLocalFileData(data: string, format: string, refId?: string) {
|
|
192
|
+
if (!isNullable(refId) && this._catalog.has(refId)) {
|
|
193
|
+
const dbDetails = guaranteeNonNullable(this._catalog.get(refId));
|
|
194
|
+
return {
|
|
195
|
+
dbReference: refId,
|
|
196
|
+
columnNames: dbDetails.columns.map((col) => col[0] as string),
|
|
197
|
+
};
|
|
198
|
+
}
|
|
131
199
|
const schema = LegendDataCubeDuckDBEngine.DUCKDB_DEFAULT_SCHEMA_NAME;
|
|
132
200
|
LegendDataCubeDuckDBEngine.ingestFileTableCounter += 1;
|
|
133
201
|
const table = `${LegendDataCubeDuckDBEngine.INGEST_TABLE_NAME_PREFIX}${LegendDataCubeDuckDBEngine.ingestFileTableCounter}`;
|
|
@@ -144,8 +212,6 @@ export class LegendDataCubeDuckDBEngine {
|
|
|
144
212
|
name: table,
|
|
145
213
|
header: true,
|
|
146
214
|
detect: true,
|
|
147
|
-
dateFormat: 'YYYY-MM-DD',
|
|
148
|
-
timestampFormat: 'YYYY-MM-DD', // make sure Date is not auto-converted to timestamp
|
|
149
215
|
escape: LegendDataCubeDuckDBEngine.ESCAPE_CHAR,
|
|
150
216
|
quote: LegendDataCubeDuckDBEngine.QUOTE_CHAR,
|
|
151
217
|
});
|
|
@@ -161,12 +227,22 @@ export class LegendDataCubeDuckDBEngine {
|
|
|
161
227
|
const tableSpec = (await connection.query(`DESCRIBE ${schema}.${table}`))
|
|
162
228
|
.toArray()
|
|
163
229
|
.map((spec) => [
|
|
164
|
-
spec[LegendDataCubeDuckDBEngine.COLUMN_NAME],
|
|
165
|
-
spec[LegendDataCubeDuckDBEngine.COLUMN_TYPE],
|
|
230
|
+
spec[LegendDataCubeDuckDBEngine.COLUMN_NAME] as string,
|
|
231
|
+
spec[LegendDataCubeDuckDBEngine.COLUMN_TYPE] as string,
|
|
166
232
|
]);
|
|
167
233
|
await connection.close();
|
|
168
234
|
|
|
169
|
-
|
|
235
|
+
const ref = isNullable(refId) ? uuid() : refId;
|
|
236
|
+
this._catalog.set(ref, {
|
|
237
|
+
schemaName: schema,
|
|
238
|
+
tableName: table,
|
|
239
|
+
columns: tableSpec,
|
|
240
|
+
} satisfies DuckDBCatalogTable);
|
|
241
|
+
|
|
242
|
+
return {
|
|
243
|
+
dbReference: ref,
|
|
244
|
+
columnNames: tableSpec.map((spec) => spec[0] as string),
|
|
245
|
+
};
|
|
170
246
|
}
|
|
171
247
|
|
|
172
248
|
async runSQLQuery(sql: string) {
|
|
@@ -176,16 +252,21 @@ export class LegendDataCubeDuckDBEngine {
|
|
|
176
252
|
|
|
177
253
|
const data = result.toArray();
|
|
178
254
|
const columnNames = result.schema.fields.map((field) => field.name);
|
|
255
|
+
const columnTypesIds = result.schema.fields.map((field) => field.typeId);
|
|
179
256
|
const rows = data.map((row) => {
|
|
180
257
|
const tdsRow = new TDSRow();
|
|
181
|
-
tdsRow.values = columnNames.map((column) => {
|
|
258
|
+
tdsRow.values = columnNames.map((column, idx) => {
|
|
182
259
|
const value = row[column] as unknown;
|
|
183
260
|
// NOTE: DuckDB WASM returns ArrayBuffer for numeric value, such as for count(*)
|
|
184
261
|
// so we need to convert it to number
|
|
185
262
|
if (ArrayBuffer.isView(value)) {
|
|
186
263
|
return row[column].valueOf() as number;
|
|
187
|
-
|
|
264
|
+
} else if (columnTypesIds[idx] === Type.Date) {
|
|
265
|
+
return formatDate(new Date(Number(value)), DATE_FORMAT);
|
|
266
|
+
} else if (columnTypesIds[idx] === Type.Timestamp) {
|
|
267
|
+
return formatDate(new Date(Number(value)), DATE_TIME_FORMAT);
|
|
188
268
|
} else if (typeof value === 'bigint') {
|
|
269
|
+
// BigInt is not supported by ag-grid, so we need to convert it to native number
|
|
189
270
|
return Number(value);
|
|
190
271
|
}
|
|
191
272
|
return value as string | number | boolean | null;
|
|
@@ -210,13 +291,16 @@ export class LegendDataCubeDuckDBEngine {
|
|
|
210
291
|
col.type = PRIMITIVE_TYPE.BOOLEAN;
|
|
211
292
|
break;
|
|
212
293
|
}
|
|
213
|
-
case Type.Timestamp:
|
|
214
294
|
case Type.Date:
|
|
215
295
|
case Type.DateDay:
|
|
216
296
|
case Type.DateMillisecond: {
|
|
217
297
|
col.type = PRIMITIVE_TYPE.DATE;
|
|
218
298
|
break;
|
|
219
299
|
}
|
|
300
|
+
case Type.Timestamp: {
|
|
301
|
+
col.type = PRIMITIVE_TYPE.DATETIME;
|
|
302
|
+
break;
|
|
303
|
+
}
|
|
220
304
|
case Type.Utf8:
|
|
221
305
|
case Type.LargeUtf8: {
|
|
222
306
|
col.type = PRIMITIVE_TYPE.STRING;
|
|
@@ -269,3 +353,9 @@ export class LegendDataCubeDuckDBEngine {
|
|
|
269
353
|
await this._database?.terminate();
|
|
270
354
|
}
|
|
271
355
|
}
|
|
356
|
+
|
|
357
|
+
type DuckDBCatalogTable = {
|
|
358
|
+
schemaName: string;
|
|
359
|
+
tableName: string;
|
|
360
|
+
columns: string[][];
|
|
361
|
+
};
|
|
@@ -42,6 +42,8 @@ import {
|
|
|
42
42
|
assertErrorThrown,
|
|
43
43
|
formatDate,
|
|
44
44
|
isString,
|
|
45
|
+
returnUndefOnError,
|
|
46
|
+
UnsupportedOperationError,
|
|
45
47
|
uuid,
|
|
46
48
|
} from '@finos/legend-shared';
|
|
47
49
|
import type { LegendDataCubeDataCubeEngine } from '../LegendDataCubeDataCubeEngine.js';
|
|
@@ -58,8 +60,14 @@ import {
|
|
|
58
60
|
import type { DepotServerClient } from '@finos/legend-server-depot';
|
|
59
61
|
import { LegendDataCubeBlockingWindowState } from '../../components/LegendDataCubeBlockingWindow.js';
|
|
60
62
|
import { LegendDataCubeDeleteConfirmation } from '../../components/builder/LegendDataCubeDeleteConfirmation.js';
|
|
61
|
-
import {
|
|
63
|
+
import {
|
|
64
|
+
LegendDataCubeAbout,
|
|
65
|
+
LegendDataCubeReleaseLogManager,
|
|
66
|
+
} from '../../components/builder/LegendDataCubeBuilder.js';
|
|
62
67
|
import { LegendDataCubeSourceViewer } from '../../components/builder/LegendDataCubeSourceViewer.js';
|
|
68
|
+
import { LOCAL_FILE_QUERY_DATA_CUBE_SOURCE_TYPE } from '../model/LocalFileDataCubeSource.js';
|
|
69
|
+
import type { LegendDataCubeSourceLoaderState } from './source/LegendDataCubeSourceLoaderState.js';
|
|
70
|
+
import { LocalFileDataCubeSourceLoaderState } from './source/LocalFileDataCubeSourceLoaderState.js';
|
|
63
71
|
|
|
64
72
|
export class LegendDataCubeBuilderState {
|
|
65
73
|
readonly uuid = uuid();
|
|
@@ -121,6 +129,8 @@ export class LegendDataCubeBuilderStore {
|
|
|
121
129
|
|
|
122
130
|
readonly initializeState = ActionState.create();
|
|
123
131
|
readonly aboutDisplay: DisplayState;
|
|
132
|
+
readonly releaseLogDisplay: DisplayState;
|
|
133
|
+
readonly releaseNotesDisplay: DisplayState;
|
|
124
134
|
|
|
125
135
|
readonly creator: LegendDataCubeCreatorState;
|
|
126
136
|
|
|
@@ -136,6 +146,8 @@ export class LegendDataCubeBuilderStore {
|
|
|
136
146
|
builder?: LegendDataCubeBuilderState | undefined;
|
|
137
147
|
readonly sourceViewerDisplay: DisplayState;
|
|
138
148
|
|
|
149
|
+
sourceLoader?: LegendDataCubeSourceLoaderState | undefined;
|
|
150
|
+
|
|
139
151
|
private passedFirstLoad = false;
|
|
140
152
|
|
|
141
153
|
constructor(baseStore: LegendDataCubeBaseStore) {
|
|
@@ -143,6 +155,9 @@ export class LegendDataCubeBuilderStore {
|
|
|
143
155
|
builder: observable,
|
|
144
156
|
setBuilder: action,
|
|
145
157
|
|
|
158
|
+
sourceLoader: observable,
|
|
159
|
+
setSourceLoader: action,
|
|
160
|
+
|
|
146
161
|
dataCubeToDelete: observable,
|
|
147
162
|
setDataCubeToDelete: action,
|
|
148
163
|
});
|
|
@@ -169,6 +184,23 @@ export class LegendDataCubeBuilderStore {
|
|
|
169
184
|
},
|
|
170
185
|
);
|
|
171
186
|
|
|
187
|
+
this.releaseLogDisplay = this.layoutService.newDisplay(
|
|
188
|
+
'Release Log',
|
|
189
|
+
() => <LegendDataCubeReleaseLogManager showOnlyLatestNotes={false} />,
|
|
190
|
+
{
|
|
191
|
+
...DEFAULT_ALERT_WINDOW_CONFIG,
|
|
192
|
+
height: 500,
|
|
193
|
+
},
|
|
194
|
+
);
|
|
195
|
+
this.releaseNotesDisplay = this.layoutService.newDisplay(
|
|
196
|
+
'Release Notes',
|
|
197
|
+
() => <LegendDataCubeReleaseLogManager showOnlyLatestNotes={true} />,
|
|
198
|
+
{
|
|
199
|
+
...DEFAULT_ALERT_WINDOW_CONFIG,
|
|
200
|
+
height: 350,
|
|
201
|
+
},
|
|
202
|
+
);
|
|
203
|
+
|
|
172
204
|
this.creator = new LegendDataCubeCreatorState(this);
|
|
173
205
|
this.loader = new LegendDataCubeLoaderState(this);
|
|
174
206
|
this.saverDisplay = new LegendDataCubeBlockingWindowState(
|
|
@@ -201,6 +233,10 @@ export class LegendDataCubeBuilderStore {
|
|
|
201
233
|
this.builder = val;
|
|
202
234
|
}
|
|
203
235
|
|
|
236
|
+
setSourceLoader(val: LegendDataCubeSourceLoaderState | undefined) {
|
|
237
|
+
this.sourceLoader = val;
|
|
238
|
+
}
|
|
239
|
+
|
|
204
240
|
private updateWindowTitle(persistentDataCube: PersistentDataCube) {
|
|
205
241
|
this.application.layoutService.setWindowTitle(
|
|
206
242
|
`\u229E ${persistentDataCube.name}${this.builder ? ` - ${formatDate(new Date(this.builder.startTime), 'HH:mm:ss EEE MMM dd yyyy')}` : ''}`,
|
|
@@ -253,6 +289,44 @@ export class LegendDataCubeBuilderStore {
|
|
|
253
289
|
}
|
|
254
290
|
}
|
|
255
291
|
|
|
292
|
+
private getSourceLoader(
|
|
293
|
+
specification: DataCubeSpecification,
|
|
294
|
+
persistentDataCube: PersistentDataCube,
|
|
295
|
+
) {
|
|
296
|
+
const sourceData = specification.source;
|
|
297
|
+
const onSuccess = async () => {
|
|
298
|
+
const dataCubeId = persistentDataCube.id;
|
|
299
|
+
this.finalizeLoad(specification, persistentDataCube, dataCubeId);
|
|
300
|
+
this.loadState.pass();
|
|
301
|
+
};
|
|
302
|
+
const onError = async (error: unknown) => {
|
|
303
|
+
assertErrorThrown(error);
|
|
304
|
+
this.alertService.alertError(error, {
|
|
305
|
+
message: `DataCube Load Failure: ${error.message}`,
|
|
306
|
+
});
|
|
307
|
+
this.application.navigationService.navigator.updateCurrentLocation(
|
|
308
|
+
generateBuilderRoute(null),
|
|
309
|
+
);
|
|
310
|
+
this.loadState.fail();
|
|
311
|
+
};
|
|
312
|
+
switch (sourceData._type) {
|
|
313
|
+
case LOCAL_FILE_QUERY_DATA_CUBE_SOURCE_TYPE:
|
|
314
|
+
return new LocalFileDataCubeSourceLoaderState(
|
|
315
|
+
this.application,
|
|
316
|
+
this.engine,
|
|
317
|
+
this.alertService,
|
|
318
|
+
sourceData,
|
|
319
|
+
persistentDataCube,
|
|
320
|
+
onSuccess,
|
|
321
|
+
onError,
|
|
322
|
+
);
|
|
323
|
+
default:
|
|
324
|
+
throw new UnsupportedOperationError(
|
|
325
|
+
`Can't create source loader for unsupported type '${sourceData._type}'`,
|
|
326
|
+
);
|
|
327
|
+
}
|
|
328
|
+
}
|
|
329
|
+
|
|
256
330
|
async loadDataCube(dataCubeId: string | undefined) {
|
|
257
331
|
// internalize the parameters and clean them from the URL
|
|
258
332
|
const sourceData =
|
|
@@ -295,32 +369,17 @@ export class LegendDataCubeBuilderStore {
|
|
|
295
369
|
const specification = DataCubeSpecification.serialization.fromJson(
|
|
296
370
|
persistentDataCube.content,
|
|
297
371
|
);
|
|
298
|
-
this.setBuilder(
|
|
299
|
-
new LegendDataCubeBuilderState(specification, persistentDataCube),
|
|
300
|
-
);
|
|
301
|
-
this.updateWindowTitle(persistentDataCube);
|
|
302
372
|
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
const idx = recentlyViewedDataCubes.findIndex(
|
|
306
|
-
(data) => data === dataCubeId,
|
|
373
|
+
const sourceLoader = returnUndefOnError(() =>
|
|
374
|
+
this.getSourceLoader(specification, persistentDataCube),
|
|
307
375
|
);
|
|
308
|
-
if (
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
recentlyViewedDataCubes.pop();
|
|
313
|
-
}
|
|
314
|
-
recentlyViewedDataCubes.unshift(dataCubeId);
|
|
315
|
-
} else {
|
|
316
|
-
recentlyViewedDataCubes.splice(idx, 1);
|
|
317
|
-
recentlyViewedDataCubes.unshift(dataCubeId);
|
|
376
|
+
if (sourceLoader !== undefined) {
|
|
377
|
+
this.setSourceLoader(sourceLoader);
|
|
378
|
+
sourceLoader.display.open();
|
|
379
|
+
return;
|
|
318
380
|
}
|
|
319
|
-
this.application.userDataService.persistValue(
|
|
320
|
-
LegendDataCubeUserDataKey.RECENTLY_VIEWED_DATA_CUBES,
|
|
321
|
-
recentlyViewedDataCubes,
|
|
322
|
-
);
|
|
323
381
|
|
|
382
|
+
this.finalizeLoad(specification, persistentDataCube, dataCubeId);
|
|
324
383
|
this.loadState.pass();
|
|
325
384
|
} catch (error) {
|
|
326
385
|
assertErrorThrown(error);
|
|
@@ -336,6 +395,36 @@ export class LegendDataCubeBuilderStore {
|
|
|
336
395
|
}
|
|
337
396
|
}
|
|
338
397
|
|
|
398
|
+
private finalizeLoad(
|
|
399
|
+
specification: DataCubeSpecification,
|
|
400
|
+
persistentDataCube: PersistentDataCube,
|
|
401
|
+
dataCubeId: string,
|
|
402
|
+
) {
|
|
403
|
+
this.setBuilder(
|
|
404
|
+
new LegendDataCubeBuilderState(specification, persistentDataCube),
|
|
405
|
+
);
|
|
406
|
+
this.updateWindowTitle(persistentDataCube);
|
|
407
|
+
|
|
408
|
+
// update the list of stack of recently viewed DataCubes
|
|
409
|
+
const recentlyViewedDataCubes = this.getRecentlyViewedDataCubes();
|
|
410
|
+
const idx = recentlyViewedDataCubes.findIndex(
|
|
411
|
+
(data) => data === dataCubeId,
|
|
412
|
+
);
|
|
413
|
+
if (idx === -1) {
|
|
414
|
+
if (recentlyViewedDataCubes.length >= RECENTLY_VIEWED_DATA_CUBES_LIMIT) {
|
|
415
|
+
recentlyViewedDataCubes.pop();
|
|
416
|
+
}
|
|
417
|
+
recentlyViewedDataCubes.unshift(dataCubeId);
|
|
418
|
+
} else {
|
|
419
|
+
recentlyViewedDataCubes.splice(idx, 1);
|
|
420
|
+
recentlyViewedDataCubes.unshift(dataCubeId);
|
|
421
|
+
}
|
|
422
|
+
this.application.userDataService.persistValue(
|
|
423
|
+
LegendDataCubeUserDataKey.RECENTLY_VIEWED_DATA_CUBES,
|
|
424
|
+
recentlyViewedDataCubes,
|
|
425
|
+
);
|
|
426
|
+
}
|
|
427
|
+
|
|
339
428
|
private async generatePersistentDataCube(
|
|
340
429
|
api: DataCubeAPI,
|
|
341
430
|
name: string,
|
|
@@ -391,6 +480,7 @@ export class LegendDataCubeBuilderStore {
|
|
|
391
480
|
// Another way to avoid reloading the whole app it to force update
|
|
392
481
|
// the <DataCube/> component using the key prop that ties to an ID
|
|
393
482
|
// of the builder.
|
|
483
|
+
this.builder.setPersistentDataCube(newPersistentDataCube);
|
|
394
484
|
this.application.navigationService.navigator.updateCurrentLocation(
|
|
395
485
|
generateBuilderRoute(newPersistentDataCube.id),
|
|
396
486
|
);
|
|
@@ -439,6 +529,7 @@ export class LegendDataCubeBuilderStore {
|
|
|
439
529
|
// Another way to avoid reloading the whole app it to force update
|
|
440
530
|
// the <DataCube/> component using the key prop that ties to an ID
|
|
441
531
|
// of the builder.
|
|
532
|
+
this.builder.setPersistentDataCube(newPersistentDataCube);
|
|
442
533
|
this.application.navigationService.navigator.updateCurrentLocation(
|
|
443
534
|
generateBuilderRoute(newPersistentDataCube.id),
|
|
444
535
|
);
|
|
@@ -0,0 +1,104 @@
|
|
|
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 {
|
|
18
|
+
ActionState,
|
|
19
|
+
assertErrorThrown,
|
|
20
|
+
type PlainObject,
|
|
21
|
+
} from '@finos/legend-shared';
|
|
22
|
+
import type { LegendDataCubeApplicationStore } from '../../LegendDataCubeBaseStore.js';
|
|
23
|
+
import type { LegendDataCubeDataCubeEngine } from '../../LegendDataCubeDataCubeEngine.js';
|
|
24
|
+
import {
|
|
25
|
+
DEFAULT_TOOL_PANEL_WINDOW_CONFIG,
|
|
26
|
+
type DataCubeAlertService,
|
|
27
|
+
} from '@finos/legend-data-cube';
|
|
28
|
+
import { LegendDataCubeBlockingWindowState } from '../../../components/LegendDataCubeBlockingWindow.js';
|
|
29
|
+
import { generateBuilderRoute } from '../../../__lib__/LegendDataCubeNavigation.js';
|
|
30
|
+
import { LegendDataCubeSourceLoader } from '../../../components/builder/source/LegendDataCubeSourceLoader.js';
|
|
31
|
+
import type { PersistentDataCube } from '@finos/legend-graph';
|
|
32
|
+
|
|
33
|
+
export abstract class LegendDataCubeSourceLoaderState {
|
|
34
|
+
protected readonly _application: LegendDataCubeApplicationStore;
|
|
35
|
+
protected readonly _engine: LegendDataCubeDataCubeEngine;
|
|
36
|
+
protected readonly _alertService: DataCubeAlertService;
|
|
37
|
+
|
|
38
|
+
readonly display: LegendDataCubeBlockingWindowState;
|
|
39
|
+
readonly finalizeState = ActionState.create();
|
|
40
|
+
|
|
41
|
+
protected readonly onSuccess: () => Promise<void>;
|
|
42
|
+
protected readonly onError: (error: unknown) => Promise<void>;
|
|
43
|
+
readonly sourceData: PlainObject;
|
|
44
|
+
readonly persistentDataCube: PersistentDataCube;
|
|
45
|
+
|
|
46
|
+
constructor(
|
|
47
|
+
application: LegendDataCubeApplicationStore,
|
|
48
|
+
engine: LegendDataCubeDataCubeEngine,
|
|
49
|
+
alertService: DataCubeAlertService,
|
|
50
|
+
sourceData: PlainObject,
|
|
51
|
+
persistentDataCube: PersistentDataCube,
|
|
52
|
+
onSuccess: () => Promise<void>,
|
|
53
|
+
onError: (error: unknown) => Promise<void>,
|
|
54
|
+
) {
|
|
55
|
+
this._application = application;
|
|
56
|
+
this._engine = engine;
|
|
57
|
+
this._alertService = alertService;
|
|
58
|
+
|
|
59
|
+
this.sourceData = sourceData;
|
|
60
|
+
this.persistentDataCube = persistentDataCube;
|
|
61
|
+
this.onSuccess = onSuccess;
|
|
62
|
+
this.onError = onError;
|
|
63
|
+
|
|
64
|
+
this.display = new LegendDataCubeBlockingWindowState(
|
|
65
|
+
'Load DataCube',
|
|
66
|
+
() => <LegendDataCubeSourceLoader state={this} />,
|
|
67
|
+
{
|
|
68
|
+
...DEFAULT_TOOL_PANEL_WINDOW_CONFIG,
|
|
69
|
+
width: 500,
|
|
70
|
+
minWidth: 500,
|
|
71
|
+
},
|
|
72
|
+
() => {
|
|
73
|
+
this._application.navigationService.navigator.updateCurrentLocation(
|
|
74
|
+
generateBuilderRoute(null),
|
|
75
|
+
);
|
|
76
|
+
},
|
|
77
|
+
);
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
abstract initialize(): void;
|
|
81
|
+
|
|
82
|
+
abstract get label(): string;
|
|
83
|
+
|
|
84
|
+
abstract get isValid(): boolean;
|
|
85
|
+
|
|
86
|
+
abstract load(source: PlainObject | undefined): Promise<PlainObject>;
|
|
87
|
+
|
|
88
|
+
async finalize() {
|
|
89
|
+
this.finalizeState.inProgress();
|
|
90
|
+
try {
|
|
91
|
+
await this.load(this.sourceData);
|
|
92
|
+
this.display.close();
|
|
93
|
+
await this.onSuccess();
|
|
94
|
+
this.finalizeState.pass();
|
|
95
|
+
} catch (error) {
|
|
96
|
+
assertErrorThrown(error);
|
|
97
|
+
this._alertService.alertError(error, {
|
|
98
|
+
message: `DataCube Load Failure: ${error.message}`,
|
|
99
|
+
});
|
|
100
|
+
await this.onError(error);
|
|
101
|
+
this.finalizeState.fail();
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
}
|
|
@@ -17,7 +17,7 @@
|
|
|
17
17
|
import {
|
|
18
18
|
ActionState,
|
|
19
19
|
csvStringify,
|
|
20
|
-
|
|
20
|
+
guaranteeNonNullable,
|
|
21
21
|
IllegalStateError,
|
|
22
22
|
parseCSVFile,
|
|
23
23
|
type PlainObject,
|
|
@@ -30,7 +30,6 @@ import type { LegendDataCubeApplicationStore } from '../../LegendDataCubeBaseSto
|
|
|
30
30
|
import { action, makeObservable, observable } from 'mobx';
|
|
31
31
|
import type { LegendDataCubeDataCubeEngine } from '../../LegendDataCubeDataCubeEngine.js';
|
|
32
32
|
import {
|
|
33
|
-
LocalFileDataCubeSource,
|
|
34
33
|
LocalFileDataCubeSourceFormat,
|
|
35
34
|
RawLocalFileQueryDataCubeSource,
|
|
36
35
|
} from '../../model/LocalFileDataCubeSource.js';
|
|
@@ -39,7 +38,7 @@ export class LocalFileDataCubeSourceBuilderState extends LegendDataCubeSourceBui
|
|
|
39
38
|
readonly processState = ActionState.create();
|
|
40
39
|
|
|
41
40
|
fileName?: string | undefined;
|
|
42
|
-
fileFormat?:
|
|
41
|
+
fileFormat?: LocalFileDataCubeSourceFormat | undefined;
|
|
43
42
|
// NOTE: type string is suitable for CSV/Excel, etc. but will not be appropriate
|
|
44
43
|
// for other format that we want to support, e.g. arrow/parquet
|
|
45
44
|
fileData?: string | undefined;
|
|
@@ -74,7 +73,7 @@ export class LocalFileDataCubeSourceBuilderState extends LegendDataCubeSourceBui
|
|
|
74
73
|
this.fileName = fileName;
|
|
75
74
|
}
|
|
76
75
|
|
|
77
|
-
setFileFormat(format:
|
|
76
|
+
setFileFormat(format: LocalFileDataCubeSourceFormat | undefined) {
|
|
78
77
|
this.fileFormat = format;
|
|
79
78
|
}
|
|
80
79
|
|
|
@@ -114,7 +113,7 @@ export class LocalFileDataCubeSourceBuilderState extends LegendDataCubeSourceBui
|
|
|
114
113
|
csvStringify(result.data, { escapeChar: `'`, quoteChar: `'` }),
|
|
115
114
|
);
|
|
116
115
|
this.setFileName(fileName);
|
|
117
|
-
this.setFileFormat(
|
|
116
|
+
this.setFileFormat(LocalFileDataCubeSourceFormat.CSV);
|
|
118
117
|
this.setRowCount(result.data.length);
|
|
119
118
|
this.setPreviewText(
|
|
120
119
|
csvStringify(result.data.slice(0, 100), {
|
|
@@ -160,19 +159,15 @@ export class LocalFileDataCubeSourceBuilderState extends LegendDataCubeSourceBui
|
|
|
160
159
|
);
|
|
161
160
|
}
|
|
162
161
|
|
|
163
|
-
const
|
|
162
|
+
const tableDetails = guaranteeNonNullable(
|
|
164
163
|
await this._engine.ingestLocalFileData(this.fileData, this.fileFormat),
|
|
165
|
-
|
|
166
|
-
`Can't generate data source`,
|
|
164
|
+
`Can't generate source data: failed to ingest data from local file`,
|
|
167
165
|
);
|
|
168
166
|
const rawSource = new RawLocalFileQueryDataCubeSource();
|
|
169
|
-
rawSource.count = this.rowCount;
|
|
170
167
|
rawSource.fileName = this.fileName;
|
|
171
|
-
rawSource.
|
|
172
|
-
rawSource.
|
|
173
|
-
rawSource.
|
|
174
|
-
rawSource.table = source.table;
|
|
175
|
-
rawSource.runtime = source.runtime;
|
|
168
|
+
rawSource.fileFormat = this.fileFormat;
|
|
169
|
+
rawSource._ref = tableDetails.dbReference;
|
|
170
|
+
rawSource.columnNames = tableDetails.columnNames;
|
|
176
171
|
|
|
177
172
|
return RawLocalFileQueryDataCubeSource.serialization.toJson(rawSource);
|
|
178
173
|
}
|