@finos/legend-application-studio
Version:
Legend Studio application core
589 lines • 49.8 kB
JavaScript
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