@finos/legend-application-studio 28.19.116 → 28.20.0

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 (51) hide show
  1. package/lib/__lib__/LegendStudioEvent.d.ts +1 -0
  2. package/lib/__lib__/LegendStudioEvent.d.ts.map +1 -1
  3. package/lib/__lib__/LegendStudioEvent.js +1 -0
  4. package/lib/__lib__/LegendStudioEvent.js.map +1 -1
  5. package/lib/components/editor/editor-group/accessor/AccessorQueryBuilder.d.ts +22 -0
  6. package/lib/components/editor/editor-group/accessor/AccessorQueryBuilder.d.ts.map +1 -0
  7. package/lib/components/editor/editor-group/accessor/AccessorQueryBuilder.js +43 -0
  8. package/lib/components/editor/editor-group/accessor/AccessorQueryBuilder.js.map +1 -0
  9. package/lib/components/editor/editor-group/accessor/AccessorQueryBuilderHelper.d.ts.map +1 -1
  10. package/lib/components/editor/editor-group/accessor/AccessorQueryBuilderHelper.js +8 -16
  11. package/lib/components/editor/editor-group/accessor/AccessorQueryBuilderHelper.js.map +1 -1
  12. package/lib/components/editor/editor-group/dataProduct/DataProductEditor.d.ts.map +1 -1
  13. package/lib/components/editor/editor-group/dataProduct/DataProductEditor.js +111 -37
  14. package/lib/components/editor/editor-group/dataProduct/DataProductEditor.js.map +1 -1
  15. package/lib/components/editor/editor-group/dataProduct/DataProductQueryBuilderHelper.d.ts.map +1 -1
  16. package/lib/components/editor/editor-group/dataProduct/DataProductQueryBuilderHelper.js +2 -2
  17. package/lib/components/editor/editor-group/dataProduct/DataProductQueryBuilderHelper.js.map +1 -1
  18. package/lib/components/editor/editor-group/service-editor/ServiceRegistrationEditor.d.ts.map +1 -1
  19. package/lib/components/editor/editor-group/service-editor/ServiceRegistrationEditor.js +12 -3
  20. package/lib/components/editor/editor-group/service-editor/ServiceRegistrationEditor.js.map +1 -1
  21. package/lib/components/editor/editor-group/uml-editor/ClassQueryBuilder.d.ts +3 -1
  22. package/lib/components/editor/editor-group/uml-editor/ClassQueryBuilder.d.ts.map +1 -1
  23. package/lib/components/editor/editor-group/uml-editor/ClassQueryBuilder.js +4 -5
  24. package/lib/components/editor/editor-group/uml-editor/ClassQueryBuilder.js.map +1 -1
  25. package/lib/index.css +2 -2
  26. package/lib/index.css.map +1 -1
  27. package/lib/package.json +1 -1
  28. package/lib/stores/editor/editor-state/element-editor-state/dataProduct/DataProductEditorState.d.ts +2 -1
  29. package/lib/stores/editor/editor-state/element-editor-state/dataProduct/DataProductEditorState.d.ts.map +1 -1
  30. package/lib/stores/editor/editor-state/element-editor-state/dataProduct/DataProductEditorState.js +12 -4
  31. package/lib/stores/editor/editor-state/element-editor-state/dataProduct/DataProductEditorState.js.map +1 -1
  32. package/lib/stores/editor/editor-state/element-editor-state/service/ServiceRegistrationState.d.ts +4 -0
  33. package/lib/stores/editor/editor-state/element-editor-state/service/ServiceRegistrationState.d.ts.map +1 -1
  34. package/lib/stores/editor/editor-state/element-editor-state/service/ServiceRegistrationState.js +29 -0
  35. package/lib/stores/editor/editor-state/element-editor-state/service/ServiceRegistrationState.js.map +1 -1
  36. package/lib/stores/graph-modifier/DSL_DataProduct_GraphModifierHelper.d.ts +5 -1
  37. package/lib/stores/graph-modifier/DSL_DataProduct_GraphModifierHelper.d.ts.map +1 -1
  38. package/lib/stores/graph-modifier/DSL_DataProduct_GraphModifierHelper.js +12 -0
  39. package/lib/stores/graph-modifier/DSL_DataProduct_GraphModifierHelper.js.map +1 -1
  40. package/package.json +9 -9
  41. package/src/__lib__/LegendStudioEvent.ts +1 -0
  42. package/src/components/editor/editor-group/accessor/AccessorQueryBuilder.tsx +81 -0
  43. package/src/components/editor/editor-group/accessor/{AccessorQueryBuilderHelper.ts → AccessorQueryBuilderHelper.tsx} +14 -1
  44. package/src/components/editor/editor-group/dataProduct/DataProductEditor.tsx +225 -34
  45. package/src/components/editor/editor-group/dataProduct/DataProductQueryBuilderHelper.ts +4 -3
  46. package/src/components/editor/editor-group/service-editor/ServiceRegistrationEditor.tsx +67 -1
  47. package/src/components/editor/editor-group/uml-editor/ClassQueryBuilder.tsx +16 -6
  48. package/src/stores/editor/editor-state/element-editor-state/dataProduct/DataProductEditorState.ts +14 -5
  49. package/src/stores/editor/editor-state/element-editor-state/service/ServiceRegistrationState.ts +39 -0
  50. package/src/stores/graph-modifier/DSL_DataProduct_GraphModifierHelper.ts +27 -0
  51. package/tsconfig.json +2 -1
@@ -91,11 +91,22 @@ import {
91
91
  useState,
92
92
  } from 'react';
93
93
  import {
94
+ assertErrorThrown,
94
95
  filterByType,
96
+ guaranteeNonNullable,
95
97
  guaranteeType,
98
+ isNonNullable,
96
99
  UserSearchService,
97
100
  } from '@finos/legend-shared';
98
- import { InlineLambdaEditor, LineageViewer } from '@finos/legend-query-builder';
101
+ import {
102
+ AccessorQueryBuilderState,
103
+ getCompatibleRuntimesFromAccessorOwner,
104
+ InlineLambdaEditor,
105
+ LineageViewer,
106
+ QueryBuilderActionConfig,
107
+ QueryBuilderAdvancedWorkflowState,
108
+ type QueryBuilderState,
109
+ } from '@finos/legend-query-builder';
99
110
  import { action, autorun, flowResult } from 'mobx';
100
111
  import { useAuth } from 'react-oidc-context';
101
112
  import { CODE_EDITOR_LANGUAGE } from '@finos/legend-code-editor';
@@ -126,6 +137,11 @@ import {
126
137
  observer_DataProductLink,
127
138
  DataProduct_Region,
128
139
  DataProduct_DeliveryFrequency,
140
+ AppDirOwner,
141
+ AppDirNode,
142
+ observe_AppDirOwner,
143
+ observe_AppDirNode,
144
+ AppDirLevel,
129
145
  } from '@finos/legend-graph';
130
146
  import {
131
147
  accessPoint_setClassification,
@@ -159,6 +175,10 @@ import {
159
175
  dataProductDiagram_setTitle,
160
176
  dataProductDiagram_setDescription,
161
177
  operationalMetadata_setUpdateFrequency,
178
+ dataProduct_setOwner,
179
+ appDirOwner_setProduction,
180
+ appDirOwner_setProdParallel,
181
+ appDirNode_setAppDirId,
162
182
  } from '../../../../stores/graph-modifier/DSL_DataProduct_GraphModifierHelper.js';
163
183
  import { LEGEND_STUDIO_TEST_ID } from '../../../../__lib__/LegendStudioTesting.js';
164
184
  import { LEGEND_STUDIO_APPLICATION_NAVIGATION_CONTEXT_KEY } from '../../../../__lib__/LegendStudioApplicationNavigationContext.js';
@@ -802,6 +822,90 @@ export const LakehouseDataProductAccessPointEditor = observer(
802
822
  const generateLineage = editorStore.applicationStore.guardUnhandledError(
803
823
  () => flowResult(accessPointState.generateLineage()),
804
824
  );
825
+
826
+ const editLambdaInQueryBuilder =
827
+ editorStore.applicationStore.guardUnhandledError(async () => {
828
+ const embeddedQueryBuilderState = editorStore.embeddedQueryBuilderState;
829
+ const applicationStore = editorStore.applicationStore;
830
+ const ingestDefinition =
831
+ editorStore.graphManagerState.graph.ownIngests.find(isNonNullable);
832
+ if (!ingestDefinition) {
833
+ applicationStore.notificationService.notifyError(
834
+ 'No ingest definition found in this project',
835
+ );
836
+ return;
837
+ }
838
+ await flowResult(
839
+ embeddedQueryBuilderState.setEmbeddedQueryBuilderConfiguration({
840
+ setupQueryBuilderState: async (): Promise<QueryBuilderState> => {
841
+ const queryBuilderState = new AccessorQueryBuilderState(
842
+ applicationStore,
843
+ undefined,
844
+ editorStore.graphManagerState,
845
+ QueryBuilderAdvancedWorkflowState.INSTANCE,
846
+ QueryBuilderActionConfig.INSTANCE,
847
+ applicationStore.config.options.queryBuilderConfig,
848
+ editorStore.editorMode.getSourceInfo(),
849
+ );
850
+ queryBuilderState.changeAccessorOwner(ingestDefinition);
851
+ const compatibleRuntimes = getCompatibleRuntimesFromAccessorOwner(
852
+ ingestDefinition,
853
+ editorStore.graphManagerState,
854
+ );
855
+ if (compatibleRuntimes.length > 0) {
856
+ queryBuilderState.changeSelectedRuntime(
857
+ guaranteeNonNullable(compatibleRuntimes[0]),
858
+ );
859
+ }
860
+ queryBuilderState.initializeWithQuery(accessPoint.func);
861
+ return queryBuilderState;
862
+ },
863
+ actionConfigs: [
864
+ {
865
+ key: 'save-query-btn',
866
+ renderer: (
867
+ queryBuilderState: QueryBuilderState,
868
+ ): React.ReactNode => {
869
+ const save = applicationStore.guardUnhandledError(
870
+ async () => {
871
+ try {
872
+ const rawLambda =
873
+ queryBuilderState.buildQueryForPersistence();
874
+ accessPoint.func = rawLambda;
875
+ embeddedQueryBuilderState.setEmbeddedQueryBuilderConfiguration(
876
+ undefined,
877
+ );
878
+ await flowResult(
879
+ lambdaEditorState.convertLambdaObjectToGrammarString({
880
+ pretty: true,
881
+ }),
882
+ );
883
+ } catch (error) {
884
+ assertErrorThrown(error);
885
+ applicationStore.notificationService.notifyError(
886
+ `Can't save access point lambda: ${error.message}`,
887
+ );
888
+ }
889
+ },
890
+ );
891
+ return (
892
+ <button
893
+ className="query-builder__dialog__header__custom-action"
894
+ tabIndex={-1}
895
+ disabled={props.isReadOnly}
896
+ onClick={save}
897
+ >
898
+ Save Lambda
899
+ </button>
900
+ );
901
+ },
902
+ },
903
+ ],
904
+ disableCompile: true,
905
+ }),
906
+ );
907
+ });
908
+
805
909
  return (
806
910
  <PanelDnDEntry
807
911
  ref={ref}
@@ -866,17 +970,7 @@ export const LakehouseDataProductAccessPointEditor = observer(
866
970
  disabled={props.isReadOnly}
867
971
  title="Edit sample values"
868
972
  style={{
869
- border: '1px solid var(--color-blue-200)',
870
- borderRadius: '4px',
871
- padding: '0.5rem 0.75rem',
872
- background: 'var(--color-blue-200)',
873
973
  cursor: props.isReadOnly ? 'not-allowed' : 'pointer',
874
- display: 'flex',
875
- alignItems: 'center',
876
- gap: '0.5rem',
877
- color: 'white',
878
- fontSize: '1.2rem',
879
- whiteSpace: 'nowrap',
880
974
  }}
881
975
  >
882
976
  <PencilEditIcon />
@@ -894,16 +988,7 @@ export const LakehouseDataProductAccessPointEditor = observer(
894
988
  border: accessPointState.hasRelationElementMismatch
895
989
  ? '2px solid var(--color-red-300)'
896
990
  : '1px solid var(--color-blue-200)',
897
- borderRadius: '4px',
898
- padding: '0.5rem 0.75rem',
899
- background: 'var(--color-blue-200)',
900
991
  cursor: props.isReadOnly ? 'not-allowed' : 'pointer',
901
- display: 'flex',
902
- alignItems: 'center',
903
- gap: '0.5rem',
904
- color: 'white',
905
- fontSize: '1.2rem',
906
- whiteSpace: 'nowrap',
907
992
  }}
908
993
  >
909
994
  <PencilEditIcon />
@@ -918,17 +1003,7 @@ export const LakehouseDataProductAccessPointEditor = observer(
918
1003
  disabled={props.isReadOnly}
919
1004
  title="Add sample values"
920
1005
  style={{
921
- border: '1px solid var(--color-blue-200)',
922
- borderRadius: '4px',
923
- padding: '0.5rem 0.75rem',
924
- background: 'var(--color-blue-200)',
925
1006
  cursor: props.isReadOnly ? 'not-allowed' : 'pointer',
926
- display: 'flex',
927
- alignItems: 'center',
928
- gap: '0.5rem',
929
- color: 'white',
930
- fontSize: '1.2rem',
931
- whiteSpace: 'nowrap',
932
1007
  }}
933
1008
  >
934
1009
  <PlusIcon />
@@ -1138,6 +1213,15 @@ export const LakehouseDataProductAccessPointEditor = observer(
1138
1213
  <div className="access-point-editor__generic-entry">
1139
1214
  <div className="access-point-editor__entry__container">
1140
1215
  <div className="access-point-editor__entry">
1216
+ <button
1217
+ className="access-point-editor__query-builder-btn"
1218
+ onClick={editLambdaInQueryBuilder}
1219
+ disabled={props.isReadOnly}
1220
+ title="Edit lambda with query builder"
1221
+ >
1222
+ <PencilEditIcon />
1223
+ <span>Query Builder</span>
1224
+ </button>
1141
1225
  <InlineLambdaEditor
1142
1226
  className={'access-point-editor__lambda-editor'}
1143
1227
  disabled={
@@ -2370,6 +2454,112 @@ const DataProductIconEditor = observer(
2370
2454
  },
2371
2455
  );
2372
2456
 
2457
+ const DataProductOwnershipEditor = observer(
2458
+ (props: { product: DataProduct; isReadOnly: boolean }) => {
2459
+ const { product, isReadOnly } = props;
2460
+ const owner =
2461
+ product.owner instanceof AppDirOwner ? product.owner : undefined;
2462
+
2463
+ const ensureOwner = (): AppDirOwner => {
2464
+ if (owner) {
2465
+ return owner;
2466
+ }
2467
+ const newOwner = observe_AppDirOwner(new AppDirOwner());
2468
+ dataProduct_setOwner(product, newOwner);
2469
+ return newOwner;
2470
+ };
2471
+
2472
+ const handleProductionChange: ChangeEventHandler<HTMLInputElement> = (
2473
+ event,
2474
+ ) => {
2475
+ const currentOwner = ensureOwner();
2476
+ const val = event.target.value;
2477
+ if (val === '') {
2478
+ appDirOwner_setProduction(currentOwner, undefined);
2479
+ if (!currentOwner.prodParallel) {
2480
+ dataProduct_setOwner(product, undefined);
2481
+ }
2482
+ return;
2483
+ }
2484
+ const num = Number(val);
2485
+ if (Number.isNaN(num)) {
2486
+ return;
2487
+ }
2488
+ if (currentOwner.production) {
2489
+ appDirNode_setAppDirId(currentOwner.production, num);
2490
+ return;
2491
+ }
2492
+ const node = observe_AppDirNode(new AppDirNode());
2493
+ node.appDirId = num;
2494
+ node.level = AppDirLevel.DEPLOYMENT;
2495
+ appDirOwner_setProduction(currentOwner, node);
2496
+ };
2497
+
2498
+ const handleProdParallelChange: ChangeEventHandler<HTMLInputElement> = (
2499
+ event,
2500
+ ) => {
2501
+ const currentOwner = ensureOwner();
2502
+ const val = event.target.value;
2503
+ if (val === '') {
2504
+ appDirOwner_setProdParallel(currentOwner, undefined);
2505
+ if (!currentOwner.production) {
2506
+ dataProduct_setOwner(product, undefined);
2507
+ }
2508
+ return;
2509
+ }
2510
+ const num = Number(val);
2511
+ if (Number.isNaN(num)) {
2512
+ return;
2513
+ }
2514
+ if (currentOwner.prodParallel) {
2515
+ appDirNode_setAppDirId(currentOwner.prodParallel, num);
2516
+ return;
2517
+ }
2518
+ const node = observe_AppDirNode(new AppDirNode());
2519
+ node.appDirId = num;
2520
+ node.level = AppDirLevel.DEPLOYMENT;
2521
+ appDirOwner_setProdParallel(currentOwner, node);
2522
+ };
2523
+
2524
+ return (
2525
+ <>
2526
+ <div className="panel__content__form__section">
2527
+ <div className="panel__content__form__section__header__label">
2528
+ Ownership
2529
+ </div>
2530
+ <div className="panel__content__form__section__header__prompt">
2531
+ Set the AppDir ownership for this Data Product.
2532
+ </div>
2533
+ <div className="panel__content__form__section__header__prompt">
2534
+ Production AppDir ID
2535
+ </div>
2536
+ <input
2537
+ className="input input-group__input panel__content__form__section__input input--dark input--small"
2538
+ type="number"
2539
+ disabled={isReadOnly}
2540
+ value={owner?.production?.appDirId ?? ''}
2541
+ onChange={handleProductionChange}
2542
+ placeholder="Enter production AppDir ID"
2543
+ />
2544
+ </div>
2545
+ <div className="panel__content__form__section">
2546
+ <div className="panel__content__form__section__header__prompt">
2547
+ Prod Parallel AppDir ID
2548
+ </div>
2549
+ <input
2550
+ className="input input-group__input panel__content__form__section__input input--dark input--small"
2551
+ type="number"
2552
+ disabled={isReadOnly}
2553
+ value={owner?.prodParallel?.appDirId ?? ''}
2554
+ onChange={handleProdParallelChange}
2555
+ placeholder="Enter prod parallel AppDir ID"
2556
+ />
2557
+ </div>
2558
+ </>
2559
+ );
2560
+ },
2561
+ );
2562
+
2373
2563
  const HomeTab = observer(
2374
2564
  (props: {
2375
2565
  dataProductEditorState: DataProductEditorState;
@@ -2543,6 +2733,10 @@ const HomeTab = observer(
2543
2733
  )}
2544
2734
  </div>
2545
2735
  <DataProductIconEditor product={product} isReadOnly={isReadOnly} />
2736
+ <DataProductOwnershipEditor
2737
+ product={product}
2738
+ isReadOnly={isReadOnly}
2739
+ />
2546
2740
  </div>
2547
2741
  </div>
2548
2742
  );
@@ -3341,10 +3535,7 @@ export const DataProductEditor = observer(() => {
3341
3535
  </PanelHeaderActions>
3342
3536
  </div>
3343
3537
 
3344
- <div
3345
- className="panel"
3346
- style={{ padding: '1rem', flexDirection: 'row' }}
3347
- >
3538
+ <div className="panel data-product-editor__content-panel">
3348
3539
  <DataProductSidebar dataProductEditorState={dataProductEditorState} />
3349
3540
  <ResizablePanelGroup orientation="vertical">
3350
3541
  <ResizablePanel>{renderActivivtyBarTab()}</ResizablePanel>
@@ -27,7 +27,7 @@ import {
27
27
  DataProductQueryBuilderState,
28
28
  QueryBuilderActionConfig,
29
29
  QueryBuilderAdvancedWorkflowState,
30
- buildDataProductAccessor,
30
+ resolveDataProductAccessor,
31
31
  } from '@finos/legend-query-builder';
32
32
  import type { DepotEntityWithOrigin } from '@finos/legend-storage';
33
33
  import {
@@ -91,11 +91,12 @@ export const queryDataProduct = async (
91
91
  defaultExecutionContext.func,
92
92
  editorStore.graphManagerState.graph,
93
93
  );
94
- accessor = buildDataProductAccessor(
95
- relationMetadata,
94
+ accessor = resolveDataProductAccessor(
96
95
  dataProduct,
97
96
  defaultExecutionContext,
98
97
  editorStore.graphManagerState.graph,
98
+ undefined,
99
+ relationMetadata,
99
100
  );
100
101
  }
101
102
  await flowResult(
@@ -15,6 +15,7 @@
15
15
  */
16
16
 
17
17
  import { observer } from 'mobx-react-lite';
18
+ import { useEffect } from 'react';
18
19
  import { ServiceEditorState } from '../../../../stores/editor/editor-state/element-editor-state/service/ServiceEditorState.js';
19
20
  import {
20
21
  clsx,
@@ -22,6 +23,8 @@ import {
22
23
  CustomSelectorInput,
23
24
  CheckSquareIcon,
24
25
  SquareIcon,
26
+ ExternalLinkSquareIcon,
27
+ CircleNotchIcon,
25
28
  } from '@finos/legend-art';
26
29
  import { prettyCONSTName } from '@finos/legend-shared';
27
30
  import { LEGEND_STUDIO_TEST_ID } from '../../../../__lib__/LegendStudioTesting.js';
@@ -30,7 +33,10 @@ import { flowResult } from 'mobx';
30
33
  import { useEditorStore } from '../../EditorStoreProvider.js';
31
34
  import { useApplicationStore } from '@finos/legend-application';
32
35
  import { MASTER_SNAPSHOT_ALIAS } from '@finos/legend-server-depot';
33
- import { LATEST_PROJECT_REVISION } from '../../../../stores/editor/editor-state/element-editor-state/service/ServiceRegistrationState.js';
36
+ import {
37
+ LATEST_PROJECT_REVISION,
38
+ generateServiceManagementUrl,
39
+ } from '../../../../stores/editor/editor-state/element-editor-state/service/ServiceRegistrationState.js';
34
40
 
35
41
  export const ServiceRegistrationEditor = observer(() => {
36
42
  const editorStore = useEditorStore();
@@ -136,6 +142,12 @@ export const ServiceRegistrationEditor = observer(() => {
136
142
  !selectedServiceType ||
137
143
  registrationState.registrationState.isInProgress;
138
144
 
145
+ useEffect(() => {
146
+ flowResult(registrationState.checkServiceRegistration()).catch(
147
+ applicationStore.alertUnhandledError,
148
+ );
149
+ }, [registrationState, applicationStore]);
150
+
139
151
  return (
140
152
  <div
141
153
  data-testid={LEGEND_STUDIO_TEST_ID.SERVICE_REGISTRATION_EDITOR}
@@ -169,6 +181,60 @@ export const ServiceRegistrationEditor = observer(() => {
169
181
  {`${registrationState.registrationState.message}...`}
170
182
  </div>
171
183
  )}
184
+ <div className="panel__content__form__section">
185
+ <div className="panel__content__form__section__header__label">
186
+ Deployment Status
187
+ </div>
188
+ <div className="panel__content__form__section__header__prompt">
189
+ Environments where this service is currently deployed
190
+ </div>
191
+ <div className="service-registration-editor__deployment-status">
192
+ {registrationState.deploymentCheckState.isInProgress && (
193
+ <div className="service-registration-editor__deployment-status__loading">
194
+ <CircleNotchIcon className="service-registration-editor__deployment-status__loading-icon" />
195
+ <span>Fetching deployment status...</span>
196
+ </div>
197
+ )}
198
+ {registrationState.deploymentCheckState.hasCompleted &&
199
+ registrationState.registeredEnvs.length === 0 && (
200
+ <div className="service-registration-editor__deployment-status__empty">
201
+ Service is not deployed to any environment
202
+ </div>
203
+ )}
204
+ {registrationState.deploymentCheckState.hasCompleted &&
205
+ registrationState.registeredEnvs.length > 0 && (
206
+ <div className="service-registration-editor__deployment-status__envs">
207
+ <span className="service-registration-editor__deployment-status__label">
208
+ Service is deployed to
209
+ </span>
210
+ {registrationState.registeredEnvs.map((env) => {
211
+ const envConfig =
212
+ registrationState.registrationOptions.find(
213
+ (e) => e.env === env,
214
+ );
215
+ return envConfig ? (
216
+ <a
217
+ key={env}
218
+ className="service-editor__deployment-link"
219
+ href={generateServiceManagementUrl(
220
+ envConfig.managementUrl,
221
+ serviceState.service.pattern,
222
+ )}
223
+ target="_blank"
224
+ rel="noopener noreferrer"
225
+ title={`Open in ${env}`}
226
+ >
227
+ <span className="service-editor__deployment-link__env">
228
+ {env.toUpperCase()}
229
+ </span>
230
+ <ExternalLinkSquareIcon />
231
+ </a>
232
+ ) : null;
233
+ })}
234
+ </div>
235
+ )}
236
+ </div>
237
+ </div>
172
238
  <div className="panel__content__form__section">
173
239
  <div className="panel__content__form__section__header__label">
174
240
  Activate Service
@@ -183,7 +183,9 @@ enum PROMOTE_QUERY_TYPE {
183
183
 
184
184
  export const NewFunctionModal = observer(
185
185
  (props: {
186
- _class: Class | undefined;
186
+ _class?: Class | undefined;
187
+ defaultFunctionName?: string | undefined;
188
+ defaultPackagePath?: string | undefined;
187
189
  close: () => void;
188
190
  showModal: boolean;
189
191
  promoteToFunction: (
@@ -192,18 +194,26 @@ export const NewFunctionModal = observer(
192
194
  ) => Promise<void>;
193
195
  isReadOnly?: boolean;
194
196
  }) => {
195
- const { isReadOnly, close, _class, showModal, promoteToFunction } = props;
197
+ const {
198
+ isReadOnly,
199
+ close,
200
+ _class,
201
+ defaultFunctionName,
202
+ defaultPackagePath,
203
+ showModal,
204
+ promoteToFunction,
205
+ } = props;
196
206
  const editorStore = useEditorStore();
197
207
  const applicationStore = editorStore.applicationStore;
198
208
  const nameRef = useRef<HTMLInputElement>(null);
199
- const defaultFunctionname = _class
200
- ? `${_class.name}_QueryFunction`
201
- : `QueryFunction`;
209
+ const defaultFunctionname =
210
+ defaultFunctionName ??
211
+ (_class ? `${_class.name}_QueryFunction` : `QueryFunction`);
202
212
  const [functionPath, setFunctionPath] =
203
213
  useState<string>(defaultFunctionname);
204
214
  const [packagePath, funcName] = resolvePackagePathAndElementName(
205
215
  functionPath,
206
- _class?.package?.path ?? 'model::functions',
216
+ defaultPackagePath ?? _class?.package?.path ?? 'model::functions',
207
217
  );
208
218
  const [isValid, setIsValid] = useState(true);
209
219
 
@@ -63,6 +63,8 @@ import {
63
63
  DataProductDiagram,
64
64
  observe_DataProductDiagram,
65
65
  stub_Mapping,
66
+ AppDirOwner,
67
+ type AppDirNode,
66
68
  } from '@finos/legend-graph';
67
69
  import type { EditorStore } from '../../../EditorStore.js';
68
70
  import { ElementEditorState } from '../ElementEditorState.js';
@@ -1192,7 +1194,7 @@ export class DataProductEditorState extends ElementEditorState {
1192
1194
  this.ingestionManager,
1193
1195
  ).deployDataProduct(
1194
1196
  grammar,
1195
- guaranteeNonNullable(this.associatedIngest?.appDirDeployment),
1197
+ guaranteeNonNullable(this.appDirDeployment),
1196
1198
  (val: string) =>
1197
1199
  this.editorStore.applicationStore.alertService.setBlockingAlert({
1198
1200
  message: val,
@@ -1261,9 +1263,7 @@ export class DataProductEditorState extends ElementEditorState {
1261
1263
  }
1262
1264
 
1263
1265
  get validForDeployment(): boolean {
1264
- return Boolean(
1265
- this.associatedIngest?.appDirDeployment && this.ingestionManager,
1266
- );
1266
+ return Boolean(this.appDirDeployment && this.ingestionManager);
1267
1267
  }
1268
1268
 
1269
1269
  get accessPoints(): AccessPoint[] {
@@ -1275,7 +1275,7 @@ export class DataProductEditorState extends ElementEditorState {
1275
1275
  }
1276
1276
 
1277
1277
  get deployValidationMessage(): string {
1278
- if (!this.associatedIngest?.appDirDeployment) {
1278
+ if (!this.appDirDeployment) {
1279
1279
  return 'No app dir deployment found';
1280
1280
  } else if (!this.ingestionManager) {
1281
1281
  return 'No ingestion manager found';
@@ -1283,6 +1283,15 @@ export class DataProductEditorState extends ElementEditorState {
1283
1283
  return 'Deploy';
1284
1284
  }
1285
1285
 
1286
+ // Use explicit owner from the data product if available, otherwise fall back to the associated ingest's app dir deployment
1287
+ get appDirDeployment(): AppDirNode | undefined {
1288
+ const owner = this.product.owner;
1289
+ if (owner instanceof AppDirOwner) {
1290
+ return owner.production;
1291
+ }
1292
+ return this.associatedIngest?.appDirDeployment;
1293
+ }
1294
+
1286
1295
  // We need to get the associated Ingest to get the app dir deployment
1287
1296
  // We could do a more in depth check on the access point lambdas to check which ingest it uses but for now
1288
1297
  // we will assume all ingests have the same DID
@@ -255,7 +255,9 @@ export class ServiceConfigState {
255
255
 
256
256
  export class ServiceRegistrationState extends ServiceConfigState {
257
257
  readonly service: Service;
258
+ readonly deploymentCheckState = ActionState.create();
258
259
  activatePostRegistration = true;
260
+ registeredEnvs: string[] = [];
259
261
 
260
262
  constructor(
261
263
  editorStore: EditorStore,
@@ -267,8 +269,11 @@ export class ServiceRegistrationState extends ServiceConfigState {
267
269
 
268
270
  makeObservable(this, {
269
271
  activatePostRegistration: observable,
272
+ registeredEnvs: observable,
270
273
  setActivatePostRegistration: action,
274
+ setRegisteredEnvs: action,
271
275
  registerService: flow,
276
+ checkServiceRegistration: flow,
272
277
  });
273
278
 
274
279
  this.service = service;
@@ -277,6 +282,40 @@ export class ServiceRegistrationState extends ServiceConfigState {
277
282
  this.activatePostRegistration = val;
278
283
  }
279
284
 
285
+ setRegisteredEnvs(envs: string[]): void {
286
+ this.registeredEnvs = envs;
287
+ }
288
+
289
+ *checkServiceRegistration(): GeneratorFn<void> {
290
+ this.deploymentCheckState.inProgress();
291
+ const envs: string[] = [];
292
+ const servicePattern = this.service.pattern.startsWith('/')
293
+ ? this.service.pattern.substring(1)
294
+ : this.service.pattern;
295
+ for (const envConfig of this.registrationOptions) {
296
+ try {
297
+ const isRegistered =
298
+ (yield this.editorStore.graphManagerState.graphManager.checkServiceRegisteredByPattern(
299
+ envConfig.executionUrl,
300
+ servicePattern,
301
+ )) as boolean;
302
+ if (isRegistered) {
303
+ envs.push(envConfig.env);
304
+ }
305
+ } catch (error) {
306
+ assertErrorThrown(error);
307
+ this.editorStore.applicationStore.logService.warn(
308
+ LogEvent.create(
309
+ LEGEND_STUDIO_APP_EVENT.SERVICE_REGISTRATION_CHECK_FAILURE,
310
+ ),
311
+ `Can't check registration status for env '${envConfig.env}': ${error.message}`,
312
+ );
313
+ }
314
+ }
315
+ this.setRegisteredEnvs(envs);
316
+ this.deploymentCheckState.complete();
317
+ }
318
+
280
319
  *registerService(): GeneratorFn<void> {
281
320
  try {
282
321
  this.registrationState.inProgress();
@@ -43,6 +43,9 @@ import {
43
43
  DataProductOperationalMetadata,
44
44
  type DataProduct_DeliveryFrequency,
45
45
  type DataProduct_Region,
46
+ type DataProductOwner,
47
+ type AppDirOwner,
48
+ type AppDirNode,
46
49
  } from '@finos/legend-graph';
47
50
  import { addUniqueEntry, deleteEntry, swapEntry } from '@finos/legend-shared';
48
51
  import { action } from 'mobx';
@@ -409,3 +412,27 @@ export const operationalMetadata_deleteCoverageRegion = action(
409
412
  }
410
413
  },
411
414
  );
415
+
416
+ export const dataProduct_setOwner = action(
417
+ (product: DataProduct, owner: DataProductOwner | undefined) => {
418
+ product.owner = owner;
419
+ },
420
+ );
421
+
422
+ export const appDirOwner_setProduction = action(
423
+ (owner: AppDirOwner, appDirNode: AppDirNode | undefined) => {
424
+ owner.production = appDirNode;
425
+ },
426
+ );
427
+
428
+ export const appDirOwner_setProdParallel = action(
429
+ (owner: AppDirOwner, appDirNode: AppDirNode | undefined) => {
430
+ owner.prodParallel = appDirNode;
431
+ },
432
+ );
433
+
434
+ export const appDirNode_setAppDirId = action(
435
+ (node: AppDirNode, appDirId: number) => {
436
+ node.appDirId = appDirId;
437
+ },
438
+ );
package/tsconfig.json CHANGED
@@ -72,7 +72,6 @@
72
72
  "./src/application/LegendStudioApplicationConfig.ts",
73
73
  "./src/application/LegendStudioPluginManager.ts",
74
74
  "./src/components/__test-utils__/EmbeddedQueryBuilderTestUtils.ts",
75
- "./src/components/editor/editor-group/accessor/AccessorQueryBuilderHelper.ts",
76
75
  "./src/components/editor/editor-group/dataProduct/DataProductQueryBuilderHelper.ts",
77
76
  "./src/components/shared/StudioSDLCErrors.ts",
78
77
  "./src/stores/LegendStudioApplicationPlugin.ts",
@@ -248,6 +247,8 @@
248
247
  "./src/components/editor/editor-group/ProtocolValueBuilder.tsx",
249
248
  "./src/components/editor/editor-group/RuntimeEditor.tsx",
250
249
  "./src/components/editor/editor-group/UnsupportedElementEditor.tsx",
250
+ "./src/components/editor/editor-group/accessor/AccessorQueryBuilder.tsx",
251
+ "./src/components/editor/editor-group/accessor/AccessorQueryBuilderHelper.tsx",
251
252
  "./src/components/editor/editor-group/connection-editor/ConnectionEditor.tsx",
252
253
  "./src/components/editor/editor-group/connection-editor/DatabaseBuilderWizard.tsx",
253
254
  "./src/components/editor/editor-group/connection-editor/DatabaseEditorHelper.tsx",