UNPKG

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

Version:
322 lines 16.5 kB
/** * 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 {} from '@finos/legend-art'; import { addUniqueEntry, assertErrorThrown, assertType, deleteEntry, hashArray, LogEvent, UnsupportedOperationError, } from '@finos/legend-shared'; import { action, computed, makeObservable, observable } from 'mobx'; import { Class, GRAPH_MANAGER_EVENT, PackageableElementExplicitReference, PropertyExplicitReference, getAllClassConstraints, isStubbed_RawLambda, TYPE_CAST_TOKEN, } from '@finos/legend-graph'; import { graphFetchTree_addSubTree, graphFetchTree_removeSubTree, QueryBuilderExplorerTreePropertyNodeData, QueryBuilderExplorerTreeSubTypeNodeData, } from '@finos/legend-query-builder'; import { DataQualityPropertyGraphFetchTree, DataQualityRootGraphFetchTree, } from '../../graph/metamodel/pure/packageableElements/data-quality/DataQualityGraphFetchTree.js'; import { ConstraintState } from '../states/ConstraintState.js'; import { DATA_QUALITY_HASH_STRUCTURE } from '../../graph/metamodel/DSL_DataQuality_HashUtils.js'; import { dataQualityGraphFetchTree_removeConstraints, graphFetchTree_removeAllSubTrees, } from '../../graph-manager/DSL_DataQuality_GraphModifierHelper.js'; export class DataQualityGraphFetchTreeNodeData { id; label; tree; parentId; editorStore; isOpen; isReadOnly; childrenIds = []; constraints = []; constructor(editorStore, id, label, parentId, graphFetchTreeNode) { makeObservable(this, { hashCode: computed, constraints: observable, isReadOnly: observable, setConstraintsForClass: action, setConstraints: action, setIsReadOnly: action, }); this.id = id; this.label = label; this.parentId = parentId; this.tree = graphFetchTreeNode; // NOTE: in this tree, every node is open this.isOpen = true; this.editorStore = editorStore; } setIsReadOnly(isReadOnly) { this.isReadOnly = isReadOnly; } setConstraintsForClass(_class, constraintsToSelect) { const constraints = getAllClassConstraints(_class).map((constraint) => new ConstraintState(constraint)); const lambdas = new Map(); const index = new Map(); constraints.forEach((constraintState) => { if (!isStubbed_RawLambda(constraintState.constraint.functionDefinition)) { lambdas.set(constraintState.lambdaId, constraintState.constraint.functionDefinition); index.set(constraintState.lambdaId, constraintState); if (constraintsToSelect.find((constraintName) => constraintState.constraint.name === constraintName)) { constraintState.isSelected = true; } } }); if (lambdas.size) { this.editorStore.graphManagerState.graphManager .lambdasToPureCode(lambdas) .then((res) => { res.forEach((grammarText, key) => { const constraintState = index.get(key); constraintState?.setLambdaString(constraintState.extractLambdaString(grammarText)); }); this.setConstraints(constraints); }) .catch((error) => { assertErrorThrown(error); this.editorStore.applicationStore.logService.error(LogEvent.create(GRAPH_MANAGER_EVENT.PARSING_FAILURE), error); }); } } setConstraints(constraints) { this.constraints = constraints; } get type() { if (this.tree instanceof DataQualityPropertyGraphFetchTree) { return this.tree.property.value.genericType.value.rawType; } if (this.tree instanceof DataQualityRootGraphFetchTree) { return this.tree.class.value; } throw new UnsupportedOperationError(`Can't get type of Graph Fetch Tree`, this.tree); } get hashCode() { return hashArray([ DATA_QUALITY_HASH_STRUCTURE.DATA_QUALITY_GRAPH_FETCH_TREE_NODE_DATA, this.id, this.label, this.tree, this.parentId ?? '', hashArray(this.childrenIds), ]); } } export const generateRootGraphFetchTreeNodeID = (parentNodeId, classValue) => `${parentNodeId ? `${parentNodeId}.` : ''}${classValue ? `${TYPE_CAST_TOKEN}${classValue}` : ''}`; const getPrunableNodes = (treeData) => Array.from(treeData.nodes.values()).filter((node) => (node.type instanceof Class && node.childrenIds.length === 0 && node.constraints.length === 0) || // orphan node (node.tree instanceof DataQualityPropertyGraphFetchTree && node.parentId && !treeData.nodes.has(node.parentId))); const removeNode = (treeData, node) => { const parentNode = node.parentId ? treeData.nodes.get(node.parentId) : undefined; if (parentNode) { deleteEntry(parentNode.childrenIds, node.id); graphFetchTree_removeSubTree(parentNode.tree, node.tree); } else { if (node.tree instanceof DataQualityRootGraphFetchTree) { deleteEntry(treeData.rootIds, node.id); dataQualityGraphFetchTree_removeConstraints(node.tree); graphFetchTree_removeAllSubTrees(node.tree); } graphFetchTree_removeSubTree(treeData.tree, node.tree); } treeData.nodes.delete(node.id); }; const pruneTreeData = (treeData) => { let prunableNodes = getPrunableNodes(treeData); while (prunableNodes.length) { prunableNodes.forEach((node) => { removeNode(treeData, node); }); prunableNodes = getPrunableNodes(treeData); } }; export const removeNodeRecursively = (treeData, node) => { removeNode(treeData, node); pruneTreeData(treeData); }; export const generateGraphFetchTreeNodeID = (property, parentNodeId, subType) => `${parentNodeId ? `${parentNodeId}.` : ''}${property.name}${subType ? `${TYPE_CAST_TOKEN}${subType.path}` : ''}`; const buildGraphFetchSubTree = (editorStore, tree, parentNode, nodes, fetchConstraints, isReadOnly) => { assertType(tree, DataQualityPropertyGraphFetchTree, 'Graph fetch sub-tree must be a property graph fetch tree'); const property = tree.property.value; const subType = tree.subType?.value; const parentNodeId = parentNode?.id; const node = new DataQualityGraphFetchTreeNodeData(editorStore, generateGraphFetchTreeNodeID(property, parentNodeId, subType), property.name, parentNodeId, tree); node.setIsReadOnly(isReadOnly); if (subType) { node.setConstraintsForClass(subType, tree.constraints); } else if (node.type instanceof Class && fetchConstraints && !isReadOnly) { node.setConstraintsForClass(node.type, tree.constraints); } tree.subTrees.forEach((subTree) => { const subTreeNode = buildGraphFetchSubTree(editorStore, subTree, node, nodes, fetchConstraints, isReadOnly); addUniqueEntry(node.childrenIds, subTreeNode.id); nodes.set(subTreeNode.id, subTreeNode); }); return node; }; const buildRootGraphFetchSubTree = (editorStore, tree, parentNode, nodes, fetchConstraints, isReadonly) => { const parentNodeId = parentNode?.id; const node = new DataQualityGraphFetchTreeNodeData(editorStore, generateRootGraphFetchTreeNodeID(parentNodeId, tree.class.valueForSerialization ?? ''), tree.class.value.name, parentNodeId, tree); node.setIsReadOnly(isReadonly); if (node.type instanceof Class && fetchConstraints && !isReadonly) { node.setConstraintsForClass(node.type, tree.constraints); } tree.subTrees.forEach((subTree) => { const subTreeNode = buildGraphFetchSubTree(editorStore, subTree, node, nodes, fetchConstraints, isReadonly); addUniqueEntry(node.childrenIds, subTreeNode.id); nodes.set(subTreeNode.id, subTreeNode); }); return node; }; export const buildGraphFetchTreeData = (editorStore, tree, displayRoot, fetchConstraints, isReadOnly) => { const rootIds = []; const nodes = new Map(); if (displayRoot) { const rootNode = buildRootGraphFetchSubTree(editorStore, tree, undefined, nodes, fetchConstraints, isReadOnly); addUniqueEntry(rootIds, rootNode.id); nodes.set(rootNode.id, rootNode); } else { tree.subTrees.forEach((subTree) => { const subTreeNode = buildGraphFetchSubTree(editorStore, subTree, undefined, nodes, fetchConstraints, isReadOnly); addUniqueEntry(rootIds, subTreeNode.id); nodes.set(subTreeNode.id, subTreeNode); }); } return { rootIds, nodes, tree }; }; export const isGraphFetchTreeDataEmpty = (treeData) => treeData.tree.subTrees.length === 0; export const isConstraintsClassesTreeEmpty = (treeData) => treeData.rootIds.length === 0; export const updateNodeConstraints = action((treeData, node, constraints, addConstraint) => { //update the node of graph fetch tree present const updatedTreeNode = node.tree; if (addConstraint) { constraints.forEach((constraint) => { updatedTreeNode.constraints.push(constraint.name); }); } else { updatedTreeNode.constraints = updatedTreeNode.constraints.filter((constraintName) => !constraints.find((constraint) => constraint.name === constraintName)); } }); export const addQueryBuilderPropertyNode = (treeData, explorerTreeData, node, dataQualityState) => { const editorStore = dataQualityState.editorStore; const rootNodeId = generateRootGraphFetchTreeNodeID(undefined, treeData.tree.class.valueForSerialization ?? ''); //root node and property node handled differently //handling property node if (node instanceof QueryBuilderExplorerTreePropertyNodeData || node instanceof QueryBuilderExplorerTreeSubTypeNodeData) { // traverse the property node all the way to the root and resolve the // chain of property that leads to this property node const propertyGraphFetchTrees = []; if (node instanceof QueryBuilderExplorerTreePropertyNodeData) { propertyGraphFetchTrees.push(new DataQualityPropertyGraphFetchTree(PropertyExplicitReference.create(node.property), undefined)); } let parentExplorerTreeNode = explorerTreeData.nodes.get(node.parentId); while (parentExplorerTreeNode instanceof QueryBuilderExplorerTreePropertyNodeData || parentExplorerTreeNode instanceof QueryBuilderExplorerTreeSubTypeNodeData) { let subType = node instanceof QueryBuilderExplorerTreeSubTypeNodeData ? PackageableElementExplicitReference.create(node.subclass) : undefined; let subtypeAssigned = node instanceof QueryBuilderExplorerTreeSubTypeNodeData; while (parentExplorerTreeNode instanceof QueryBuilderExplorerTreeSubTypeNodeData) { if (!subtypeAssigned) { subType = PackageableElementExplicitReference.create(parentExplorerTreeNode.subclass); subtypeAssigned = true; } parentExplorerTreeNode = explorerTreeData.nodes.get(parentExplorerTreeNode.parentId); } if (parentExplorerTreeNode instanceof QueryBuilderExplorerTreePropertyNodeData && parentExplorerTreeNode.mappingData.entityMappedProperty?.subType && parentExplorerTreeNode.type instanceof Class) { subType = PackageableElementExplicitReference.create(parentExplorerTreeNode.type); } if (parentExplorerTreeNode instanceof QueryBuilderExplorerTreePropertyNodeData) { const propertyGraphFetchTree = new DataQualityPropertyGraphFetchTree(PropertyExplicitReference.create(parentExplorerTreeNode.property), subType); if (propertyGraphFetchTrees.length > 0) { propertyGraphFetchTree.subTrees.push(propertyGraphFetchTrees[0]); } propertyGraphFetchTrees.unshift(propertyGraphFetchTree); parentExplorerTreeNode = explorerTreeData.nodes.get(parentExplorerTreeNode.parentId); } else { dataQualityState.applicationStore.notificationService.notifyError(`Can't cast the root class of graph fetch structure to its subtype`); return; } } // traverse the chain of property from the root class to find a node in the // current graph fetch tree that matches any of this property, consider // this the starting point // // If we found a match, use that as the starting point, otherwise, use the root // of the graph fetch tree as the starting point // // NOTE: here we assume that we don't allow specifying duplicated nodes. // perhaps we need to allow that behavior in the future so people can customize the // shape of the object (e.g. same field but under different aliases) //current node id will be pointing root node id let currentNodeId = rootNodeId; let parentNode = treeData.nodes.get(rootNodeId); let newSubTree; for (const propertyGraphFetchTree of propertyGraphFetchTrees) { currentNodeId = generateGraphFetchTreeNodeID(propertyGraphFetchTree.property.value, currentNodeId, propertyGraphFetchTree.subType?.value); const existingGraphFetchNode = treeData.nodes.get(currentNodeId); if (existingGraphFetchNode) { parentNode = existingGraphFetchNode; } else { newSubTree = propertyGraphFetchTree; break; } } // construct the query builder graph fetch subtree from the starting point if (newSubTree) { if (!parentNode) { let rootNode = treeData.nodes.get(rootNodeId); if (!rootNode) { rootNode = buildRootGraphFetchSubTree(editorStore, treeData.tree, undefined, treeData.nodes, true, false); addUniqueEntry(treeData.rootIds, rootNode.id); treeData.nodes.set(rootNode.id, rootNode); } parentNode = rootNode; } const childNode = buildGraphFetchSubTree(editorStore, newSubTree, parentNode, treeData.nodes, true, false); treeData.nodes.set(childNode.id, childNode); addUniqueEntry(parentNode.childrenIds, childNode.id); graphFetchTree_addSubTree(parentNode.tree, childNode.tree, dataQualityState.dataQualityQueryBuilderState.observerContext); } } else { //this case arises when root node is dragged let rootNodeFromTree = treeData.nodes.get(rootNodeId); //check if root node already exists in data quality root graph fetch tree if (!rootNodeFromTree) { rootNodeFromTree = buildRootGraphFetchSubTree(editorStore, treeData.tree, undefined, treeData.nodes, true, false); addUniqueEntry(treeData.rootIds, rootNodeFromTree.id); } treeData.nodes.set(rootNodeFromTree.id, rootNodeFromTree); } }; export const buildDefaultDataQualityRootGraphFetchTree = action((selectedClass) => { const dataQualityRootGraphFetchTree = new DataQualityRootGraphFetchTree(PackageableElementExplicitReference.create(selectedClass)); dataQualityRootGraphFetchTree.constraints = selectedClass.constraints.map((constraint) => constraint.name); dataQualityRootGraphFetchTree.subTrees = selectedClass.properties .filter((property) => property.multiplicity.lowerBound > 0) .map((property) => new DataQualityPropertyGraphFetchTree(PropertyExplicitReference.create(property), undefined)); return dataQualityRootGraphFetchTree; }); //# sourceMappingURL=DataQualityGraphFetchTreeUtil.js.map