UNPKG

@finos/legend-extension-dsl-data-quality

Version:
186 lines 17.3 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 { useEffect, useRef, useState } from 'react'; import { observer } from 'mobx-react-lite'; import { useDrag } from 'react-dnd'; import { useApplicationStore } from '@finos/legend-application'; import { guaranteeNonNullable, prettyCONSTName } from '@finos/legend-shared'; import { flowResult } from 'mobx'; import { DATA_QUALITY_VALIDATION_TEST_ID } from './constants/DataQualityConstants.js'; import { QueryBuilderExplorerTreeNodeData, QUERY_BUILDER_EXPLORER_TREE_DND_TYPE, QueryBuilderExplorerTreePropertyNodeData, QueryBuilderExplorerTreeRootNodeData, QueryBuilderExplorerTreeSubTypeNodeData, checkForDeprecatedNode, getQueryBuilderPropertyNodeData, getQueryBuilderSubTypeNodeData, QueryBuilderPropertyInfoTooltip, QueryBuilderRootClassInfoTooltip, QueryBuilderSubclassInfoTooltip, renderPropertyTypeIcon, getQueryBuilderExplorerTreeNodeSortRank, } from '@finos/legend-query-builder'; import { CheckIcon, ChevronDownIcon, ChevronRightIcon, clsx, CompressIcon, InfoCircleIcon, MenuContentItemIcon, MenuContentItemLabel, MoreVerticalIcon, PanelHeaderActions, BlankPanelContent, DragPreviewLayer, MenuContent, MenuContentItem, PanelHeader, PanelHeaderActionItem, PanelLoadingIndicator, PURE_ClassIcon, TreeView, useDragPreviewLayer, ControlledDropdownMenu, } from '@finos/legend-art'; import { Class, DerivedProperty, Enumeration, PrimitiveType, TYPE_CAST_TOKEN, getAllClassDerivedProperties, getAllClassProperties, getAllOwnClassProperties, } from '@finos/legend-graph'; export const QueryBuilderExplorerTreeNodeContainer = observer((props) => { const { node, level, stepPaddingInRem, onNodeSelect, innerProps } = props; const { dataQualityState } = innerProps; const { dataQualityQueryBuilderState } = dataQualityState; const [isSelectedFromContextMenu] = useState(false); const explorerState = dataQualityQueryBuilderState.explorerState; const [, dragConnector, dragPreviewConnector] = useDrag(() => ({ type: node.type instanceof Enumeration ? QUERY_BUILDER_EXPLORER_TREE_DND_TYPE.ENUM_PROPERTY : node.type instanceof Class ? QUERY_BUILDER_EXPLORER_TREE_DND_TYPE.CLASS_PROPERTY : QUERY_BUILDER_EXPLORER_TREE_DND_TYPE.PRIMITIVE_PROPERTY, item: () => node instanceof QueryBuilderExplorerTreeNodeData ? { node } : {}, }), [node]); const ref = useRef(null); dragConnector(ref); useDragPreviewLayer(dragPreviewConnector); const isExpandable = Boolean(node.childrenIds.length); const isDerivedProperty = node instanceof QueryBuilderExplorerTreePropertyNodeData && node.property instanceof DerivedProperty; const isMultiple = (node instanceof QueryBuilderExplorerTreePropertyNodeData && (node.property.multiplicity.upperBound === undefined || node.property.multiplicity.upperBound > 1)) || (node instanceof QueryBuilderExplorerTreeSubTypeNodeData && (node.multiplicity.upperBound === undefined || node.multiplicity.upperBound > 1)); const allowPreview = node.mappingData.mapped && node instanceof QueryBuilderExplorerTreePropertyNodeData && node.type instanceof PrimitiveType && !node.isPartOfDerivedPropertyBranch; const nodeExpandIcon = isExpandable ? (node.isOpen ? (_jsx(ChevronDownIcon, {})) : (_jsx(ChevronRightIcon, {}))) : (_jsx("div", {})); const propertyName = explorerState.humanizePropertyName ? node instanceof QueryBuilderExplorerTreeSubTypeNodeData ? TYPE_CAST_TOKEN + prettyCONSTName(node.label) : prettyCONSTName(node.label) : node instanceof QueryBuilderExplorerTreeSubTypeNodeData ? TYPE_CAST_TOKEN + node.label : node.label; const selectNode = () => onNodeSelect?.(node); if (!node.mappingData.mapped && // NOTE: we always want to show at least the root node !(node instanceof QueryBuilderExplorerTreeRootNodeData) && !explorerState.showUnmappedProperties) { return null; } return (_jsxs("div", { className: clsx('data-quality-tree-view__node__container data-quality-validation-explorer-tree__node__container', { 'data-quality-validation-explorer-tree__node__container--selected-from-context-menu': isSelectedFromContextMenu, 'data-quality-validation-explorer-tree__node__container--unmapped': !node.mappingData.mapped, 'data-quality-validation-explorer-tree__node__container--selected': node.isSelected, }), title: !node.mappingData.mapped ? node instanceof QueryBuilderExplorerTreeRootNodeData ? 'Root class is not mapped' : 'Property is not mapped' : undefined, onClick: selectNode, ref: node.mappingData.mapped ? ref : undefined, style: { paddingLeft: `${(level - 1) * (stepPaddingInRem ?? 1) + 0.5}rem`, display: 'flex', }, children: [node instanceof QueryBuilderExplorerTreeRootNodeData && (_jsxs(_Fragment, { children: [_jsxs("div", { className: "data-quality-tree-view__node__icon data-quality-validation-explorer-tree__node__icon", children: [_jsx("div", { className: "data-quality-validation-explorer-tree__expand-icon", children: nodeExpandIcon }), _jsx("div", { className: "data-quality-validation-explorer-tree__type-icon", children: _jsx(PURE_ClassIcon, {}) })] }), _jsx("div", { className: "data-quality-tree-view__node__label data-quality-validation-explorer-tree__node__label data-quality-validation-explorer-tree__node__label--with-action", children: node.label }), _jsx("div", { className: "data-quality-validation-explorer-tree__node__actions", children: _jsx(QueryBuilderRootClassInfoTooltip, { _class: guaranteeNonNullable(dataQualityQueryBuilderState.class), children: _jsx("div", { className: "data-quality-validation-explorer-tree__node__action data-quality-validation-explorer-tree__node__info", "data-testid": DATA_QUALITY_VALIDATION_TEST_ID.DATA_QUALITY_VALIDATION_TOOLTIP_ICON, children: _jsx(InfoCircleIcon, {}) }) }) })] })), (node instanceof QueryBuilderExplorerTreePropertyNodeData || node instanceof QueryBuilderExplorerTreeSubTypeNodeData) && (_jsxs(_Fragment, { children: [_jsxs("div", { className: "data-quality-tree-view__node__icon data-quality-validation-explorer-tree__node__icon", ref: node.elementRef, children: [_jsx("div", { className: "data-quality-validation-explorer-tree__expand-icon", children: nodeExpandIcon }), _jsx("div", { className: "data-quality-validation-explorer-tree__type-icon", children: renderPropertyTypeIcon(node.type) })] }), _jsxs("div", { className: clsx('data-quality-tree-view__node__label data-quality-validation-explorer-tree__node__label data-quality-validation-explorer-tree__node__label--with-action', { 'data-quality-validation-explorer-tree__node__label--with-preview': allowPreview, }, { 'data-quality-validation-explorer-tree__node__label--highlight': node.isHighlighting, }), onAnimationEnd: () => node.setIsHighlighting(false), children: [_jsx("div", { className: clsx('data-quality-validation-explorer-tree__node__label--property__name', { 'data-quality-validation-explorer-tree__node__label--deprecated': checkForDeprecatedNode(node, explorerState.queryBuilderState.graphManagerState.graph, explorerState.nonNullableTreeData), }), children: propertyName }), isDerivedProperty && (_jsx("div", { className: "data-quality-validation-explorer-tree__node__label__derived-property", title: "Property is derived and may require user to specify parameter values", children: "(...)" })), isMultiple && (_jsx("div", { className: "data-quality-validation-explorer-tree__node__label__multiple", title: "Multiple values of this property can cause row explosion", children: "*" }))] }), _jsxs("div", { className: "data-quality-validation-explorer-tree__node__actions", children: [node instanceof QueryBuilderExplorerTreePropertyNodeData && (_jsx(QueryBuilderPropertyInfoTooltip, { title: propertyName, property: node.property, path: node.id, isMapped: node.mappingData.mapped, type: node.type, children: _jsx("div", { className: "data-quality-validation-explorer-tree__node__action data-quality-validation-explorer-tree__node__info", "data-testid": DATA_QUALITY_VALIDATION_TEST_ID.DATA_QUALITY_VALIDATION_TOOLTIP_ICON, children: _jsx(InfoCircleIcon, {}) }) })), node instanceof QueryBuilderExplorerTreeSubTypeNodeData && (_jsx(QueryBuilderSubclassInfoTooltip, { subclass: node.subclass, path: node.id, isMapped: node.mappingData.mapped, multiplicity: node.multiplicity, children: _jsx("div", { className: "data-quality-validation-explorer-tree__node__action data-quality-validation-explorer-tree__node__info", "data-testid": DATA_QUALITY_VALIDATION_TEST_ID.DATA_QUALITY_VALIDATION_TOOLTIP_ICON, children: _jsx(InfoCircleIcon, {}) }) }))] })] }))] })); }); const QueryBuilderExplorerTreeNodeView = observer((props) => { const { node, level, onNodeSelect, getChildNodes, stepPaddingInRem, innerProps, } = props; const { dataQualityState } = innerProps; const { dataQualityQueryBuilderState } = dataQualityState; if (!node.mappingData.mapped && // NOTE: we always want to show at least the root node !(node instanceof QueryBuilderExplorerTreeRootNodeData) && !dataQualityQueryBuilderState.explorerState.showUnmappedProperties) { return null; } return (_jsxs("div", { className: "data-quality-tree-view__node__block", children: [_jsx(QueryBuilderExplorerTreeNodeContainer, { node: node, level: level + 1, stepPaddingInRem: stepPaddingInRem, onNodeSelect: onNodeSelect, innerProps: innerProps }), node.isOpen && getChildNodes(node).map((childNode) => (_jsx(QueryBuilderExplorerTreeNodeView, { node: childNode, level: level + 1, onNodeSelect: onNodeSelect, getChildNodes: getChildNodes, innerProps: innerProps }, childNode.id)))] })); }); const QueryBuilderExplorerTree = observer((props) => { const { dataQualityState } = props; const { dataQualityQueryBuilderState } = dataQualityState; const explorerState = dataQualityQueryBuilderState.explorerState; const treeData = explorerState.nonNullableTreeData; const onNodeSelect = (node) => { if (node.childrenIds.length) { node.isOpen = !node.isOpen; if (node.isOpen && (node instanceof QueryBuilderExplorerTreePropertyNodeData || node instanceof QueryBuilderExplorerTreeSubTypeNodeData) && node.type instanceof Class) { (node instanceof QueryBuilderExplorerTreeSubTypeNodeData ? getAllOwnClassProperties(node.type) : getAllClassProperties(node.type).concat(getAllClassDerivedProperties(node.type))).forEach((property) => { const propertyTreeNodeData = getQueryBuilderPropertyNodeData(property, node, guaranteeNonNullable(explorerState.mappingModelCoverageAnalysisResult)); if (propertyTreeNodeData) { treeData.nodes.set(propertyTreeNodeData.id, propertyTreeNodeData); } }); node.type._subclasses.forEach((subclass) => { const subTypeTreeNodeData = getQueryBuilderSubTypeNodeData(subclass, node, guaranteeNonNullable(explorerState.mappingModelCoverageAnalysisResult)); treeData.nodes.set(subTypeTreeNodeData.id, subTypeTreeNodeData); }); } } explorerState.refreshTree(); }; const getChildNodes = (node) => { const dataToReturn = node.childrenIds .map((id) => treeData.nodes.get(id)) .filter((childNode) => childNode instanceof QueryBuilderExplorerTreeSubTypeNodeData || childNode instanceof QueryBuilderExplorerTreePropertyNodeData) // simple properties come first .sort((a, b) => a.label.localeCompare(b.label)) .sort((a, b) => getQueryBuilderExplorerTreeNodeSortRank(b) - getQueryBuilderExplorerTreeNodeSortRank(a)); return dataToReturn; }; return (_jsx(TreeView, { components: { TreeNodeContainer: QueryBuilderExplorerTreeNodeContainer, TreeNodeView: QueryBuilderExplorerTreeNodeView, }, className: "data-quality-validation-explorer-tree__root", treeData: treeData, onNodeSelect: onNodeSelect, getChildNodes: getChildNodes, innerProps: { dataQualityState, } })); }); export const DataQualityExplorerPanel = observer((props) => { const { dataQualityState } = props; const { dataQualityQueryBuilderState } = dataQualityState; const explorerState = dataQualityQueryBuilderState.explorerState; const applicationStore = useApplicationStore(); const collapseTree = () => { if (explorerState.treeData) { Array.from(explorerState.treeData.nodes.values()).forEach((node) => { node.isOpen = false; }); explorerState.refreshTree(); } }; const toggleShowUnmappedProperties = () => explorerState.setShowUnmappedProperties(!explorerState.showUnmappedProperties); const toggleHumanizePropertyName = () => explorerState.setHumanizePropertyName(!explorerState.humanizePropertyName); const toggleHighlightUsedProperties = () => explorerState.setHighlightUsedProperties(!explorerState.highlightUsedProperties); useEffect(() => { flowResult(explorerState.analyzeMappingModelCoverage()).catch((error) => { applicationStore.alertUnhandledError(error); }); }, [ applicationStore, explorerState, dataQualityQueryBuilderState.executionContextState.mapping, ]); return (_jsxs("div", { "data-testid": DATA_QUALITY_VALIDATION_TEST_ID.DATA_QUALITY_VALIDATION_EXPLORER, className: clsx('panel data-quality-validation__explorer', { backdrop__element: applicationStore.layoutService.showBackdrop, }), children: [_jsx(PanelHeader, { title: "explorer", children: _jsxs(PanelHeaderActions, { children: [_jsx(PanelHeaderActionItem, { onClick: collapseTree, title: "Collapse Tree", children: _jsx(CompressIcon, {}) }), _jsx(ControlledDropdownMenu, { className: "panel__header__action", title: "Show Options Menu...", content: _jsxs(MenuContent, { children: [_jsxs(MenuContentItem, { onClick: toggleShowUnmappedProperties, children: [_jsx(MenuContentItemIcon, { children: explorerState.showUnmappedProperties ? (_jsx(CheckIcon, {})) : null }), _jsx(MenuContentItemLabel, { children: "Show Unmapped Properties" })] }), _jsxs(MenuContentItem, { onClick: toggleHumanizePropertyName, children: [_jsx(MenuContentItemIcon, { children: explorerState.humanizePropertyName ? (_jsx(CheckIcon, {})) : null }), _jsx(MenuContentItemLabel, { children: "Humanize Property Name" })] }), _jsxs(MenuContentItem, { onClick: toggleHighlightUsedProperties, children: [_jsx(MenuContentItemIcon, { children: explorerState.highlightUsedProperties ? (_jsx(CheckIcon, {})) : null }), _jsx(MenuContentItemLabel, { children: "Highlight already used properties" })] })] }), menuProps: { anchorOrigin: { vertical: 'bottom', horizontal: 'left' }, transformOrigin: { vertical: 'top', horizontal: 'left' }, elevation: 7, }, children: _jsx(MoreVerticalIcon, { className: "data-quality-validation__icon__more-options" }) })] }) }), _jsxs("div", { className: "panel__content data-quality-validation-explorer-tree__content", children: [_jsx(PanelLoadingIndicator, { isLoading: explorerState.mappingModelCoverageAnalysisState.isInProgress }), _jsx(DragPreviewLayer, { labelGetter: (item) => explorerState.humanizePropertyName ? prettyCONSTName(item.node.label) : item.node.label, types: Object.values(QUERY_BUILDER_EXPLORER_TREE_DND_TYPE) }), explorerState.mappingModelCoverageAnalysisState.isInProgress ? (_jsx(BlankPanelContent, { children: explorerState.mappingModelCoverageAnalysisState.message })) : (_jsxs(_Fragment, { children: [!explorerState.treeData && (_jsx(BlankPanelContent, { children: "Specify the class, mapping, and runtime to start building query" })), explorerState.treeData && (_jsx(QueryBuilderExplorerTree, { dataQualityState: dataQualityState }))] }))] })] })); }); //# sourceMappingURL=DataQualityExplorerPanel.js.map