@finos/legend-application-studio
Version:
Legend Studio application core
575 lines • 46.9 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 { 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