mirror of
https://github.com/datahub-project/datahub.git
synced 2025-07-30 12:59:51 +00:00
221 lines
6.4 KiB
TypeScript
221 lines
6.4 KiB
TypeScript
import Component from '@ember/component';
|
|
import { classNames } from '@ember-decorators/component';
|
|
import { computed } from '@ember/object';
|
|
import GraphDb, { INode, IEdge } from 'wherehows-web/utils/graph-db';
|
|
import { IDatasetLineage } from 'wherehows-web/typings/api/datasets/relationships';
|
|
import { DatasetEntity } from '@datahub/data-models/entity/dataset/dataset-entity';
|
|
import {
|
|
ISearchEntityRenderProps,
|
|
IEntityRenderCommonPropsSearch
|
|
} from '@datahub/data-models/types/search/search-entity-render-prop';
|
|
|
|
/**
|
|
* Interface that adds more fields to a standard node
|
|
*/
|
|
interface IExpandedNode<T> extends INode<T> {
|
|
// number of children of this node
|
|
nChildren: string;
|
|
// number of parents of this node
|
|
nParents: string;
|
|
}
|
|
|
|
/**
|
|
* Interface to define the properties of a level that are
|
|
* useful to render a level on the ui
|
|
*/
|
|
interface ILevel {
|
|
nodes: Array<IExpandedNode<IDatasetLineage>>;
|
|
collapsed: boolean;
|
|
show: boolean;
|
|
level: number;
|
|
label: string;
|
|
}
|
|
|
|
/**
|
|
* default values for new properties that we can render on the view
|
|
*/
|
|
const defaultField: Partial<ISearchEntityRenderProps> & {
|
|
showInAutoCompletion: boolean;
|
|
showInResultsPreview: boolean;
|
|
showInFacets: boolean;
|
|
desc: string;
|
|
example: string;
|
|
} = {
|
|
showInAutoCompletion: false,
|
|
showInResultsPreview: true,
|
|
showInFacets: false,
|
|
desc: '',
|
|
example: ''
|
|
};
|
|
|
|
/**
|
|
* returns the title of a level given the level number
|
|
* @param level number of the level
|
|
*/
|
|
const getLabel = (level: number): string => {
|
|
if (level < 0) {
|
|
return `Up ${Math.abs(level)} Level`;
|
|
}
|
|
|
|
if (level > 0) {
|
|
return `Down ${Math.abs(level)} Level`;
|
|
}
|
|
|
|
if (level === 0) {
|
|
return `Current Dataset`;
|
|
}
|
|
|
|
return '';
|
|
};
|
|
|
|
/**
|
|
* This component will render a list of nodes grouped by levels, leveraging search-result view
|
|
*/
|
|
@classNames('dataset-relationship-levels')
|
|
export default class DatasetRelationshipLevels extends Component {
|
|
/**
|
|
* Nodes to render on this page
|
|
*/
|
|
nodes!: Array<INode<IDatasetLineage>>;
|
|
|
|
/**
|
|
* Relationships between nodes
|
|
*/
|
|
edges!: Array<IEdge<IDatasetLineage>>;
|
|
|
|
/**
|
|
* Action passed into this component. Will be trigger when a node is selected
|
|
* @param _node
|
|
*/
|
|
toggleNode!: (node: INode<IDatasetLineage>) => void;
|
|
|
|
/**
|
|
* Fields to render for the dataset. We are going to transform the default
|
|
* ones and correct the path given that the dataset is wrapped inside a node.
|
|
* Also we are going to add a couple of more properties that belongs to the node
|
|
* and lineage relation.
|
|
*/
|
|
searchConfig: IEntityRenderCommonPropsSearch = {
|
|
attributes: [
|
|
...DatasetEntity.renderProps.search.attributes.map(
|
|
(field): ISearchEntityRenderProps => ({
|
|
...field,
|
|
fieldName: `payload.dataset.${field.fieldName}`
|
|
})
|
|
),
|
|
{
|
|
...defaultField,
|
|
fieldName: 'payload.type',
|
|
displayName: 'Type'
|
|
},
|
|
{
|
|
...defaultField,
|
|
fieldName: 'payload.actor',
|
|
displayName: 'Actor'
|
|
},
|
|
{
|
|
...defaultField,
|
|
fieldName: 'nChildren',
|
|
displayName: '# Children'
|
|
},
|
|
{
|
|
...defaultField,
|
|
fieldName: 'nParents',
|
|
displayName: '# Parents'
|
|
}
|
|
]
|
|
};
|
|
|
|
/**
|
|
* We are going to transform nodes and edges into a graphDb for
|
|
* convenience, that way it is easier to query the number of children or parents.
|
|
*/
|
|
@computed('nodes', 'edges')
|
|
get graphDb(): GraphDb<IDatasetLineage> {
|
|
return GraphDb.create({
|
|
nodes: this.nodes,
|
|
edges: this.edges
|
|
}) as GraphDb<IDatasetLineage>;
|
|
}
|
|
|
|
/**
|
|
* Will check if we should show a node. In order to determine that, we need to check if the node
|
|
* is upstream or downstream. If the node is downstream, them node will show if parent is selected,
|
|
* if upstream, node will show is child is selected.
|
|
* Also, root node is always visible.
|
|
*/
|
|
getShouldShow(node: INode<IDatasetLineage>): boolean {
|
|
const children = this.graphDb.childrenByNodeId[node.id];
|
|
const parents = this.graphDb.parentsByNodeId[node.id];
|
|
const parentSelected = parents.any((parent): boolean => !!parent.selected);
|
|
const childSelected = children.any((child): boolean => !!child.selected);
|
|
const isRootNode = node.level === 0;
|
|
const isDownStreamNodeWithParentSelected = parentSelected && node.level > 0;
|
|
const isUpStreamNodeWithNodeSelected = childSelected && node.level < 0;
|
|
return isRootNode || isDownStreamNodeWithParentSelected || isUpStreamNodeWithNodeSelected;
|
|
}
|
|
|
|
/**
|
|
* Will generate a level given the first node of the level
|
|
* @param node
|
|
*/
|
|
generateLevel(node: INode<IDatasetLineage>): ILevel {
|
|
const level: ILevel = {
|
|
label: getLabel(node.level),
|
|
level: node.level,
|
|
collapsed: false,
|
|
show: false,
|
|
nodes: []
|
|
};
|
|
return level;
|
|
}
|
|
|
|
/**
|
|
* Will add a node to a level. A level is shown if any of the nodes is visible. A level is collapse
|
|
* if any of its nodes is selected
|
|
* @param level
|
|
* @param node
|
|
*/
|
|
addNodeToLevel(level: ILevel, node: IExpandedNode<IDatasetLineage>): ILevel {
|
|
const shouldShow = this.getShouldShow(node);
|
|
if (shouldShow) {
|
|
return {
|
|
...level,
|
|
show: level.show || shouldShow,
|
|
collapsed: level.collapsed || !!node.selected,
|
|
nodes: [...level.nodes, node]
|
|
};
|
|
}
|
|
return level;
|
|
}
|
|
|
|
/**
|
|
* Will add more properties to the regular nodes like the number of parents of children
|
|
*/
|
|
@computed('graphDb.nodes')
|
|
get decoratedNodes(): Array<IExpandedNode<IDatasetLineage>> {
|
|
return this.graphDb.nodes.map(
|
|
(node): IExpandedNode<IDatasetLineage> => ({
|
|
...node,
|
|
nChildren: node.loaded || node.level < 0 ? `${this.graphDb.childrenByNodeId[node.id].length}` : '-',
|
|
nParents: node.loaded || node.level > 0 ? `${this.graphDb.parentsByNodeId[node.id].length}` : '-'
|
|
})
|
|
);
|
|
}
|
|
|
|
/**
|
|
* Will transform the list of nodes into an array of levels and inside those levels,
|
|
* you can find the nodes.
|
|
*/
|
|
@computed('decoratedNodes')
|
|
get computedNodes(): Array<ILevel> {
|
|
return this.decoratedNodes.reduce((nodesArr: Array<ILevel>, node): Array<ILevel> => {
|
|
const positionArray = node.level - this.graphDb.minLevel;
|
|
const newLevel = nodesArr[positionArray] || this.generateLevel(node);
|
|
const result = [...nodesArr];
|
|
result[positionArray] = this.addNodeToLevel(newLevel, node);
|
|
return result;
|
|
}, []);
|
|
}
|
|
}
|