@finos/legend-application-studio 28.18.131 → 28.18.133

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 (54) hide show
  1. package/lib/application/LegendIngestionConfiguration.d.ts +1 -0
  2. package/lib/application/LegendIngestionConfiguration.d.ts.map +1 -1
  3. package/lib/application/LegendIngestionConfiguration.js +3 -1
  4. package/lib/application/LegendIngestionConfiguration.js.map +1 -1
  5. package/lib/components/editor/Editor.d.ts.map +1 -1
  6. package/lib/components/editor/Editor.js +2 -1
  7. package/lib/components/editor/Editor.js.map +1 -1
  8. package/lib/components/editor/editor-group/dataProduct/DataPoductEditor.d.ts.map +1 -1
  9. package/lib/components/editor/editor-group/dataProduct/DataPoductEditor.js +52 -4
  10. package/lib/components/editor/editor-group/dataProduct/DataPoductEditor.js.map +1 -1
  11. package/lib/index.css +1 -1
  12. package/lib/package.json +1 -1
  13. package/lib/stores/editor/EditorStore.d.ts +2 -1
  14. package/lib/stores/editor/EditorStore.d.ts.map +1 -1
  15. package/lib/stores/editor/EditorStore.js +24 -11
  16. package/lib/stores/editor/EditorStore.js.map +1 -1
  17. package/lib/stores/editor/EditorTabManagerState.js +1 -1
  18. package/lib/stores/editor/EditorTabManagerState.js.map +1 -1
  19. package/lib/stores/editor/editor-state/element-editor-state/ElementEditorInitialConfiguration.d.ts +7 -0
  20. package/lib/stores/editor/editor-state/element-editor-state/ElementEditorInitialConfiguration.d.ts.map +1 -1
  21. package/lib/stores/editor/editor-state/element-editor-state/ElementEditorInitialConfiguration.js +17 -0
  22. package/lib/stores/editor/editor-state/element-editor-state/ElementEditorInitialConfiguration.js.map +1 -1
  23. package/lib/stores/editor/editor-state/element-editor-state/dataProduct/DataProductEditorState.d.ts +17 -3
  24. package/lib/stores/editor/editor-state/element-editor-state/dataProduct/DataProductEditorState.d.ts.map +1 -1
  25. package/lib/stores/editor/editor-state/element-editor-state/dataProduct/DataProductEditorState.js +73 -3
  26. package/lib/stores/editor/editor-state/element-editor-state/dataProduct/DataProductEditorState.js.map +1 -1
  27. package/lib/stores/ingestion/AdhocDataProductDeployResponse.d.ts +21 -0
  28. package/lib/stores/ingestion/AdhocDataProductDeployResponse.d.ts.map +1 -0
  29. package/lib/stores/ingestion/AdhocDataProductDeployResponse.js +24 -0
  30. package/lib/stores/ingestion/AdhocDataProductDeployResponse.js.map +1 -0
  31. package/lib/stores/ingestion/IngestDeploymentServerClient.d.ts +4 -0
  32. package/lib/stores/ingestion/IngestDeploymentServerClient.d.ts.map +1 -1
  33. package/lib/stores/ingestion/IngestDeploymentServerClient.js +5 -0
  34. package/lib/stores/ingestion/IngestDeploymentServerClient.js.map +1 -1
  35. package/lib/stores/ingestion/IngestionManager.d.ts +4 -1
  36. package/lib/stores/ingestion/IngestionManager.d.ts.map +1 -1
  37. package/lib/stores/ingestion/IngestionManager.js +25 -7
  38. package/lib/stores/ingestion/IngestionManager.js.map +1 -1
  39. package/lib/stores/lazy-text-editor/LazyTextEditorStore.d.ts.map +1 -1
  40. package/lib/stores/lazy-text-editor/LazyTextEditorStore.js +1 -1
  41. package/lib/stores/lazy-text-editor/LazyTextEditorStore.js.map +1 -1
  42. package/package.json +6 -6
  43. package/src/application/LegendIngestionConfiguration.ts +3 -1
  44. package/src/components/editor/Editor.tsx +2 -0
  45. package/src/components/editor/editor-group/dataProduct/DataPoductEditor.tsx +146 -0
  46. package/src/stores/editor/EditorStore.ts +38 -20
  47. package/src/stores/editor/EditorTabManagerState.ts +1 -1
  48. package/src/stores/editor/editor-state/element-editor-state/ElementEditorInitialConfiguration.ts +26 -0
  49. package/src/stores/editor/editor-state/element-editor-state/dataProduct/DataProductEditorState.ts +120 -2
  50. package/src/stores/ingestion/AdhocDataProductDeployResponse.ts +29 -0
  51. package/src/stores/ingestion/IngestDeploymentServerClient.ts +18 -0
  52. package/src/stores/ingestion/IngestionManager.ts +51 -11
  53. package/src/stores/lazy-text-editor/LazyTextEditorStore.ts +1 -0
  54. package/tsconfig.json +1 -0
@@ -20,7 +20,7 @@ import {
20
20
  usingModelSchema,
21
21
  } from '@finos/legend-shared';
22
22
  import type { AuthProviderProps } from 'react-oidc-context';
23
- import { createModelSchema, primitive, raw } from 'serializr';
23
+ import { createModelSchema, optional, primitive, raw } from 'serializr';
24
24
 
25
25
  export class IngestDeploymentOIDC {
26
26
  redirectPath!: string;
@@ -51,6 +51,7 @@ export class IngestDeploymentServerConfig {
51
51
  export class IngestionDeploymentConfiguration {
52
52
  oidcConfig!: IngestDeploymentOIDC;
53
53
  defaultServer!: IngestDeploymentServerConfig;
54
+ useDefaultServer: boolean | undefined;
54
55
 
55
56
  static readonly serialization = new SerializationFactory(
56
57
  createModelSchema(IngestionDeploymentConfiguration, {
@@ -58,6 +59,7 @@ export class IngestionDeploymentConfiguration {
58
59
  defaultServer: usingModelSchema(
59
60
  IngestDeploymentServerConfig.serialization.schema,
60
61
  ),
62
+ useDefaultServer: optional(primitive()),
61
63
  }),
62
64
  );
63
65
  }
@@ -161,6 +161,7 @@ export const Editor = withEditorStore(
161
161
  patchReleaseVersionId,
162
162
  workspaceId,
163
163
  workspaceType,
164
+ studioParams,
164
165
  ),
165
166
  ).catch(applicationStore.alertUnhandledError);
166
167
  }, [
@@ -170,6 +171,7 @@ export const Editor = withEditorStore(
170
171
  projectId,
171
172
  workspaceId,
172
173
  workspaceType,
174
+ studioParams,
173
175
  ]);
174
176
 
175
177
  useEffect(() => {
@@ -18,6 +18,7 @@ import { observer } from 'mobx-react-lite';
18
18
  import { useEditorStore } from '../../EditorStoreProvider.js';
19
19
  import {
20
20
  DataProductEditorState,
21
+ generateUrlToDeployOnOpen,
21
22
  LakehouseAccessPointState,
22
23
  } from '../../../../stores/editor/editor-state/element-editor-state/dataProduct/DataProductEditorState.js';
23
24
  import {
@@ -34,11 +35,21 @@ import {
34
35
  TimesIcon,
35
36
  PlusIcon,
36
37
  PanelHeaderActionItem,
38
+ RocketIcon,
39
+ Modal,
40
+ ModalHeader,
41
+ ModalTitle,
42
+ ModalBody,
43
+ ModalFooter,
44
+ ModalFooterButton,
37
45
  } from '@finos/legend-art';
38
46
  import { useRef, useState, useEffect } from 'react';
39
47
  import { filterByType } from '@finos/legend-shared';
40
48
  import { InlineLambdaEditor } from '@finos/legend-query-builder';
41
49
  import { flowResult } from 'mobx';
50
+ import { useAuth } from 'react-oidc-context';
51
+ import { CODE_EDITOR_LANGUAGE } from '@finos/legend-code-editor';
52
+ import { CodeEditor } from '@finos/legend-lego/code-editor';
42
53
 
43
54
  const NewAccessPointAccessPOint = observer(
44
55
  (props: { dataProductEditorState: DataProductEditorState }) => {
@@ -254,6 +265,83 @@ const DataProductEditorSplashScreen = observer(
254
265
  },
255
266
  );
256
267
 
268
+ const DataproductDeploymenetModal = observer(
269
+ (props: { state: DataProductEditorState }) => {
270
+ const { state } = props;
271
+ const applicationStore = state.editorStore.applicationStore;
272
+ return (
273
+ <Dialog
274
+ open={state.deploymentState.isInProgress}
275
+ classes={{ container: 'search-modal__container' }}
276
+ >
277
+ <Modal
278
+ darkMode={
279
+ !applicationStore.layoutService.TEMPORARY__isLightColorThemeEnabled
280
+ }
281
+ className="database-builder"
282
+ >
283
+ <ModalHeader>
284
+ <ModalTitle title="Deploy Data Product" />
285
+ </ModalHeader>
286
+ <ModalBody>
287
+ <div>{state.deploymentState.message}</div>
288
+ </ModalBody>
289
+ </Modal>
290
+ </Dialog>
291
+ );
292
+ },
293
+ );
294
+
295
+ const DataProductDeploymentResponseModal = observer(
296
+ (props: { state: DataProductEditorState }) => {
297
+ const { state } = props;
298
+ const applicationStore = state.editorStore.applicationStore;
299
+ const closeModal = (): void => state.setDeployResponse(undefined);
300
+ return (
301
+ <Dialog
302
+ open={Boolean(state.deployResponse)}
303
+ classes={{
304
+ root: 'editor-modal__root-container',
305
+ container: 'editor-modal__container',
306
+ paper: 'editor-modal__content',
307
+ }}
308
+ onClose={closeModal}
309
+ >
310
+ <Modal
311
+ darkMode={
312
+ !applicationStore.layoutService.TEMPORARY__isLightColorThemeEnabled
313
+ }
314
+ className="editor-modal"
315
+ >
316
+ <ModalHeader>
317
+ <ModalTitle title="Validation Error" />
318
+ </ModalHeader>
319
+ <ModalBody>
320
+ <PanelContent>
321
+ <CodeEditor
322
+ inputValue={JSON.stringify(
323
+ state.deployResponse?.content ?? {},
324
+ null,
325
+ 2,
326
+ )}
327
+ isReadOnly={true}
328
+ language={CODE_EDITOR_LANGUAGE.JSON}
329
+ />
330
+ </PanelContent>
331
+ </ModalBody>
332
+ <ModalFooter>
333
+ <ModalFooterButton
334
+ onClick={closeModal}
335
+ text="Close"
336
+ type="secondary"
337
+ />
338
+ </ModalFooter>
339
+ </Modal>
340
+ </Dialog>
341
+ );
342
+ },
343
+ );
344
+
257
345
  export const DataProductEditor = observer(() => {
258
346
  const editorStore = useEditorStore();
259
347
  const dataProductEditorState =
@@ -266,6 +354,30 @@ export const DataProductEditor = observer(() => {
266
354
  const openNewModal = () => {
267
355
  dataProductEditorState.setAccessPointModal(true);
268
356
  };
357
+ const auth = useAuth();
358
+ const deployDataProduct = (): void => {
359
+ // Trigger OAuth flow if not authenticated
360
+ if (!auth.isAuthenticated) {
361
+ // remove this redirect if we move to do oauth at the beginning of opening studio
362
+ auth
363
+ .signinRedirect({
364
+ state: generateUrlToDeployOnOpen(dataProductEditorState),
365
+ })
366
+ .catch(editorStore.applicationStore.alertUnhandledError);
367
+ return;
368
+ }
369
+ // Use the token for deployment
370
+ const token = auth.user?.access_token;
371
+ if (token) {
372
+ flowResult(dataProductEditorState.deploy(token)).catch(
373
+ editorStore.applicationStore.alertUnhandledError,
374
+ );
375
+ } else {
376
+ editorStore.applicationStore.notificationService.notifyError(
377
+ 'Authentication failed. No token available.',
378
+ );
379
+ }
380
+ };
269
381
 
270
382
  useEffect(() => {
271
383
  flowResult(dataProductEditorState.convertAccessPointsFuncObjects()).catch(
@@ -273,6 +385,18 @@ export const DataProductEditor = observer(() => {
273
385
  );
274
386
  }, [dataProductEditorState]);
275
387
 
388
+ useEffect(() => {
389
+ if (dataProductEditorState.deployOnOpen) {
390
+ flowResult(dataProductEditorState.deploy(auth.user?.access_token)).catch(
391
+ editorStore.applicationStore.alertUnhandledError,
392
+ );
393
+ }
394
+ }, [
395
+ auth,
396
+ editorStore.applicationStore.alertUnhandledError,
397
+ dataProductEditorState,
398
+ ]);
399
+
276
400
  return (
277
401
  <div className="data-product-editor">
278
402
  <div className="panel">
@@ -286,6 +410,20 @@ export const DataProductEditor = observer(() => {
286
410
  <div className="panel__header__title__label">data product</div>
287
411
  <div className="panel__header__title__content">{product.name}</div>
288
412
  </div>
413
+ <PanelHeaderActions>
414
+ <div className="btn__dropdown-combo btn__dropdown-combo--primary">
415
+ <button
416
+ className="btn__dropdown-combo__label"
417
+ onClick={deployDataProduct}
418
+ title={dataProductEditorState.deployValidationMessage}
419
+ tabIndex={-1}
420
+ disabled={!dataProductEditorState.deployValidationMessage}
421
+ >
422
+ <RocketIcon className="btn__dropdown-combo__label__icon" />
423
+ <div className="btn__dropdown-combo__label__title">Deploy</div>
424
+ </button>
425
+ </div>
426
+ </PanelHeaderActions>
289
427
  </div>
290
428
  <div className="panel">
291
429
  <PanelHeader>
@@ -324,6 +462,14 @@ export const DataProductEditor = observer(() => {
324
462
  dataProductEditorState={dataProductEditorState}
325
463
  />
326
464
  )}
465
+ {dataProductEditorState.deploymentState.isInProgress && (
466
+ <DataproductDeploymenetModal state={dataProductEditorState} />
467
+ )}
468
+ {dataProductEditorState.deployResponse && (
469
+ <DataProductDeploymentResponseModal
470
+ state={dataProductEditorState}
471
+ />
472
+ )}
327
473
  </div>
328
474
  </div>
329
475
  </div>
@@ -566,6 +566,34 @@ export class EditorStore implements CommandRegistrar {
566
566
  this.explorerTreeState = new ExplorerTreeState(this);
567
567
  }
568
568
 
569
+ deCodeStudioQueryParams(
570
+ studioParams: Partial<StudioQueryParams> | undefined,
571
+ ): void {
572
+ if (this.editorConfig) {
573
+ // If editor config is already set, we do not decode again
574
+ return;
575
+ }
576
+ if (!studioParams) {
577
+ // If studio params are not provided, we do not decode
578
+ return;
579
+ }
580
+ const editorConfig = studioParams[LEGEND_STUDIO_QUERY_PARAMS.EDITOR_CONFIG];
581
+ if (!editorConfig) {
582
+ return;
583
+ }
584
+ const _config = returnUndefOnError(() =>
585
+ EditorInitialConfiguration.serialization.fromJson(
586
+ JSON.parse(atob(editorConfig)),
587
+ ),
588
+ );
589
+ const config = guaranteeNonNullable(
590
+ _config,
591
+ `error reading and serializing config ${editorConfig}`,
592
+ );
593
+
594
+ this.editorConfig = config;
595
+ }
596
+
569
597
  internalizeEntityPath(
570
598
  params: Partial<WorkspaceEditorPathParams>,
571
599
  studioParams: Partial<StudioQueryParams> | undefined,
@@ -574,27 +602,14 @@ export class EditorStore implements CommandRegistrar {
574
602
  const workspaceType = params.groupWorkspaceId
575
603
  ? WorkspaceType.GROUP
576
604
  : WorkspaceType.USER;
577
- const editorConfig =
578
- studioParams?.[LEGEND_STUDIO_QUERY_PARAMS.EDITOR_CONFIG];
605
+
579
606
  const workspaceId = guaranteeNonNullable(
580
607
  params.groupWorkspaceId ?? params.workspaceId,
581
608
  `Workspace/group workspace ID is not provided`,
582
609
  );
583
610
  if (entityPath) {
584
611
  this.initialEntityPath = entityPath;
585
- if (editorConfig) {
586
- const _config = returnUndefOnError(() =>
587
- EditorInitialConfiguration.serialization.fromJson(
588
- JSON.parse(atob(editorConfig)),
589
- ),
590
- );
591
- const config = guaranteeNonNullable(
592
- _config,
593
- `error reading and serializing config ${editorConfig}`,
594
- );
595
-
596
- this.editorConfig = config;
597
- }
612
+ this.deCodeStudioQueryParams(studioParams);
598
613
  this.applicationStore.navigationService.navigator.updateCurrentLocation(
599
614
  generateEditorRoute(
600
615
  guaranteeNonNullable(projectId),
@@ -616,6 +631,7 @@ export class EditorStore implements CommandRegistrar {
616
631
  patchReleaseVersionId: string | undefined,
617
632
  workspaceId: string,
618
633
  workspaceType: WorkspaceType,
634
+ studioParams: Partial<StudioQueryParams> | undefined,
619
635
  ): GeneratorFn<void> {
620
636
  if (!this.initState.isInInitialState) {
621
637
  /**
@@ -647,7 +663,7 @@ export class EditorStore implements CommandRegistrar {
647
663
  return;
648
664
  }
649
665
  this.initState.inProgress();
650
-
666
+ this.deCodeStudioQueryParams(studioParams);
651
667
  // TODO: when we genericize the way to initialize an application page
652
668
  this.applicationStore.assistantService.setIsHidden(false);
653
669
 
@@ -792,8 +808,12 @@ export class EditorStore implements CommandRegistrar {
792
808
  env: this.applicationStore.config.env,
793
809
  tabSize: DEFAULT_TAB_SIZE,
794
810
  clientConfig: {
795
- baseUrl: this.applicationStore.config.engineServerUrl,
796
- queryBaseUrl: this.applicationStore.config.engineQueryServerUrl,
811
+ baseUrl:
812
+ this.editorConfig?.engineServerUrl ??
813
+ this.applicationStore.config.engineServerUrl,
814
+ queryBaseUrl:
815
+ this.editorConfig?.engineQueryServerUrl ??
816
+ this.applicationStore.config.engineQueryServerUrl,
797
817
  enableCompression: true,
798
818
  payloadDebugger,
799
819
  },
@@ -1068,8 +1088,6 @@ export class EditorStore implements CommandRegistrar {
1068
1088
  this.graphManagerState.graph.getElement(this.initialEntityPath),
1069
1089
  this.editorConfig,
1070
1090
  );
1071
- // we may not want to set as undefined if using it for other things
1072
- this.editorConfig = undefined;
1073
1091
  } catch {
1074
1092
  const elementPath = this.initialEntityPath;
1075
1093
  this.initialEntityPath = undefined;
@@ -182,7 +182,7 @@ export class EditorTabManagerState extends TabManagerState {
182
182
  } else if (element instanceof Service) {
183
183
  return new ServiceEditorState(this.editorStore, element);
184
184
  } else if (element instanceof DataProduct) {
185
- return new DataProductEditorState(this.editorStore, element);
185
+ return new DataProductEditorState(this.editorStore, element, config);
186
186
  } else if (element instanceof GenerationSpecification) {
187
187
  return new GenerationSpecificationEditorState(this.editorStore, element);
188
188
  } else if (element instanceof FileGenerationSpecification) {
@@ -48,6 +48,18 @@ export class IngestElementEditorInitialConfiguration extends ElementEditorInitia
48
48
  );
49
49
  }
50
50
 
51
+ export class DataProductElementEditorInitialConfiguration extends ElementEditorInitialConfiguration {
52
+ deployOnOpen?: boolean;
53
+ type = PACKAGEABLE_ELEMENT_TYPE._DATA_PRODUCT;
54
+
55
+ static readonly serialization = new SerializationFactory(
56
+ createModelSchema(DataProductElementEditorInitialConfiguration, {
57
+ _type: usingConstantValueSchema(PACKAGEABLE_ELEMENT_TYPE._DATA_PRODUCT),
58
+ deployOnOpen: optional(primitive()),
59
+ }),
60
+ );
61
+ }
62
+
51
63
  const serializeElementEditorInitialConfiguration = (
52
64
  protocol: ElementEditorInitialConfiguration,
53
65
  ): PlainObject<ElementEditorInitialConfiguration> => {
@@ -56,6 +68,11 @@ const serializeElementEditorInitialConfiguration = (
56
68
  IngestElementEditorInitialConfiguration.serialization.schema,
57
69
  protocol,
58
70
  );
71
+ } else if (protocol instanceof DataProductElementEditorInitialConfiguration) {
72
+ return serialize(
73
+ DataProductElementEditorInitialConfiguration.serialization.schema,
74
+ protocol,
75
+ );
59
76
  }
60
77
  throw new UnsupportedOperationError(
61
78
  `Can't serialize element config`,
@@ -72,6 +89,11 @@ const deseralizeElementEditorInitialConfiguration = (
72
89
  IngestElementEditorInitialConfiguration.serialization.schema,
73
90
  json,
74
91
  );
92
+ case PACKAGEABLE_ELEMENT_TYPE._DATA_PRODUCT:
93
+ return deserialize(
94
+ DataProductElementEditorInitialConfiguration.serialization.schema,
95
+ json,
96
+ );
75
97
  default: {
76
98
  throw new UnsupportedOperationError(
77
99
  `Can't deserialize element config`,
@@ -83,6 +105,8 @@ const deseralizeElementEditorInitialConfiguration = (
83
105
 
84
106
  export class EditorInitialConfiguration {
85
107
  elementEditorConfiguration?: ElementEditorInitialConfiguration;
108
+ engineServerUrl: string | undefined;
109
+ engineQueryServerUrl?: string | undefined;
86
110
 
87
111
  static readonly serialization = new SerializationFactory(
88
112
  createModelSchema(EditorInitialConfiguration, {
@@ -92,6 +116,8 @@ export class EditorInitialConfiguration {
92
116
  deseralizeElementEditorInitialConfiguration,
93
117
  ),
94
118
  ),
119
+ engineServerUrl: optional(primitive()),
120
+ engineQueryServerUrl: optional(primitive()),
95
121
  }),
96
122
  );
97
123
  }
@@ -18,6 +18,7 @@ import {
18
18
  DataProduct,
19
19
  LakehouseAccessPoint,
20
20
  type PackageableElement,
21
+ type IngestDefinition,
21
22
  type AccessPoint,
22
23
  stub_RawLambda,
23
24
  LakehouseTargetEnv,
@@ -40,6 +41,9 @@ import {
40
41
  LogEvent,
41
42
  deleteEntry,
42
43
  filterByType,
44
+ ActionState,
45
+ guaranteeNonNullable,
46
+ assertTrue,
43
47
  } from '@finos/legend-shared';
44
48
  import {
45
49
  dataProduct_addAccessPoint,
@@ -47,6 +51,13 @@ import {
47
51
  dataProduct_deleteAccessPoint,
48
52
  } from '../../../../graph-modifier/DSL_DataProduct_GraphModifierHelper.js';
49
53
  import { LambdaEditorState } from '@finos/legend-query-builder';
54
+ import type { IngestionManager } from '../../../../ingestion/IngestionManager.js';
55
+ import {
56
+ DataProductElementEditorInitialConfiguration,
57
+ EditorInitialConfiguration,
58
+ } from '../ElementEditorInitialConfiguration.js';
59
+ import { EXTERNAL_APPLICATION_NAVIGATION__generateUrlWithEditorConfig } from '../../../../../__lib__/LegendStudioNavigation.js';
60
+ import type { AdhocDataProductDeployResponse } from '../../../../ingestion/AdhocDataProductDeployResponse.js';
50
61
 
51
62
  export class AccessPointState {
52
63
  readonly state: AccessPointGroupState;
@@ -181,10 +192,10 @@ export class AccessPointGroupState {
181
192
 
182
193
  constructor(val: AccessPointGroup, editorState: DataProductEditorState) {
183
194
  this.value = val;
195
+ this.state = editorState;
184
196
  this.accessPointStates = val.accessPoints.map((e) =>
185
197
  this.buildAccessPointState(e),
186
198
  );
187
- this.state = editorState;
188
199
  makeObservable(this, {
189
200
  value: observable,
190
201
  accessPointStates: observable,
@@ -213,12 +224,41 @@ export class AccessPointGroupState {
213
224
  }
214
225
  }
215
226
 
227
+ const createEditorInitialConfiguration = (): EditorInitialConfiguration => {
228
+ const config = new EditorInitialConfiguration();
229
+ const ingest = new DataProductElementEditorInitialConfiguration();
230
+ ingest.deployOnOpen = true;
231
+ config.elementEditorConfiguration = ingest;
232
+ return config;
233
+ };
234
+
235
+ const editorInitialConfigToBase64 = (val: EditorInitialConfiguration): string =>
236
+ btoa(JSON.stringify(EditorInitialConfiguration.serialization.toJson(val)));
237
+
238
+ export const generateUrlToDeployOnOpen = (
239
+ val: DataProductEditorState,
240
+ ): string => {
241
+ return val.editorStore.applicationStore.navigationService.navigator.generateAddress(
242
+ EXTERNAL_APPLICATION_NAVIGATION__generateUrlWithEditorConfig(
243
+ val.editorStore.editorMode.generateElementLink(val.product.path),
244
+ editorInitialConfigToBase64(createEditorInitialConfiguration()),
245
+ ),
246
+ );
247
+ };
248
+
216
249
  export class DataProductEditorState extends ElementEditorState {
217
250
  accessPointModal = false;
251
+ deploymentState = ActionState.create();
218
252
  accessPointGroupStates: AccessPointGroupState[] = [];
219
253
  isConvertingTransformLambdaObjects = false;
254
+ deployOnOpen = false;
255
+ deployResponse: AdhocDataProductDeployResponse | undefined;
220
256
 
221
- constructor(editorStore: EditorStore, element: PackageableElement) {
257
+ constructor(
258
+ editorStore: EditorStore,
259
+ element: PackageableElement,
260
+ config?: EditorInitialConfiguration,
261
+ ) {
222
262
  super(editorStore, element);
223
263
 
224
264
  makeObservable(this, {
@@ -226,6 +266,11 @@ export class DataProductEditorState extends ElementEditorState {
226
266
  accessPointModal: observable,
227
267
  accessPointGroupStates: observable,
228
268
  isConvertingTransformLambdaObjects: observable,
269
+ deploy: flow,
270
+ deployOnOpen: observable,
271
+ deployResponse: observable,
272
+ setDeployOnOpen: action,
273
+ setDeployResponse: action,
229
274
  setAccessPointModal: action,
230
275
  addAccessPoint: action,
231
276
  convertAccessPointsFuncObjects: flow,
@@ -233,6 +278,20 @@ export class DataProductEditorState extends ElementEditorState {
233
278
  this.accessPointGroupStates = this.product.accessPointGroups.map(
234
279
  (e) => new AccessPointGroupState(e, this),
235
280
  );
281
+ const elementConfig = config?.elementEditorConfiguration;
282
+ if (elementConfig instanceof DataProductElementEditorInitialConfiguration) {
283
+ this.deployOnOpen = elementConfig.deployOnOpen ?? false;
284
+ }
285
+ }
286
+
287
+ setDeployOnOpen(value: boolean): void {
288
+ this.deployOnOpen = value;
289
+ }
290
+
291
+ setDeployResponse(
292
+ response: AdhocDataProductDeployResponse | undefined,
293
+ ): void {
294
+ this.deployResponse = response;
236
295
  }
237
296
 
238
297
  *convertAccessPointsFuncObjects(): GeneratorFn<void> {
@@ -303,6 +362,38 @@ export class DataProductEditorState extends ElementEditorState {
303
362
  return new AccessPointGroupState(group, this);
304
363
  }
305
364
 
365
+ *deploy(token: string | undefined): GeneratorFn<void> {
366
+ try {
367
+ assertTrue(
368
+ this.validForDeployment,
369
+ 'Data product definition is not valid for deployment',
370
+ );
371
+ this.deploymentState.inProgress();
372
+ // The grammar we provide will be for the current data product + all ingests (used for compilation)
373
+ const grammar =
374
+ (yield this.editorStore.graphManagerState.graphManager.elementsToPureCode(
375
+ [...this.editorStore.graphManagerState.graph.ingests, this.product],
376
+ )) as unknown as string;
377
+
378
+ const response = (yield guaranteeNonNullable(
379
+ this.ingestionManager,
380
+ ).deployDataProduct(
381
+ grammar,
382
+ guaranteeNonNullable(this.associatedIngest?.appDirDeployment),
383
+ this.deploymentState,
384
+ token,
385
+ )) as unknown as AdhocDataProductDeployResponse;
386
+ this.setDeployResponse(response);
387
+ } catch (error) {
388
+ assertErrorThrown(error);
389
+ this.editorStore.applicationStore.notificationService.notifyError(
390
+ `Ingest definition failed to deploy: ${error.message}`,
391
+ );
392
+ } finally {
393
+ this.deploymentState.complete();
394
+ }
395
+ }
396
+
306
397
  get product(): DataProduct {
307
398
  return guaranteeType(
308
399
  this.element,
@@ -311,10 +402,37 @@ export class DataProductEditorState extends ElementEditorState {
311
402
  );
312
403
  }
313
404
 
405
+ get validForDeployment(): boolean {
406
+ return Boolean(
407
+ this.associatedIngest?.appDirDeployment && this.ingestionManager,
408
+ );
409
+ }
410
+
314
411
  get accessPoints(): AccessPoint[] {
315
412
  return this.product.accessPointGroups.map((e) => e.accessPoints).flat();
316
413
  }
317
414
 
415
+ get ingestionManager(): IngestionManager | undefined {
416
+ return this.editorStore.ingestionManager;
417
+ }
418
+
419
+ get deployValidationMessage(): string {
420
+ if (!this.associatedIngest?.appDirDeployment) {
421
+ return 'No app dir deployment found';
422
+ } else if (!this.ingestionManager) {
423
+ return 'No ingestion manager found';
424
+ }
425
+ return 'Deploy';
426
+ }
427
+
428
+ // We need to get the associated Ingest to get the app dir deployment
429
+ // We could do a more in depth check on the access point lambdas to check which ingest it uses but for now
430
+ // we will assume all ingests have the same DID
431
+ // we get the last one, to prioritize the ones in the current project followed by dependency ones
432
+ get associatedIngest(): IngestDefinition | undefined {
433
+ return this.editorStore.graphManagerState.graph.ingests.slice(-1)[0];
434
+ }
435
+
318
436
  override reprocess(
319
437
  newElement: PackageableElement,
320
438
  editorStore: EditorStore,
@@ -0,0 +1,29 @@
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 type { PlainObject } from '@finos/legend-shared';
18
+
19
+ export class AdhocDataProductDeployResponse {
20
+ content!: PlainObject;
21
+ }
22
+
23
+ export const createAdhocDataProductDeployResponse = (
24
+ json: PlainObject<AdhocDataProductDeployResponse>,
25
+ ): AdhocDataProductDeployResponse => {
26
+ const response = new AdhocDataProductDeployResponse();
27
+ response.content = json;
28
+ return response;
29
+ };
@@ -26,9 +26,12 @@ import type {
26
26
  IngestDefinitionValidationResponse,
27
27
  } from './IngestionDeploymentResponse.js';
28
28
  import type { IngestDeploymentServerConfig } from '../../application/LegendIngestionConfiguration.js';
29
+ import type { AdhocDataProductDeployResponse } from './AdhocDataProductDeployResponse.js';
29
30
 
30
31
  export class IngestDeploymentServerClient extends AbstractServerClient {
31
32
  environmentClassification: string;
33
+
34
+ private DATA_PRODUCT_URL = 'data-product';
32
35
  constructor(config: IngestDeploymentServerConfig) {
33
36
  super({
34
37
  baseUrl: config.ingestServerUrl,
@@ -49,6 +52,9 @@ export class IngestDeploymentServerClient extends AbstractServerClient {
49
52
  Authorization: `Bearer ${token}`,
50
53
  });
51
54
 
55
+ private _dataProduct = (): string =>
56
+ `${this.baseUrl}/${this.DATA_PRODUCT_URL}/api/entitlements/sdlc/deploy/definitions`;
57
+
52
58
  private _ingest = (): string =>
53
59
  `${this.baseUrl}/api/ingest/sdlc/deploy/definitions`;
54
60
 
@@ -80,4 +86,16 @@ export class IngestDeploymentServerClient extends AbstractServerClient {
80
86
  this._tokenWithTextPlain(token),
81
87
  );
82
88
  }
89
+
90
+ deployDataProduct(
91
+ fullGrammar: string,
92
+ token: string | undefined,
93
+ ): Promise<PlainObject<AdhocDataProductDeployResponse>> {
94
+ return this.post(
95
+ `${this._dataProduct()}`,
96
+ fullGrammar,
97
+ undefined,
98
+ this._tokenWithTextPlain(token),
99
+ );
100
+ }
83
101
  }