UNPKG

@finos/legend-application-studio

Version:
589 lines 49.8 kB
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime"; /** * 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 { 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, 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 { 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, 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, } 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 { QueryDatabaseState } from '../../../stores/editor/editor-state/element-editor-state/database/QueryDatabaseState.js'; import { isElementSupportedByDataCube, openDataCube, } from '../../../stores/editor/data-cube/LegendStudioDataCubeHelper.js'; import { queryDataProduct } from '../editor-group/dataProduct/DataProductQueryBuilderHelper.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 = (event) => { 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) => { 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 = () => { setCanRenameElement(false); setElementRenameValidationErrorMessage(''); explorerTreeState.setElementToRename(undefined); }; const onEnter = () => pathInputRef.current?.focus(); useEffect(() => { if (element) { setPath(element instanceof ConcreteFunctionDefinition ? getFunctionNameWithPath(element) : element.path); } }, [element]); return (_jsx(Dialog, { open: Boolean(element), onClose: abort, slotProps: { transition: { onEnter: onEnter, }, paper: { classes: { root: 'search-modal__inner-container' }, }, }, classes: { container: 'search-modal__container' }, children: _jsxs("form", { className: "modal modal--dark search-modal explorer__element-renamer", children: [_jsx("div", { className: "modal__title", children: "Rename Element" }), _jsxs("div", { className: "input-group", children: [_jsx("input", { className: "input-group__input input--dark explorer__element-renamer__input", ref: pathInputRef, value: path, placeholder: "Enter element path", onChange: changePath }), elementRenameValidationErrorMessage && (_jsx("div", { className: "input-group__error-message", children: elementRenameValidationErrorMessage }))] }), _jsxs("div", { className: "search-modal__actions", children: [_jsx("button", { type: "button", className: "btn btn--dark", onClick: abort, children: "Cancel" }), _jsx("button", { className: "btn btn--dark", disabled: !canRenameElement, onClick: rename, children: "Rename" })] })] }) })); }); const GENERATION_DEFAULT_DEPTH = 100; const GENERATION_DEFAULT_FORMAT = CLASS_MOCK_DATA_GENERATION_FORMAT.JSON; const getMockDataEditorLanguage = (format) => { 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 = (event) => { setDepth(parseInt(event.target.value, 10)); }; // class const classSelectorRef = useRef(null); const elementFilterOption = createFilter({ ignoreCase: true, ignoreAccents: false, stringify: (option) => 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) => { if (val.value === selectedClass) { return; } explorerTreeState.setClassToGenerateSampleData(val.value); }; useEffect(() => { if (selectedClass) { setResultText(createMockDataForClassWithFormat(selectedClass, format, depth)); } }, [selectedClass, format, depth]); const abort = () => { setFormat(GENERATION_DEFAULT_FORMAT); setDepth(GENERATION_DEFAULT_DEPTH); explorerTreeState.setClassToGenerateSampleData(undefined); }; const regenerate = () => { if (selectedClass) { setResultText(createMockDataForClassWithFormat(selectedClass, format, depth)); } }; const onEnter = () => classSelectorRef.current?.focus(); return (_jsx(Dialog, { open: Boolean(selectedClass), onClose: abort, slotProps: { transition: { onEnter: onEnter, }, paper: { classes: { root: 'search-modal__inner-container' }, }, }, classes: { container: 'search-modal__container' }, children: _jsxs("div", { className: "modal modal--dark search-modal explorer__element-renamer", children: [_jsx("div", { className: "modal__title", children: "Generate Sample Data" }), _jsxs("div", { className: "sample-data-generator__controller", children: [_jsx("div", { className: "sample-data-generator__controller__icon", title: "class", children: _jsx(PURE_ClassIcon, {}) }), _jsx(CustomSelectorInput, { inputRef: classSelectorRef, className: "sample-data-generator__controller__class-selector", options: classOptions, onChange: changeClass, value: selectedClassOption, darkMode: !applicationStore.layoutService .TEMPORARY__isLightColorThemeEnabled, filterOption: elementFilterOption, formatOptionLabel: getPackageableElementOptionFormatter({ darkMode: !applicationStore.layoutService .TEMPORARY__isLightColorThemeEnabled, }) }), _jsx("div", { className: "sample-data-generator__controller__icon", title: "format", children: _jsx(CodeIcon, {}) }), _jsxs(ControlledDropdownMenu, { className: "sample-data-generator__controller__format-selector", title: "Choose Element Type...", content: _jsx(MenuContent, { className: "sample-data-generator__controller__format-selector__options", children: Object.values(CLASS_MOCK_DATA_GENERATION_FORMAT).map((val) => (_jsx(MenuContentItem, { className: "sample-data-generator__controller__format-selector__option", onClick: () => setFormat(val), children: val }, val))) }), children: [_jsx("div", { className: "sample-data-generator__controller__format-selector__label", children: format }), _jsx("div", { className: "sample-data-generator__controller__format-selector__dropdown-indicator", children: _jsx(CaretDownIcon, {}) })] }), _jsx("div", { className: "sample-data-generator__controller__icon", title: "depth", children: _jsx(LevelDownIcon, {}) }), _jsx("input", { className: "input input--dark sample-data-generator__controller__depth", value: depth, type: "number", onChange: changeDepth })] }), _jsx("div", { className: "sample-data-generator__result", children: _jsx(CodeEditor, { inputValue: resultText, isReadOnly: true, hideGutter: true, language: getMockDataEditorLanguage(format) }) }), _jsxs("div", { className: "search-modal__actions", children: [_jsx("button", { type: "button", className: "btn btn--dark", onClick: abort, children: "Cancel" }), _jsx("button", { className: "btn btn--dark", onClick: regenerate, children: "Regenerate" })] })] }) })); }); const isRelationalDatabase = (val) => (val instanceof Database ? val : undefined); const ExplorerContextMenu = observer(forwardRef(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 _jsx(Fragment, { children: action }, config.key); }) .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 = () => { 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 openCubeViewer = editorStore.applicationStore.guardUnhandledError(async () => { if (node?.packageableElement) { await openDataCube(node.packageableElement, editorStore); } }); const buildDatabaseQuery = editorStore.applicationStore.guardUnhandledError(async () => { if (node?.packageableElement instanceof Database) { const state = new QueryDatabaseState(node.packageableElement, editorStore); flowResult(state.init()).catch(editorStore.applicationStore.alertUnhandledError); } }); 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 = () => { if (isRelationalDatabaseConnection(node?.packageableElement)) { editorStore.panelGroupDisplayState.open(); editorStore.setActivePanelMode(PANEL_MODE.SQL_PLAYGROUND); editorStore.studioSqlPlaygroundState.setConnection(guaranteeType(node?.packageableElement, PackageableConnection)); } }; const removeElement = () => { if (node) { flowResult(editorStore.graphEditorMode.deleteElement(node.packageableElement)).catch(applicationStore.alertUnhandledError); } }; const renameElement = () => { if (node) { editorStore.explorerTreeState.setElementToRename(node.packageableElement); } }; const openElementInViewerMode = () => { if (node && projectId) { applicationStore.navigationService.navigator.visitAddress(applicationStore.navigationService.navigator.generateAddress(generateViewEntityRoute(projectId, node.packageableElement.path))); } }; const copyPath = () => { if (node) { applicationStore.clipboardService .copyTextToClipboard(node.packageableElement.path) .then(() => applicationStore.notificationService.notifySuccess('Copied element path to clipboard')) .catch(applicationStore.alertUnhandledError); } }; const copyWorkspaceElementLink = () => { 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 = () => { 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) => () => editorStore.newElementState.openModal(type, _package); const isDependencyProjectRoot = () => node?.packageableElement instanceof Package && editorStore.graphManagerState.graph.dependencyManager.roots.includes(node.packageableElement); const viewProject = () => { 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 = () => { 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 (_jsxs(MenuContent, { "data-testid": LEGEND_STUDIO_TEST_ID.EXPLORER_CONTEXT_MENU, children: [_jsx(MenuContentItem, { onClick: viewProject, children: _jsx(MenuContentItemLabel, { children: "View Project" }) }), node && (_jsx(MenuContentItem, { onClick: viewSDLCProject, children: _jsx(MenuContentItemLabel, { children: "View SDLC Project" }) }))] })); } if (_package && !isReadOnly) { return (_jsxs(MenuContent, { "data-testid": LEGEND_STUDIO_TEST_ID.EXPLORER_CONTEXT_MENU, children: [Array.from(elementTypesWithCategory.entries()).map((entry) => (_jsxs("div", { className: "editor-group__view-mode__option__group editor-group__view-mode__option__group--native", children: [_jsx("div", { className: "editor-group__view-mode__option__group__name", children: entry[0] }), _jsx("div", { className: "editor-group__view-mode__option__group__options editor-group__view-mode__option__group__options--center", children: entry[1].map((type) => (_jsxs(MenuContentItem, { onClick: createNewElement(type), children: [_jsx(MenuContentItemIcon, { children: getElementTypeIcon(type, editorStore) }), _jsx(MenuContentItemLabel, { children: toTitleCase(getElementTypeLabel(editorStore, type)) })] }, type))) })] }, entry[0]))), node && (_jsxs(_Fragment, { children: [_jsxs(MenuContentItem, { onClick: renameElement, children: [_jsx(MenuContentItemBlankIcon, {}), _jsx(MenuContentItemLabel, { children: "Rename" })] }), _jsxs(MenuContentItem, { onClick: removeElement, children: [_jsx(MenuContentItemBlankIcon, {}), _jsx(MenuContentItemLabel, { children: "Remove" })] })] }))] })); } if (!node) { return null; } return (_jsxs(MenuContent, { "data-testid": LEGEND_STUDIO_TEST_ID.EXPLORER_CONTEXT_MENU, children: [node.packageableElement instanceof DataProduct && (_jsxs(_Fragment, { children: [_jsx(MenuContentItem, { onClick: buildDataProductQuery, children: "Query..." }), _jsx(MenuContentItem, { onClick: openLegendSqlPlayground, children: "Run SQL..." }), _jsx(MenuContentDivider, {})] })), node.packageableElement instanceof Class && (_jsxs(_Fragment, { children: [_jsx(MenuContentItem, { onClick: buildQuery, children: "Query..." }), _jsx(MenuContentItem, { onClick: generateSampleData, children: "Generate Sample Data..." }), _jsx(MenuContentDivider, {})] })), node.packageableElement instanceof Service && (_jsx(_Fragment, { children: _jsx(MenuContentItem, { onClick: buildServiceQuery, children: "Query..." }) })), node.packageableElement instanceof IngestDefinition && (_jsxs(_Fragment, { children: [_jsx(MenuContentItem, { onClick: openLegendSqlPlayground, children: "Run SQL..." }), _jsx(MenuContentDivider, {})] })), isElementSupportedByDataCube(node.packageableElement) && (_jsxs(_Fragment, { children: [_jsx(MenuContentItem, { onClick: openCubeViewer, children: "Data Cube (BETA)..." }), _jsx(MenuContentDivider, {})] })), isRelationalDatabaseConnection(node.packageableElement) && (_jsxs(_Fragment, { children: [_jsx(MenuContentItem, { onClick: openSQLPlayground, children: "Execute SQL..." }), _jsx(MenuContentItem, { onClick: buildDatabase, children: "Build Database..." }), _jsx(MenuContentDivider, {})] })), isRelationalDatabase(node.packageableElement) && (_jsxs(_Fragment, { children: [_jsx(MenuContentItem, { onClick: generateModelsFromDatabaseSpecification, children: "Build Models" }), _jsx(MenuContentItem, { onClick: buildDatabaseQuery, children: "Query (Beta)..." }), _jsx(MenuContentDivider, {})] })), extraExplorerContextMenuItems, Boolean(extraExplorerContextMenuItems.length) && (_jsx(MenuContentDivider, {})), !isReadOnly && (_jsxs(_Fragment, { children: [_jsx(MenuContentItem, { onClick: renameElement, children: "Rename" }), _jsx(MenuContentItem, { onClick: removeElement, children: "Remove" })] })), _jsx(MenuContentDivider, {}), !editorStore.isInViewerMode && !isDependencyProjectElement && (_jsx(MenuContentItem, { onClick: openElementInViewerMode, children: "View in Project" })), _jsx(MenuContentItem, { onClick: copyPath, children: "Copy Path" }), _jsx(MenuContentItem, { onClick: copyWorkspaceElementLink, children: "Copy Link" }), isDependencyProjectElement && (_jsx(MenuContentItem, { onClick: copySDLCProjectLink, children: "Copy SDLC Project Link" }))] })); })); const ProjectConfig = observer(() => { const editorStore = useEditorStore(); const openConfigurationEditor = () => 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 (_jsxs("div", { className: clsx('tree-view__node__container explorer__package-tree__node__container explorer__floating-item', { 'explorer__package-tree__node__container--selected': isSelected }), onClick: openConfigurationEditor, children: [_jsx("div", { className: "tree-view__node__icon explorer__package-tree__node__icon", children: _jsx("div", { className: "explorer__package-tree__node__icon__type explorer__config__icon", children: _jsx("div", { children: _jsx(SettingsEthernetIcon, {}) }) }) }), _jsx("button", { className: "tree-view__node__label explorer__package-tree__node__label", tabIndex: -1, title: "Project configuration", children: "config" })] })); }); const PackageTreeNodeContainer = observer((props) => { 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 ? (_jsx("div", {})) : node.isOpen ? (_jsx(ChevronDownIcon, {})) : (_jsx(ChevronRightIcon, {})); const iconPackageColor = isGeneratedElement(node.packageableElement) ? 'color--generated' : isSystemElement(node.packageableElement) ? 'color--system' : isDependencyElement(node.packageableElement) ? 'color--dependency' : ''; const nodeIcon = isPackage ? (node.isOpen ? (_jsx("div", { className: iconPackageColor, children: _jsx(FolderOpenIcon, {}) })) : (_jsx("div", { className: iconPackageColor, children: _jsx(FolderIcon, {}) }))) : (getElementIcon(node.packageableElement, editorStore)); const selectNode = () => onNodeSelect?.(node); const onContextMenuOpen = () => setIsSelectedFromContextMenu(true); const onContextMenuClose = () => setIsSelectedFromContextMenu(false); return (_jsx(ContextMenu, { content: _jsx(ExplorerContextMenu, { node: node, nodeIsImmutable: isContextImmutable }), disabled: disableContextMenu, menuProps: { elevation: 7 }, onOpen: onContextMenuOpen, onClose: onContextMenuClose, children: _jsxs("div", { className: clsx('tree-view__node__container explorer__package-tree__node__container', { 'menu__trigger--on-menu-open': !node.isSelected && isSelectedFromContextMenu, }, { 'explorer__package-tree__node__container--selected': node.isSelected, }), ref: ref, onClick: selectNode, style: { paddingLeft: `${level * (stepPaddingInRem ?? 1)}rem`, display: 'flex', }, children: [_jsxs("div", { className: "tree-view__node__icon explorer__package-tree__node__icon", children: [_jsx("div", { className: "explorer__package-tree__node__icon__expand", children: expandIcon }), _jsx("div", { className: "explorer__package-tree__node__icon__type", children: nodeIcon })] }), _jsx("button", { className: "tree-view__node__label explorer__package-tree__node__label", tabIndex: -1, title: node.packageableElement.path, children: extractDependencyGACoordinateFromRootPackageName(node.label) ?? node.label })] }) })); }); const ExplorerDropdownMenu = observer(() => { const editorStore = useEditorStore(); const _package = editorStore.explorerTreeState.getSelectedNodePackage(); const createNewElement = (type) => () => 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 (_jsx(MenuContent, { "data-testid": LEGEND_STUDIO_TEST_ID.EXPLORER_CONTEXT_MENU, children: Array.from(elementTypesWithCategory.entries()).map((entry) => (_jsxs("div", { className: "editor-group__view-mode__option__group editor-group__view-mode__option__group--native", children: [_jsx("div", { className: "editor-group__view-mode__option__group__name", children: entry[0] }), _jsx("div", { className: "editor-group__view-mode__option__group__options editor-group__view-mode__option__group__options--center", children: entry[1].map((type) => (_jsxs(MenuContentItem, { onClick: createNewElement(type), children: [_jsx(MenuContentItemIcon, { children: getElementTypeIcon(type, editorStore) }), _jsx(MenuContentItemLabel, { children: toTitleCase(getElementTypeLabel(editorStore, type)) })] }, type))) })] }, entry[0]))) })); }); const ExplorerTrees = observer(() => { const editorStore = useEditorStore(); const { disableGraphEditing } = editorStore; const isInGrammarTextMode = editorStore.graphEditorMode.mode === GRAPH_EDITOR_MODE.GRAMMAR_TEXT; const openModelImport = () => 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) => editorStore.explorerTreeState.onTreeNodeSelect(node, treeData); const getChildNodes = (node) => getTreeChildNodes(editorStore, node, treeData); const deselectTreeNode = () => { 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) => editorStore.explorerTreeState.onTreeNodeSelect(node, generationTreeData, ROOT_PACKAGE_NAME.MODEL_GENERATION); const getGenerationTreeChildNodes = (node) => getTreeChildNodes(editorStore, node, generationTreeData); // Generated Files Tree const generationFileTreeData = editorStore.explorerTreeState.getArtifactsGenerationTreeData(); const onGenerationFileTreeNodeSelect = (node) => editorStore.graphState.graphGenerationState.onTreeNodeSelect(node, generationFileTreeData); const getGenerationFileTreeChildNodes = (node) => getFileSystemChildNodes(node, generationFileTreeData); // System Tree const systemTreeData = editorStore.explorerTreeState.getTreeData(ROOT_PACKAGE_NAME.SYSTEM); const onSystemTreeNodeSelect = (node) => editorStore.explorerTreeState.onTreeNodeSelect(node, systemTreeData, ROOT_PACKAGE_NAME.SYSTEM); const getSystemTreeChildNodes = (node) => getTreeChildNodes(editorStore, node, systemTreeData); // Dependency Tree const dependencyTreeData = editorStore.explorerTreeState.getTreeData(ROOT_PACKAGE_NAME.PROJECT_DEPENDENCY_ROOT); const onDependencyTreeSelect = (node) => editorStore.explorerTreeState.onTreeNodeSelect(node, dependencyTreeData, ROOT_PACKAGE_NAME.PROJECT_DEPENDENCY_ROOT); const getDependencyTreeChildNodes = (node) => getTreeChildNodes(editorStore, node, dependencyTreeData, true); const showPackageTrees = treeData.nodes.size || graph.dependencyManager.hasDependencies; return (_jsxs(ContextMenu, { className: "explorer__content", disabled: isInGrammarTextMode || disableGraphEditing, content: _jsx(ExplorerContextMenu, {}), menuProps: { elevation: 7 }, children: [_jsxs("div", { "data-testid": LEGEND_STUDIO_TEST_ID.EXPLORER_TREES, children: [editorStore.explorerTreeState.buildState.hasCompleted && showPackageTrees && (_jsxs(_Fragment, { children: [_jsx(TreeView, { components: { TreeNodeContainer: PackageTreeNodeContainer, }, treeData: treeData, onNodeSelect: onNodeSelect, getChildNodes: getChildNodes, innerProps: { disableContextMenu: isInGrammarTextMode, } }), _jsx(ElementRenamer, {}), _jsx(SampleDataGenerator, {}), editorStore.explorerTreeState.databaseBuilderState && (_jsx(DatabaseBuilderWizard, { databaseBuilderState: editorStore.explorerTreeState.databaseBuilderState, isReadOnly: false })), editorStore.explorerTreeState.databaseModelBuilderState && (_jsx(DatabaseModelBuilder, { databaseModelBuilderState: editorStore.explorerTreeState.databaseModelBuilderState, isReadOnly: false })), editorStore.projectConfigurationEditorState .projectConfiguration && _jsx(ProjectConfig, {}), Boolean(editorStore.graphManagerState.systemModel.allOwnElements.length) && (_jsx(TreeView, { components: { TreeNodeContainer: PackageTreeNodeContainer, }, treeData: systemTreeData, onNodeSelect: onSystemTreeNodeSelect, getChildNodes: getSystemTreeChildNodes, innerProps: { disableContextMenu: true, } })), graph.dependencyManager.hasDependencies && (_jsx(TreeView, { components: { TreeNodeContainer: PackageTreeNodeContainer, }, treeData: dependencyTreeData, onNodeSelect: onDependencyTreeSelect, getChildNodes: getDependencyTreeChildNodes, innerProps: { disableContextMenu: isInGrammarTextMode, isContextImmutable: true, } })), Boolean(graph.generationModel.allOwnElements.length) && (_jsx(TreeView, { components: { TreeNodeContainer: PackageTreeNodeContainer, }, treeData: generationTreeData, onNodeSelect: onGenerationTreeNodeSelect, getChildNodes: getGenerationTreeChildNodes, innerProps: { disableContextMenu: isInGrammarTextMode, isContextImmutable: true, } })), _jsx("div", {}), Boolean(editorStore.graphState.graphGenerationState.rootFileDirectory .children.length) && (_jsxs(_Fragment, { children: [_jsx("div", { className: "explorer__content__separator" }), _jsx(FileSystemTree, { selectedNode: editorStore.explorerTreeState.selectedNode, directoryTreeData: generationFileTreeData, onNodeSelect: onGenerationFileTreeNodeSelect, getFileElementTreeChildNodes: getGenerationFileTreeChildNodes })] }))] })), editorStore.explorerTreeState.buildState.hasCompleted && !showPackageTrees && (_jsxs("div", { className: "explorer__content--empty", children: [_jsx("div", { className: "explorer__content--empty__text", children: "Your workspace is empty, you can add elements or load existing model/entites for quick adding" }), _jsx("button", { className: "btn--dark explorer__content--empty__btn", onClick: openModelImport, children: "Open Model Importer" })] }))] }), _jsx("div", { className: "explorer__deselector", onClick: deselectTreeNode })] })); }); const ProjectExplorerActionPanel = observer((props) => { const { disabled } = props; const editorStore = useEditorStore(); const isInGrammarMode = editorStore.graphEditorMode.mode === GRAPH_EDITOR_MODE.GRAMMAR_TEXT; const showSearchModal = () => editorStore.setShowSearchElementCommand(true); // Explorer tree const selectedTreeNode = editorStore.explorerTreeState.selectedNode; const collapseTree = () => { const treeData = editorStore.explorerTreeState.getTreeData(); treeData.nodes.forEach((node) => { node.isOpen = false; }); editorStore.explorerTreeState.setTreeData({ ...treeData }); }; const showModelImporter = () => editorStore.tabManagerState.openTab(editorStore.modelImporterState); const openConfigurationEditor = () => editorStore.tabManagerState.openTab(editorStore.projectConfigurationEditorState); return (_jsxs("div", { className: "panel__header__actions", children: [_jsx("button", { className: "panel__header__action", disabled: disabled, title: "Open Model Importer (F2)", onClick: showModelImporter, children: _jsx(FileImportIcon, {}) }), editorStore.editorMode.supportSdlcOperations && (_jsx("button", { className: "panel__header__action panel__header__action--config", disabled: disabled, title: "Project Configuration Panel", onClick: openConfigurationEditor, children: _jsx(SettingsEthernetIcon, {}) })), !editorStore.disableGraphEditing && (_jsx(ControlledDropdownMenu, { className: "panel__header__action", title: "New Element... (Ctrl + Shift + N)", disabled: disabled || isInGrammarMode || (selectedTreeNode && isElementReadOnly(selectedTreeNode.packageableElement)), content: _jsx(ExplorerDropdownMenu, {}), menuProps: { anchorOrigin: { vertical: 'bottom', horizontal: 'left' }, transformOrigin: { vertical: 'top', horizontal: 'left' }, elevation: 7, }, children: _jsx(PlusIcon, {}) })), _jsx("button", { className: "panel__header__action", disabled: disabled, onClick: collapseTree, tabIndex: -1, title: "Collapse All", children: _jsx(CompressIcon, {}) }), _jsx("button", { className: "panel__header__action", disabled: disabled, tabIndex: -1, onClick: showSearchModal, title: "Open Element... (Ctrl + P)", children: _jsx(SearchIcon, {}) })] })); }); 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 = () => editorStore.setActiveActivity(ACTIVITY_MODE.CONFLICT_RESOLUTION); const buildGrapnInConflictResolutionMode = () => { editorStore.conflictResolutionState.confirmHasResolvedAllConflicts(); flowResult(editorStore.conflictResolutionState.buildGraphInConflictResolutionMode()).catch(applicationStore.alertUnhandledError); }; return (_jsxs("div", { className: "panel explorer", children: [_jsxs("div", { className: "panel__header side-bar__header", children: [_jsx("div", { className: "panel__header__title", children: _jsx("div", { className: "panel__header__title__content side-bar__header__title__content", children: "EXPLORER" }) }), editorStore.editorMode.disableEditing && !editorStore.editorMode.label && (_jsxs("div", { className: "panel__header__title side-bar__header__title__viewer-mode-badge", children: [_jsx(LockIcon, {}), "READ-ONLY"] })), editorStore.editorMode.label && (_jsx("div", { className: "panel__header__title side-bar__header__title__viewer-mode-badge", children: editorStore.editorMode.label }))] }), _jsx("div", { className: "panel__content side-bar__content", children: _jsxs("div", { className: "panel explorer", children: [_jsxs("div", { className: "panel__header explorer__header", children: [sdlcState.currentProject && (_jsxs(_Fragment, { children: [_jsx("div", { className: "panel__header__title__label", children: sdlcState.currentWorkspace && !editorStore.isInViewerMode ? 'workspace' : 'project' }), _jsxs("div", { className: "panel__header__title__content", children: [editorStore.isInViewerMode && sdlcState.currentProject.name, !editorStore.isInViewerMode && (sdlcState.currentWorkspace?.workspaceId ?? '(unknown) ')] })] })), _jsx(ProjectExplorerActionPanel, { disabled: !editorStore.explorerTreeState.buildState.hasCompleted })] }), editorStore.explorerTreeState.buildState.hasCompleted && (_jsx(CreateNewElementModal, {})), _jsxs("div", { className: "panel__content explorer__content__container", children: [showConflictResolutionContent && (_jsxs(_Fragment, { children: [!editorStore.conflictResolutionState.conflicts.length && (_jsxs("div", { className: "explorer__content--empty", children: [_jsx("div", { className: "explorer__content--empty__text", children: "All conflicts have been resolved, you can build the graph now to start testing your changes" }), _jsx("button", { className: "btn--dark btn--conflict btn--important explorer__content--empty__btn", onClick: buildGrapnInConflictResolutionMode, children: "Build Graph" })] })), Boolean(editorStore.conflictResolutionState.conflicts.length) && (_jsxs("div", { className: "explorer__content--empty", children: [_jsx("div", { className: "explorer__content--empty__text", children: `Can't build graph as workspace contains merge conflicts, please resolve them before trying to build the graph again` }), _jsx("button", { className: "btn--dark btn--conflict btn--important explorer__content--empty__btn", onClick: goToConflictResolutionTab, children: "Resolve Merge Conflicts" })] }))] })), !showConflictResolutionContent && (_jsxs(_Fragment, { children: [_jsx(PanelLoadingIndicator, { isLoading: isLoading }), showExplorerTrees && _jsx(ExplorerTrees, {}), !showExplorerTrees && !editorStore.graphManagerState.graphBuildState.hasFailed && (_jsx("div", { className: "explorer__content__progress-msg", children: 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 && (_jsx(BlankPanelContent, { children: _jsxs("div", { className: "explorer__content__failure-notice", children: [_jsx("div", { className: "explorer__content__failure-notice__icon", children: _jsx(ExclamationTriangleIcon, {}) }), _jsx("div", { className: "explorer__content__failure-notice__text", children: "Failed to build graph" })] }) }))] }))] })] }) }), editorStore.legendSQLStudioPlaygroundState.isOpen && (_jsx(LegendSQLPlaygroundModal, { playgroundState: editorStore.legendSQLStudioPlaygroundState }))] })); }); //# sourceMappingURL=Explorer.js.map