@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.
- package/lib/__lib__/LegendStudioUserDataHelper.d.ts +4 -1
- package/lib/__lib__/LegendStudioUserDataHelper.d.ts.map +1 -1
- package/lib/__lib__/LegendStudioUserDataHelper.js +18 -0
- package/lib/__lib__/LegendStudioUserDataHelper.js.map +1 -1
- package/lib/components/editor/editor-group/EditorGroup.d.ts.map +1 -1
- package/lib/components/editor/editor-group/EditorGroup.js +5 -0
- package/lib/components/editor/editor-group/EditorGroup.js.map +1 -1
- package/lib/components/editor/editor-group/accessor/AccessorQueryBuilder.d.ts +22 -0
- package/lib/components/editor/editor-group/accessor/AccessorQueryBuilder.d.ts.map +1 -0
- package/lib/components/editor/editor-group/accessor/AccessorQueryBuilder.js +43 -0
- package/lib/components/editor/editor-group/accessor/AccessorQueryBuilder.js.map +1 -0
- package/lib/components/editor/editor-group/accessor/AccessorQueryBuilderHelper.d.ts.map +1 -1
- package/lib/components/editor/editor-group/accessor/AccessorQueryBuilderHelper.js +8 -16
- package/lib/components/editor/editor-group/accessor/AccessorQueryBuilderHelper.js.map +1 -1
- package/lib/components/editor/editor-group/dataProduct/DataProductEditor.d.ts.map +1 -1
- package/lib/components/editor/editor-group/dataProduct/DataProductEditor.js +123 -80
- package/lib/components/editor/editor-group/dataProduct/DataProductEditor.js.map +1 -1
- package/lib/components/editor/editor-group/database-editor/DatabaseAnnotationDisplay.d.ts +26 -0
- package/lib/components/editor/editor-group/database-editor/DatabaseAnnotationDisplay.d.ts.map +1 -0
- package/lib/components/editor/editor-group/database-editor/DatabaseAnnotationDisplay.js +101 -0
- package/lib/components/editor/editor-group/database-editor/DatabaseAnnotationDisplay.js.map +1 -0
- package/lib/components/editor/editor-group/database-editor/DatabaseDiagramCanvas.d.ts +23 -0
- package/lib/components/editor/editor-group/database-editor/DatabaseDiagramCanvas.d.ts.map +1 -0
- package/lib/components/editor/editor-group/database-editor/DatabaseDiagramCanvas.js +434 -0
- package/lib/components/editor/editor-group/database-editor/DatabaseDiagramCanvas.js.map +1 -0
- package/lib/components/editor/editor-group/database-editor/DatabaseDiagramHelper.d.ts +242 -0
- package/lib/components/editor/editor-group/database-editor/DatabaseDiagramHelper.d.ts.map +1 -0
- package/lib/components/editor/editor-group/database-editor/DatabaseDiagramHelper.js +371 -0
- package/lib/components/editor/editor-group/database-editor/DatabaseDiagramHelper.js.map +1 -0
- package/lib/components/editor/editor-group/database-editor/DatabaseEditor.d.ts +29 -0
- package/lib/components/editor/editor-group/database-editor/DatabaseEditor.d.ts.map +1 -0
- package/lib/components/editor/editor-group/database-editor/DatabaseEditor.js +78 -0
- package/lib/components/editor/editor-group/database-editor/DatabaseEditor.js.map +1 -0
- package/lib/components/editor/editor-group/database-editor/DatabaseSchemaTree.d.ts +30 -0
- package/lib/components/editor/editor-group/database-editor/DatabaseSchemaTree.d.ts.map +1 -0
- package/lib/components/editor/editor-group/database-editor/DatabaseSchemaTree.js +331 -0
- package/lib/components/editor/editor-group/database-editor/DatabaseSchemaTree.js.map +1 -0
- package/lib/components/editor/editor-group/database-editor/DatabaseTableNode.d.ts +104 -0
- package/lib/components/editor/editor-group/database-editor/DatabaseTableNode.d.ts.map +1 -0
- package/lib/components/editor/editor-group/database-editor/DatabaseTableNode.js +151 -0
- package/lib/components/editor/editor-group/database-editor/DatabaseTableNode.js.map +1 -0
- package/lib/components/editor/editor-group/ingest-editor/IngestDefinitionEditor.d.ts.map +1 -1
- package/lib/components/editor/editor-group/ingest-editor/IngestDefinitionEditor.js +3 -78
- package/lib/components/editor/editor-group/ingest-editor/IngestDefinitionEditor.js.map +1 -1
- package/lib/components/editor/editor-group/uml-editor/ClassQueryBuilder.d.ts +3 -1
- package/lib/components/editor/editor-group/uml-editor/ClassQueryBuilder.d.ts.map +1 -1
- package/lib/components/editor/editor-group/uml-editor/ClassQueryBuilder.js +4 -5
- package/lib/components/editor/editor-group/uml-editor/ClassQueryBuilder.js.map +1 -1
- package/lib/index.css +2 -2
- package/lib/index.css.map +1 -1
- package/lib/package.json +4 -1
- package/lib/stores/editor/EditorTabManagerState.d.ts.map +1 -1
- package/lib/stores/editor/EditorTabManagerState.js +5 -3
- package/lib/stores/editor/EditorTabManagerState.js.map +1 -1
- package/lib/stores/editor/editor-state/element-editor-state/DatabaseEditorState.d.ts +252 -0
- package/lib/stores/editor/editor-state/element-editor-state/DatabaseEditorState.d.ts.map +1 -0
- package/lib/stores/editor/editor-state/element-editor-state/DatabaseEditorState.js +755 -0
- package/lib/stores/editor/editor-state/element-editor-state/DatabaseEditorState.js.map +1 -0
- package/lib/stores/editor/editor-state/element-editor-state/dataProduct/DataProductEditorState.d.ts +2 -1
- package/lib/stores/editor/editor-state/element-editor-state/dataProduct/DataProductEditorState.d.ts.map +1 -1
- package/lib/stores/editor/editor-state/element-editor-state/dataProduct/DataProductEditorState.js +12 -4
- package/lib/stores/editor/editor-state/element-editor-state/dataProduct/DataProductEditorState.js.map +1 -1
- package/lib/stores/graph-modifier/DSL_DataProduct_GraphModifierHelper.d.ts +5 -1
- package/lib/stores/graph-modifier/DSL_DataProduct_GraphModifierHelper.d.ts.map +1 -1
- package/lib/stores/graph-modifier/DSL_DataProduct_GraphModifierHelper.js +12 -0
- package/lib/stores/graph-modifier/DSL_DataProduct_GraphModifierHelper.js.map +1 -1
- package/package.json +12 -9
- package/src/__lib__/LegendStudioUserDataHelper.ts +30 -0
- package/src/components/editor/editor-group/EditorGroup.tsx +4 -0
- package/src/components/editor/editor-group/accessor/AccessorQueryBuilder.tsx +81 -0
- package/src/components/editor/editor-group/accessor/{AccessorQueryBuilderHelper.ts → AccessorQueryBuilderHelper.tsx} +14 -1
- package/src/components/editor/editor-group/dataProduct/DataProductEditor.tsx +225 -86
- package/src/components/editor/editor-group/database-editor/DatabaseAnnotationDisplay.tsx +200 -0
- package/src/components/editor/editor-group/database-editor/DatabaseDiagramCanvas.tsx +701 -0
- package/src/components/editor/editor-group/database-editor/DatabaseDiagramHelper.ts +555 -0
- package/src/components/editor/editor-group/database-editor/DatabaseEditor.tsx +246 -0
- package/src/components/editor/editor-group/database-editor/DatabaseSchemaTree.tsx +1053 -0
- package/src/components/editor/editor-group/database-editor/DatabaseTableNode.tsx +465 -0
- package/src/components/editor/editor-group/ingest-editor/IngestDefinitionEditor.tsx +2 -242
- package/src/components/editor/editor-group/uml-editor/ClassQueryBuilder.tsx +16 -6
- package/src/stores/editor/EditorTabManagerState.ts +4 -5
- package/src/stores/editor/editor-state/element-editor-state/DatabaseEditorState.ts +938 -0
- package/src/stores/editor/editor-state/element-editor-state/dataProduct/DataProductEditorState.ts +14 -5
- package/src/stores/graph-modifier/DSL_DataProduct_GraphModifierHelper.ts +27 -0
- 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 {
|
|
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
|
+
};
|