/** * 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 React, { Fragment, useRef, useEffect, useState, forwardRef, } from 'react'; import { observer } from 'mobx-react-lite'; import { type TreeNodeContainerProps, clsx, Dialog, MenuContent, MenuContentItem, MenuContentItemBlankIcon, MenuContentItemIcon, MenuContentItemLabel, ContextMenu, ControlledDropdownMenu, PanelLoadingIndicator, BlankPanelContent, TreeView, ChevronDownIcon, ChevronRightIcon, CompressIcon, FolderIcon, FolderOpenIcon, PlusIcon, LockIcon, ExclamationTriangleIcon, SearchIcon, FileImportIcon, SettingsEthernetIcon, MenuContentDivider, createFilter, CustomSelectorInput, type SelectComponent, LevelDownIcon, CaretDownIcon, PURE_ClassIcon, CodeIcon, } from '@finos/legend-art'; import { getElementIcon, getElementTypeIcon } from '../../ElementIconUtils.js'; import { getElementTypeLabel, CreateNewElementModal, } from './CreateNewElementModal.js'; import { useDrag } from 'react-dnd'; import { ElementDragSource } from '../../../stores/editor/utils/DnDUtils.js'; import { LEGEND_STUDIO_TEST_ID } from '../../../__lib__/LegendStudioTesting.js'; import { ACTIVITY_MODE, GRAPH_EDITOR_MODE, PANEL_MODE, } from '../../../stores/editor/EditorConfig.js'; import { getTreeChildNodes } from '../../../stores/editor/utils/PackageTreeUtils.js'; import type { PackageTreeNodeData } from '../../../stores/editor/utils/TreeUtils.js'; import { type FileSystemTreeNodeData, getFileSystemChildNodes, } from '../../../stores/editor/utils/FileSystemTreeUtils.js'; import { FileSystemTree } from '../editor-group/element-generation-editor/FileSystemViewer.js'; import { generateViewEntityRoute, generateViewProjectByGAVRoute, } from '../../../__lib__/LegendStudioNavigation.js'; import { guaranteeNonEmptyString, guaranteeNonNullable, guaranteeType, isNonNullable, toTitleCase, } from '@finos/legend-shared'; import { flowResult } from 'mobx'; import { useEditorStore } from '../EditorStoreProvider.js'; import { ELEMENT_PATH_DELIMITER, ROOT_PACKAGE_NAME, Package, isValidFullPath, isValidPath, isGeneratedElement, isSystemElement, isDependencyElement, isElementReadOnly, ConcreteFunctionDefinition, Class, type PackageableElement, isMainGraphElement, getFunctionSignature, getFunctionNameWithPath, getElementRootPackage, PackageableConnection, guaranteeRelationalDatabaseConnection, extractDependencyGACoordinateFromRootPackageName, Database, DEPENDENCY_ROOT_PACKAGE_PREFIX, Service, isRelationalDatabaseConnection, LegendSDLC, DataProduct, IngestDefinition, isAccessorDataProductOrIngestDefinition, } from '@finos/legend-graph'; import { ActionAlertActionType, ActionAlertType, useApplicationStore, } from '@finos/legend-application'; import { getPackageableElementOptionFormatter, type PackageableElementOption, } from '@finos/legend-lego/graph-editor'; import { LegendSQLPlaygroundModal } from '../LegendSQLPlaygroundModal.js'; import { PACKAGEABLE_ELEMENT_GROUP_BY_CATEGORY, PACKAGEABLE_ELEMENT_TYPE, } from '../../../stores/editor/utils/ModelClassifierUtils.js'; import { useLegendStudioApplicationStore } from '../../LegendStudioFrameworkProvider.js'; import { queryClass } from '../editor-group/uml-editor/ClassQueryBuilder.js'; import { createViewSDLCProjectHandler } from '../../../stores/editor/DependencyProjectViewerHelper.js'; import { MASTER_SNAPSHOT_ALIAS, SNAPSHOT_VERSION_ALIAS, } from '@finos/legend-server-depot'; import { CLASS_MOCK_DATA_GENERATION_FORMAT, createMockDataForClassWithFormat, } from '../../../stores/editor/utils/MockDataUtils.js'; import { CODE_EDITOR_LANGUAGE } from '@finos/legend-code-editor'; import { CodeEditor } from '@finos/legend-lego/code-editor'; import { DatabaseBuilderWizard } from '../editor-group/connection-editor/DatabaseBuilderWizard.js'; import { DatabaseModelBuilder } from '../editor-group/connection-editor/DatabaseModelBuilder.js'; import { queryService } from '../editor-group/service-editor/ServiceExecutionQueryEditor.js'; import { isElementSupportedByDataCube, openDataCube, } from '../../../stores/editor/data-cube/LegendStudioDataCubeHelper.js'; import { queryDataProduct } from '../editor-group/dataProduct/DataProductQueryBuilderHelper.js'; import { queryAccessorSource } from '../editor-group/accessor/AccessorQueryBuilderHelper.js'; const ElementRenamer = observer(() => { const editorStore = useEditorStore(); const applicationStore = useApplicationStore(); const explorerTreeState = editorStore.explorerTreeState; const element = explorerTreeState.elementToRename; const [path, setPath] = useState( (element instanceof ConcreteFunctionDefinition ? getFunctionNameWithPath(element) : element?.path) ?? '', ); const [canRenameElement, setCanRenameElement] = useState(false); const [ elementRenameValidationErrorMessage, setElementRenameValidationErrorMessage, ] = useState(''); const pathInputRef = useRef(null); const changePath: React.ChangeEventHandler = ( event, ): void => { const currentValue = event.target.value; setPath(currentValue); const isElementPathNonEmpty = currentValue !== ''; const isNotTopLevelElement = element instanceof Package || currentValue.includes(ELEMENT_PATH_DELIMITER); const isValidElementPath = (element instanceof Package && isValidPath(currentValue)) || isValidFullPath(currentValue); let existingElement = editorStore.graphManagerState.graph.getNullableElement( currentValue, true, ); existingElement = existingElement instanceof Package ? isMainGraphElement(existingElement) ? existingElement : undefined : existingElement; const isElementUnique = !existingElement || existingElement === element; const errorMessage = !isElementPathNonEmpty ? `Element path cannot be empty` : !isNotTopLevelElement ? `Creating top level element is not allowed` : !isValidElementPath ? `Element path is not valid` : !isElementUnique ? `Element of the same path already existed` : ''; setElementRenameValidationErrorMessage(errorMessage); setCanRenameElement( isElementPathNonEmpty && isNotTopLevelElement && isValidElementPath && isElementUnique, ); }; const rename = (event: React.MouseEvent): void => { event.preventDefault(); if (element && canRenameElement) { explorerTreeState.setElementToRename(undefined); flowResult( editorStore.graphEditorMode.renameElement( element, element instanceof ConcreteFunctionDefinition ? path + getFunctionSignature(element) : path, ), ) .then(() => { setCanRenameElement(false); setElementRenameValidationErrorMessage(''); }) .catch(applicationStore.alertUnhandledError); } }; const abort = (): void => { setCanRenameElement(false); setElementRenameValidationErrorMessage(''); explorerTreeState.setElementToRename(undefined); }; const onEnter = (): void => pathInputRef.current?.focus(); useEffect(() => { if (element) { setPath( element instanceof ConcreteFunctionDefinition ? getFunctionNameWithPath(element) : element.path, ); } }, [element]); return (
Rename Element
{elementRenameValidationErrorMessage && (
{elementRenameValidationErrorMessage}
)}
); }); const GENERATION_DEFAULT_DEPTH = 100; const GENERATION_DEFAULT_FORMAT = CLASS_MOCK_DATA_GENERATION_FORMAT.JSON; const getMockDataEditorLanguage = ( format: CLASS_MOCK_DATA_GENERATION_FORMAT, ): CODE_EDITOR_LANGUAGE => { switch (format) { case CLASS_MOCK_DATA_GENERATION_FORMAT.JSON: return CODE_EDITOR_LANGUAGE.JSON; case CLASS_MOCK_DATA_GENERATION_FORMAT.XML: return CODE_EDITOR_LANGUAGE.XML; case CLASS_MOCK_DATA_GENERATION_FORMAT.YAML: return CODE_EDITOR_LANGUAGE.YAML; default: return CODE_EDITOR_LANGUAGE.TEXT; } }; const SampleDataGenerator = observer(() => { const editorStore = useEditorStore(); const applicationStore = editorStore.applicationStore; const explorerTreeState = editorStore.explorerTreeState; const selectedClass = explorerTreeState.classToGenerateSampleData; const [format, setFormat] = useState(GENERATION_DEFAULT_FORMAT); const [depth, setDepth] = useState(GENERATION_DEFAULT_DEPTH); const [resultText, setResultText] = useState(''); const changeDepth: React.ChangeEventHandler = (event) => { setDepth(parseInt(event.target.value, 10)); }; // class const classSelectorRef = useRef(null); const elementFilterOption = createFilter({ ignoreCase: true, ignoreAccents: false, stringify: (option: { data: PackageableElementOption }): string => option.data.value.path, }); const classOptions = editorStore.graphManagerState.usableClasses.map( (_class) => ({ value: _class, label: _class.name, }), ); const selectedClassOption = selectedClass ? { value: selectedClass, label: selectedClass.name, } : null; const changeClass = (val: PackageableElementOption): void => { if (val.value === selectedClass) { return; } explorerTreeState.setClassToGenerateSampleData(val.value); }; useEffect(() => { if (selectedClass) { setResultText( createMockDataForClassWithFormat(selectedClass, format, depth), ); } }, [selectedClass, format, depth]); const abort = (): void => { setFormat(GENERATION_DEFAULT_FORMAT); setDepth(GENERATION_DEFAULT_DEPTH); explorerTreeState.setClassToGenerateSampleData(undefined); }; const regenerate = (): void => { if (selectedClass) { setResultText( createMockDataForClassWithFormat(selectedClass, format, depth), ); } }; const onEnter = (): void => classSelectorRef.current?.focus(); return (
Generate Sample Data
{Object.values(CLASS_MOCK_DATA_GENERATION_FORMAT).map((val) => ( setFormat(val)} > {val} ))} } >
{format}
); }); const isRelationalDatabase = ( val: PackageableElement | undefined, ): Database | undefined => (val instanceof Database ? val : undefined); const ExplorerContextMenu = observer( forwardRef< HTMLDivElement, { node?: PackageTreeNodeData | undefined; nodeIsImmutable?: boolean | undefined; } >(function ExplorerContextMenu(props, ref) { const { node, nodeIsImmutable } = props; const editorStore = useEditorStore(); const applicationStore = useLegendStudioApplicationStore(); const extraExplorerContextMenuItems = editorStore.pluginManager .getApplicationPlugins() .flatMap( (plugin) => plugin.getExtraExplorerContextMenuItemRendererConfigurations?.() ?? [], ) .map((config) => { const action = config.renderer(editorStore, node?.packageableElement); if (!action) { return null; } return {action}; }) .filter(isNonNullable); const projectId = editorStore.sdlcState.currentProject?.projectId; const isReadOnly = editorStore.disableGraphEditing || Boolean(nodeIsImmutable); const isDependencyProjectElement = node && isDependencyElement(node.packageableElement); const _package = node ? node.packageableElement instanceof Package ? node.packageableElement : undefined : editorStore.graphManagerState.graph.root; const elementTypesWithCategory = _package === editorStore.graphManagerState.graph.root ? new Map([ [ PACKAGEABLE_ELEMENT_GROUP_BY_CATEGORY.MODEL, [PACKAGEABLE_ELEMENT_TYPE.PACKAGE], ], ]) : editorStore.supportedElementTypesWithCategory; // actions const buildQuery = editorStore.applicationStore.guardUnhandledError( async () => { if (node?.packageableElement instanceof Class) { await queryClass(node.packageableElement, editorStore); } }, ); const buildDataProductQuery = editorStore.applicationStore.guardUnhandledError(async () => { if (node?.packageableElement instanceof DataProduct) { await queryDataProduct(node.packageableElement, editorStore); } }); const openLegendSqlPlayground = (): void => { if ( node?.packageableElement && isAccessorDataProductOrIngestDefinition(node.packageableElement) ) { editorStore.legendSQLStudioPlaygroundState.open( node.packageableElement, ); } }; const buildServiceQuery = editorStore.applicationStore.guardUnhandledError( async () => { if (node?.packageableElement instanceof Service) { await queryService(node.packageableElement, editorStore); } }, ); const buildAccessorQuery = editorStore.applicationStore.guardUnhandledError( async () => { if ( node?.packageableElement instanceof IngestDefinition || node?.packageableElement instanceof Database ) { await queryAccessorSource(node.packageableElement, editorStore); } }, ); const openCubeViewer = editorStore.applicationStore.guardUnhandledError( async () => { if (node?.packageableElement) { await openDataCube(node.packageableElement, editorStore); } }, ); const generateSampleData = editorStore.applicationStore.guardUnhandledError( async () => { if (node?.packageableElement instanceof Class) { editorStore.explorerTreeState.setClassToGenerateSampleData( node.packageableElement, ); } }, ); const buildDatabase = editorStore.applicationStore.guardUnhandledError( async () => { if (isRelationalDatabaseConnection(node?.packageableElement)) { editorStore.explorerTreeState.buildDatabase( guaranteeRelationalDatabaseConnection(node?.packageableElement), editorStore.isInViewerMode, ); } }, ); const generateModelsFromDatabaseSpecification = editorStore.applicationStore.guardUnhandledError(async () => { const database = isRelationalDatabase(node?.packageableElement); if (database) { if (database.joins.length === 0) { applicationStore.alertService.setActionAlertInfo({ message: 'You are attempting to build models but have defined no joins. Are you sure you wish to proceed?', type: ActionAlertType.CAUTION, actions: [ { label: 'Proceed', type: ActionAlertActionType.PROCEED_WITH_CAUTION, handler: () => { editorStore.explorerTreeState.buildDatabaseModels( database, editorStore.disableGraphEditing, ); }, }, { label: 'Abort', type: ActionAlertActionType.PROCEED, default: true, }, ], }); } else { editorStore.explorerTreeState.buildDatabaseModels( database, editorStore.disableGraphEditing, ); } } }); const openSQLPlayground = (): void => { if (isRelationalDatabaseConnection(node?.packageableElement)) { editorStore.panelGroupDisplayState.open(); editorStore.setActivePanelMode(PANEL_MODE.SQL_PLAYGROUND); editorStore.studioSqlPlaygroundState.setConnection( guaranteeType(node?.packageableElement, PackageableConnection), ); } }; const removeElement = (): void => { if (node) { flowResult( editorStore.graphEditorMode.deleteElement(node.packageableElement), ).catch(applicationStore.alertUnhandledError); } }; const renameElement = (): void => { if (node) { editorStore.explorerTreeState.setElementToRename( node.packageableElement, ); } }; const openElementInViewerMode = (): void => { if (node && projectId) { applicationStore.navigationService.navigator.visitAddress( applicationStore.navigationService.navigator.generateAddress( generateViewEntityRoute(projectId, node.packageableElement.path), ), ); } }; const copyPath = (): void => { if (node) { applicationStore.clipboardService .copyTextToClipboard(node.packageableElement.path) .then(() => applicationStore.notificationService.notifySuccess( 'Copied element path to clipboard', ), ) .catch(applicationStore.alertUnhandledError); } }; const copyWorkspaceElementLink = (): void => { if (node) { const dependency = editorStore.projectConfigurationEditorState.projectConfiguration?.projectDependencies.find( (dep) => DEPENDENCY_ROOT_PACKAGE_PREFIX + dep.projectId === getElementRootPackage(node.packageableElement).name, ); if (dependency) { applicationStore.clipboardService .copyTextToClipboard( applicationStore.navigationService.navigator.generateAddress( editorStore.editorMode.generateDependencyElementLink( node.packageableElement.path, dependency, ), ), ) .then(() => applicationStore.notificationService.notifySuccess( 'Copied workspace element link to clipboard', ), ) .catch(applicationStore.alertUnhandledError); } else { applicationStore.clipboardService .copyTextToClipboard( applicationStore.navigationService.navigator.generateAddress( editorStore.editorMode.generateElementLink( node.packageableElement.path, ), ), ) .then(() => applicationStore.notificationService.notifySuccess( 'Copied workspace element link to clipboard', ), ) .catch(applicationStore.alertUnhandledError); } } }; const copySDLCProjectLink = (): void => { if (node) { const dependency = editorStore.projectConfigurationEditorState.projectConfiguration?.projectDependencies.find( (dep) => DEPENDENCY_ROOT_PACKAGE_PREFIX + dep.projectId === getElementRootPackage(node.packageableElement).name, ); if (dependency) { applicationStore.clipboardService .copyTextToClipboard( applicationStore.navigationService.navigator.generateAddress( generateViewProjectByGAVRoute( guaranteeNonNullable(dependency.groupId), guaranteeNonNullable(dependency.artifactId), dependency.versionId === MASTER_SNAPSHOT_ALIAS ? SNAPSHOT_VERSION_ALIAS : dependency.versionId, ), ), ) .then(() => applicationStore.notificationService.notifySuccess( 'Copied SDLC project link to clipboard', ), ) .catch(applicationStore.alertUnhandledError); } } }; const createNewElement = (type: string): (() => void) => (): void => editorStore.newElementState.openModal(type, _package); const isDependencyProjectRoot = (): boolean => node?.packageableElement instanceof Package && editorStore.graphManagerState.graph.dependencyManager.roots.includes( node.packageableElement, ); const viewProject = (): void => { if (node?.packageableElement.name) { const projectOrigin = editorStore.graphManagerState.graph.dependencyManager.projectDependencyModelsIndex.get( node.packageableElement.name.substring( DEPENDENCY_ROOT_PACKAGE_PREFIX.length, ), )?.origin; if (projectOrigin instanceof LegendSDLC) { applicationStore.navigationService.navigator.visitAddress( applicationStore.navigationService.navigator.generateAddress( generateViewProjectByGAVRoute( guaranteeNonNullable(projectOrigin.groupId), guaranteeNonNullable(projectOrigin.artifactId), projectOrigin.versionId === MASTER_SNAPSHOT_ALIAS ? SNAPSHOT_VERSION_ALIAS : projectOrigin.versionId, ), ), ); } } }; const viewSDLCProject = (): void => { if (node?.packageableElement.name) { const sdlcProjectOrigin = editorStore.graphManagerState.graph.dependencyManager.projectDependencyModelsIndex.get( node.packageableElement.name.substring( DEPENDENCY_ROOT_PACKAGE_PREFIX.length, ), )?.origin; if (sdlcProjectOrigin instanceof LegendSDLC) { createViewSDLCProjectHandler( applicationStore, editorStore.depotServerClient, )( guaranteeNonEmptyString(sdlcProjectOrigin.groupId), guaranteeNonEmptyString(sdlcProjectOrigin.artifactId), ).catch(applicationStore.alertUnhandledError); } } }; if (isDependencyProjectRoot()) { return ( View Project {node && ( View SDLC Project )} ); } if (_package && !isReadOnly) { return ( {Array.from(elementTypesWithCategory.entries()).map((entry) => (
{entry[0]}
{entry[1].map((type) => ( {getElementTypeIcon(type, editorStore)} {toTitleCase(getElementTypeLabel(editorStore, type))} ))}
))} {node && ( <> Rename Remove )}
); } if (!node) { return null; } return ( {node.packageableElement instanceof DataProduct && ( <> Query... Run SQL... )} {node.packageableElement instanceof Class && ( <> Query... Generate Sample Data... )} {node.packageableElement instanceof Service && ( <> Query... )} {node.packageableElement instanceof IngestDefinition && ( <> Query... Run SQL... )} {isElementSupportedByDataCube(node.packageableElement) && ( <> Data Cube (BETA)... )} {isRelationalDatabaseConnection(node.packageableElement) && ( <> Execute SQL... Build Database... )} {isRelationalDatabase(node.packageableElement) && ( <> Query... Build Models )} {extraExplorerContextMenuItems} {Boolean(extraExplorerContextMenuItems.length) && ( )} {!isReadOnly && ( <> Rename Remove )} {!editorStore.isInViewerMode && !isDependencyProjectElement && ( View in Project )} Copy Path Copy Link {isDependencyProjectElement && ( Copy SDLC Project Link )} ); }), ); const ProjectConfig = observer(() => { const editorStore = useEditorStore(); const openConfigurationEditor = (): void => editorStore.tabManagerState.openTab( editorStore.projectConfigurationEditorState, ); const isSelected = editorStore.tabManagerState.currentTab === editorStore.projectConfigurationEditorState && // if we select non-element like packages, we need to deselect project configuration // so maybe a good TODO is to move this to explorer tree state !editorStore.explorerTreeState.selectedNode; return (
); }); const PackageTreeNodeContainer = observer( ( props: TreeNodeContainerProps< PackageTreeNodeData, { disableContextMenu: boolean; isContextImmutable?: boolean } >, ) => { const { node, level, stepPaddingInRem, onNodeSelect, innerProps } = props; const editorStore = useEditorStore(); const [isSelectedFromContextMenu, setIsSelectedFromContextMenu] = useState(false); const { disableContextMenu, isContextImmutable } = innerProps; const [, dragConnector] = useDrag( () => ({ type: node.dndType, item: new ElementDragSource(node), }), [node], ); const ref = useRef(null); dragConnector(ref); const isPackage = node.packageableElement instanceof Package; const expandIcon = !isPackage ? (
) : node.isOpen ? ( ) : ( ); const iconPackageColor = isGeneratedElement(node.packageableElement) ? 'color--generated' : isSystemElement(node.packageableElement) ? 'color--system' : isDependencyElement(node.packageableElement) ? 'color--dependency' : ''; const nodeIcon = isPackage ? ( node.isOpen ? (
) : (
) ) : ( getElementIcon(node.packageableElement, editorStore) ); const selectNode = (): void => onNodeSelect?.(node); const onContextMenuOpen = (): void => setIsSelectedFromContextMenu(true); const onContextMenuClose = (): void => setIsSelectedFromContextMenu(false); return ( } disabled={disableContextMenu} menuProps={{ elevation: 7 }} onOpen={onContextMenuOpen} onClose={onContextMenuClose} >
{expandIcon}
{nodeIcon}
); }, ); const ExplorerDropdownMenu = observer(() => { const editorStore = useEditorStore(); const _package = editorStore.explorerTreeState.getSelectedNodePackage(); const createNewElement = (type: string): (() => void) => (): void => editorStore.newElementState.openModal(type, _package); const elementTypesWithCategory = _package === editorStore.graphManagerState.graph.root ? new Map([ [ PACKAGEABLE_ELEMENT_GROUP_BY_CATEGORY.MODEL, [PACKAGEABLE_ELEMENT_TYPE.PACKAGE], ], ]) : editorStore.supportedElementTypesWithCategory; return ( {Array.from(elementTypesWithCategory.entries()).map((entry) => (
{entry[0]}
{entry[1].map((type) => ( {getElementTypeIcon(type, editorStore)} {toTitleCase(getElementTypeLabel(editorStore, type))} ))}
))}
); }); const ExplorerTrees = observer(() => { const editorStore = useEditorStore(); const { disableGraphEditing } = editorStore; const isInGrammarTextMode = editorStore.graphEditorMode.mode === GRAPH_EDITOR_MODE.GRAMMAR_TEXT; const openModelImport = (): void => editorStore.tabManagerState.openTab(editorStore.modelImporterState); const graph = editorStore.graphManagerState.graph; // Explorer tree const treeData = editorStore.explorerTreeState.getTreeData(); const selectedTreeNode = editorStore.explorerTreeState.selectedNode; const onNodeSelect = (node: PackageTreeNodeData): void => editorStore.explorerTreeState.onTreeNodeSelect(node, treeData); const getChildNodes = (node: PackageTreeNodeData): PackageTreeNodeData[] => getTreeChildNodes(editorStore, node, treeData); const deselectTreeNode = (): void => { if (selectedTreeNode) { selectedTreeNode.isSelected = false; editorStore.explorerTreeState.setTreeData({ ...treeData }); } editorStore.explorerTreeState.setSelectedNode(undefined); }; // Generated Tree const generationTreeData = editorStore.explorerTreeState.getTreeData( ROOT_PACKAGE_NAME.MODEL_GENERATION, ); const onGenerationTreeNodeSelect = (node: PackageTreeNodeData): void => editorStore.explorerTreeState.onTreeNodeSelect( node, generationTreeData, ROOT_PACKAGE_NAME.MODEL_GENERATION, ); const getGenerationTreeChildNodes = ( node: PackageTreeNodeData, ): PackageTreeNodeData[] => getTreeChildNodes(editorStore, node, generationTreeData); // Generated Files Tree const generationFileTreeData = editorStore.explorerTreeState.getArtifactsGenerationTreeData(); const onGenerationFileTreeNodeSelect = (node: FileSystemTreeNodeData): void => editorStore.graphState.graphGenerationState.onTreeNodeSelect( node, generationFileTreeData, ); const getGenerationFileTreeChildNodes = ( node: FileSystemTreeNodeData, ): FileSystemTreeNodeData[] => getFileSystemChildNodes(node, generationFileTreeData); // System Tree const systemTreeData = editorStore.explorerTreeState.getTreeData( ROOT_PACKAGE_NAME.SYSTEM, ); const onSystemTreeNodeSelect = (node: PackageTreeNodeData): void => editorStore.explorerTreeState.onTreeNodeSelect( node, systemTreeData, ROOT_PACKAGE_NAME.SYSTEM, ); const getSystemTreeChildNodes = ( node: PackageTreeNodeData, ): PackageTreeNodeData[] => getTreeChildNodes(editorStore, node, systemTreeData); // Dependency Tree const dependencyTreeData = editorStore.explorerTreeState.getTreeData( ROOT_PACKAGE_NAME.PROJECT_DEPENDENCY_ROOT, ); const onDependencyTreeSelect = (node: PackageTreeNodeData): void => editorStore.explorerTreeState.onTreeNodeSelect( node, dependencyTreeData, ROOT_PACKAGE_NAME.PROJECT_DEPENDENCY_ROOT, ); const getDependencyTreeChildNodes = ( node: PackageTreeNodeData, ): PackageTreeNodeData[] => getTreeChildNodes(editorStore, node, dependencyTreeData, true); const showPackageTrees = treeData.nodes.size || graph.dependencyManager.hasDependencies; return ( } menuProps={{ elevation: 7 }} >
{editorStore.explorerTreeState.buildState.hasCompleted && showPackageTrees && ( <> {/* MAIN PROJECT TREE */} {editorStore.explorerTreeState.databaseBuilderState && ( )} {editorStore.explorerTreeState.databaseModelBuilderState && ( )} {editorStore.projectConfigurationEditorState .projectConfiguration && } {/* SYSTEM TREE */} {Boolean( editorStore.graphManagerState.systemModel.allOwnElements.length, ) && ( )} {/* DEPENDENCY TREE */} {graph.dependencyManager.hasDependencies && ( )} {/* GENERATION SPECIFICATION */} {Boolean(graph.generationModel.allOwnElements.length) && ( )}
{/* FILE GENERATION SPECIFICATION */} {Boolean( editorStore.graphState.graphGenerationState.rootFileDirectory .children.length, ) && ( <>
)} )} {editorStore.explorerTreeState.buildState.hasCompleted && !showPackageTrees && (
Your workspace is empty, you can add elements or load existing model/entites for quick adding
)}
); }); const ProjectExplorerActionPanel = observer((props: { disabled: boolean }) => { const { disabled } = props; const editorStore = useEditorStore(); const isInGrammarMode = editorStore.graphEditorMode.mode === GRAPH_EDITOR_MODE.GRAMMAR_TEXT; const showSearchModal = (): void => editorStore.setShowSearchElementCommand(true); // Explorer tree const selectedTreeNode = editorStore.explorerTreeState.selectedNode; const collapseTree = (): void => { const treeData = editorStore.explorerTreeState.getTreeData(); treeData.nodes.forEach((node) => { node.isOpen = false; }); editorStore.explorerTreeState.setTreeData({ ...treeData }); }; const showModelImporter = (): void => editorStore.tabManagerState.openTab(editorStore.modelImporterState); const openConfigurationEditor = (): void => editorStore.tabManagerState.openTab( editorStore.projectConfigurationEditorState, ); return (
{ } {editorStore.editorMode.supportSdlcOperations && ( )} {!editorStore.disableGraphEditing && ( } menuProps={{ anchorOrigin: { vertical: 'bottom', horizontal: 'left' }, transformOrigin: { vertical: 'top', horizontal: 'left' }, elevation: 7, }} > )}
); }); export const Explorer = observer(() => { const editorStore = useEditorStore(); const applicationStore = useApplicationStore(); const sdlcState = editorStore.sdlcState; const isLoading = ((!editorStore.explorerTreeState.buildState.hasCompleted && editorStore.graphEditorMode.mode !== GRAPH_EDITOR_MODE.GRAMMAR_TEXT) || editorStore.graphState.isUpdatingGraph) && !editorStore.graphManagerState.graphBuildState.hasFailed; const showExplorerTrees = editorStore.graphManagerState.graphBuildState.hasSucceeded && editorStore.explorerTreeState.buildState.hasCompleted && // NOTE: if not in viewer mode, we would only show the explorer tree // when graph is properly observed to make sure edit after that can trigger // change detection. Realistically, this doesn't not affect user as they // don't edit elements that fast in form mode, but this could throw off // test runner (editorStore.isInViewerMode || editorStore.graphEditorMode.mode === GRAPH_EDITOR_MODE.GRAMMAR_TEXT || editorStore.changeDetectionState.graphObserveState.hasSucceeded); // conflict resolution const showConflictResolutionContent = editorStore.isInConflictResolutionMode && !editorStore.conflictResolutionState.hasResolvedAllConflicts; const goToConflictResolutionTab = (): void => editorStore.setActiveActivity(ACTIVITY_MODE.CONFLICT_RESOLUTION); const buildGrapnInConflictResolutionMode = (): void => { editorStore.conflictResolutionState.confirmHasResolvedAllConflicts(); flowResult( editorStore.conflictResolutionState.buildGraphInConflictResolutionMode(), ).catch(applicationStore.alertUnhandledError); }; return (
EXPLORER
{editorStore.editorMode.disableEditing && !editorStore.editorMode.label && (
READ-ONLY
)} {editorStore.editorMode.label && (
{editorStore.editorMode.label}
)}
{sdlcState.currentProject && ( <>
{sdlcState.currentWorkspace && !editorStore.isInViewerMode ? 'workspace' : 'project'}
{editorStore.isInViewerMode && sdlcState.currentProject.name} {!editorStore.isInViewerMode && (sdlcState.currentWorkspace?.workspaceId ?? '(unknown) ')}
)}
{editorStore.explorerTreeState.buildState.hasCompleted && ( )}
{showConflictResolutionContent && ( <> {!editorStore.conflictResolutionState.conflicts.length && (
All conflicts have been resolved, you can build the graph now to start testing your changes
)} {Boolean( editorStore.conflictResolutionState.conflicts.length, ) && (
{`Can't build graph as workspace contains merge conflicts, please resolve them before trying to build the graph again`}
)} )} {!showConflictResolutionContent && ( <> {showExplorerTrees && } {!showExplorerTrees && !editorStore.graphManagerState.graphBuildState.hasFailed && (
{editorStore.initState.message ?? editorStore.graphManagerState.systemBuildState .message ?? editorStore.graphManagerState.dependenciesBuildState .message ?? editorStore.graphManagerState.generationsBuildState .message ?? editorStore.graphManagerState.graphBuildState.message ?? editorStore.changeDetectionState.graphObserveState .message}
)} {!showExplorerTrees && editorStore.graphManagerState.graphBuildState.hasFailed && (
Failed to build graph
)} )}
{editorStore.legendSQLStudioPlaygroundState.isOpen && ( )}
); });