UNPKG

@finos/legend-application-studio

Version:
403 lines 21.4 kB
/** * Copyright (c) 2020-present, Goldman Sachs * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ import { observable, flow, action, computed, makeObservable, flowResult, } from 'mobx'; import { LogEvent, assertTrue, assertErrorThrown, guaranteeNonNullable, isNonNullable, ActionState, } from '@finos/legend-shared'; import { ElementEditorState } from './element-editor-state/ElementEditorState.js'; import { ElementFileGenerationState } from './element-editor-state/ElementFileGenerationState.js'; import { ArtifactGenerationExtensionResult, Class, Enumeration, GenerationSpecification, ELEMENT_PATH_DELIMITER, } from '@finos/legend-graph'; import { ExternalFormatState } from './ExternalFormatState.js'; import { ExplorerTreeRootPackageLabel } from '../ExplorerTreeState.js'; import { LEGEND_STUDIO_APP_EVENT } from '../../../__lib__/LegendStudioEvent.js'; import { FileSystem_Directory, FileSystem_File, GENERATION_FILE_ROOT_NAME, buildFileSystemDirectory, getFileSystemTreeData, reprocessOpenNodes, openNode, populateDirectoryTreeNodeChildren, } from '../utils/FileSystemTreeUtils.js'; import { ArtifactGenerationViewerState } from './ArtifactGenerationViewerState.js'; import { generationSpecification_addFileGeneration, generationSpecification_addGenerationElement, } from '../../graph-modifier/DSL_Generation_GraphModifierHelper.js'; export const DEFAULT_GENERATION_SPECIFICATION_NAME = 'MyGenerationSpecification'; export class DEPREACTED_GlobalFileGenerationState { graphGenerationState; editorStore; // NOTE: this will eventually be removed once we also do model/schema import using external format // See https://github.com/finos/legend-studio/issues/866 fileGenerationConfigurations = []; constructor(graphGenerationState, editorStore) { makeObservable(this, { fileGenerationConfigurations: observable, fileGenerationConfigurationOptions: computed, supportedFileGenerationConfigurationsForCurrentElement: computed, setFileGenerationConfigurations: action, fetchAvailableFileGenerationDescriptions: flow, DEPREACTED_generateFiles: flow, }); this.graphGenerationState = graphGenerationState; this.editorStore = editorStore; } get fileGenerationConfigurationOptions() { return this.fileGenerationConfigurations .toSorted((a, b) => a.label.localeCompare(b.label)) .map((config) => ({ label: config.label, value: config.key, })); } get supportedFileGenerationConfigurationsForCurrentElement() { if (this.editorStore.tabManagerState.currentTab instanceof ElementEditorState) { const currentElement = this.editorStore.tabManagerState.currentTab.element; // NOTE: For now we only allow classes and enumerations for all types of generations. const extraFileGenerationScopeFilterConfigurations = this.editorStore.pluginManager .getApplicationPlugins() .flatMap((plugin) => plugin.getExtraFileGenerationScopeFilterConfigurations?.() ?? []); return this.fileGenerationConfigurations.filter((generationType) => { const scopeFilters = extraFileGenerationScopeFilterConfigurations.filter((configuration) => configuration.type.toLowerCase() === generationType.key); if (scopeFilters.length) { return scopeFilters.some((scopeFilter) => scopeFilter.filter(currentElement)); } return (currentElement instanceof Class || currentElement instanceof Enumeration); }); } return []; } /** * Generated file generations in the graph. * NOTE: This method does not update graph and application only the files are generated. */ *DEPREACTED_generateFiles(generationOutputIndex) { try { const generationSpecs = this.editorStore.graphManagerState.graph.ownGenerationSpecifications; if (!generationSpecs.length) { return; } assertTrue(generationSpecs.length === 1, `Can't generate models: only one generation specification permitted to generate`); const generationSpec = generationSpecs[0]; const fileGenerations = generationSpec.fileGenerations; // we don't need to keep 'fetching' the main model as it won't grow with each file generation for (const fileGeneration of fileGenerations) { let result = []; try { const mode = this.editorStore.graphState.graphGenerationState.globalFileGenerationState.getFileGenerationConfiguration(fileGeneration.value.type).generationMode; result = (yield this.editorStore.graphManagerState.graphManager.generateFile(fileGeneration.value, mode, this.editorStore.graphManagerState.graph, this.editorStore.graphEditorMode.getGraphTextInputOption())); } catch (error) { assertErrorThrown(error); throw new Error(`Can't generate files using specification '${fileGeneration.value.path}'. Error: ${error.message}`); } generationOutputIndex.set(fileGeneration.value.path, result); } } catch (error) { assertErrorThrown(error); this.editorStore.applicationStore.logService.error(LogEvent.create(LEGEND_STUDIO_APP_EVENT.GENERATION_FAILURE), error); this.editorStore.graphState.editorStore.applicationStore.notificationService.notifyError(`${error.message}`); } } setFileGenerationConfigurations(fileGenerationConfigurations) { this.fileGenerationConfigurations = fileGenerationConfigurations; } getFileGenerationConfiguration(type) { return guaranteeNonNullable(this.fileGenerationConfigurations.find((config) => config.key === type), `Can't find configuration description for file generation type '${type}'`); } *fetchAvailableFileGenerationDescriptions() { try { const availableFileGenerationDescriptions = (yield this.editorStore.graphManagerState.graphManager.getAvailableGenerationConfigurationDescriptions()); this.setFileGenerationConfigurations(availableFileGenerationDescriptions); this.editorStore.elementGenerationStates = this.fileGenerationConfigurations.map((config) => new ElementFileGenerationState(this.editorStore, config.key)); } catch (error) { assertErrorThrown(error); this.editorStore.applicationStore.logService.error(LogEvent.create(LEGEND_STUDIO_APP_EVENT.GENERATION_FAILURE), error); this.editorStore.applicationStore.notificationService.notifyError(error); } } } export class GraphGenerationState { editorStore; isRunningGlobalGenerate = false; generatedEntities = new Map(); clearingGenerationEntitiesState = ActionState.create(); externalFormatState; globalFileGenerationState; // file generation output rootFileDirectory; filesIndex = new Map(); selectedNode; // Note for now generation of artifact generations is set to false. // This is to explore the performance impact of inlcuding this in the generation action and whether we always // generate artifacts or add inputs to the api to choose which ones we generate. enableArtifactGeneration = false; constructor(editorStore) { makeObservable(this, { isRunningGlobalGenerate: observable, generatedEntities: observable.shallow, clearingGenerationEntitiesState: observable, externalFormatState: observable, enableArtifactGeneration: observable, globalFileGenerationState: observable, rootFileDirectory: observable, filesIndex: observable, selectedNode: observable.ref, processGenerationResult: action, reprocessGenerationFileState: action, setEnableArtifactGeneration: action, reprocessNodeTree: action, onTreeNodeSelect: action, setSelectedNode: action, emptyGeneratedArtifacts: action, possiblyAddMissingGenerationSpecifications: flow, globalGenerate: flow, generateModels: flow, generateArtifacts: flow, clearGenerations: flow, }); this.editorStore = editorStore; this.externalFormatState = new ExternalFormatState(editorStore); this.globalFileGenerationState = new DEPREACTED_GlobalFileGenerationState(this, editorStore); this.rootFileDirectory = new FileSystem_Directory(GENERATION_FILE_ROOT_NAME); } setEnableArtifactGeneration(val) { this.enableArtifactGeneration = val; } findGenerationParentPath(genChildPath) { const genEntity = Array.from(this.generatedEntities.entries()).find(([, genEntities]) => genEntities.find((m) => m.path === genChildPath)); return genEntity?.[0]; } /** * Global generation is tied to the generation specification of the project. Every time a generation element * is added, they will be added to the generation specification */ *globalGenerate() { if (this.editorStore.graphState.checkIfApplicationUpdateOperationIsRunning()) { return; } this.isRunningGlobalGenerate = true; try { yield flowResult(this.generateModels()); yield flowResult(this.generateArtifacts()); } catch (error) { assertErrorThrown(error); this.editorStore.applicationStore.logService.error(LogEvent.create(LEGEND_STUDIO_APP_EVENT.GENERATION_FAILURE), error); this.editorStore.graphState.editorStore.applicationStore.notificationService.notifyError(`${error.message}`); } finally { this.isRunningGlobalGenerate = false; } } *generateModels() { try { this.generatedEntities = new Map(); // reset the map of generated entities const generationSpecs = this.editorStore.graphManagerState.graph.ownGenerationSpecifications; if (!generationSpecs.length) { return; } assertTrue(generationSpecs.length === 1, `Can't generate models: only one generation specification permitted to generate`); const generationSpec = generationSpecs[0]; const generationNodes = generationSpec.generationNodes; for (let i = 0; i < generationNodes.length; i++) { const node = generationNodes[i]; let generatedEntities = []; try { generatedEntities = (yield this.editorStore.graphManagerState.graphManager.generateModel(node.generationElement.value, this.editorStore.graphManagerState.graph, this.editorStore.graphEditorMode.getGraphTextInputOption())); } catch (error) { assertErrorThrown(error); throw new Error(`Can't generate models: failure occured at step ${i + 1} with specification '${node.generationElement.value.path}'. Error: ${error.message}`); } this.generatedEntities.set(node.generationElement.value.path, generatedEntities); yield flowResult(this.editorStore.graphState.updateGenerationGraphAndApplication()); } } catch (error) { assertErrorThrown(error); this.editorStore.applicationStore.logService.error(LogEvent.create(LEGEND_STUDIO_APP_EVENT.GENERATION_FAILURE), error); this.editorStore.graphState.editorStore.applicationStore.notificationService.notifyError(`${error.message}`); } } /** * Generated artifacts generations in graph * NOTE: This method does not update graph and application only the files are generated. */ *generateArtifacts() { try { this.emptyGeneratedArtifacts(); const generationOutputIndex = new Map(); // handle deprecated file generations yield flowResult(this.globalFileGenerationState.DEPREACTED_generateFiles(generationOutputIndex)); let artifacts = new ArtifactGenerationExtensionResult(); if (this.enableArtifactGeneration) { artifacts = (yield this.editorStore.graphManagerState.graphManager.generateArtifacts(this.editorStore.graphManagerState.graph, this.editorStore.graphEditorMode.getGraphTextInputOption())); } // handle results this.processGenerationResult(artifacts, generationOutputIndex); } catch (error) { assertErrorThrown(error); this.editorStore.applicationStore.logService.error(LogEvent.create(LEGEND_STUDIO_APP_EVENT.GENERATION_FAILURE), error); this.editorStore.graphState.editorStore.applicationStore.notificationService.notifyError(`${error.message}`); } } // Artifact Generation Tree processGenerationResult(artifacts, generationOutputIndex) { // empty file index and the directory, we keep the open nodes to reprocess them this.emptyGeneratedArtifacts(); const directoryTreeData = this.editorStore.graphState.editorStore.explorerTreeState .artifactsGenerationTreeData; const openedNodeIds = directoryTreeData ? Array.from(directoryTreeData.nodes.values()) .filter((node) => node.isOpen) .map((node) => node.id) : []; // we read the generation outputs and clean const generationResultIndex = new Map(); // handle DEPRECATED file gen for backward compatible Array.from(generationOutputIndex.entries()).forEach((entry) => { const generator = this.editorStore.graphManagerState.graph.getNullableFileGeneration(entry[0]); const rootFolder = generator?.generationOutputPath ?? generator?.path.split(ELEMENT_PATH_DELIMITER).join('_'); const generationOutputs = entry[1]; generationOutputs.forEach((genOutput) => { genOutput.cleanFileName(rootFolder); if (generationResultIndex.has(genOutput.fileName)) { this.editorStore.applicationStore.logService.warn(LogEvent.create(LEGEND_STUDIO_APP_EVENT.GENERATION_FAILURE), `Found 2 generation outputs with same path '${genOutput.fileName}'`); } generationResultIndex.set(genOutput.fileName, { value: genOutput, parentId: generator?.path, }); }); }); artifacts.values .map((v) => v.artifactsByExtensionElements) .flat() .forEach((artifactByElement) => { artifactByElement.files.forEach((genOutput) => { genOutput.cleanFileName(); if (generationResultIndex.has(genOutput.fileName)) { this.editorStore.applicationStore.logService.warn(LogEvent.create(LEGEND_STUDIO_APP_EVENT.GENERATION_FAILURE), `Found 2 generation outputs with same path '${genOutput.fileName}'`); } generationResultIndex.set(genOutput.fileName, { value: genOutput, parentId: artifactByElement.element, }); }); }); // take generation outputs and put them into the root directory buildFileSystemDirectory(this.rootFileDirectory, generationResultIndex, this.filesIndex); // after building root directory set the generation tree data this.editorStore.graphState.editorStore.explorerTreeState.setArtifactsGenerationTreeData(getFileSystemTreeData(this.editorStore.graphState.graphGenerationState.rootFileDirectory, ExplorerTreeRootPackageLabel.FILE_GENERATION)); this.editorStore.graphState.editorStore.explorerTreeState.setArtifactsGenerationTreeData(this.reprocessNodeTree(Array.from(generationResultIndex.values()), this.editorStore.graphState.editorStore.explorerTreeState.getArtifactsGenerationTreeData(), openedNodeIds)); this.editorStore.tabManagerState.tabs = this.editorStore.tabManagerState.tabs .map((e) => this.reprocessGenerationFileState(e)) .filter(isNonNullable); } reprocessGenerationFileState(editorState) { if (editorState instanceof ArtifactGenerationViewerState) { const fileNode = this.filesIndex.get(editorState.artifact.path); if (fileNode) { editorState.artifact = fileNode; return editorState; } else { return undefined; } } return editorState; } reprocessNodeTree(generationResult, treeData, openedNodeIds) { reprocessOpenNodes(treeData, this.filesIndex, this.rootFileDirectory, openedNodeIds, true); const selectedFileNodePath = this.selectedNode?.fileNode.path ?? (generationResult.length === 1 ? generationResult[0].value.fileName : undefined); if (selectedFileNodePath) { const file = this.filesIndex.get(selectedFileNodePath); if (file) { const node = openNode(file, treeData, true); if (node) { this.onTreeNodeSelect(node, treeData, true); } } else { this.selectedNode = undefined; } } return treeData; } onTreeNodeSelect(node, treeData, reprocess) { if (node.childrenIds?.length) { node.isOpen = !node.isOpen; if (node.fileNode instanceof FileSystem_Directory) { populateDirectoryTreeNodeChildren(node, treeData); } } if (!reprocess && node.fileNode instanceof FileSystem_File) { this.editorStore.graphEditorMode.openFileSystem_File(node.fileNode); } this.setSelectedNode(node); this.editorStore.graphState.editorStore.explorerTreeState.setArtifactsGenerationTreeData({ ...treeData }); } setSelectedNode(node) { if (this.selectedNode) { this.selectedNode.isSelected = false; } if (node) { node.isSelected = true; } this.selectedNode = node; } emptyGeneratedArtifacts() { this.filesIndex = new Map(); this.rootFileDirectory = new FileSystem_Directory(GENERATION_FILE_ROOT_NAME); } /** * Used to clear generation entities as well as the generation model */ *clearGenerations() { this.clearingGenerationEntitiesState.inProgress(); this.generatedEntities = new Map(); this.emptyGeneratedArtifacts(); yield flowResult(this.editorStore.graphState.updateGenerationGraphAndApplication()); this.clearingGenerationEntitiesState.complete(); } /** * Method adds generation specification if * 1. no generation specification has been defined in graph * 2. there exists a generation element */ *possiblyAddMissingGenerationSpecifications() { if (!this.editorStore.graphManagerState.graph.ownGenerationSpecifications .length) { const modelGenerationElements = this.editorStore.pluginManager .getPureGraphManagerPlugins() .flatMap((plugin) => plugin.getExtraModelGenerationElementGetters?.() ?? []) .flatMap((getter) => getter(this.editorStore.graphManagerState.graph)); const fileGenerations = this.editorStore.graphManagerState.graph.ownFileGenerations; if (modelGenerationElements.length || fileGenerations.length) { const generationSpec = new GenerationSpecification(DEFAULT_GENERATION_SPECIFICATION_NAME); modelGenerationElements.forEach((e) => generationSpecification_addGenerationElement(generationSpec, e)); fileGenerations.forEach((e) => generationSpecification_addFileGeneration(generationSpec, e)); // NOTE: add generation specification at the same package as the first generation element found. // we might want to revisit this decision? const specPackage = guaranteeNonNullable([...modelGenerationElements, ...fileGenerations][0]?.package); yield flowResult(this.editorStore.graphEditorMode.addElement(generationSpec, specPackage.path, false)); } } } } //# sourceMappingURL=GraphGenerationState.js.map