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:
parent
c635034dab
commit
087d8d561e
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -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()]],
|
||||||
|
|
Loading…
Reference in New Issue