@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.
Files changed (103) hide show
  1. package/lib/__lib__/LegendDataCubeNavigation.d.ts +2 -0
  2. package/lib/__lib__/LegendDataCubeNavigation.d.ts.map +1 -1
  3. package/lib/__lib__/LegendDataCubeNavigation.js +2 -0
  4. package/lib/__lib__/LegendDataCubeNavigation.js.map +1 -1
  5. package/lib/application/LegendDataCubeApplicationConfig.d.ts +4 -0
  6. package/lib/application/LegendDataCubeApplicationConfig.d.ts.map +1 -1
  7. package/lib/application/LegendDataCubeApplicationConfig.js +5 -0
  8. package/lib/application/LegendDataCubeApplicationConfig.js.map +1 -1
  9. package/lib/application/__test-utils__/LegendDataCubeApplicationTestUtils.d.ts +18 -0
  10. package/lib/application/__test-utils__/LegendDataCubeApplicationTestUtils.d.ts.map +1 -0
  11. package/lib/application/__test-utils__/LegendDataCubeApplicationTestUtils.js +48 -0
  12. package/lib/application/__test-utils__/LegendDataCubeApplicationTestUtils.js.map +1 -0
  13. package/lib/components/LegendDataCubeBlockingWindow.d.ts +2 -1
  14. package/lib/components/LegendDataCubeBlockingWindow.d.ts.map +1 -1
  15. package/lib/components/LegendDataCubeBlockingWindow.js +9 -3
  16. package/lib/components/LegendDataCubeBlockingWindow.js.map +1 -1
  17. package/lib/components/__test-utils__/LegendDataCubeStoreTestUtils.d.ts +42 -0
  18. package/lib/components/__test-utils__/LegendDataCubeStoreTestUtils.d.ts.map +1 -0
  19. package/lib/components/__test-utils__/LegendDataCubeStoreTestUtils.js +104 -0
  20. package/lib/components/__test-utils__/LegendDataCubeStoreTestUtils.js.map +1 -0
  21. package/lib/components/builder/LegendDataCubeBuilder.d.ts +5 -0
  22. package/lib/components/builder/LegendDataCubeBuilder.d.ts.map +1 -1
  23. package/lib/components/builder/LegendDataCubeBuilder.js +26 -5
  24. package/lib/components/builder/LegendDataCubeBuilder.js.map +1 -1
  25. package/lib/components/builder/LegendDataCubeBuilderStoreProvider.d.ts.map +1 -1
  26. package/lib/components/builder/LegendDataCubeBuilderStoreProvider.js +2 -1
  27. package/lib/components/builder/LegendDataCubeBuilderStoreProvider.js.map +1 -1
  28. package/lib/components/builder/LegendDataCubeSourceViewer.d.ts.map +1 -1
  29. package/lib/components/builder/LegendDataCubeSourceViewer.js +61 -1
  30. package/lib/components/builder/LegendDataCubeSourceViewer.js.map +1 -1
  31. package/lib/components/builder/source/LegendDataCubeSourceLoader.d.ts +25 -0
  32. package/lib/components/builder/source/LegendDataCubeSourceLoader.d.ts.map +1 -0
  33. package/lib/components/builder/source/LegendDataCubeSourceLoader.js +47 -0
  34. package/lib/components/builder/source/LegendDataCubeSourceLoader.js.map +1 -0
  35. package/lib/components/builder/source/LocalFileDataCubeSourceBuilder.d.ts.map +1 -1
  36. package/lib/components/builder/source/LocalFileDataCubeSourceBuilder.js +1 -2
  37. package/lib/components/builder/source/LocalFileDataCubeSourceBuilder.js.map +1 -1
  38. package/lib/components/builder/source/LocalFileDataCubeSourceLoader.d.ts +22 -0
  39. package/lib/components/builder/source/LocalFileDataCubeSourceLoader.d.ts.map +1 -0
  40. package/lib/components/builder/source/LocalFileDataCubeSourceLoader.js +28 -0
  41. package/lib/components/builder/source/LocalFileDataCubeSourceLoader.js.map +1 -0
  42. package/lib/components/builder/source/UserDefinedFunctionDataCubeSourceBuilder.d.ts.map +1 -1
  43. package/lib/index.css +2 -2
  44. package/lib/index.css.map +1 -1
  45. package/lib/package.json +7 -5
  46. package/lib/stores/LegendDataCubeBaseStore.d.ts +2 -1
  47. package/lib/stores/LegendDataCubeBaseStore.d.ts.map +1 -1
  48. package/lib/stores/LegendDataCubeBaseStore.js +6 -3
  49. package/lib/stores/LegendDataCubeBaseStore.js.map +1 -1
  50. package/lib/stores/LegendDataCubeDataCubeEngine.d.ts +8 -5
  51. package/lib/stores/LegendDataCubeDataCubeEngine.d.ts.map +1 -1
  52. package/lib/stores/LegendDataCubeDataCubeEngine.js +132 -114
  53. package/lib/stores/LegendDataCubeDataCubeEngine.js.map +1 -1
  54. package/lib/stores/LegendDataCubeDuckDBEngine.d.ts +11 -4
  55. package/lib/stores/LegendDataCubeDuckDBEngine.d.ts.map +1 -1
  56. package/lib/stores/LegendDataCubeDuckDBEngine.js +92 -20
  57. package/lib/stores/LegendDataCubeDuckDBEngine.js.map +1 -1
  58. package/lib/stores/builder/LegendDataCubeBuilderStore.d.ts +7 -0
  59. package/lib/stores/builder/LegendDataCubeBuilderStore.d.ts.map +1 -1
  60. package/lib/stores/builder/LegendDataCubeBuilderStore.js +68 -17
  61. package/lib/stores/builder/LegendDataCubeBuilderStore.js.map +1 -1
  62. package/lib/stores/builder/source/LegendDataCubeSourceLoaderState.d.ts +39 -0
  63. package/lib/stores/builder/source/LegendDataCubeSourceLoaderState.d.ts.map +1 -0
  64. package/lib/stores/builder/source/LegendDataCubeSourceLoaderState.js +66 -0
  65. package/lib/stores/builder/source/LegendDataCubeSourceLoaderState.js.map +1 -0
  66. package/lib/stores/builder/source/LocalFileDataCubeSourceBuilderState.d.ts +3 -2
  67. package/lib/stores/builder/source/LocalFileDataCubeSourceBuilderState.d.ts.map +1 -1
  68. package/lib/stores/builder/source/LocalFileDataCubeSourceBuilderState.js +7 -10
  69. package/lib/stores/builder/source/LocalFileDataCubeSourceBuilderState.js.map +1 -1
  70. package/lib/stores/builder/source/LocalFileDataCubeSourceLoaderState.d.ts +45 -0
  71. package/lib/stores/builder/source/LocalFileDataCubeSourceLoaderState.d.ts.map +1 -0
  72. package/lib/stores/builder/source/LocalFileDataCubeSourceLoaderState.js +142 -0
  73. package/lib/stores/builder/source/LocalFileDataCubeSourceLoaderState.js.map +1 -0
  74. package/lib/stores/model/LegendQueryDataCubeSource.d.ts +2 -1
  75. package/lib/stores/model/LegendQueryDataCubeSource.d.ts.map +1 -1
  76. package/lib/stores/model/LegendQueryDataCubeSource.js +1 -0
  77. package/lib/stores/model/LegendQueryDataCubeSource.js.map +1 -1
  78. package/lib/stores/model/LocalFileDataCubeSource.d.ts +3 -8
  79. package/lib/stores/model/LocalFileDataCubeSource.d.ts.map +1 -1
  80. package/lib/stores/model/LocalFileDataCubeSource.js +5 -15
  81. package/lib/stores/model/LocalFileDataCubeSource.js.map +1 -1
  82. package/package.json +17 -15
  83. package/src/__lib__/LegendDataCubeNavigation.ts +21 -0
  84. package/src/application/LegendDataCubeApplicationConfig.ts +10 -0
  85. package/src/application/__test-utils__/LegendDataCubeApplicationTestUtils.ts +52 -0
  86. package/src/components/LegendDataCubeBlockingWindow.tsx +9 -2
  87. package/src/components/__test-utils__/LegendDataCubeStoreTestUtils.tsx +231 -0
  88. package/src/components/builder/LegendDataCubeBuilder.tsx +51 -6
  89. package/src/components/builder/LegendDataCubeBuilderStoreProvider.tsx +2 -0
  90. package/src/components/builder/LegendDataCubeSourceViewer.tsx +171 -1
  91. package/src/components/builder/source/LegendDataCubeSourceLoader.tsx +111 -0
  92. package/src/components/builder/source/LocalFileDataCubeSourceBuilder.tsx +1 -2
  93. package/src/components/builder/source/LocalFileDataCubeSourceLoader.tsx +58 -0
  94. package/src/stores/LegendDataCubeBaseStore.ts +13 -6
  95. package/src/stores/LegendDataCubeDataCubeEngine.ts +167 -131
  96. package/src/stores/LegendDataCubeDuckDBEngine.ts +110 -20
  97. package/src/stores/builder/LegendDataCubeBuilderStore.tsx +114 -23
  98. package/src/stores/builder/source/LegendDataCubeSourceLoaderState.tsx +104 -0
  99. package/src/stores/builder/source/LocalFileDataCubeSourceBuilderState.ts +9 -14
  100. package/src/stores/builder/source/LocalFileDataCubeSourceLoaderState.ts +232 -0
  101. package/src/stores/model/LegendQueryDataCubeSource.ts +2 -0
  102. package/src/stores/model/LocalFileDataCubeSource.ts +6 -15
  103. 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) => columnNames.push(col.name));
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: true,
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
- return { schema, table, tableSpec };
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
- // BigInt is not supported by ag-grid, so we need to convert it to native number
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 { LegendDataCubeAbout } from '../../components/builder/LegendDataCubeBuilder.js';
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
- // update the list of stack of recently viewed DataCubes
304
- const recentlyViewedDataCubes = this.getRecentlyViewedDataCubes();
305
- const idx = recentlyViewedDataCubes.findIndex(
306
- (data) => data === dataCubeId,
373
+ const sourceLoader = returnUndefOnError(() =>
374
+ this.getSourceLoader(specification, persistentDataCube),
307
375
  );
308
- if (idx === -1) {
309
- if (
310
- recentlyViewedDataCubes.length >= RECENTLY_VIEWED_DATA_CUBES_LIMIT
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
- guaranteeType,
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?: string | undefined;
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: string | undefined) {
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(fileFormat);
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 source = guaranteeType(
162
+ const tableDetails = guaranteeNonNullable(
164
163
  await this._engine.ingestLocalFileData(this.fileData, this.fileFormat),
165
- LocalFileDataCubeSource,
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.db = source.db;
172
- rawSource.model = source.model;
173
- rawSource.schema = source.schema;
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
  }