-
Notifications
You must be signed in to change notification settings - Fork 2
/
Copy pathgraph-builder.ts
90 lines (72 loc) · 3.21 KB
/
graph-builder.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
import { MermaidNode } from "./mermaid-node";
import { GitHubIssueReference } from "./models";
import { NullablePartial } from "./utils";
export interface GraphEdge {
from: MermaidNode;
to: MermaidNode;
}
export interface Graph {
vertices: MermaidNode[];
edges: GraphEdge[];
}
interface InternalGraphNode {
value?: MermaidNode;
predecessors: InternalGraphNode[];
successors: InternalGraphNode[];
}
export class GraphBuilder {
private readonly nodes = new Map<string, InternalGraphNode>();
constructor(private readonly includeFinishNode: boolean) {}
private buildNodeKey(issueRef: GitHubIssueReference): string {
return `${issueRef.repoOwner}/${issueRef.repoName}/${issueRef.issueNumber}`;
}
private getOrCreateGraphNode(issueReference: GitHubIssueReference, issue?: MermaidNode): InternalGraphNode {
const nodeKey = this.buildNodeKey(issueReference);
let graphNode = this.nodes.get(nodeKey);
if (!graphNode) {
graphNode = { value: issue, predecessors: [], successors: [] };
this.nodes.set(nodeKey, graphNode);
} else if (issue) {
graphNode.value = issue;
}
return graphNode;
}
public addIssue(issueReference: GitHubIssueReference, issue: MermaidNode): void {
this.getOrCreateGraphNode(issueReference, issue);
}
public addDependency(from: GitHubIssueReference, to: GitHubIssueReference) {
const fromNode = this.getOrCreateGraphNode(from);
const toNode = this.getOrCreateGraphNode(to);
fromNode.successors.push(toNode);
toNode.predecessors.push(fromNode);
}
public getGraph(): Graph {
const graphNodes = Array.from(this.nodes.values());
const vertices = graphNodes.map(x => x.value).filter((x): x is MermaidNode => x !== null);
const startNode = MermaidNode.createStartNode();
const finishNode = this.includeFinishNode ? MermaidNode.createFinishNode() : null;
const edgesFromStartNode: NullablePartial<GraphEdge>[] = graphNodes
.filter(x => x.predecessors.length === 0)
.map(x => ({ from: startNode, to: x.value }));
// No need to look at 'includeFinishNode' here. Edges to finish node will be filtered by 'filterNullEdges' later
// because finishNode is null
const edgesToFinishNode: NullablePartial<GraphEdge>[] = graphNodes
.filter(x => x.successors.length === 0)
.map(x => ({ from: x.value, to: finishNode }));
const internalEdges: NullablePartial<GraphEdge>[] = graphNodes
.map(x => x.successors.map(y => ({ from: x.value, to: y.value })))
.flat();
const allVertices = [startNode, ...vertices, finishNode].filter(this.filterNullVertices);
const allEdges = [...edgesFromStartNode, ...internalEdges, ...edgesToFinishNode].filter(this.filterNullEdges);
return {
vertices: allVertices,
edges: allEdges,
};
}
private filterNullVertices(x: MermaidNode | null): x is MermaidNode {
return Boolean(x);
}
private filterNullEdges(x: NullablePartial<GraphEdge>): x is GraphEdge {
return Boolean(x) && Boolean(x.from) && Boolean(x.to);
}
}