@finos/legend-application-studio 28.21.51 → 28.21.52

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 (61) hide show
  1. package/lib/components/editor/Editor.d.ts.map +1 -1
  2. package/lib/components/editor/Editor.js +2 -1
  3. package/lib/components/editor/Editor.js.map +1 -1
  4. package/lib/components/editor/editor-group/ingest-editor/IngestDefinitionEditor.d.ts.map +1 -1
  5. package/lib/components/editor/editor-group/ingest-editor/IngestDefinitionEditor.js +17 -2
  6. package/lib/components/editor/editor-group/ingest-editor/IngestDefinitionEditor.js.map +1 -1
  7. package/lib/components/editor/embedded-cube/EmbeddedCube.d.ts +23 -0
  8. package/lib/components/editor/embedded-cube/EmbeddedCube.d.ts.map +1 -0
  9. package/lib/components/editor/embedded-cube/EmbeddedCube.js +38 -0
  10. package/lib/components/editor/embedded-cube/EmbeddedCube.js.map +1 -0
  11. package/lib/components/editor/embedded-cube/EmbeddedCubeEditor.d.ts +35 -0
  12. package/lib/components/editor/embedded-cube/EmbeddedCubeEditor.d.ts.map +1 -0
  13. package/lib/components/editor/embedded-cube/EmbeddedCubeEditor.js +177 -0
  14. package/lib/components/editor/embedded-cube/EmbeddedCubeEditor.js.map +1 -0
  15. package/lib/components/extensions/Core_LegendStudioApplicationPlugin.d.ts.map +1 -1
  16. package/lib/components/extensions/Core_LegendStudioApplicationPlugin.js +4 -0
  17. package/lib/components/extensions/Core_LegendStudioApplicationPlugin.js.map +1 -1
  18. package/lib/index.css +1 -1
  19. package/lib/package.json +3 -1
  20. package/lib/stores/editor/EditorStore.d.ts +2 -0
  21. package/lib/stores/editor/EditorStore.d.ts.map +1 -1
  22. package/lib/stores/editor/EditorStore.js +6 -1
  23. package/lib/stores/editor/EditorStore.js.map +1 -1
  24. package/lib/stores/editor/embedded-cube/EmbeddedCubeState.d.ts +81 -0
  25. package/lib/stores/editor/embedded-cube/EmbeddedCubeState.d.ts.map +1 -0
  26. package/lib/stores/editor/embedded-cube/EmbeddedCubeState.js +186 -0
  27. package/lib/stores/editor/embedded-cube/EmbeddedCubeState.js.map +1 -0
  28. package/lib/stores/editor/embedded-cube/LegendStudioCubeDataProductCatalog.d.ts +51 -0
  29. package/lib/stores/editor/embedded-cube/LegendStudioCubeDataProductCatalog.d.ts.map +1 -0
  30. package/lib/stores/editor/embedded-cube/LegendStudioCubeDataProductCatalog.js +148 -0
  31. package/lib/stores/editor/embedded-cube/LegendStudioCubeDataProductCatalog.js.map +1 -0
  32. package/lib/stores/editor/embedded-cube/LegendStudioCubeHost.d.ts +39 -0
  33. package/lib/stores/editor/embedded-cube/LegendStudioCubeHost.d.ts.map +1 -0
  34. package/lib/stores/editor/embedded-cube/LegendStudioCubeHost.js +60 -0
  35. package/lib/stores/editor/embedded-cube/LegendStudioCubeHost.js.map +1 -0
  36. package/lib/stores/editor/embedded-cube/LegendStudioCubeIngestCatalog.d.ts +57 -0
  37. package/lib/stores/editor/embedded-cube/LegendStudioCubeIngestCatalog.d.ts.map +1 -0
  38. package/lib/stores/editor/embedded-cube/LegendStudioCubeIngestCatalog.js +148 -0
  39. package/lib/stores/editor/embedded-cube/LegendStudioCubeIngestCatalog.js.map +1 -0
  40. package/lib/stores/editor/embedded-cube/LegendStudioCubeWorkspace.d.ts +58 -0
  41. package/lib/stores/editor/embedded-cube/LegendStudioCubeWorkspace.d.ts.map +1 -0
  42. package/lib/stores/editor/embedded-cube/LegendStudioCubeWorkspace.js +94 -0
  43. package/lib/stores/editor/embedded-cube/LegendStudioCubeWorkspace.js.map +1 -0
  44. package/lib/stores/graph-modifier/DSL_Ingest_GraphModifierHelper.d.ts +57 -0
  45. package/lib/stores/graph-modifier/DSL_Ingest_GraphModifierHelper.d.ts.map +1 -0
  46. package/lib/stores/graph-modifier/DSL_Ingest_GraphModifierHelper.js +151 -0
  47. package/lib/stores/graph-modifier/DSL_Ingest_GraphModifierHelper.js.map +1 -0
  48. package/package.json +13 -11
  49. package/src/components/editor/Editor.tsx +2 -0
  50. package/src/components/editor/editor-group/ingest-editor/IngestDefinitionEditor.tsx +65 -0
  51. package/src/components/editor/embedded-cube/EmbeddedCube.tsx +57 -0
  52. package/src/components/editor/embedded-cube/EmbeddedCubeEditor.tsx +448 -0
  53. package/src/components/extensions/Core_LegendStudioApplicationPlugin.tsx +4 -0
  54. package/src/stores/editor/EditorStore.ts +6 -1
  55. package/src/stores/editor/embedded-cube/EmbeddedCubeState.ts +251 -0
  56. package/src/stores/editor/embedded-cube/LegendStudioCubeDataProductCatalog.ts +242 -0
  57. package/src/stores/editor/embedded-cube/LegendStudioCubeHost.ts +82 -0
  58. package/src/stores/editor/embedded-cube/LegendStudioCubeIngestCatalog.ts +235 -0
  59. package/src/stores/editor/embedded-cube/LegendStudioCubeWorkspace.ts +151 -0
  60. package/src/stores/graph-modifier/DSL_Ingest_GraphModifierHelper.ts +214 -0
  61. package/tsconfig.json +8 -0
@@ -0,0 +1,251 @@
1
+ /**
2
+ * Copyright (c) 2026-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 type { IngestDefinition } from '@finos/legend-graph';
18
+ import type { PlainObject } from '@finos/legend-shared';
19
+ import { action, computed, flowResult, makeObservable, observable } from 'mobx';
20
+ import {
21
+ getIngestDataSetNames,
22
+ getIngestMaterializedViews,
23
+ ingestDefinition_addMaterializedView,
24
+ ingestDefinition_updateMaterializedView,
25
+ type IngestMaterializedView,
26
+ type IngestMaterializedViewOptions,
27
+ } from '../../graph-modifier/DSL_Ingest_GraphModifierHelper.js';
28
+ import type { EditorStore } from '../EditorStore.js';
29
+ import { IngestDefinitionEditorState } from '../editor-state/element-editor-state/ingest/IngestDefinitionEditorState.js';
30
+
31
+ /** A data set's name: a letter or an underscore, then letters, digits and underscores */
32
+ const DATA_SET_NAME_PATTERN = /^[A-Za-z_][A-Za-z0-9_]*$/u;
33
+
34
+ /**
35
+ * Legend Cube, opened full screen from an ingest definition's editor to build
36
+ * a materialized view of it from the project's ingest data sets (Create
37
+ * MatView). Studio's own shortcuts are off while it is open. Cube
38
+ * itself, and its host, are loaded with the dialog (`EmbeddedCube`), so
39
+ * nothing here imports them.
40
+ */
41
+ export class EmbeddedCubeState {
42
+ readonly editorStore: EditorStore;
43
+ /** The definition the materialized view is added to; none while closed */
44
+ ingest: IngestDefinition | undefined;
45
+ /**
46
+ * The materialized view opened to edit (Edit MatView, PLAN §11.18), by
47
+ * name: Cube opens on its lambda, and Save MatView replaces it; none for a
48
+ * new one
49
+ */
50
+ matView: string | undefined;
51
+
52
+ constructor(editorStore: EditorStore) {
53
+ makeObservable(this, {
54
+ ingest: observable.ref,
55
+ matView: observable,
56
+ matViewDataSet: computed,
57
+ matViewLambda: computed,
58
+ open: action,
59
+ close: action,
60
+ addMaterializedView: action,
61
+ saveMaterializedView: action,
62
+ });
63
+ this.editorStore = editorStore;
64
+ }
65
+
66
+ get isOpen(): boolean {
67
+ return this.ingest !== undefined;
68
+ }
69
+
70
+ /** Opens Cube on a new materialized view of the definition, or on one of its views to edit */
71
+ open(ingest: IngestDefinition, matView?: string): void {
72
+ this.ingest = ingest;
73
+ this.matView = matView;
74
+ }
75
+
76
+ close(): void {
77
+ this.ingest = undefined;
78
+ this.matView = undefined;
79
+ }
80
+
81
+ /** The materialized view opened to edit, as the definition holds it */
82
+ get matViewDataSet(): IngestMaterializedView | undefined {
83
+ const { ingest, matView } = this;
84
+ return ingest && matView !== undefined
85
+ ? getIngestMaterializedViews(ingest).find((view) => view.name === matView)
86
+ : undefined;
87
+ }
88
+
89
+ /** The lambda (protocol JSON) of the materialized view opened to edit */
90
+ get matViewLambda(): PlainObject | undefined {
91
+ return this.matViewDataSet?.lambda;
92
+ }
93
+
94
+ /** Why the name can't be the new materialized view's; none when it can */
95
+ getMaterializedViewNameProblem(name: string): string | undefined {
96
+ const trimmed = name.trim();
97
+ if (!trimmed) {
98
+ return 'Name the materialized view.';
99
+ }
100
+ if (!DATA_SET_NAME_PATTERN.test(trimmed)) {
101
+ return 'A data set name is letters, digits and underscores, and starts with a letter or an underscore.';
102
+ }
103
+ return this.ingest && getIngestDataSetNames(this.ingest).includes(trimmed)
104
+ ? `The ingest definition already has a data set named ${trimmed}.`
105
+ : undefined;
106
+ }
107
+
108
+ /**
109
+ * The classifications a materialized view's data can take: those a data
110
+ * product's access points offer; none when Studio's config gives none,
111
+ * and the classification is then typed
112
+ */
113
+ get classifications(): string[] {
114
+ return (
115
+ this.editorStore.applicationStore.config.options.dataProductConfig
116
+ ?.classifications ?? []
117
+ );
118
+ }
119
+
120
+ /** Why the classification can't be the new materialized view's; none when it can */
121
+ getMaterializedViewClassificationProblem(
122
+ sensitivity: string,
123
+ ): string | undefined {
124
+ const trimmed = sensitivity.trim();
125
+ if (!trimmed) {
126
+ return "Classify the materialized view's data.";
127
+ }
128
+ const { classifications } = this;
129
+ return classifications.length && !classifications.includes(trimmed)
130
+ ? `${trimmed} isn't one of the data classifications.`
131
+ : undefined;
132
+ }
133
+
134
+ /**
135
+ * Adds the lambda to the ingest definition as a materialized view, with
136
+ * its primary key and the classification of its data, closes the cube and
137
+ * shows the definition with it
138
+ */
139
+ addMaterializedView(
140
+ name: string,
141
+ lambda: PlainObject,
142
+ options: IngestMaterializedViewOptions,
143
+ ): void {
144
+ const { ingest } = this;
145
+ if (
146
+ !ingest ||
147
+ this.getMaterializedViewNameProblem(name) !== undefined ||
148
+ this.getMaterializedViewClassificationProblem(options.sensitivity) !==
149
+ undefined
150
+ ) {
151
+ return;
152
+ }
153
+ ingestDefinition_addMaterializedView(ingest, name.trim(), lambda, {
154
+ primaryKey: options.primaryKey,
155
+ sensitivity: options.sensitivity.trim(),
156
+ });
157
+ this.closeOn(
158
+ ingest,
159
+ `Added materialized view ${name.trim()} to ${ingest.name}`,
160
+ );
161
+ }
162
+
163
+ /**
164
+ * Why Save MatView must ask again before it replaces the view opened to
165
+ * edit with the step that runs, given the step's columns (unknown, the key
166
+ * isn't checked): its data has no classification, as Studio wrote a view
167
+ * before it asked for one, or one that isn't among the classifications; or
168
+ * its primary key has a column the step no longer has. None when the view
169
+ * can be saved as it is
170
+ */
171
+ getMaterializedViewSaveProblems(
172
+ columns: readonly string[] | undefined,
173
+ ): string[] {
174
+ const view = this.matViewDataSet;
175
+ if (!view) {
176
+ return [];
177
+ }
178
+ const problems: string[] = [];
179
+ const classificationProblem =
180
+ view.sensitivity === undefined
181
+ ? `${view.name} has no classification of its data.`
182
+ : this.getMaterializedViewClassificationProblem(view.sensitivity);
183
+ if (classificationProblem !== undefined) {
184
+ problems.push(classificationProblem);
185
+ }
186
+ const gone = columns
187
+ ? view.primaryKey.filter((column) => !columns.includes(column))
188
+ : [];
189
+ if (gone.length) {
190
+ problems.push(
191
+ gone.length === 1
192
+ ? `Its primary key's ${gone.join(', ')} is no longer a column of the cube.`
193
+ : `Its primary key's ${gone.join(', ')} are no longer columns of the cube.`,
194
+ );
195
+ }
196
+ return problems;
197
+ }
198
+
199
+ /**
200
+ * Replaces the lambda of the materialized view opened to edit, in place
201
+ * (Save MatView), with the primary key and classification given, or its
202
+ * own; closes the cube and shows the definition with it. As a view added,
203
+ * a view saved has a classification of its data
204
+ */
205
+ saveMaterializedView(
206
+ lambda: PlainObject,
207
+ options?: IngestMaterializedViewOptions,
208
+ ): void {
209
+ const { ingest, matView, matViewDataSet } = this;
210
+ if (
211
+ !ingest ||
212
+ matView === undefined ||
213
+ !matViewDataSet ||
214
+ this.getMaterializedViewClassificationProblem(
215
+ (options ? options.sensitivity : matViewDataSet.sensitivity) ?? '',
216
+ ) !== undefined
217
+ ) {
218
+ return;
219
+ }
220
+ ingestDefinition_updateMaterializedView(
221
+ ingest,
222
+ matView,
223
+ lambda,
224
+ options && {
225
+ primaryKey: options.primaryKey,
226
+ sensitivity: options.sensitivity.trim(),
227
+ },
228
+ );
229
+ this.closeOn(
230
+ ingest,
231
+ `Saved materialized view ${matView} of ${ingest.name}`,
232
+ );
233
+ }
234
+
235
+ /** Closes the cube, then shows the definition's grammar with the change */
236
+ private closeOn(ingest: IngestDefinition, message: string): void {
237
+ this.close();
238
+ const editorState = this.editorStore.tabManagerState.tabs.find(
239
+ (tab): tab is IngestDefinitionEditorState =>
240
+ tab instanceof IngestDefinitionEditorState && tab.ingest === ingest,
241
+ );
242
+ if (editorState) {
243
+ flowResult(editorState.generateElementGrammar()).catch(
244
+ this.editorStore.applicationStore.alertUnhandledError,
245
+ );
246
+ }
247
+ this.editorStore.applicationStore.notificationService.notifySuccess(
248
+ message,
249
+ );
250
+ }
251
+ }
@@ -0,0 +1,242 @@
1
+ /**
2
+ * Copyright (c) 2026-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
+ dataProductAccessor,
19
+ lambda,
20
+ type IR,
21
+ type ModelContext,
22
+ type Schema,
23
+ } from '@finos/legend-cube';
24
+ import {
25
+ createCubeDataProductModel,
26
+ CubeAccessPoint,
27
+ CubeAccessPointGroup,
28
+ CubeDataProductCandidate,
29
+ CubeDataProductDescription,
30
+ CubeDataProductEnvironmentType,
31
+ CubeEngineError,
32
+ CubeEngineErrorKind,
33
+ type CubeAccessPointLocation,
34
+ type CubeDataProductCatalog,
35
+ type CubeDataProductProject,
36
+ type CubeEngine,
37
+ type NodeId,
38
+ } from '@finos/legend-cube-builder';
39
+ import { LakehouseAccessPoint, type DataProduct } from '@finos/legend-graph';
40
+ import type { EditorStore } from '../EditorStore.js';
41
+ import {
42
+ LEGEND_STUDIO_CUBE_WORKSPACE_VERSION,
43
+ type LegendStudioCubeWorkspace,
44
+ } from './LegendStudioCubeWorkspace.js';
45
+
46
+ // Legend Cube in Studio: a cube reads the data products of the project being
47
+ // edited, as the workspace holds them, never the platform's deployed ones.
48
+ // Their lakehouse access points are typed by Cube's engine on the workspace,
49
+ // as Studio types them for its own queries, and run on the project's
50
+ // lakehouse runtime.
51
+
52
+ /** The one class a Studio cube reads the project's data products as, so the tab offers no other */
53
+ const STUDIO_CUBE_DATA_PRODUCT_CLASS =
54
+ CubeDataProductEnvironmentType.PRODUCTION;
55
+
56
+ /** The deployment a workspace data product is picked from: none yet */
57
+ const NO_DEPLOYMENT_ID = '0';
58
+
59
+ export const LEGEND_STUDIO_CUBE_DATA_PRODUCT_MESSAGE = {
60
+ GONE: (dataProduct: string): string =>
61
+ `The project has no data product ${dataProduct} any more.`,
62
+ NOT_LAKEHOUSE: 'Cube reads lakehouse access points only',
63
+ NO_ACCESS_POINT: (accessPoint: string, dataProduct: string): string =>
64
+ `Data product ${dataProduct} has no lakehouse access point "${accessPoint}".`,
65
+ };
66
+
67
+ const keyOf = (group: string, accessPoint: string): string =>
68
+ `${group}\u0000${accessPoint}`;
69
+
70
+ /** The lambda that types an access point: `{| #P{dataProduct.accessPoint}#}` */
71
+ const typingLambdaOf = (dataProduct: string, accessPoint: string): IR =>
72
+ lambda([], [dataProductAccessor([dataProduct, accessPoint])]);
73
+
74
+ /**
75
+ * The project's own data products, for the Data Product tab and for
76
+ * re-checking saved sources. Their access points are typed by the engine, a
77
+ * product's in one call, on the workspace
78
+ */
79
+ export class LegendStudioCubeDataProductCatalog
80
+ implements CubeDataProductCatalog
81
+ {
82
+ readonly environmentTypes = [STUDIO_CUBE_DATA_PRODUCT_CLASS];
83
+ readonly editorStore: EditorStore;
84
+ readonly workspace: LegendStudioCubeWorkspace;
85
+ private readonly engine: CubeEngine;
86
+
87
+ constructor(
88
+ editorStore: EditorStore,
89
+ workspace: LegendStudioCubeWorkspace,
90
+ engine: CubeEngine,
91
+ ) {
92
+ this.editorStore = editorStore;
93
+ this.workspace = workspace;
94
+ this.engine = engine;
95
+ }
96
+
97
+ /** The project's own data products, not its dependencies' */
98
+ private get dataProducts(): DataProduct[] {
99
+ return this.editorStore.graphManagerState.graph.ownDataProducts;
100
+ }
101
+
102
+ /** The model a cube of the workspace's data products names */
103
+ private get model(): ModelContext {
104
+ return createCubeDataProductModel({
105
+ ...this.workspace.project,
106
+ versionId: LEGEND_STUDIO_CUBE_WORKSPACE_VERSION,
107
+ environmentType: STUDIO_CUBE_DATA_PRODUCT_CLASS,
108
+ });
109
+ }
110
+
111
+ private candidateOf(dataProduct: DataProduct): CubeDataProductCandidate {
112
+ return new CubeDataProductCandidate({
113
+ id: dataProduct.path,
114
+ deploymentId: NO_DEPLOYMENT_ID,
115
+ dataProductPath: dataProduct.path,
116
+ title: dataProduct.title ?? dataProduct.name,
117
+ description: dataProduct.description,
118
+ ...this.workspace.project,
119
+ versionId: LEGEND_STUDIO_CUBE_WORKSPACE_VERSION,
120
+ environmentType: STUDIO_CUBE_DATA_PRODUCT_CLASS,
121
+ });
122
+ }
123
+
124
+ private dataProductOf(path: string): DataProduct {
125
+ const dataProduct = this.dataProducts.find((each) => each.path === path);
126
+ if (!dataProduct) {
127
+ throw new CubeEngineError(
128
+ CubeEngineErrorKind.COMPILE,
129
+ LEGEND_STUDIO_CUBE_DATA_PRODUCT_MESSAGE.GONE(path),
130
+ );
131
+ }
132
+ return dataProduct;
133
+ }
134
+
135
+ /** Every data product of the project; the tab matches the text itself */
136
+ async search(): Promise<readonly CubeDataProductCandidate[]> {
137
+ return this.dataProducts.map((each) => this.candidateOf(each));
138
+ }
139
+
140
+ /** A product's access point groups, its lakehouse access points typed on the workspace */
141
+ async describe(
142
+ candidate: CubeDataProductCandidate,
143
+ ): Promise<CubeDataProductDescription> {
144
+ const dataProduct = this.dataProductOf(candidate.dataProductPath);
145
+ const lambdas = new Map<NodeId, IR>();
146
+ dataProduct.accessPointGroups.forEach((group) =>
147
+ group.accessPoints
148
+ .filter((point) => point instanceof LakehouseAccessPoint)
149
+ .forEach((point) =>
150
+ lambdas.set(
151
+ keyOf(group.id, point.id),
152
+ typingLambdaOf(dataProduct.path, point.id),
153
+ ),
154
+ ),
155
+ );
156
+ const schemas = lambdas.size
157
+ ? await this.engine.typeLambdas(this.model, lambdas)
158
+ : new Map<NodeId, Schema | CubeEngineError>();
159
+ return new CubeDataProductDescription(
160
+ candidate,
161
+ dataProduct.accessPointGroups
162
+ .filter((group) => group.accessPoints.length > 0)
163
+ .map(
164
+ (group) =>
165
+ new CubeAccessPointGroup({
166
+ id: group.id,
167
+ title: group.title,
168
+ accessPoints: group.accessPoints.map((point) => {
169
+ const typed = schemas.get(keyOf(group.id, point.id));
170
+ return new CubeAccessPoint({
171
+ id: point.id,
172
+ title: point.title,
173
+ description: point.description,
174
+ schema: typed instanceof CubeEngineError ? undefined : typed,
175
+ disabledReason:
176
+ point instanceof LakehouseAccessPoint
177
+ ? typed instanceof CubeEngineError
178
+ ? typed.firstLine
179
+ : undefined
180
+ : LEGEND_STUDIO_CUBE_DATA_PRODUCT_MESSAGE.NOT_LAKEHOUSE,
181
+ });
182
+ }),
183
+ }),
184
+ ),
185
+ );
186
+ }
187
+
188
+ /** The saved sources' schemas, typed on the workspace in one call */
189
+ async resolveSchemas(
190
+ project: CubeDataProductProject,
191
+ sources: ReadonlyMap<NodeId, CubeAccessPointLocation>,
192
+ ): Promise<Map<NodeId, Schema | CubeEngineError>> {
193
+ const resolved = new Map<NodeId, Schema | CubeEngineError>();
194
+ const lambdas = new Map<NodeId, IR>();
195
+ sources.forEach((source, nodeId) => {
196
+ const point = this.dataProducts
197
+ .find((each) => each.path === source.dataProduct)
198
+ ?.accessPointGroups.find(
199
+ (group) => group.id === source.accessPointGroup,
200
+ )
201
+ ?.accessPoints.find((each) => each.id === source.accessPoint);
202
+ if (point instanceof LakehouseAccessPoint) {
203
+ lambdas.set(
204
+ nodeId,
205
+ typingLambdaOf(source.dataProduct, source.accessPoint),
206
+ );
207
+ } else {
208
+ resolved.set(
209
+ nodeId,
210
+ new CubeEngineError(
211
+ CubeEngineErrorKind.COMPILE,
212
+ LEGEND_STUDIO_CUBE_DATA_PRODUCT_MESSAGE.NO_ACCESS_POINT(
213
+ source.accessPoint,
214
+ source.dataProduct,
215
+ ),
216
+ nodeId,
217
+ ),
218
+ );
219
+ }
220
+ });
221
+ if (lambdas.size) {
222
+ (
223
+ await this.engine.typeLambdas(
224
+ createCubeDataProductModel(project),
225
+ lambdas,
226
+ )
227
+ ).forEach((schema, nodeId) => resolved.set(nodeId, schema));
228
+ }
229
+ // in the order the sources were given
230
+ return new Map(
231
+ [...sources.keys()].map((nodeId) => [
232
+ nodeId,
233
+ resolved.get(nodeId) as Schema | CubeEngineError,
234
+ ]),
235
+ );
236
+ }
237
+
238
+ /** No marketplace: a workspace data product has no page there yet */
239
+ getMarketplaceLink(): string | undefined {
240
+ return undefined;
241
+ }
242
+ }
@@ -0,0 +1,82 @@
1
+ /**
2
+ * Copyright (c) 2026-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
+ buildCubeEngine,
19
+ LocalModelCatalog,
20
+ type CubeEngine,
21
+ type CubeHost,
22
+ } from '@finos/legend-cube-builder';
23
+ import type { LegendStudioApplicationStore } from '../../LegendStudioBaseStore.js';
24
+ import type { EditorStore } from '../EditorStore.js';
25
+ import { LegendStudioCubeDataProductCatalog } from './LegendStudioCubeDataProductCatalog.js';
26
+ import { LegendStudioCubeIngestCatalog } from './LegendStudioCubeIngestCatalog.js';
27
+ import { LegendStudioCubeWorkspace } from './LegendStudioCubeWorkspace.js';
28
+
29
+ /**
30
+ * Studio as the host of Legend Cube: Studio's engine and application store,
31
+ * and the project's own ingest definitions and data products as the only
32
+ * sources, typed and run on the workspace and the project's lakehouse
33
+ * runtime. No sample data, examples, databases or services. One per opening
34
+ * of the cube.
35
+ */
36
+ export class LegendStudioCubeHost implements CubeHost {
37
+ readonly applicationStore: LegendStudioApplicationStore;
38
+ readonly engine: CubeEngine;
39
+ readonly modelCatalog: LocalModelCatalog;
40
+ readonly project: LegendStudioCubeWorkspace;
41
+ readonly ingestCatalog: LegendStudioCubeIngestCatalog;
42
+ readonly dataProductCatalog: LegendStudioCubeDataProductCatalog;
43
+ readonly offersSampleData = false;
44
+
45
+ constructor(editorStore: EditorStore) {
46
+ const { applicationStore } = editorStore;
47
+ const { config } = applicationStore;
48
+ this.applicationStore = applicationStore;
49
+ this.project = new LegendStudioCubeWorkspace(editorStore);
50
+ this.ingestCatalog = new LegendStudioCubeIngestCatalog(
51
+ editorStore,
52
+ this.project,
53
+ );
54
+ // the engine Studio's own editor uses, configured the same way; a run
55
+ // reads the workspace, and names the project's lakehouse environment
56
+ this.engine = buildCubeEngine(
57
+ {
58
+ baseUrl:
59
+ editorStore.editorConfig?.engineServerUrl ?? config.engineServerUrl,
60
+ queryBaseUrl:
61
+ editorStore.editorConfig?.engineQueryServerUrl ??
62
+ config.engineQueryServerUrl,
63
+ queryClientName: config.engineQueryClientName,
64
+ enableCompression: true,
65
+ useCookieAuthOnly: config.engineUseCookieAuthOnly,
66
+ },
67
+ applicationStore.tracerService,
68
+ {
69
+ ingestCatalog: this.ingestCatalog,
70
+ ingestDefinitionSource: this.ingestCatalog,
71
+ lakehouseEnvironment: this.project,
72
+ dataProductModelSource: this.project,
73
+ },
74
+ );
75
+ this.dataProductCatalog = new LegendStudioCubeDataProductCatalog(
76
+ editorStore,
77
+ this.project,
78
+ this.engine,
79
+ );
80
+ this.modelCatalog = new LocalModelCatalog(this.engine);
81
+ }
82
+ }