UNPKG

@finos/legend-studio

Version:
320 lines (305 loc) 10.6 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 type { EditorStore } from '../EditorStore.js'; import { observable, action, makeAutoObservable } from 'mobx'; import { LEGEND_STUDIO_APP_EVENT } from '../LegendStudioAppEvent.js'; import type { TreeData } from '@finos/legend-art'; import { type GenerationTreeNodeData, type GenerationFile, type GenerationOutputResult, GenerationDirectory, GENERATION_FILE_ROOT_NAME, getGenerationTreeData, openNode, populateDirectoryTreeNodeChildren, buildGenerationDirectory, reprocessOpenNodes, } from '../shared/FileGenerationTreeUtil.js'; import { type GeneratorFn, assertErrorThrown, deepEqual, isEmpty, LogEvent, } from '@finos/legend-shared'; import { type FileGenerationSpecification, type GenerationOutput, type GenerationProperty, ConfigurationProperty, GenerationPropertyItemType, PackageableElement, PackageableElementReference, PackageableElementExplicitReference, ELEMENT_PATH_DELIMITER, getNullableFileGenerationConfig, } from '@finos/legend-graph'; import { configurationProperty_setValue, fileGeneration_addConfigurationProperty, fileGeneration_addScopeElement, fileGeneration_deleteScopeElement, } from '../graphModifier/DSLGeneration_GraphModifierHelper.js'; export class FileGenerationState { editorStore: EditorStore; fileGeneration: FileGenerationSpecification; isGenerating = false; root: GenerationDirectory; directoryTreeData?: TreeData<GenerationTreeNodeData> | undefined; selectedNode?: GenerationTreeNodeData | undefined; filesIndex = new Map<string, GenerationFile>(); constructor( editorStore: EditorStore, fileGeneration: FileGenerationSpecification, ) { makeAutoObservable(this, { editorStore: false, fileGeneration: false, directoryTreeData: observable.ref, selectedNode: observable.ref, resetFileGeneration: action, setIsGeneration: action, setDirectoryTreeData: action, processGenerationResult: action, reprocessNodeTree: action, setSelectedNode: action, onTreeNodeSelect: action, addScopeElement: action, deleteScopeElement: action, updateFileGenerationParameters: action, }); this.editorStore = editorStore; this.fileGeneration = fileGeneration; this.root = new GenerationDirectory(GENERATION_FILE_ROOT_NAME); } resetFileGeneration(): void { this.fileGeneration.configurationProperties = []; } setIsGeneration(isGenerating: boolean): void { this.isGenerating = isGenerating; } setDirectoryTreeData( directoryTreeData: TreeData<GenerationTreeNodeData>, ): void { this.directoryTreeData = directoryTreeData; } getOrCreateDirectory = (directoryName: string): GenerationDirectory => GenerationDirectory.getOrCreateDirectory(this.root, directoryName, true); *generate(): GeneratorFn<void> { this.isGenerating = true; try { // avoid wasting a network call when the scope is empty, we can short-circuit this if (!this.fileGeneration.scopeElements.length) { this.selectedNode = undefined; this.processGenerationResult([]); return; } const mode = this.editorStore.graphState.graphGenerationState.getFileGenerationConfiguration( this.fileGeneration.type, ).generationMode; const result = (yield this.editorStore.graphManagerState.graphManager.generateFile( this.fileGeneration, mode, this.editorStore.graphManagerState.graph, )) as GenerationOutput[]; this.processGenerationResult(result); } catch (error) { assertErrorThrown(error); this.selectedNode = undefined; this.processGenerationResult([]); this.editorStore.applicationStore.log.error( LogEvent.create(LEGEND_STUDIO_APP_EVENT.GENERATION_FAILURE), error, ); this.editorStore.applicationStore.notifyError(error); } finally { this.isGenerating = false; } } processGenerationResult(output: GenerationOutput[]): void { this.root = new GenerationDirectory(GENERATION_FILE_ROOT_NAME); this.filesIndex = new Map<string, GenerationFile>(); const openedNodeIds = this.directoryTreeData ? Array.from(this.directoryTreeData.nodes.values()) .filter((node) => node.isOpen) .map((node) => node.id) : []; const generationResultIndex = new Map<string, GenerationOutputResult>(); const rootFolder = this.fileGeneration.generationOutputPath ?? this.fileGeneration.path.split(ELEMENT_PATH_DELIMITER).join('_'); output.forEach((entry) => { entry.cleanFileName(rootFolder); if (generationResultIndex.has(entry.fileName)) { this.editorStore.applicationStore.log.warn( LogEvent.create(LEGEND_STUDIO_APP_EVENT.GENERATION_FAILURE), 'Found 2 generation outputs with same path', ); } generationResultIndex.set(entry.fileName, { generationOutput: entry, parentId: this.fileGeneration.path, }); }); // take generation outputs and put them into the root directory buildGenerationDirectory(this.root, generationResultIndex, this.filesIndex); this.directoryTreeData = getGenerationTreeData(this.root); this.reprocessNodeTree( Array.from(generationResultIndex.values()), this.directoryTreeData, openedNodeIds, ); } reprocessNodeTree( generationResult: GenerationOutputResult[], treeData: TreeData<GenerationTreeNodeData>, openedNodeIds: string[], ): void { reprocessOpenNodes(treeData, this.filesIndex, this.root, openedNodeIds); // select the current file node if available, else select the first output const selectedFileNodePath = generationResult.length === 1 || (this.selectedNode === undefined && generationResult.length !== 0) ? (generationResult[0] as GenerationOutputResult).generationOutput .fileName : this.selectedNode?.fileNode.path; if (selectedFileNodePath) { const file = this.filesIndex.get(selectedFileNodePath); if (file) { const node = openNode(file, treeData); if (node) { this.onTreeNodeSelect(node, treeData); } } else { this.selectedNode = undefined; } } this.setDirectoryTreeData({ ...treeData }); } setSelectedNode(node?: GenerationTreeNodeData): void { if (this.selectedNode) { this.selectedNode.isSelected = false; } if (node) { node.isSelected = true; } this.selectedNode = node; } onTreeNodeSelect( node: GenerationTreeNodeData, treeData: TreeData<GenerationTreeNodeData>, ): void { if (node.childrenIds?.length) { node.isOpen = !node.isOpen; if (node.fileNode instanceof GenerationDirectory) { populateDirectoryTreeNodeChildren(node, treeData); } } this.setSelectedNode(node); this.setDirectoryTreeData({ ...treeData }); } getScopeElement( element: PackageableElement | string, ): PackageableElementReference<PackageableElement> | string | undefined { return this.fileGeneration.scopeElements.find((el) => el instanceof PackageableElementReference ? el.value === element : element === el, ); } addScopeElement(element: PackageableElement | string): void { const el = this.getScopeElement(element); if (!el) { fileGeneration_addScopeElement( this.fileGeneration, element instanceof PackageableElement ? PackageableElementExplicitReference.create(element) : element, ); } } deleteScopeElement(element: PackageableElement | string): void { const el = this.getScopeElement(element); if (el) { fileGeneration_deleteScopeElement(this.fileGeneration, el); } } updateFileGenerationParameters( fileGeneration: FileGenerationSpecification, generationProperty: GenerationProperty, newValue: unknown, ): void { if (generationProperty.type === GenerationPropertyItemType.MAP) { if ( !newValue || isEmpty(newValue) || deepEqual(newValue, generationProperty.defaultValue) ) { fileGeneration.configurationProperties = fileGeneration.configurationProperties.filter( (e) => e.name !== generationProperty.name, ); } else { const configProperty = getNullableFileGenerationConfig( fileGeneration, generationProperty.name, ); if (configProperty) { configurationProperty_setValue(configProperty, { ...(newValue as object), }); } else { const newItem = new ConfigurationProperty( generationProperty.name, newValue, ); fileGeneration_addConfigurationProperty(fileGeneration, newItem); } } } else { const configProperty = getNullableFileGenerationConfig( fileGeneration, generationProperty.name, ); let useDefaultValue = generationProperty.defaultValue === newValue; if (generationProperty.type === GenerationPropertyItemType.BOOLEAN) { useDefaultValue = (generationProperty.defaultValue === 'true') === (newValue as boolean); } const newConfigValue = useDefaultValue ? undefined : newValue; if (newConfigValue !== undefined) { if (configProperty) { configurationProperty_setValue(configProperty, newConfigValue); } else { const newItem = new ConfigurationProperty( generationProperty.name, newConfigValue, ); fileGeneration_addConfigurationProperty(fileGeneration, newItem); } } else { fileGeneration.configurationProperties = fileGeneration.configurationProperties.filter( (e) => e.name !== generationProperty.name, ); } } } }