@finos/legend-application-studio 28.19.117 → 28.21.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 (85) hide show
  1. package/lib/__lib__/LegendStudioUserDataHelper.d.ts +4 -1
  2. package/lib/__lib__/LegendStudioUserDataHelper.d.ts.map +1 -1
  3. package/lib/__lib__/LegendStudioUserDataHelper.js +18 -0
  4. package/lib/__lib__/LegendStudioUserDataHelper.js.map +1 -1
  5. package/lib/components/editor/editor-group/EditorGroup.d.ts.map +1 -1
  6. package/lib/components/editor/editor-group/EditorGroup.js +5 -0
  7. package/lib/components/editor/editor-group/EditorGroup.js.map +1 -1
  8. package/lib/components/editor/editor-group/accessor/AccessorQueryBuilder.d.ts +22 -0
  9. package/lib/components/editor/editor-group/accessor/AccessorQueryBuilder.d.ts.map +1 -0
  10. package/lib/components/editor/editor-group/accessor/AccessorQueryBuilder.js +43 -0
  11. package/lib/components/editor/editor-group/accessor/AccessorQueryBuilder.js.map +1 -0
  12. package/lib/components/editor/editor-group/accessor/AccessorQueryBuilderHelper.d.ts.map +1 -1
  13. package/lib/components/editor/editor-group/accessor/AccessorQueryBuilderHelper.js +8 -16
  14. package/lib/components/editor/editor-group/accessor/AccessorQueryBuilderHelper.js.map +1 -1
  15. package/lib/components/editor/editor-group/dataProduct/DataProductEditor.d.ts.map +1 -1
  16. package/lib/components/editor/editor-group/dataProduct/DataProductEditor.js +123 -80
  17. package/lib/components/editor/editor-group/dataProduct/DataProductEditor.js.map +1 -1
  18. package/lib/components/editor/editor-group/database-editor/DatabaseAnnotationDisplay.d.ts +26 -0
  19. package/lib/components/editor/editor-group/database-editor/DatabaseAnnotationDisplay.d.ts.map +1 -0
  20. package/lib/components/editor/editor-group/database-editor/DatabaseAnnotationDisplay.js +101 -0
  21. package/lib/components/editor/editor-group/database-editor/DatabaseAnnotationDisplay.js.map +1 -0
  22. package/lib/components/editor/editor-group/database-editor/DatabaseDiagramCanvas.d.ts +23 -0
  23. package/lib/components/editor/editor-group/database-editor/DatabaseDiagramCanvas.d.ts.map +1 -0
  24. package/lib/components/editor/editor-group/database-editor/DatabaseDiagramCanvas.js +434 -0
  25. package/lib/components/editor/editor-group/database-editor/DatabaseDiagramCanvas.js.map +1 -0
  26. package/lib/components/editor/editor-group/database-editor/DatabaseDiagramHelper.d.ts +242 -0
  27. package/lib/components/editor/editor-group/database-editor/DatabaseDiagramHelper.d.ts.map +1 -0
  28. package/lib/components/editor/editor-group/database-editor/DatabaseDiagramHelper.js +371 -0
  29. package/lib/components/editor/editor-group/database-editor/DatabaseDiagramHelper.js.map +1 -0
  30. package/lib/components/editor/editor-group/database-editor/DatabaseEditor.d.ts +29 -0
  31. package/lib/components/editor/editor-group/database-editor/DatabaseEditor.d.ts.map +1 -0
  32. package/lib/components/editor/editor-group/database-editor/DatabaseEditor.js +78 -0
  33. package/lib/components/editor/editor-group/database-editor/DatabaseEditor.js.map +1 -0
  34. package/lib/components/editor/editor-group/database-editor/DatabaseSchemaTree.d.ts +30 -0
  35. package/lib/components/editor/editor-group/database-editor/DatabaseSchemaTree.d.ts.map +1 -0
  36. package/lib/components/editor/editor-group/database-editor/DatabaseSchemaTree.js +331 -0
  37. package/lib/components/editor/editor-group/database-editor/DatabaseSchemaTree.js.map +1 -0
  38. package/lib/components/editor/editor-group/database-editor/DatabaseTableNode.d.ts +104 -0
  39. package/lib/components/editor/editor-group/database-editor/DatabaseTableNode.d.ts.map +1 -0
  40. package/lib/components/editor/editor-group/database-editor/DatabaseTableNode.js +151 -0
  41. package/lib/components/editor/editor-group/database-editor/DatabaseTableNode.js.map +1 -0
  42. package/lib/components/editor/editor-group/ingest-editor/IngestDefinitionEditor.d.ts.map +1 -1
  43. package/lib/components/editor/editor-group/ingest-editor/IngestDefinitionEditor.js +3 -78
  44. package/lib/components/editor/editor-group/ingest-editor/IngestDefinitionEditor.js.map +1 -1
  45. package/lib/components/editor/editor-group/uml-editor/ClassQueryBuilder.d.ts +3 -1
  46. package/lib/components/editor/editor-group/uml-editor/ClassQueryBuilder.d.ts.map +1 -1
  47. package/lib/components/editor/editor-group/uml-editor/ClassQueryBuilder.js +4 -5
  48. package/lib/components/editor/editor-group/uml-editor/ClassQueryBuilder.js.map +1 -1
  49. package/lib/index.css +2 -2
  50. package/lib/index.css.map +1 -1
  51. package/lib/package.json +4 -1
  52. package/lib/stores/editor/EditorTabManagerState.d.ts.map +1 -1
  53. package/lib/stores/editor/EditorTabManagerState.js +5 -3
  54. package/lib/stores/editor/EditorTabManagerState.js.map +1 -1
  55. package/lib/stores/editor/editor-state/element-editor-state/DatabaseEditorState.d.ts +252 -0
  56. package/lib/stores/editor/editor-state/element-editor-state/DatabaseEditorState.d.ts.map +1 -0
  57. package/lib/stores/editor/editor-state/element-editor-state/DatabaseEditorState.js +755 -0
  58. package/lib/stores/editor/editor-state/element-editor-state/DatabaseEditorState.js.map +1 -0
  59. package/lib/stores/editor/editor-state/element-editor-state/dataProduct/DataProductEditorState.d.ts +2 -1
  60. package/lib/stores/editor/editor-state/element-editor-state/dataProduct/DataProductEditorState.d.ts.map +1 -1
  61. package/lib/stores/editor/editor-state/element-editor-state/dataProduct/DataProductEditorState.js +12 -4
  62. package/lib/stores/editor/editor-state/element-editor-state/dataProduct/DataProductEditorState.js.map +1 -1
  63. package/lib/stores/graph-modifier/DSL_DataProduct_GraphModifierHelper.d.ts +5 -1
  64. package/lib/stores/graph-modifier/DSL_DataProduct_GraphModifierHelper.d.ts.map +1 -1
  65. package/lib/stores/graph-modifier/DSL_DataProduct_GraphModifierHelper.js +12 -0
  66. package/lib/stores/graph-modifier/DSL_DataProduct_GraphModifierHelper.js.map +1 -1
  67. package/package.json +12 -9
  68. package/src/__lib__/LegendStudioUserDataHelper.ts +30 -0
  69. package/src/components/editor/editor-group/EditorGroup.tsx +4 -0
  70. package/src/components/editor/editor-group/accessor/AccessorQueryBuilder.tsx +81 -0
  71. package/src/components/editor/editor-group/accessor/{AccessorQueryBuilderHelper.ts → AccessorQueryBuilderHelper.tsx} +14 -1
  72. package/src/components/editor/editor-group/dataProduct/DataProductEditor.tsx +225 -86
  73. package/src/components/editor/editor-group/database-editor/DatabaseAnnotationDisplay.tsx +200 -0
  74. package/src/components/editor/editor-group/database-editor/DatabaseDiagramCanvas.tsx +701 -0
  75. package/src/components/editor/editor-group/database-editor/DatabaseDiagramHelper.ts +555 -0
  76. package/src/components/editor/editor-group/database-editor/DatabaseEditor.tsx +246 -0
  77. package/src/components/editor/editor-group/database-editor/DatabaseSchemaTree.tsx +1053 -0
  78. package/src/components/editor/editor-group/database-editor/DatabaseTableNode.tsx +465 -0
  79. package/src/components/editor/editor-group/ingest-editor/IngestDefinitionEditor.tsx +2 -242
  80. package/src/components/editor/editor-group/uml-editor/ClassQueryBuilder.tsx +16 -6
  81. package/src/stores/editor/EditorTabManagerState.ts +4 -5
  82. package/src/stores/editor/editor-state/element-editor-state/DatabaseEditorState.ts +938 -0
  83. package/src/stores/editor/editor-state/element-editor-state/dataProduct/DataProductEditorState.ts +14 -5
  84. package/src/stores/graph-modifier/DSL_DataProduct_GraphModifierHelper.ts +27 -0
  85. package/tsconfig.json +9 -1
@@ -22,7 +22,6 @@ import {
22
22
  DATA_PRODUCT_TAB,
23
23
  DATA_PRODUCT_TYPE,
24
24
  DataProductEditorState,
25
- generateUrlToDeployOnOpen,
26
25
  LakehouseAccessPointState,
27
26
  ModelAccessPointGroupState,
28
27
  } from '../../../../stores/editor/editor-state/element-editor-state/dataProduct/DataProductEditorState.js';
@@ -68,7 +67,6 @@ import {
68
67
  ResizablePanel,
69
68
  ResizablePanelGroup,
70
69
  ResizablePanelSplitter,
71
- RocketIcon,
72
70
  Switch,
73
71
  TimesIcon,
74
72
  Tooltip,
@@ -91,13 +89,23 @@ import {
91
89
  useState,
92
90
  } from 'react';
93
91
  import {
92
+ assertErrorThrown,
94
93
  filterByType,
94
+ guaranteeNonNullable,
95
95
  guaranteeType,
96
+ isNonNullable,
96
97
  UserSearchService,
97
98
  } from '@finos/legend-shared';
98
- import { InlineLambdaEditor, LineageViewer } from '@finos/legend-query-builder';
99
+ import {
100
+ AccessorQueryBuilderState,
101
+ getCompatibleRuntimesFromAccessorOwner,
102
+ InlineLambdaEditor,
103
+ LineageViewer,
104
+ QueryBuilderActionConfig,
105
+ QueryBuilderAdvancedWorkflowState,
106
+ type QueryBuilderState,
107
+ } from '@finos/legend-query-builder';
99
108
  import { action, autorun, flowResult } from 'mobx';
100
- import { useAuth } from 'react-oidc-context';
101
109
  import { CODE_EDITOR_LANGUAGE } from '@finos/legend-code-editor';
102
110
  import { CodeEditor } from '@finos/legend-lego/code-editor';
103
111
  import {
@@ -126,6 +134,11 @@ import {
126
134
  observer_DataProductLink,
127
135
  DataProduct_Region,
128
136
  DataProduct_DeliveryFrequency,
137
+ AppDirOwner,
138
+ AppDirNode,
139
+ observe_AppDirOwner,
140
+ observe_AppDirNode,
141
+ AppDirLevel,
129
142
  } from '@finos/legend-graph';
130
143
  import {
131
144
  accessPoint_setClassification,
@@ -159,6 +172,10 @@ import {
159
172
  dataProductDiagram_setTitle,
160
173
  dataProductDiagram_setDescription,
161
174
  operationalMetadata_setUpdateFrequency,
175
+ dataProduct_setOwner,
176
+ appDirOwner_setProduction,
177
+ appDirOwner_setProdParallel,
178
+ appDirNode_setAppDirId,
162
179
  } from '../../../../stores/graph-modifier/DSL_DataProduct_GraphModifierHelper.js';
163
180
  import { LEGEND_STUDIO_TEST_ID } from '../../../../__lib__/LegendStudioTesting.js';
164
181
  import { LEGEND_STUDIO_APPLICATION_NAVIGATION_CONTEXT_KEY } from '../../../../__lib__/LegendStudioApplicationNavigationContext.js';
@@ -802,6 +819,90 @@ export const LakehouseDataProductAccessPointEditor = observer(
802
819
  const generateLineage = editorStore.applicationStore.guardUnhandledError(
803
820
  () => flowResult(accessPointState.generateLineage()),
804
821
  );
822
+
823
+ const editLambdaInQueryBuilder =
824
+ editorStore.applicationStore.guardUnhandledError(async () => {
825
+ const embeddedQueryBuilderState = editorStore.embeddedQueryBuilderState;
826
+ const applicationStore = editorStore.applicationStore;
827
+ const ingestDefinition =
828
+ editorStore.graphManagerState.graph.ownIngests.find(isNonNullable);
829
+ if (!ingestDefinition) {
830
+ applicationStore.notificationService.notifyError(
831
+ 'No ingest definition found in this project',
832
+ );
833
+ return;
834
+ }
835
+ await flowResult(
836
+ embeddedQueryBuilderState.setEmbeddedQueryBuilderConfiguration({
837
+ setupQueryBuilderState: async (): Promise<QueryBuilderState> => {
838
+ const queryBuilderState = new AccessorQueryBuilderState(
839
+ applicationStore,
840
+ undefined,
841
+ editorStore.graphManagerState,
842
+ QueryBuilderAdvancedWorkflowState.INSTANCE,
843
+ QueryBuilderActionConfig.INSTANCE,
844
+ applicationStore.config.options.queryBuilderConfig,
845
+ editorStore.editorMode.getSourceInfo(),
846
+ );
847
+ queryBuilderState.changeAccessorOwner(ingestDefinition);
848
+ const compatibleRuntimes = getCompatibleRuntimesFromAccessorOwner(
849
+ ingestDefinition,
850
+ editorStore.graphManagerState,
851
+ );
852
+ if (compatibleRuntimes.length > 0) {
853
+ queryBuilderState.changeSelectedRuntime(
854
+ guaranteeNonNullable(compatibleRuntimes[0]),
855
+ );
856
+ }
857
+ queryBuilderState.initializeWithQuery(accessPoint.func);
858
+ return queryBuilderState;
859
+ },
860
+ actionConfigs: [
861
+ {
862
+ key: 'save-query-btn',
863
+ renderer: (
864
+ queryBuilderState: QueryBuilderState,
865
+ ): React.ReactNode => {
866
+ const save = applicationStore.guardUnhandledError(
867
+ async () => {
868
+ try {
869
+ const rawLambda =
870
+ queryBuilderState.buildQueryForPersistence();
871
+ accessPoint.func = rawLambda;
872
+ embeddedQueryBuilderState.setEmbeddedQueryBuilderConfiguration(
873
+ undefined,
874
+ );
875
+ await flowResult(
876
+ lambdaEditorState.convertLambdaObjectToGrammarString({
877
+ pretty: true,
878
+ }),
879
+ );
880
+ } catch (error) {
881
+ assertErrorThrown(error);
882
+ applicationStore.notificationService.notifyError(
883
+ `Can't save access point lambda: ${error.message}`,
884
+ );
885
+ }
886
+ },
887
+ );
888
+ return (
889
+ <button
890
+ className="query-builder__dialog__header__custom-action"
891
+ tabIndex={-1}
892
+ disabled={props.isReadOnly}
893
+ onClick={save}
894
+ >
895
+ Save Lambda
896
+ </button>
897
+ );
898
+ },
899
+ },
900
+ ],
901
+ disableCompile: true,
902
+ }),
903
+ );
904
+ });
905
+
805
906
  return (
806
907
  <PanelDnDEntry
807
908
  ref={ref}
@@ -866,17 +967,7 @@ export const LakehouseDataProductAccessPointEditor = observer(
866
967
  disabled={props.isReadOnly}
867
968
  title="Edit sample values"
868
969
  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
970
  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
971
  }}
881
972
  >
882
973
  <PencilEditIcon />
@@ -894,16 +985,7 @@ export const LakehouseDataProductAccessPointEditor = observer(
894
985
  border: accessPointState.hasRelationElementMismatch
895
986
  ? '2px solid var(--color-red-300)'
896
987
  : '1px solid var(--color-blue-200)',
897
- borderRadius: '4px',
898
- padding: '0.5rem 0.75rem',
899
- background: 'var(--color-blue-200)',
900
988
  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
989
  }}
908
990
  >
909
991
  <PencilEditIcon />
@@ -918,17 +1000,7 @@ export const LakehouseDataProductAccessPointEditor = observer(
918
1000
  disabled={props.isReadOnly}
919
1001
  title="Add sample values"
920
1002
  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
1003
  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
1004
  }}
933
1005
  >
934
1006
  <PlusIcon />
@@ -1138,6 +1210,15 @@ export const LakehouseDataProductAccessPointEditor = observer(
1138
1210
  <div className="access-point-editor__generic-entry">
1139
1211
  <div className="access-point-editor__entry__container">
1140
1212
  <div className="access-point-editor__entry">
1213
+ <button
1214
+ className="access-point-editor__query-builder-btn"
1215
+ onClick={editLambdaInQueryBuilder}
1216
+ disabled={props.isReadOnly}
1217
+ title="Edit lambda with query builder"
1218
+ >
1219
+ <PencilEditIcon />
1220
+ <span>Query Builder</span>
1221
+ </button>
1141
1222
  <InlineLambdaEditor
1142
1223
  className={'access-point-editor__lambda-editor'}
1143
1224
  disabled={
@@ -2370,6 +2451,112 @@ const DataProductIconEditor = observer(
2370
2451
  },
2371
2452
  );
2372
2453
 
2454
+ const DataProductOwnershipEditor = observer(
2455
+ (props: { product: DataProduct; isReadOnly: boolean }) => {
2456
+ const { product, isReadOnly } = props;
2457
+ const owner =
2458
+ product.owner instanceof AppDirOwner ? product.owner : undefined;
2459
+
2460
+ const ensureOwner = (): AppDirOwner => {
2461
+ if (owner) {
2462
+ return owner;
2463
+ }
2464
+ const newOwner = observe_AppDirOwner(new AppDirOwner());
2465
+ dataProduct_setOwner(product, newOwner);
2466
+ return newOwner;
2467
+ };
2468
+
2469
+ const handleProductionChange: ChangeEventHandler<HTMLInputElement> = (
2470
+ event,
2471
+ ) => {
2472
+ const currentOwner = ensureOwner();
2473
+ const val = event.target.value;
2474
+ if (val === '') {
2475
+ appDirOwner_setProduction(currentOwner, undefined);
2476
+ if (!currentOwner.prodParallel) {
2477
+ dataProduct_setOwner(product, undefined);
2478
+ }
2479
+ return;
2480
+ }
2481
+ const num = Number(val);
2482
+ if (Number.isNaN(num)) {
2483
+ return;
2484
+ }
2485
+ if (currentOwner.production) {
2486
+ appDirNode_setAppDirId(currentOwner.production, num);
2487
+ return;
2488
+ }
2489
+ const node = observe_AppDirNode(new AppDirNode());
2490
+ node.appDirId = num;
2491
+ node.level = AppDirLevel.DEPLOYMENT;
2492
+ appDirOwner_setProduction(currentOwner, node);
2493
+ };
2494
+
2495
+ const handleProdParallelChange: ChangeEventHandler<HTMLInputElement> = (
2496
+ event,
2497
+ ) => {
2498
+ const currentOwner = ensureOwner();
2499
+ const val = event.target.value;
2500
+ if (val === '') {
2501
+ appDirOwner_setProdParallel(currentOwner, undefined);
2502
+ if (!currentOwner.production) {
2503
+ dataProduct_setOwner(product, undefined);
2504
+ }
2505
+ return;
2506
+ }
2507
+ const num = Number(val);
2508
+ if (Number.isNaN(num)) {
2509
+ return;
2510
+ }
2511
+ if (currentOwner.prodParallel) {
2512
+ appDirNode_setAppDirId(currentOwner.prodParallel, num);
2513
+ return;
2514
+ }
2515
+ const node = observe_AppDirNode(new AppDirNode());
2516
+ node.appDirId = num;
2517
+ node.level = AppDirLevel.DEPLOYMENT;
2518
+ appDirOwner_setProdParallel(currentOwner, node);
2519
+ };
2520
+
2521
+ return (
2522
+ <>
2523
+ <div className="panel__content__form__section">
2524
+ <div className="panel__content__form__section__header__label">
2525
+ Ownership
2526
+ </div>
2527
+ <div className="panel__content__form__section__header__prompt">
2528
+ Set the AppDir ownership for this Data Product.
2529
+ </div>
2530
+ <div className="panel__content__form__section__header__prompt">
2531
+ Production AppDir ID
2532
+ </div>
2533
+ <input
2534
+ className="input input-group__input panel__content__form__section__input input--dark input--small"
2535
+ type="number"
2536
+ disabled={isReadOnly}
2537
+ value={owner?.production?.appDirId ?? ''}
2538
+ onChange={handleProductionChange}
2539
+ placeholder="Enter production AppDir ID"
2540
+ />
2541
+ </div>
2542
+ <div className="panel__content__form__section">
2543
+ <div className="panel__content__form__section__header__prompt">
2544
+ Prod Parallel AppDir ID
2545
+ </div>
2546
+ <input
2547
+ className="input input-group__input panel__content__form__section__input input--dark input--small"
2548
+ type="number"
2549
+ disabled={isReadOnly}
2550
+ value={owner?.prodParallel?.appDirId ?? ''}
2551
+ onChange={handleProdParallelChange}
2552
+ placeholder="Enter prod parallel AppDir ID"
2553
+ />
2554
+ </div>
2555
+ </>
2556
+ );
2557
+ },
2558
+ );
2559
+
2373
2560
  const HomeTab = observer(
2374
2561
  (props: {
2375
2562
  dataProductEditorState: DataProductEditorState;
@@ -2543,6 +2730,10 @@ const HomeTab = observer(
2543
2730
  )}
2544
2731
  </div>
2545
2732
  <DataProductIconEditor product={product} isReadOnly={isReadOnly} />
2733
+ <DataProductOwnershipEditor
2734
+ product={product}
2735
+ isReadOnly={isReadOnly}
2736
+ />
2546
2737
  </div>
2547
2738
  </div>
2548
2739
  );
@@ -3158,35 +3349,10 @@ export const DataProductEditor = observer(() => {
3158
3349
  editorStore.tabManagerState.getCurrentEditorState(DataProductEditorState);
3159
3350
  const product = dataProductEditorState.product;
3160
3351
  const isReadOnly = dataProductEditorState.isReadOnly;
3161
- const auth = useAuth();
3162
3352
  const [showPreview, setShowPreview] = useState(false);
3163
3353
  const [dataProductViewerState, setDataProductViewerState] =
3164
3354
  useState<DataProductViewerState>();
3165
3355
 
3166
- const deployDataProduct = (): void => {
3167
- // Trigger OAuth flow if not authenticated
3168
- if (!auth.isAuthenticated) {
3169
- // remove this redirect if we move to do oauth at the beginning of opening studio
3170
- auth
3171
- .signinRedirect({
3172
- state: generateUrlToDeployOnOpen(dataProductEditorState),
3173
- })
3174
- .catch(editorStore.applicationStore.alertUnhandledError);
3175
- return;
3176
- }
3177
- // Use the token for deployment
3178
- const token = auth.user?.access_token;
3179
- if (token) {
3180
- flowResult(dataProductEditorState.deploy(token)).catch(
3181
- editorStore.applicationStore.alertUnhandledError,
3182
- );
3183
- } else {
3184
- editorStore.applicationStore.notificationService.notifyError(
3185
- 'Authentication failed. No token available.',
3186
- );
3187
- }
3188
- };
3189
-
3190
3356
  const selectedActivity = dataProductEditorState.selectedTab;
3191
3357
  const renderActivivtyBarTab = (): React.ReactNode => {
3192
3358
  switch (selectedActivity) {
@@ -3233,18 +3399,6 @@ export const DataProductEditor = observer(() => {
3233
3399
  );
3234
3400
  }, [dataProductEditorState]);
3235
3401
 
3236
- useEffect(() => {
3237
- if (dataProductEditorState.deployOnOpen) {
3238
- flowResult(dataProductEditorState.deploy(auth.user?.access_token)).catch(
3239
- editorStore.applicationStore.alertUnhandledError,
3240
- );
3241
- }
3242
- }, [
3243
- auth,
3244
- editorStore.applicationStore.alertUnhandledError,
3245
- dataProductEditorState,
3246
- ]);
3247
-
3248
3402
  useEffect(
3249
3403
  () =>
3250
3404
  autorun(
@@ -3326,25 +3480,10 @@ export const DataProductEditor = observer(() => {
3326
3480
  )}
3327
3481
  </button>
3328
3482
  </div>
3329
- <div className="btn__dropdown-combo btn__dropdown-combo--primary">
3330
- <button
3331
- className="btn__dropdown-combo__label"
3332
- onClick={deployDataProduct}
3333
- title={dataProductEditorState.deployValidationMessage}
3334
- tabIndex={-1}
3335
- disabled={!dataProductEditorState.deployValidationMessage}
3336
- >
3337
- <RocketIcon className="btn__dropdown-combo__label__icon" />
3338
- <div className="btn__dropdown-combo__label__title">Deploy</div>
3339
- </button>
3340
- </div>
3341
3483
  </PanelHeaderActions>
3342
3484
  </div>
3343
3485
 
3344
- <div
3345
- className="panel"
3346
- style={{ padding: '1rem', flexDirection: 'row' }}
3347
- >
3486
+ <div className="panel data-product-editor__content-panel">
3348
3487
  <DataProductSidebar dataProductEditorState={dataProductEditorState} />
3349
3488
  <ResizablePanelGroup orientation="vertical">
3350
3489
  <ResizablePanel>{renderActivivtyBarTab()}</ResizablePanel>
@@ -0,0 +1,200 @@
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 { BasePopover, clsx, HashtagIcon, TagIcon } from '@finos/legend-art';
18
+ import type { StereotypeReference, TaggedValue } from '@finos/legend-graph';
19
+ import { useState } from 'react';
20
+
21
+ /**
22
+ * Read-only display for `stereotypes` + `taggedValues` on any
23
+ * `AnnotatedElement` (Database, Schema, Table, View, Column). Mirrors the
24
+ * Pure DSL form `<<profile.stereo>> { tag = "value" }` but rendered as small
25
+ * inline pills so it can sit next to a relation name in a header row, in a
26
+ * tree row, or in a dedicated section.
27
+ *
28
+ * Three layout modes:
29
+ * - `inline` (default): pills flow next to the surrounding row content.
30
+ * - `block`: pills wrap into their own block (used in side-panel sections
31
+ * where the parent has its own column structure).
32
+ * - `compact`: only renders a single combined badge with the count — used
33
+ * when horizontal space is tight (e.g. the canvas table-node header).
34
+ * Hover shows the full content via `title`.
35
+ *
36
+ * The component is dependency-free (no MobX) so it can be used from any of
37
+ * the editor's sub-components without observability concerns — the inputs
38
+ * are static metamodel arrays.
39
+ */
40
+
41
+ const formatStereotype = (stereotype: StereotypeReference): string => {
42
+ // Pure surface syntax: `profile.stereotypeName`. We mimic that for the
43
+ // tooltip + compact-mode label so users see the same identifier the
44
+ // grammar uses.
45
+ const profilePath =
46
+ stereotype.ownerReference.valueForSerialization ??
47
+ stereotype.value._OWNER.path;
48
+ return `${profilePath}.${stereotype.value.value}`;
49
+ };
50
+
51
+ const formatTaggedValue = (taggedValue: TaggedValue): string => {
52
+ // Same rationale as above — match the grammar form `profile.tag = "value"`.
53
+ const profilePath =
54
+ taggedValue.tag.ownerReference.valueForSerialization ??
55
+ taggedValue.tag.value._OWNER.path;
56
+ return `${profilePath}.${taggedValue.tag.value.value} = "${taggedValue.value}"`;
57
+ };
58
+
59
+ /**
60
+ * Click-to-open popover badge used in compact mode. Kept separate from the
61
+ * parent component so the `useState` hook for the popover anchor is
62
+ * unconditionally called (the parent has an early-return for the empty case
63
+ * which would otherwise put this hook below a conditional).
64
+ */
65
+ function CompactAnnotationBadge(props: {
66
+ stereotypes: StereotypeReference[];
67
+ taggedValues: TaggedValue[];
68
+ className?: string | undefined;
69
+ }): React.ReactElement {
70
+ const { stereotypes, taggedValues, className } = props;
71
+ const [anchor, setAnchor] = useState<HTMLElement | null>(null);
72
+ const total = stereotypes.length + taggedValues.length;
73
+ return (
74
+ <>
75
+ <button
76
+ type="button"
77
+ className={clsx(
78
+ 'database-annotation',
79
+ 'database-annotation--compact',
80
+ className,
81
+ )}
82
+ onClick={(event) => {
83
+ // Stop propagation so clicking the badge inside a clickable canvas
84
+ // node header (or tree row) doesn't also trigger the parent's
85
+ // selection handler.
86
+ event.stopPropagation();
87
+ setAnchor(event.currentTarget);
88
+ }}
89
+ title={`${total} annotation${total === 1 ? '' : 's'} \u2014 click to view`}
90
+ >
91
+ <TagIcon />
92
+ <span className="database-annotation__count">{total}</span>
93
+ </button>
94
+ <BasePopover
95
+ open={Boolean(anchor)}
96
+ anchorEl={anchor}
97
+ onClose={() => setAnchor(null)}
98
+ anchorOrigin={{ vertical: 'bottom', horizontal: 'left' }}
99
+ transformOrigin={{ vertical: 'top', horizontal: 'left' }}
100
+ >
101
+ <div
102
+ className="database-annotation__popover"
103
+ // Stop click events on the popover content from reaching the
104
+ // ReactFlow canvas underneath.
105
+ onClick={(event) => event.stopPropagation()}
106
+ >
107
+ {/* eslint-disable-next-line @typescript-eslint/no-use-before-define */}
108
+ <DatabaseAnnotationDisplay
109
+ stereotypes={stereotypes}
110
+ taggedValues={taggedValues}
111
+ layout="block"
112
+ />
113
+ </div>
114
+ </BasePopover>
115
+ </>
116
+ );
117
+ }
118
+
119
+ export interface DatabaseAnnotationDisplayProps {
120
+ stereotypes: StereotypeReference[];
121
+ taggedValues: TaggedValue[];
122
+ /** Layout density. Defaults to `inline`. */
123
+ layout?: 'inline' | 'block' | 'compact';
124
+ /** Optional className appended to the root element for layout overrides. */
125
+ className?: string | undefined;
126
+ }
127
+
128
+ export const DatabaseAnnotationDisplay: React.FC<
129
+ DatabaseAnnotationDisplayProps
130
+ > = ({ stereotypes, taggedValues, layout = 'inline', className }) => {
131
+ if (stereotypes.length === 0 && taggedValues.length === 0) {
132
+ return null;
133
+ }
134
+
135
+ // Compact: collapse everything into a single badge with the total count.
136
+ // Useful inside dense headers (canvas table-node) where a row of pills
137
+ // would crowd out the relation name. Click opens a popover with the full
138
+ // list rendered in `block` layout. Extracted into its own component so
139
+ // the `useState` hook is unconditionally called (we can't put a hook
140
+ // after the early-return at the top of the parent).
141
+ if (layout === 'compact') {
142
+ return (
143
+ <CompactAnnotationBadge
144
+ stereotypes={stereotypes}
145
+ taggedValues={taggedValues}
146
+ className={className}
147
+ />
148
+ );
149
+ }
150
+
151
+ return (
152
+ <div
153
+ className={clsx(
154
+ 'database-annotation',
155
+ `database-annotation--${layout}`,
156
+ className,
157
+ )}
158
+ >
159
+ {stereotypes.map((stereotype) => {
160
+ const label = formatStereotype(stereotype);
161
+ return (
162
+ // Profiles can re-use stereotype names across owners, so prefix the
163
+ // key with the owner path to keep keys unique.
164
+ <span
165
+ key={`stereo:${label}`}
166
+ className="database-annotation__stereotype"
167
+ title={`«${label}»`}
168
+ >
169
+ <HashtagIcon />
170
+ <span className="database-annotation__stereotype__label">
171
+ {stereotype.value.value}
172
+ </span>
173
+ </span>
174
+ );
175
+ })}
176
+ {taggedValues.map((taggedValue) => {
177
+ const tagName = taggedValue.tag.value.value;
178
+ return (
179
+ // Tagged values aren't unique by tag (the same tag can appear
180
+ // twice with different values), so we key off `_UUID` rather than
181
+ // a composite of tag path + name.
182
+ <span
183
+ key={taggedValue._UUID}
184
+ className="database-annotation__tagged-value"
185
+ title={formatTaggedValue(taggedValue)}
186
+ >
187
+ <TagIcon />
188
+ <span className="database-annotation__tagged-value__name">
189
+ {tagName}
190
+ </span>
191
+ <span className="database-annotation__tagged-value__sep">=</span>
192
+ <span className="database-annotation__tagged-value__value">
193
+ {taggedValue.value}
194
+ </span>
195
+ </span>
196
+ );
197
+ })}
198
+ </div>
199
+ );
200
+ };