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.
This commit is contained in:
Dandelion Mané 2018-04-26 19:09:37 -07:00 committed by GitHub
parent c635034dab
commit 087d8d561e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 117 additions and 5 deletions

View File

@ -172,26 +172,52 @@ export class Graph<NP, EP> {
* Gets the array of all out-edges from the node at the given address. * Gets the array of all out-edges from the node at the given address.
* The order of the resulting array is unspecified. * The order of the resulting array is unspecified.
*/ */
getOutEdges(nodeAddress: Address): Edge<EP>[] { getOutEdges(
nodeAddress: Address,
typeOptions?: {+nodeType?: string, +edgeType?: string}
): Edge<EP>[] {
if (nodeAddress == null) { if (nodeAddress == null) {
throw new Error(`address is ${String(nodeAddress)}`); 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) 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. * Gets the array of all in-edges to the node at the given address.
* The order of the resulting array is unspecified. * The order of the resulting array is unspecified.
*/ */
getInEdges(nodeAddress: Address): Edge<EP>[] { getInEdges(
nodeAddress: Address,
typeOptions?: {+nodeType?: string, +edgeType?: string}
): Edge<EP>[] {
if (nodeAddress == null) { if (nodeAddress == null) {
throw new Error(`address is ${String(nodeAddress)}`); 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) 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;
} }
/** /**

View File

@ -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", () => { it("gets out-edges", () => {
const nodeAndExpectedEdgePairs = [ const nodeAndExpectedEdgePairs = [
[demoData.heroNode(), [demoData.eatEdge()]], [demoData.heroNode(), [demoData.eatEdge()]],