UNPKG

@finos/legend-application-studio

Version:
575 lines 46.9 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 { useApplicationStore, DEFAULT_TAB_SIZE, } from '@finos/legend-application'; import { compareLabelFn, clsx, CustomSelectorInput, TimesIcon, Dialog, ControlledDropdownMenu, CaretDownIcon, MenuContentItem, MenuContent, Modal, ModalBody, ModalFooter, ModalHeader, PanelLoadingIndicator, TreeView, ChevronDownIcon, ChevronRightIcon, ContextMenu, CompressIcon, SubjectIcon, ViewHeadlineIcon, ExpandAllIcon, BlankPanelContent, VersionsIcon, RepoIcon, ModalFooterButton, Button, Panel, PanelHeader, PanelHeaderActions, PanelHeaderActionItem, PanelContentLists, CircleNotchIcon, } from '@finos/legend-art'; import { MASTER_SNAPSHOT_ALIAS, SNAPSHOT_VERSION_ALIAS, } from '@finos/legend-server-depot'; import {} from '@finos/legend-server-sdlc'; import { ActionState, assertErrorThrown, guaranteeNonNullable, isNonNullable, LogEvent, prettyCONSTName, compareSemVerVersions, } from '@finos/legend-shared'; import { generateGAVCoordinates } from '@finos/legend-storage'; import { flowResult } from 'mobx'; import { observer } from 'mobx-react-lite'; import { forwardRef, useCallback, useEffect, useRef, useState } from 'react'; import { ProjectConfigurationEditorState } from '../../../../stores/editor/editor-state/project-configuration-editor-state/ProjectConfigurationEditorState.js'; import { ConflictTreeNodeData, ConflictVersionNodeData, buildDependencyNodeChildren, DEPENDENCY_REPORT_TAB, openAllDependencyNodesInTree, ProjectDependencyTreeNodeData, } from '../../../../stores/editor/editor-state/project-configuration-editor-state/ProjectDependencyEditorState.js'; import { LEGEND_STUDIO_APP_EVENT } from '../../../../__lib__/LegendStudioEvent.js'; import { generateViewProjectByGAVRoute, generateViewProjectRoute, generateViewVersionRoute, } from '../../../../__lib__/LegendStudioNavigation.js'; import { LEGEND_STUDIO_TEST_ID } from '../../../../__lib__/LegendStudioTesting.js'; import { useEditorStore } from '../../EditorStoreProvider.js'; const buildProjectOption = (project) => ({ label: project.coordinates, value: project, }); const ProjectDependencyActions = observer((props) => { const { dependencyEditorState: dependencyEditorState } = props; const applicationStore = useApplicationStore(); const hasConflicts = dependencyEditorState.dependencyReport?.conflicts.length; const hasDependencyChanges = dependencyEditorState.hasDependencyChanges; const viewTree = () => { if (dependencyEditorState.dependencyReport) { dependencyEditorState.setReportTab(DEPENDENCY_REPORT_TAB.EXPLORER); } }; const viewConflict = () => { if (dependencyEditorState.dependencyReport) { dependencyEditorState.setReportTab(DEPENDENCY_REPORT_TAB.CONFLICTS); } }; const validateDependencies = () => { flowResult(dependencyEditorState.validateAndFetchDependencyReport()).catch(applicationStore.alertUnhandledError); }; return (_jsxs("div", { className: "project-dependency-editor__info", children: [_jsx(Button, { onClick: viewTree, disabled: !dependencyEditorState.dependencyReport, title: "View Dependency Explorer", text: "View Dependency Explorer" }), Boolean(hasConflicts) && (_jsx(Button, { className: "project-dependency-editor__conflicts-btn", text: "View Conflicts", onClick: viewConflict, disabled: !dependencyEditorState.dependencyReport?.conflicts.length, title: "View any conflicts in your dependencies" })), hasDependencyChanges && (_jsx(Button, { className: "project-dependency-editor__validate-btn", text: "Validate Dependencies", onClick: validateDependencies, disabled: dependencyEditorState.validatingDependenciesState.isInProgress, title: "Validate dependency changes and update dependency tree" }))] })); }); const formatOptionLabel = (option) => (_jsxs("div", { className: "project-dependency-editor__label", children: [_jsx("div", { className: "project-dependency-editor__label__tag", children: option.value.projectId }), _jsx("div", { className: "project-dependency-editor__label__name", children: option.value.coordinates })] })); const DependencyTreeNodeContextMenu = observer(forwardRef(function DependencyTreeNodeContextMenu(props, ref) { const { node } = props; const applicationStore = useApplicationStore(); const getViewProjectUrl = () => { let groupId; let artifactId; let versionId; if (node instanceof ConflictTreeNodeData) { groupId = node.conflict.groupId; artifactId = node.conflict.artifactId; } else if (node instanceof ConflictVersionNodeData) { groupId = node.versionConflict.conflict.groupId; artifactId = node.versionConflict.conflict.artifactId; versionId = node.versionConflict.version.versionId; } else if (node instanceof ProjectDependencyTreeNodeData) { groupId = node.value.groupId; artifactId = node.value.artifactId; versionId = node.value.versionId; } return generateViewProjectByGAVRoute(guaranteeNonNullable(groupId), guaranteeNonNullable(artifactId), versionId === MASTER_SNAPSHOT_ALIAS ? SNAPSHOT_VERSION_ALIAS : versionId); }; const getSDLCProjectUrl = () => { if (node instanceof ConflictTreeNodeData) { const version = node.conflict.versions[0]; return version ? generateViewProjectRoute(version.projectId) : undefined; } else if (node instanceof ConflictVersionNodeData) { return generateViewVersionRoute(node.versionConflict.version.projectId, node.versionConflict.version.artifactId); } else if (node instanceof ProjectDependencyTreeNodeData) { return generateViewVersionRoute(node.value.projectId, node.value.versionId); } return undefined; }; const sdlcProjectUrl = getSDLCProjectUrl(); const viewProjectUrl = getViewProjectUrl(); const viewProject = () => { applicationStore.navigationService.navigator.visitAddress(applicationStore.navigationService.navigator.generateAddress(viewProjectUrl)); }; const viewSDLCProject = () => { if (sdlcProjectUrl) { applicationStore.navigationService.navigator.visitAddress(applicationStore.navigationService.navigator.generateAddress(sdlcProjectUrl)); } }; return (_jsxs(MenuContent, { "data-testid": LEGEND_STUDIO_TEST_ID.EXPLORER_CONTEXT_MENU, children: [_jsx(MenuContentItem, { onClick: viewProject, children: "Visit Project" }), _jsx(MenuContentItem, { disabled: !sdlcProjectUrl, onClick: viewSDLCProject, children: "Visit SDLC Project" })] })); })); const DependencyTreeNodeContainer = (props) => { const { node, level, stepPaddingInRem, onNodeSelect } = props; const isExpandable = Boolean(node.childrenIds?.length); const selectNode = () => onNodeSelect?.(node); const value = node.value; const nodeExpandIcon = isExpandable ? (node.isOpen ? (_jsx(ChevronDownIcon, {})) : (_jsx(ChevronRightIcon, {}))) : (_jsx("div", {})); return (_jsx(ContextMenu, { content: _jsx(DependencyTreeNodeContextMenu, { node: node }), menuProps: { elevation: 7 }, children: _jsxs("div", { className: clsx('tree-view__node__container project-dependency-explorer-tree__node__container', { 'menu__trigger--on-menu-open': !node.isSelected, }, { 'project-dependency-explorer-tree__node__container--selected': node.isSelected, }), style: { paddingLeft: `${(level - 1) * (stepPaddingInRem ?? 1)}rem`, }, onClick: selectNode, children: [_jsx("div", { className: "tree-view__node__icon project-dependency-explorer-tree__node__icon", children: _jsx("div", { className: "project-dependency-explorer-tree__node__icon__expand", children: nodeExpandIcon }) }), _jsx("button", { className: "tree-view__node__label project-dependency-explorer-tree__node__label", tabIndex: -1, title: value.id, children: value.artifactId }), _jsx("div", { className: "project-dependency-explorer-tree__node__version", children: _jsx("button", { className: "project-dependency-explorer-tree__node__version-btn", title: value.versionId, tabIndex: -1, children: value.versionId }) })] }) })); }; const DependencyTreeView = (props) => { const { treeData, setTreeData } = props; const onNodeExpand = (node) => { if (node.childrenIds?.length) { node.isOpen = !node.isOpen; node.childrenIds .map((id) => treeData.nodes.get(id)) .filter(isNonNullable) .forEach((c) => buildDependencyNodeChildren(c, treeData.nodes)); } setTreeData({ ...treeData }); }; const onNodeSelect = (node) => { buildDependencyNodeChildren(node, treeData.nodes); onNodeExpand(node); setTreeData({ ...treeData }); }; const getChildNodes = (node) => { if (!node.childrenIds || node.childrenIds.length === 0) { return []; } const childrenNodes = node.childrenIds .map((id) => treeData.nodes.get(id)) .filter(isNonNullable); return childrenNodes; }; return (_jsx(TreeView, { components: { TreeNodeContainer: DependencyTreeNodeContainer, }, treeData: treeData, getChildNodes: getChildNodes, onNodeSelect: onNodeSelect, innerProps: { onNodeExpand, } })); }; const ConflictTreeNodeContainer = (props) => { const { node, level, stepPaddingInRem, onNodeSelect } = props; const isExpandable = Boolean(node.childrenIds?.length); const selectNode = () => onNodeSelect?.(node); const nodeExpandIcon = isExpandable ? (node.isOpen ? (_jsx(ChevronDownIcon, {})) : (_jsx(ChevronRightIcon, {}))) : (_jsx("div", {})); return (_jsx(ContextMenu, { content: _jsx(DependencyTreeNodeContextMenu, { node: node }), menuProps: { elevation: 7 }, children: _jsxs("div", { className: clsx('tree-view__node__container project-dependency-explorer-tree__node__container', { 'menu__trigger--on-menu-open': !node.isSelected, }, { 'project-dependency-explorer-tree__node__container--selected': node.isSelected, }), style: { paddingLeft: `${(level - 1) * (stepPaddingInRem ?? 1)}rem`, }, onClick: selectNode, children: [_jsxs("div", { className: clsx('tree-view__node__icon project-dependency-explorer-tree__node__icon', { 'tree-view__node__icon project-dependency-explorer-tree__node__icon__with__type': node instanceof ConflictTreeNodeData || node instanceof ConflictVersionNodeData, }), children: [_jsx("div", { className: "project-dependency-explorer-tree__node__icon__expand", children: nodeExpandIcon }), node instanceof ConflictTreeNodeData && (_jsx("div", { className: "project-dependency-explorer-tree__node__icon__type", children: _jsx(RepoIcon, {}) })), node instanceof ConflictVersionNodeData && (_jsx("div", { className: "project-dependency-explorer-tree__node__icon__type", children: _jsx(VersionsIcon, {}) }))] }), _jsx("button", { className: "tree-view__node__label project-dependency-explorer-tree__node__label", tabIndex: -1, title: node.description, children: node.label }), node instanceof ProjectDependencyTreeNodeData && (_jsx("div", { className: "project-dependency-explorer-tree__node__version", children: _jsx("button", { className: "project-dependency-explorer-tree__node__version-btn", title: node.value.versionId, tabIndex: -1, children: node.value.versionId }) }))] }) })); }; const ConflictDependencyTreeView = (props) => { const { treeData, setTreeData } = props; const onNodeExpand = (node) => { if (node.childrenIds?.length) { node.isOpen = !node.isOpen; } setTreeData({ ...treeData }); }; const onNodeSelect = (node) => { onNodeExpand(node); setTreeData({ ...treeData }); }; const getChildNodes = (node) => { if (!node.childrenIds || node.childrenIds.length === 0) { return []; } const childrenNodes = node.childrenIds .map((id) => treeData.nodes.get(id)) .filter(isNonNullable); return childrenNodes; }; return (_jsx(TreeView, { components: { TreeNodeContainer: ConflictTreeNodeContainer, }, treeData: treeData, getChildNodes: getChildNodes, onNodeSelect: onNodeSelect, innerProps: { onNodeExpand, } })); }; const collapseTreeData = (treeData) => { Array.from(treeData.nodes.values()).forEach((node) => { node.isOpen = false; }); }; export const getConflictsString = (report) => Array.from(report.conflictInfo.entries()) .map(([k, conflictVersionPaths]) => { const base = `project:\n${' '.repeat(DEFAULT_TAB_SIZE) + generateGAVCoordinates(k.groupId, k.artifactId, undefined)}`; const versionConflictString = conflictVersionPaths .map((conflictVersion) => { const versions = `version: ${conflictVersion.version.versionId}\n`; const paths = `paths:\n${conflictVersion.pathsToVersion .map((p, idx) => `${' '.repeat(DEFAULT_TAB_SIZE) + (idx + 1)}:\n${p .map((l) => l.id) .join('>')}`) .join('\n')}`; return versions + paths; }) .join('\n'); return `${base}\n${versionConflictString}`; }) .join('\n\n'); const ProjectDependencyConflictViewer = observer((props) => { const { report, dependencyEditorState } = props; const hasConflict = Boolean(report.conflicts.length); const collapseTree = () => { dependencyEditorState.conflictStates?.forEach((c) => { const treeData = c.treeData; Array.from(treeData.nodes.values()).forEach((n) => (n.isOpen = false)); c.setTreeData({ ...treeData }); }); }; const expandAllNodes = () => { dependencyEditorState.expandAllConflicts(); }; useEffect(() => { if (hasConflict && !dependencyEditorState.conflictStates) { dependencyEditorState.buildConflictPaths(); } }, [dependencyEditorState, hasConflict]); return (_jsxs(Panel, { className: "project-dependency-explorer", children: [_jsx(PanelHeader, { title: "conflicts", children: _jsxs(PanelHeaderActions, { children: [_jsx(PanelHeaderActionItem, { title: "Collapse Tree", disabled: !hasConflict || !dependencyEditorState.conflictStates, onClick: collapseTree, children: _jsx(CompressIcon, {}) }), _jsx(PanelHeaderActionItem, { title: "Expand All Conflict Paths", disabled: !hasConflict || !dependencyEditorState.conflictStates, onClick: expandAllNodes, children: _jsx(ExpandAllIcon, {}) })] }) }), _jsxs("div", { className: "project-dependency-explorer__content", children: [hasConflict && dependencyEditorState.conflictStates && (_jsx("div", { children: dependencyEditorState.conflictStates.map((c) => (_jsx(ConflictDependencyTreeView, { treeData: c.treeData, setTreeData: (treeData) => c.setTreeData(treeData) }, c.uuid))) })), !hasConflict && _jsx(BlankPanelContent, { children: "No Conflicts" })] })] })); }); const ProjectDependencyResolutionViewer = observer((props) => { const { dependencyEditorState } = props; const applicationStore = dependencyEditorState.editorStore.applicationStore; const [versionBacktrackCount, setVersionBacktrackCount] = useState(5); const resolutionResult = dependencyEditorState.resolutionResult; const resolveConflicts = () => { dependencyEditorState.clearResolutionResult(); flowResult(dependencyEditorState.resolveCompatibleDependencies(versionBacktrackCount)).catch(applicationStore.alertUnhandledError); }; const applyResolution = () => { flowResult(dependencyEditorState.applyResolvedDependencies()).catch(applicationStore.alertUnhandledError); }; const cancelResolution = () => { dependencyEditorState.clearResolutionResult(); }; return (_jsxs(Panel, { className: "project-dependency-explorer", children: [_jsx(PanelHeader, { title: "resolution", children: _jsx(PanelHeaderActions, { children: !resolutionResult?.success && (_jsx(_Fragment, { children: _jsxs("div", { className: "project-dependency-editor__resolve-controls", children: [_jsxs("div", { className: "project-dependency-editor__backtrack-control", children: [_jsx("input", { type: "number", min: "1", max: "100", value: versionBacktrackCount, onChange: (e) => setVersionBacktrackCount(Math.max(1, parseInt(e.target.value || '1', 10))), title: "Number of previous versions to check backward from the latest version when searching for compatible dependency versions", className: "input input--dark project-dependency-editor__backtrack-input", placeholder: "Backtrack versions" }), _jsx("div", { className: "project-dependency-editor__backtrack-label", children: "versions to check" })] }), _jsxs("button", { className: "btn--dark project-dependency-editor__resolve-btn", onClick: resolveConflicts, disabled: dependencyEditorState.resolvingCompatibleDependenciesState .isInProgress, title: "Automatically resolve conflicts by finding compatible versions", tabIndex: -1, children: [dependencyEditorState.resolvingCompatibleDependenciesState .isInProgress && (_jsx(CircleNotchIcon, { className: "icon--loading" })), "Resolve Compatible Dependencies"] })] }) })) }) }), _jsxs("div", { className: "project-dependency-explorer__content", children: [!resolutionResult && (_jsx(BlankPanelContent, { children: "Click \"Resolve Compatible Dependencies\" to find compatible versions for conflicting dependencies" })), resolutionResult && resolutionResult.success && (_jsxs("div", { className: "project-dependency-resolution", children: [_jsxs("div", { className: "project-dependency-resolution__header", children: [_jsx("div", { className: "project-dependency-resolution__title", children: "\u2713 Compatible Versions Found" }), _jsxs("div", { className: "project-dependency-resolution__subtitle", children: ["Found compatible versions by checking ", versionBacktrackCount, ' ', "versions back. Review and confirm to apply."] })] }), _jsx("div", { className: "project-dependency-resolution__list", children: resolutionResult.resolvedVersions.map((dep) => (_jsxs("div", { className: "project-dependency-resolution__item", children: [_jsxs("div", { className: "project-dependency-resolution__item-name", children: [dep.groupId, ":", dep.artifactId] }), _jsxs("div", { className: "project-dependency-resolution__item-version", children: ["\u2192 ", dep.versionId] })] }, `${dep.groupId}:${dep.artifactId}`))) }), _jsxs("div", { className: "project-dependency-resolution__actions", children: [_jsx("button", { className: "btn btn--dark project-dependency-resolution__action-btn project-dependency-resolution__action-btn--confirm", onClick: applyResolution, title: "Apply these resolved versions to your dependencies", children: "Confirm & Apply Changes" }), _jsx("button", { className: "btn btn--dark project-dependency-resolution__action-btn", onClick: cancelResolution, title: "Cancel and keep current versions", children: "Cancel" })] })] })), resolutionResult && !resolutionResult.success && (_jsxs("div", { className: "project-dependency-resolution project-dependency-resolution--failure", children: [_jsxs("div", { className: "project-dependency-resolution__header", children: [_jsx("div", { className: "project-dependency-resolution__title project-dependency-resolution__title--error", children: "\u2717 Unable to Resolve Conflicts" }), _jsxs("div", { className: "project-dependency-resolution__subtitle", children: [resolutionResult.failureReason ?? 'No compatible versions found', ' ', "within ", versionBacktrackCount, " versions."] }), _jsx("div", { className: "project-dependency-resolution__tip", children: "\uD83D\uDCA1 Tip: Try increasing the backtrack version count or manually adjust your dependency versions." })] }), _jsxs("div", { className: "project-dependency-resolution__conflicts-list", children: [_jsxs("div", { className: "project-dependency-resolution__conflicts-header", children: ["Conflicting Dependencies (", resolutionResult.conflicts.length, ")"] }), resolutionResult.conflicts.map((conflict) => { const suggestedVersionId = conflict.suggestedOverride?.versionId; return (_jsxs("div", { className: "project-dependency-resolution__conflict-item", children: [_jsxs("div", { className: "project-dependency-resolution__conflict-name", children: [conflict.groupId, ":", conflict.artifactId] }), suggestedVersionId && (_jsxs("div", { className: "project-dependency-resolution__suggested-version", children: ["\uD83D\uDCA1 Suggested Override: ", suggestedVersionId] })), _jsx("div", { className: "project-dependency-resolution__conflict-versions", children: conflict.conflictingVersions.map((versionInfo) => { const isSuggestedVersion = versionInfo.version === suggestedVersionId; return (_jsxs("div", { className: `project-dependency-resolution__conflict-version${isSuggestedVersion ? 'project-dependency-resolution__conflict-version--suggested' : ''}`, children: [_jsxs("span", { className: "project-dependency-resolution__version-label", children: [versionInfo.version, isSuggestedVersion && (_jsxs("span", { className: "project-dependency-resolution__suggested-badge", children: [' ', "[suggested override]"] }))] }), _jsx("span", { className: "project-dependency-resolution__required-by", children: "required by:" }), _jsxs("div", { className: "project-dependency-resolution__requirements", children: [versionInfo.requiredBy .slice(0, 3) .map((dep) => (_jsxs("div", { className: "project-dependency-resolution__requirement", children: [dep.groupId, ":", dep.artifactId, ":", dep.versionId] }, `${dep.groupId}:${dep.artifactId}:${dep.versionId}`))), versionInfo.requiredBy.length > 3 && (_jsxs("div", { className: "project-dependency-resolution__requirement-more", children: ["...and ", versionInfo.requiredBy.length - 3, ' ', "more"] }))] })] }, `${conflict.groupId}:${conflict.artifactId}:${versionInfo.version}`)); }) })] }, `${conflict.groupId}:${conflict.artifactId}`)); })] }), resolutionResult.suggestedOverrides && resolutionResult.suggestedOverrides.length > 0 && (_jsxs("div", { className: "project-dependency-resolution__suggested-overrides", children: [_jsxs("div", { className: "project-dependency-resolution__suggested-overrides-header", children: [_jsxs("div", { className: "project-dependency-resolution__suggested-overrides-title", children: ["\uD83D\uDCA1 Suggested Version Overrides (", resolutionResult.suggestedOverrides.length, ")"] }), _jsx("div", { className: "project-dependency-resolution__suggested-overrides-subtitle", children: "These versions may help resolve conflicts. Consider manually updating your dependencies to these versions." })] }), _jsx("div", { className: "project-dependency-resolution__suggested-overrides-list", children: resolutionResult.suggestedOverrides.map((override) => (_jsxs("div", { className: "project-dependency-resolution__suggested-override-item", children: [_jsxs("div", { className: "project-dependency-resolution__suggested-override-name", children: [override.groupId, ":", override.artifactId] }), _jsxs("div", { className: "project-dependency-resolution__suggested-override-version", children: ["\u2192 ", override.versionId] })] }, `${override.groupId}:${override.artifactId}:${override.versionId}`))) })] }))] }))] })] })); }); const ProjectDependencyReportModal = observer((props) => { const { dependencyEditorState } = props; const applicationStore = dependencyEditorState.editorStore.applicationStore; const reportTab = dependencyEditorState.reportTab; const tabs = Object.values(DEPENDENCY_REPORT_TAB); const changeTab = (tab) => () => dependencyEditorState.setReportTab(tab); const dependencyReport = dependencyEditorState.dependencyReport; const closeModal = () => dependencyEditorState.setReportTab(undefined); const [flattenView, setFlattenView] = useState(false); const [isExpandingDependencies, setIsExpandingDependencies] = useState(false); const setTreeData = (val) => { dependencyEditorState.setTreeData(val, flattenView); }; const toggleViewAsListOrAsTree = () => { setFlattenView(!flattenView); }; const treeData = flattenView ? dependencyEditorState.flattenDependencyTreeData : dependencyEditorState.dependencyTreeData; const collapseTree = () => { if (treeData) { collapseTreeData(treeData); setTreeData({ ...treeData }); } }; const openAllDependencyNodes = () => { if (treeData && dependencyReport) { setIsExpandingDependencies(true); openAllDependencyNodesInTree(treeData, dependencyReport.graph); setTreeData({ ...treeData }); setIsExpandingDependencies(false); } }; return (_jsx(Dialog, { open: Boolean(dependencyEditorState.reportTab), onClose: closeModal, classes: { root: 'editor-modal__root-container', container: 'editor-modal__container', paper: 'editor-modal__content', }, children: _jsxs(Modal, { darkMode: !applicationStore.layoutService.TEMPORARY__isLightColorThemeEnabled, className: "editor-modal", children: [_jsx(ModalHeader, { title: "Dependency Explorer" }), _jsx(ModalBody, { children: _jsxs("div", { className: "panel project-dependency-report", children: [_jsx(PanelLoadingIndicator, { isLoading: Boolean((isExpandingDependencies || dependencyEditorState.expandConflictsState.isInProgress || dependencyEditorState.buildConflictPathState .isInProgress) && !dependencyEditorState.validatingDependenciesState .isInProgress && !dependencyEditorState.fetchingDependencyInfoState .isInProgress) }), _jsx("div", { className: "panel__header project-dependency-report__tabs__header", children: _jsx("div", { className: "project-dependency-report__tabs", children: tabs.map((tab) => (_jsx("button", { onClick: changeTab(tab), className: clsx('project-dependency-report__tab', { 'project-dependency-report__tab--active': tab === reportTab, }), children: prettyCONSTName(tab) }, tab))) }) }), reportTab === DEPENDENCY_REPORT_TAB.EXPLORER && (_jsxs(Panel, { className: "project-dependency-explorer", children: [_jsxs(PanelHeader, { children: [_jsx("div", { className: "panel__header__title", children: _jsx("div", { className: "panel__header__title__label", children: "explorer" }) }), _jsxs(PanelHeaderActions, { children: [!flattenView && (_jsxs(_Fragment, { children: [_jsx(PanelHeaderActionItem, { disabled: !treeData, onClick: collapseTree, title: "Collapse Tree", children: _jsx(CompressIcon, {}) }), _jsx(PanelHeaderActionItem, { disabled: !treeData || !dependencyReport, onClick: openAllDependencyNodes, title: "Expand All Dependencies", children: _jsx(ExpandAllIcon, {}) })] })), _jsx("div", { className: "panel__header__action query-builder__functions-explorer__custom-icon", children: flattenView ? (_jsx(SubjectIcon, { title: "View as Tree", onClick: toggleViewAsListOrAsTree })) : (_jsx(ViewHeadlineIcon, { title: "View as Flatten List", onClick: toggleViewAsListOrAsTree })) })] })] }), _jsx("div", { className: "project-dependency-explorer__content", children: treeData && (_jsx(DependencyTreeView, { treeData: treeData, setTreeData: setTreeData })) })] })), reportTab === DEPENDENCY_REPORT_TAB.CONFLICTS && dependencyReport && (_jsx(ProjectDependencyConflictViewer, { report: dependencyReport, dependencyEditorState: dependencyEditorState })), reportTab === DEPENDENCY_REPORT_TAB.RESOLUTION && (_jsx(ProjectDependencyResolutionViewer, { dependencyEditorState: dependencyEditorState }))] }) }), _jsx(ModalFooter, { children: _jsx(ModalFooterButton, { onClick: closeModal, text: "Close", type: "secondary" }) })] }) })); }); const ProjectDependencyInlineExclusionsSelector = observer((props) => { const { projectDependency, isReadOnly } = props; const editorStore = useEditorStore(); const applicationStore = useApplicationStore(); const dependencyEditorState = editorStore.projectConfigurationEditorState.projectDependencyEditorState; const [selectedTransitiveDependency, setSelectedTransitiveDependency] = useState(null); const [transitiveDependencyOptions, setTransitiveDependencyOptions] = useState([]); const getTransitiveDependencies = useCallback(() => { const dependencyReport = dependencyEditorState.dependencyReport; if (!dependencyReport?.graph) { return []; } const transitiveDeps = new Map(); const existingExclusionCoordinates = dependencyEditorState.getExclusionCoordinates(projectDependency.projectId); const visitedNodes = new Set(); const traverseNode = (nodeId) => { if (visitedNodes.has(nodeId)) { return; } visitedNodes.add(nodeId); const node = dependencyReport.graph.nodes.get(nodeId); if (node?.dependencies) { for (let i = 0; i < node.dependencies.length; i++) { const dep = node.dependencies[i]; if (!dep?.groupId || !dep.artifactId) { continue; } const coordinate = generateGAVCoordinates(dep.groupId, dep.artifactId, undefined); if (existingExclusionCoordinates.indexOf(coordinate) === -1 && coordinate !== `${projectDependency.groupId}:${projectDependency.artifactId}`) { transitiveDeps.set(coordinate, { label: generateGAVCoordinates(dep.groupId, dep.artifactId, undefined), value: coordinate, groupId: dep.groupId, artifactId: dep.artifactId, }); } traverseNode(dep.id); } } }; const rootNodeId = generateGAVCoordinates(guaranteeNonNullable(projectDependency.groupId), guaranteeNonNullable(projectDependency.artifactId), guaranteeNonNullable(projectDependency.versionId)); traverseNode(rootNodeId); return Array.from(transitiveDeps.values()).sort((a, b) => a.label.localeCompare(b.label)); }, [ dependencyEditorState, projectDependency.projectId, projectDependency.groupId, projectDependency.artifactId, projectDependency.versionId, ]); useEffect(() => { setTransitiveDependencyOptions(getTransitiveDependencies()); }, [ dependencyEditorState.dependencyReport, projectDependency.projectId, getTransitiveDependencies, ]); const addExclusionFromDropdown = (option) => { if (!option) { return; } try { dependencyEditorState.addExclusionByCoordinate(projectDependency.projectId, option.value); setSelectedTransitiveDependency(null); setTransitiveDependencyOptions(getTransitiveDependencies()); applicationStore.notificationService.notifySuccess(`Exclusion added: ${option.value}. Click "Validate Dependencies" to apply changes.`); } catch (error) { assertErrorThrown(error); applicationStore.notificationService.notifyError(`Failed to add exclusion: ${error.message}`); } }; if (isReadOnly) { return null; } return (_jsx("div", { className: "project-dependency-exclusions-selector", children: _jsx(CustomSelectorInput, { className: "project-dependency-exclusions-selector__dropdown", placeholder: "Add exclusion...", options: transitiveDependencyOptions, onChange: addExclusionFromDropdown, value: selectedTransitiveDependency, isClearable: true, escapeClearsValue: true, disabled: transitiveDependencyOptions.length === 0, darkMode: !applicationStore.layoutService.TEMPORARY__isLightColorThemeEnabled }) })); }); const ProjectDependencyExclusionsList = observer((props) => { const { projectDependency, isReadOnly } = props; const editorStore = useEditorStore(); const applicationStore = useApplicationStore(); const dependencyEditorState = editorStore.projectConfigurationEditorState.projectDependencyEditorState; const [, setForceUpdate] = useState(0); const exclusions = dependencyEditorState.getExclusions(projectDependency.projectId); useEffect(() => { setForceUpdate((prev) => prev + 1); }, [ dependencyEditorState.dependencyReport, projectDependency.projectId, dependencyEditorState, ]); useEffect(() => { const interval = setInterval(() => { const currentExclusions = dependencyEditorState.getExclusions(projectDependency.projectId); if (currentExclusions.length !== exclusions.length) { setForceUpdate((prev) => prev + 1); } }, 1000); return () => clearInterval(interval); }, [exclusions.length, dependencyEditorState, projectDependency.projectId]); const removeExclusion = (exclusion) => { try { dependencyEditorState.removeExclusion(projectDependency.projectId, exclusion); if (!dependencyEditorState.hasAnyExclusions) { applicationStore.notificationService.notifySuccess(`Last exclusion removed: ${exclusion.coordinate}. Refreshing dependency tree...`); flowResult(dependencyEditorState.fetchDependencyReport()).catch(applicationStore.alertUnhandledError); } else { applicationStore.notificationService.notifySuccess(`Exclusion removed: ${exclusion.coordinate}. Click "Validate Dependencies" to apply changes.`); } } catch (error) { assertErrorThrown(error); applicationStore.notificationService.notifyError(`Failed to remove exclusion: ${error.message}`); } }; if (exclusions.length === 0) { return null; } return (_jsxs("div", { className: "project-dependency-exclusions-list", children: [_jsx("div", { className: "project-dependency-exclusions-list__header", children: _jsxs("div", { className: "project-dependency-exclusions-list__title", children: ["Exclusions (", exclusions.length, ")"] }) }), _jsx("div", { className: "project-dependency-exclusions-list__items", children: exclusions.map((exclusion) => (_jsxs("div", { className: "project-dependency-exclusions-list__item", children: [_jsx("div", { className: "project-dependency-exclusions-list__item__coordinate", children: exclusion.coordinate }), !isReadOnly && (_jsx("button", { className: "project-dependency-exclusions-list__item__remove-btn btn--dark btn--caution", onClick: () => removeExclusion(exclusion), title: "Remove exclusion", children: _jsx(TimesIcon, {}) }))] }, exclusion.coordinate))) })] })); }); const ProjectVersionDependencyEditor = observer((props) => { const { projectDependency, deleteValue, isReadOnly, projects } = props; const projectDependencyData = projects.get(projectDependency.projectId); const editorStore = useEditorStore(); const applicationStore = useApplicationStore(); const projectSelectorRef = useRef(null); const versionSelectorRef = useRef(null); const configState = editorStore.projectConfigurationEditorState; const dependencyEditorState = configState.projectDependencyEditorState; const versions = configState.versions.get(projectDependency.projectId) ?? []; const [fetchSelectedProjectVersionsStatus] = useState(ActionState.create()); // project const selectedProject = configState.projects.get(projectDependency.projectId); const selectedProjectOption = selectedProject ? buildProjectOption(selectedProject) : null; const projectDisabled = !configState.associatedProjectsAndVersionsFetched || configState.isReadOnly; const projectsOptions = Array.from(configState.projects.values()) .map(buildProjectOption) .sort(compareLabelFn); const onProjectSelectionChange = async (val) => { if ((val !== null || selectedProjectOption !== null) && (!val || !selectedProjectOption || val.value !== selectedProjectOption.value)) { projectDependency.setProjectId(val?.value.coordinates ?? ''); projectDependency.setVersionId(''); if (val) { try { fetchSelectedProjectVersionsStatus.inProgress(); const _versions = await editorStore.depotServerClient.getVersions(guaranteeNonNullable(projectDependency.groupId), guaranteeNonNullable(projectDependency.artifactId), true); configState.versions.set(val.value.coordinates, _versions); if (_versions.length) { projectDependency.setVersionId(guaranteeNonNullable(_versions[_versions.length - 1])); flowResult(dependencyEditorState.fetchDependencyReport()).catch(applicationStore.alertUnhandledError); } else { projectDependency.setVersionId(''); } fetchSelectedProjectVersionsStatus.complete(); } catch (error) { assertErrorThrown(error); editorStore.applicationStore.notificationService.notifyError(error); fetchSelectedProjectVersionsStatus.reset(); } } } }; // version const version = projectDependency.versionId; const versionOptions = versions .toSorted((v1, v2) => compareSemVerVersions(v2, v1)) .map((v) => { if (v === MASTER_SNAPSHOT_ALIAS) { return { value: v, label: SNAPSHOT_VERSION_ALIAS }; } return { value: v, label: v }; }); const selectedVersionOption = versionOptions.find((v) => v.value === version) ?? null; const versionDisabled = Boolean(!versions.length || !projectDependency.projectId.length) || !configState.associatedProjectsAndVersionsFetched || isReadOnly; const onVersionSelectionChange = (val) => { if ((val !== null || selectedVersionOption !== null) && (!val || !selectedVersionOption || val.value !== selectedVersionOption.value)) { try { projectDependency.setVersionId(val?.value ?? ''); flowResult(dependencyEditorState.fetchDependencyReport()).catch(applicationStore.alertUnhandledError); } catch (error) { assertErrorThrown(error); applicationStore.logService.error(LogEvent.create(LEGEND_STUDIO_APP_EVENT.SDLC_MANAGER_FAILURE), error); } } }; const viewProject = () => { applicationStore.navigationService.navigator.visitAddress(applicationStore.navigationService.navigator.generateAddress(generateViewProjectByGAVRoute(guaranteeNonNullable(projectDependency.groupId), guaranteeNonNullable(projectDependency.artifactId), projectDependency.versionId === MASTER_SNAPSHOT_ALIAS ? SNAPSHOT_VERSION_ALIAS : projectDependency.versionId))); }; // NOTE: This assumes that the dependant project is in the same studio instance as the current project // In the future, the studio instance may be part of the project data const viewSDLCProject = () => { if (projectDependencyData) { applicationStore.navigationService.navigator.visitAddress(applicationStore.navigationService.navigator.generateAddress(projectDependency.versionId === MASTER_SNAPSHOT_ALIAS ? generateViewProjectRoute(projectDependencyData.projectId) : generateViewVersionRoute(projectDependencyData.projectId, projectDependency.versionId))); } }; const projectSelectorPlaceholder = !projectDependency.projectId.length ? 'Choose project' : versionDisabled ? 'No project version found. Please create a new one.' : 'Select version'; return (_jsxs("div", { className: "project-dependency-editor", children: [_jsx(CustomSelectorInput, { className: "project-dependency-editor__selector", inputRef: projectSelectorRef, disabled: projectDisabled, options: projectsOptions, isClearable: true, escapeClearsValue: true, onChange: (val) => { onProjectSelectionChange(val).catch(applicationStore.alertUnhandledError); }, value: selectedProjectOption, isLoading: configState.fetchingProjectVersionsState.isInProgress, formatOptionLabel: formatOptionLabel, darkMode: !applicationStore.layoutService.TEMPORARY__isLightColorThemeEnabled }), _jsx(CustomSelectorInput, { className: "project-dependency-editor__selector", inputRef: versionSelectorRef, options: versionOptions, isClearable: true, escapeClearsValue: true, onChange: onVersionSelectionChange, value: selectedVersionOption, disabled: versionDisabled, placeholder: fetchSelectedProjectVersionsStatus.isInProgress ? 'Fetching project versions' : projectSelectorPlaceholder, isLoading: editorStore.projectConfigurationEditorState .fetchingProjectVersionsState.isInProgress || fetchSelectedProjectVersionsStatus.isInProgress, darkMode: !applicationStore.layoutService.TEMPORARY__isLightColorThemeEnabled }), selectedProject && selectedVersionOption && (_jsx(ProjectDependencyInlineExclusionsSelector, { projectDependency: projectDependency, isReadOnly: isReadOnly })), _jsxs(ControlledDropdownMenu, { className: "project-dependency-editor__visit-project-btn__dropdown-trigger btn--medium", content: _jsxs(MenuContent, { children: [_jsx(MenuContentItem, { disabled: !selectedProject || !selectedVersionOption, onClick: viewProject, title: "View project", children: "Project" }), _jsx(MenuContentItem, { title: "View SDLC project", disabled: !selectedProject || !selectedVersionOption || !projectDependencyData, onClick: viewSDLCProject, children: "SDLC project" })] }), children: ["Go to... ", _jsx(CaretDownIcon, { title: "Show more options..." })] }), _jsx("button", { className: "project-dependency-editor__remove-btn btn--dark btn--caution", disabled: isReadOnly, onClick: deleteValue, tabIndex: -1, title: "Close", children: _jsx(TimesIcon, {}) })] })); }); export const ProjectDependencyEditor = observer(() => { const editorStore = useEditorStore(); const applicationStore = useApplicationStore(); const configState = editorStore.tabManagerState.getCurrentEditorState(ProjectConfigurationEditorState); const dependencyEditorState = configState.projectDependencyEditorState; const currentProjectConfiguration = configState.currentProjectConfiguration; const deleteProjectDependency = (val) => () => { currentProjectConfiguration.deleteProjectDependency(val); flowResult(dependencyEditorState.fetchDependencyReport()).catch(applicationStore.alertUnhandledError); }; const isReadOnly = editorStore.isInViewerMode; const isLoading = configState.updatingConfigurationState.isInProgress || configState.fetchingProjectVersionsState.isInProgress || dependencyEditorState.fetchingDependencyInfoState.isInProgress || dependencyEditorState.validatingDependenciesState.isInProgress; return (_jsxs(PanelContentLists, { children: [_jsx(PanelLoadingIndicator, { isLoading: isLoading }), isLoading && (_jsx("div", { className: "project-dependency-editor__progress-msg", children: configState.updatingConfigurationState.isInProgress ? `Updating configuration...` : configState.fetchingProjectVersionsState.isInProgress ? `Fetching dependency versions` : dependencyEditorState.validatingDependenciesState.isInProgress ? 'Validating dependencies and compiling...' : dependencyEditorState.fetchingDependencyInfoState.isInProgress ? 'Updating project dependency tree and potential conflicts' : '' })), _jsx(ProjectDependencyActions, { dependencyEditorState: dependencyEditorState }), currentProjectConfiguration.projectDependencies.map((projectDependency) => (_jsxs("div", { children: [_jsx(ProjectVersionDependencyEditor, { projectDependency: projectDependency, deleteValue: deleteProjectDependency(projectDependency), isReadOnly: isReadOnly, projects: configState.projects }), _jsx(ProjectDependencyExclusionsList, { projectDependency: projectDependency, isReadOnly: isReadOnly }, `${projectDependency.projectId}-exclusions`)] }, projectDependency._UUID))), dependencyEditorState.reportTab && (_jsx(ProjectDependencyReportModal, { tab: dependencyEditorState.reportTab, dependencyEditorState: dependencyEditorState }))] })); }); //# sourceMappingURL=ProjectDependencyEditor.js.map