From 02b16fc0755976cb501fde629262c6b2dcfd075e Mon Sep 17 00:00:00 2001 From: An Phi Date: Fri, 14 Feb 2025 15:33:18 -0500 Subject: [PATCH] studio: support import execute input --- .changeset/nine-shirts-call.md | 6 + .../editor/editor-group/ModelImporter.tsx | 36 +++++ .../editor/editor-state/ModelImporterState.ts | 131 +++++++++++++++++- .../components/editor/_model-loader.scss | 23 +++ .../editor/DataCubeEditorDimensionsPanel.tsx | 2 +- .../graph-manager/AbstractPureGraphManager.ts | 8 ++ .../protocol/pure/v1/V1_PureGraphManager.ts | 56 +++++++- .../V1_PureProtocolSerialization.ts | 24 +++- .../src/SDLCServerClient.ts | 1 + 9 files changed, 281 insertions(+), 6 deletions(-) create mode 100644 .changeset/nine-shirts-call.md diff --git a/.changeset/nine-shirts-call.md b/.changeset/nine-shirts-call.md new file mode 100644 index 0000000000..2b6de7e4f2 --- /dev/null +++ b/.changeset/nine-shirts-call.md @@ -0,0 +1,6 @@ +--- +'@finos/legend-application-data-cube': patch +'@finos/legend-application-studio': patch +'@finos/legend-data-cube': patch +'@finos/legend-graph': patch +--- diff --git a/packages/legend-application-studio/src/components/editor/editor-group/ModelImporter.tsx b/packages/legend-application-studio/src/components/editor/editor-group/ModelImporter.tsx index 5f15245904..2265048a2e 100644 --- a/packages/legend-application-studio/src/components/editor/editor-group/ModelImporter.tsx +++ b/packages/legend-application-studio/src/components/editor/editor-group/ModelImporter.tsx @@ -22,6 +22,7 @@ import { MODEL_IMPORT_NATIVE_INPUT_TYPE, NativeModelImporterEditorState, ExternalFormatModelImporterState, + ExecuteInputDebugModelImporterEditorState, } from '../../../stores/editor/editor-state/ModelImporterState.js'; import { prettyCONSTName } from '@finos/legend-shared'; import { @@ -220,6 +221,33 @@ export const ModelImporter = observer(() => { externalFormatState={modelImportEditorState} /> ); + } else if ( + modelImportEditorState instanceof + ExecuteInputDebugModelImporterEditorState + ) { + return ( + +
+
+ Debug Function: +
+ + modelImportEditorState.setFunctionPath(event.target.value) + } + /> +
+
+ modelImportEditorState.setExecuteInput(val)} + /> +
+
+ ); } return null; }; @@ -248,6 +276,14 @@ export const ModelImporter = observer(() => { {prettyCONSTName(inputType)} ))} + + modelImporterState.setExecuteInputDebugModelImporter() + } + > + {`Execute Input (DEBUG)`} + {Boolean(externalFormatDescriptions.length) && ( diff --git a/packages/legend-application-studio/src/stores/editor/editor-state/ModelImporterState.ts b/packages/legend-application-studio/src/stores/editor/editor-state/ModelImporterState.ts index 6506a23edc..f625dfacbc 100644 --- a/packages/legend-application-studio/src/stores/editor/editor-state/ModelImporterState.ts +++ b/packages/legend-application-studio/src/stores/editor/editor-state/ModelImporterState.ts @@ -34,7 +34,7 @@ import { } from '@finos/legend-shared'; import { LEGEND_STUDIO_APP_EVENT } from '../../../__lib__/LegendStudioEvent.js'; import type { EditorStore } from '../EditorStore.js'; -import type { Entity } from '@finos/legend-storage'; +import { generateGAVCoordinates, type Entity } from '@finos/legend-storage'; import { DEFAULT_TAB_SIZE } from '@finos/legend-application'; import type { ModelImporterExtensionConfiguration, @@ -42,6 +42,8 @@ import type { } from '../../LegendStudioApplicationPlugin.js'; import { type ExternalFormatDescription, + GraphEntities, + LegendSDLC, type PureModel, SchemaSet, observe_SchemaSet, @@ -51,6 +53,10 @@ import { externalFormat_schemaSet_setSchemas, } from '../../graph-modifier/DSL_ExternalFormat_GraphModifierHelper.js'; import { InnerSchemaSetEditorState } from './element-editor-state/external-format/DSL_ExternalFormat_SchemaSetEditorState.js'; +import { + ProjectDependency, + UpdateProjectConfigurationCommand, +} from '@finos/legend-server-sdlc'; export enum MODEL_IMPORT_NATIVE_INPUT_TYPE { ENTITIES = 'ENTITIES', @@ -279,6 +285,120 @@ export class NativeModelImporterEditorState extends ModelImporterEditorState { } } +export class ExecuteInputDebugModelImporterEditorState extends ModelImporterEditorState { + executeInput = '{}'; + functionPath = 'test::Debugger'; + readonly loadState = ActionState.create(); + + constructor(modelImporterState: ModelImporterState) { + super(modelImporterState); + + makeObservable(this, { + executeInput: observable, + setExecuteInput: action, + + functionPath: observable, + setFunctionPath: action, + }); + } + + get label(): string { + return `Execute Input [DEBUG]`; + } + + get allowHardReplace(): boolean { + return false; + } + + get isLoadingDisabled(): boolean { + return this.loadState.isInProgress; + } + + setExecuteInput(val: string): void { + this.executeInput = val; + } + + setFunctionPath(val: string): void { + this.functionPath = val; + } + + async loadModel(): Promise { + this.loadModelActionState.inProgress(); + try { + this.editorStore.applicationStore.alertService.setBlockingAlert({ + message: 'Loading model...', + prompt: 'Please do not close the application', + showLoading: true, + }); + const result = + await this.editorStore.graphManagerState.graphManager.analyzeExecuteInput( + JSON.parse(this.executeInput), + this.functionPath, + ); + let entities: Entity[] = []; + let dependencies: ProjectDependency[] = []; + if (result.origin instanceof GraphEntities) { + entities = [...result.origin.entities, ...result.entities]; + } else if (result.origin instanceof LegendSDLC) { + entities = [...result.entities]; + dependencies = [ + new ProjectDependency( + generateGAVCoordinates( + result.origin.groupId, + result.origin.artifactId, + undefined, + ), + result.origin.versionId, + ), + ]; + } + const message = `loading entities from ${ + this.editorStore.applicationStore.config.appName + } [${this.modelImporterState.replace ? `potentially affected ` : ''} ${ + entities.length + } entities]`; + await this.editorStore.sdlcServerClient.updateEntities( + this.editorStore.sdlcState.activeProject.projectId, + this.editorStore.sdlcState.activeWorkspace, + { replace: this.modelImporterState.replace, entities, message }, + ); + + const currentProjectConfiguration = + this.editorStore.projectConfigurationEditorState.originalConfig; + const updateProjectConfigurationCommand = + new UpdateProjectConfigurationCommand( + currentProjectConfiguration.groupId, + currentProjectConfiguration.artifactId, + currentProjectConfiguration.projectStructureVersion, + `update project configuration from ${this.editorStore.applicationStore.config.appName}`, + ); + updateProjectConfigurationCommand.projectDependenciesToAdd = dependencies; + updateProjectConfigurationCommand.projectDependenciesToRemove = + currentProjectConfiguration.projectDependencies; + await flowResult( + this.editorStore.projectConfigurationEditorState.updateProjectConfiguration( + updateProjectConfigurationCommand, + ), + ); + this.editorStore.applicationStore.navigationService.navigator.reload({ + ignoreBlocking: true, + }); + } catch (error) { + assertErrorThrown(error); + this.editorStore.applicationStore.logService.error( + LogEvent.create(LEGEND_STUDIO_APP_EVENT.MODEL_LOADER_FAILURE), + error, + ); + this.editorStore.applicationStore.notificationService.notifyError(error); + } finally { + this.loadModelActionState.complete(); + this.editorStore.applicationStore.alertService.setBlockingAlert( + undefined, + ); + } + } +} + export abstract class ExtensionModelImportRendererState { importerState: ModelImporterState; @@ -541,4 +661,13 @@ export class ModelImporterState extends EditorState { return modelImporterEditorState; } } + + setExecuteInputDebugModelImporter() { + const executeInputDebugModelImporterEditorState = + this.modelImportEditorState instanceof + ExecuteInputDebugModelImporterEditorState + ? this.modelImportEditorState + : new ExecuteInputDebugModelImporterEditorState(this); + this.setImportEditorState(executeInputDebugModelImporterEditorState); + } } diff --git a/packages/legend-application-studio/style/components/editor/_model-loader.scss b/packages/legend-application-studio/style/components/editor/_model-loader.scss index 1e50420202..1d2a433152 100644 --- a/packages/legend-application-studio/style/components/editor/_model-loader.scss +++ b/packages/legend-application-studio/style/components/editor/_model-loader.scss @@ -200,5 +200,28 @@ &__editor { overflow-y: hidden; position: relative; + background: var(--color-dark-grey-50); + } + + &__debugger__function-path { + @include flexVCenter; + + border-bottom: 0.1rem solid var(--color-dark-grey-250); + height: 4rem; + padding: 0.5rem; + } + + &__debugger__function-path__label { + @include flexVCenter; + + flex-shrink: 0; + padding: 0 1rem; + font-size: 1.2rem; + color: var(--color-light-grey-400); + margin-right: 0.5rem; + } + + &__debugger__execute-input { + height: calc(100% - 4rem); } } diff --git a/packages/legend-data-cube/src/components/view/editor/DataCubeEditorDimensionsPanel.tsx b/packages/legend-data-cube/src/components/view/editor/DataCubeEditorDimensionsPanel.tsx index c3d31beb5a..914fb5a7ca 100644 --- a/packages/legend-data-cube/src/components/view/editor/DataCubeEditorDimensionsPanel.tsx +++ b/packages/legend-data-cube/src/components/view/editor/DataCubeEditorDimensionsPanel.tsx @@ -15,7 +15,7 @@ */ import { observer } from 'mobx-react-lite'; -import { DataCubeIcon, useDropdownMenu } from '@finos/legend-art'; +import { DataCubeIcon } from '@finos/legend-art'; import { type ColDef, type ColDefField, diff --git a/packages/legend-graph/src/graph-manager/AbstractPureGraphManager.ts b/packages/legend-graph/src/graph-manager/AbstractPureGraphManager.ts index 654d6bdc5f..fe5bec3913 100644 --- a/packages/legend-graph/src/graph-manager/AbstractPureGraphManager.ts +++ b/packages/legend-graph/src/graph-manager/AbstractPureGraphManager.ts @@ -607,6 +607,14 @@ export abstract class AbstractPureGraphManager { abstract serializeExecutionNode(executionNode: ExecutionNode): object; + abstract analyzeExecuteInput( + data: PlainObject, + functionPath: string, + ): Promise<{ + origin: GraphDataOrigin; + entities: Entity[]; + }>; + // ------------------------------------------- Query ------------------------------------------- abstract searchQueries( diff --git a/packages/legend-graph/src/graph-manager/protocol/pure/v1/V1_PureGraphManager.ts b/packages/legend-graph/src/graph-manager/protocol/pure/v1/V1_PureGraphManager.ts index bff8210ca0..20948ba0a1 100644 --- a/packages/legend-graph/src/graph-manager/protocol/pure/v1/V1_PureGraphManager.ts +++ b/packages/legend-graph/src/graph-manager/protocol/pure/v1/V1_PureGraphManager.ts @@ -244,7 +244,11 @@ import { getNullableIDFromTestable, getNullableTestable, } from '../../../helpers/DSL_Data_GraphManagerHelper.js'; -import { pruneSourceInformation } from '../../../../graph/MetaModelUtils.js'; +import { + extractElementNameFromPath, + extractPackagePathFromPath, + pruneSourceInformation, +} from '../../../../graph/MetaModelUtils.js'; import { V1_buildModelCoverageAnalysisResult, V1_MappingModelCoverageAnalysisInput, @@ -347,6 +351,11 @@ import type { PersistentDataCube, } from '../../../action/query/PersistentDataCube.js'; import { V1_QueryParameterValue } from './engine/query/V1_Query.js'; +import { V1_Multiplicity } from './model/packageableElements/domain/V1_Multiplicity.js'; +import { + V1_buildFunctionSignature, + V1_createGenericTypeWithElementPath, +} from './helpers/V1_DomainHelper.js'; class V1_PureModelContextDataIndex { elements: V1_PackageableElement[] = []; @@ -3044,6 +3053,51 @@ export class V1_PureGraphManager extends AbstractPureGraphManager { return this.engine.cancelUserExecutions(broadcastToCluster); } + override async analyzeExecuteInput( + data: PlainObject, + functionPath: string, + ): Promise<{ origin: GraphDataOrigin; entities: Entity[] }> { + const executeInput = V1_ExecuteInput.serialization.fromJson(data); + let origin: GraphDataOrigin | undefined; + if (executeInput.model instanceof V1_PureModelContextData) { + origin = new GraphEntities( + this.pureModelContextDataToEntities(executeInput.model), + ); + } else if ( + executeInput.model instanceof V1_PureModelContextPointer && + executeInput.model.sdlcInfo instanceof V1_LegendSDLC + ) { + origin = new LegendSDLC( + executeInput.model.sdlcInfo.groupId, + executeInput.model.sdlcInfo.artifactId, + executeInput.model.sdlcInfo.version, + ); + } + + // TODO: we dont support runtime, mapping, parameter values, + // we assume the function does not take parameters and use ->from() to + // set execution context + const func = new V1_ConcreteFunctionDefinition(); + func.name = extractElementNameFromPath(functionPath); + func.package = extractPackagePathFromPath(functionPath) ?? 'test'; + func.body = executeInput.function.body as object[]; + func.returnMultiplicity = V1_Multiplicity.ZERO_ONE; + func.returnGenericType = V1_createGenericTypeWithElementPath( + CORE_PURE_PATH.ANY, + ); + func.name = V1_buildFunctionSignature(func); + + // TODO: we can also add a new Text element with the content of + // the execute input and instruction to go to debugger function + if (!origin) { + throw new Error(`Can't analyze execute input: no origin found`); + } + return { + origin, + entities: [this.elementProtocolToEntity(func)], + }; + } + // --------------------------------------------- Query --------------------------------------------- async searchQueries( diff --git a/packages/legend-graph/src/graph-manager/protocol/pure/v1/transformation/pureProtocol/V1_PureProtocolSerialization.ts b/packages/legend-graph/src/graph-manager/protocol/pure/v1/transformation/pureProtocol/V1_PureProtocolSerialization.ts index e67412807b..a346dbc35e 100644 --- a/packages/legend-graph/src/graph-manager/protocol/pure/v1/transformation/pureProtocol/V1_PureProtocolSerialization.ts +++ b/packages/legend-graph/src/graph-manager/protocol/pure/v1/transformation/pureProtocol/V1_PureProtocolSerialization.ts @@ -20,7 +20,6 @@ import { primitive, custom, optional, - SKIP, serialize, deserialize, object, @@ -235,6 +234,26 @@ export const V1_serializePureModelContext = ( ); }; +export const V1_deserializePureModelContext = ( + json: PlainObject, +): V1_PureModelContext => { + switch (json._type) { + case V1_PureModelContextType.POINTER: + return deserialize(V1_pureModelContextPointerModelSchema, json); + case V1_PureModelContextType.DATA: + return V1_deserializePureModelContextData(json); + case V1_PureModelContextType.COMPOSITE: + return deserialize(V1_pureModelContextCompositeModelSchema, json); + case V1_PureModelContextType.TEXT: + return deserialize(V1_pureModelContextTextSchema, json); + default: + throw new UnsupportedOperationError( + `Can't deserialize Pure model context`, + json, + ); + } +}; + export const V1_pureModelContextDataPropSchema = custom( (val: V1_PureModelContextData) => V1_serializePureModelContextData(val), (val: PlainObject) => @@ -243,6 +262,5 @@ export const V1_pureModelContextDataPropSchema = custom( export const V1_pureModelContextPropSchema = custom( (val: V1_PureModelContext) => V1_serializePureModelContext(val), - // TODO: we will populate this when we need to `deserialize` - (val) => SKIP, + (val) => V1_deserializePureModelContext(val), ); diff --git a/packages/legend-server-sdlc/src/SDLCServerClient.ts b/packages/legend-server-sdlc/src/SDLCServerClient.ts index b479fb266a..b9e3f9b4e4 100644 --- a/packages/legend-server-sdlc/src/SDLCServerClient.ts +++ b/packages/legend-server-sdlc/src/SDLCServerClient.ts @@ -252,6 +252,7 @@ export class SDLCServerClient extends AbstractServerClient { ); // ------------------------------------------- Project Access ------------------------------------------- + private _authorizedActionProject = (projectId: string): string => `${this._project(projectId)}/authorizedActions`; getAutorizedActions = (