From 087d8d561e94bb3fe8c1858647c5b8b0fe9407f0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dandelion=20Man=C3=A9?= Date: Thu, 26 Apr 2018 19:09:37 -0700 Subject: [PATCH] Enable type filtering on Graph.get{In,Out}Edges (#147) This commit adds an optional `typeOptions` argument to Graph.getInEdges and Graph.getOutEdges. The `typeOptions` allow filtering the returned edges by the type of the edge, and the type of the node that the edge is connected to. This makes it much easier to use these methods to find connections that have a certain relationship, e.g. finding the author of a commit or the comments on an issue. Test plan: A new test suite was written that comprehensively tests this behavior, both for getInEdges and getOutEdges. --- src/core/graph.js | 34 ++++++++++++++-- src/core/graph.test.js | 88 +++++++++++++++++++++++++++++++++++++++++- 2 files changed, 117 insertions(+), 5 deletions(-) diff --git a/src/core/graph.js b/src/core/graph.js index 46ef89b..02eac46 100644 --- a/src/core/graph.js +++ b/src/core/graph.js @@ -172,26 +172,52 @@ export class Graph { * Gets the array of all out-edges from the node at the given address. * The order of the resulting array is unspecified. */ - getOutEdges(nodeAddress: Address): Edge[] { + getOutEdges( + nodeAddress: Address, + typeOptions?: {+nodeType?: string, +edgeType?: string} + ): Edge[] { if (nodeAddress == null) { throw new Error(`address is ${String(nodeAddress)}`); } - return this._lookupEdges(this._outEdges, nodeAddress).map((e) => + let result = this._lookupEdges(this._outEdges, nodeAddress).map((e) => this.getEdge(e) ); + + if (typeOptions != null && typeOptions.edgeType != null) { + const edgeType = typeOptions.edgeType; + result = result.filter((e) => e.address.type === edgeType); + } + if (typeOptions != null && typeOptions.nodeType != null) { + const nodeType = typeOptions.nodeType; + result = result.filter((e) => e.dst.type === nodeType); + } + return result; } /** * Gets the array of all in-edges to the node at the given address. * The order of the resulting array is unspecified. */ - getInEdges(nodeAddress: Address): Edge[] { + getInEdges( + nodeAddress: Address, + typeOptions?: {+nodeType?: string, +edgeType?: string} + ): Edge[] { if (nodeAddress == null) { throw new Error(`address is ${String(nodeAddress)}`); } - return this._lookupEdges(this._inEdges, nodeAddress).map((e) => + let result = this._lookupEdges(this._inEdges, nodeAddress).map((e) => this.getEdge(e) ); + + if (typeOptions != null && typeOptions.edgeType != null) { + const edgeType = typeOptions.edgeType; + result = result.filter((e) => e.address.type === edgeType); + } + if (typeOptions != null && typeOptions.nodeType != null) { + const nodeType = typeOptions.nodeType; + result = result.filter((e) => e.src.type === nodeType); + } + return result; } /** diff --git a/src/core/graph.test.js b/src/core/graph.test.js index 6089a2b..feb5a17 100644 --- a/src/core/graph.test.js +++ b/src/core/graph.test.js @@ -319,7 +319,93 @@ describe("graph", () => { }); }); - describe("in- and out-edges", () => { + describe("getInEdges and getOutEdges", () => { + describe("type filtering", () => { + class ExampleGraph { + graph: Graph<{}, {}>; + root: Address; + idIncrement: number; + inEdges: {[string]: Edge<{}>}; + outEdges: {[string]: Edge<{}>}; + constructor() { + this.graph = new Graph(); + this.idIncrement = 0; + this.root = this.addNode("ROOT").address; + this.inEdges = { + a1: this.addEdge("A", "1", true), + a2: this.addEdge("A", "2", true), + b1: this.addEdge("B", "1", true), + b2: this.addEdge("B", "2", true), + }; + this.outEdges = { + a1: this.addEdge("A", "1", false), + a2: this.addEdge("A", "2", false), + b1: this.addEdge("B", "1", false), + b2: this.addEdge("B", "2", false), + }; + } + + makeAddress(type: string) { + const id = (this.idIncrement++).toString(); + return { + id, + type, + pluginName: "graph-test", + repositoryName: "sourcecred", + }; + } + + addNode(type) { + const node = { + address: this.makeAddress(type), + payload: {}, + }; + this.graph.addNode(node); + return node; + } + + addEdge(nodeType, edgeType, isInEdge) { + const node = this.addNode(nodeType); + const edge = { + address: this.makeAddress(edgeType), + src: isInEdge ? node.address : this.root, + dst: isInEdge ? this.root : node.address, + payload: {}, + }; + this.graph.addEdge(edge); + return edge; + } + } + const exampleGraph = new ExampleGraph(); + [ + [ + "inEdges", + exampleGraph.inEdges, + (opts) => exampleGraph.graph.getInEdges(exampleGraph.root, opts), + ], + [ + "outEdges", + exampleGraph.outEdges, + (opts) => exampleGraph.graph.getOutEdges(exampleGraph.root, opts), + ], + ].forEach(([choice, {a1, a2, b1, b2}, getEdges]) => { + describe(choice, () => { + it("typefiltering is optional", () => { + expectSameSorted(getEdges(), [a1, a2, b1, b2]); + expectSameSorted(getEdges({}), [a1, a2, b1, b2]); + }); + it("filters on node types", () => { + expectSameSorted(getEdges({nodeType: "A"}), [a1, a2]); + }); + it("filters on edge types", () => { + expectSameSorted(getEdges({edgeType: "1"}), [a1, b1]); + }); + it("filters on node and edge types", () => { + expectSameSorted(getEdges({nodeType: "A", edgeType: "1"}), [a1]); + }); + }); + }); + }); it("gets out-edges", () => { const nodeAndExpectedEdgePairs = [ [demoData.heroNode(), [demoData.eatEdge()]],