diff --git a/src/api/loadWeightedGraph.js b/src/api/loadWeightedGraph.js new file mode 100644 index 0000000..4c49f40 --- /dev/null +++ b/src/api/loadWeightedGraph.js @@ -0,0 +1,76 @@ +// @flow + +import * as WeightedGraph from "../core/weightedGraph"; +import {type WeightedGraph as WeightedGraphT} from "../core/weightedGraph"; +import * as Weights from "../core/weights"; +import {type Weights as WeightsT} from "../core/weights"; +import {type NodeContraction} from "../core/graph"; +import {TaskReporter} from "../util/taskReporter"; +import {type IdentitySpec} from "../plugins/identity/identity"; +import {contractWeightedGraph} from "../plugins/identity/contractIdentities"; +import {nodeContractions} from "../plugins/identity/nodeContractions"; +import * as Discourse from "../plugins/discourse/loadWeightedGraph"; +import * as Github from "../plugins/github/loadWeightedGraph"; + +export type LoadWeightedGraphOptions = {| + +discourseOptions: ?Discourse.Options, + +githubOptions: ?Github.Options, + +identitySpec: IdentitySpec, + +weightsOverrides: WeightsT, +|}; + +export async function loadWeightedGraph( + options: LoadWeightedGraphOptions, + taskReporter: TaskReporter +): Promise { + taskReporter.start("load-weighted-graph"); + const { + discourseOptions, + githubOptions, + identitySpec, + weightsOverrides, + } = options; + const pluginGraphs = await _loadPluginGraphs( + discourseOptions, + githubOptions, + taskReporter + ); + const contractions = nodeContractions(identitySpec); + const result = _combineGraphs(pluginGraphs, contractions, weightsOverrides); + taskReporter.finish("load-weighted-graph"); + return result; +} + +export function _loadPluginGraphs( + discourseOptions: ?Discourse.Options, + githubOptions: ?Github.Options, + taskReporter: TaskReporter +): Promise<$ReadOnlyArray> { + const promises: Promise[] = []; + if (discourseOptions) { + const promise = Discourse.loadWeightedGraph(discourseOptions, taskReporter); + promises.push(promise); + } + if (githubOptions) { + const promise = Github.loadWeightedGraph(githubOptions, taskReporter); + promises.push(promise); + } + // It's important to use Promise.all so that we can load the plugins in + // parallel -- since loading is often IO-bound, this can be a big performance + // improvement. + return Promise.all(promises); +} + +export function _combineGraphs( + graphs: $ReadOnlyArray, + contractions: $ReadOnlyArray, + weightsOverrides: WeightsT +): WeightedGraphT { + const merged = WeightedGraph.merge(graphs); + const contracted = contractWeightedGraph(merged, contractions); + const weights = Weights.merge([contracted.weights, weightsOverrides], { + nodeResolver: (a, b) => b, + edgeResolver: (a, b) => b, + }); + return {graph: contracted.graph, weights}; +} diff --git a/src/api/loadWeightedGraph.test.js b/src/api/loadWeightedGraph.test.js new file mode 100644 index 0000000..3e43af1 --- /dev/null +++ b/src/api/loadWeightedGraph.test.js @@ -0,0 +1,51 @@ +// @flow + +import {node} from "../core/graphTestUtil"; +import * as WeightedGraph from "../core/weightedGraph"; +import * as Weights from "../core/weights"; +import {_combineGraphs} from "./loadWeightedGraph"; + +describe("api/loadWeightedGraph", () => { + // The _combineGraphs subfunction does the "interesting" work here; the + // rest is just composing IO heavy stuff (e.g. actually generating the GitHub/ + // Discourse graphs). + describe("_combineGraphs", () => { + const foo = node("foo"); + const bar = node("bar"); + const zod = node("zod"); + it("merges the input graphs", () => { + const wg1 = WeightedGraph.empty(); + const wg2 = WeightedGraph.empty(); + wg1.graph.addNode(foo); + wg1.weights.nodeWeights.set(foo.address, 3); + wg2.graph.addNode(bar); + wg2.weights.nodeWeights.set(bar.address, 3); + const expected = WeightedGraph.merge([wg1, wg2]); + expect(_combineGraphs([wg1, wg2], [], Weights.empty())).toEqual(expected); + }); + it("uses the provided contractions", () => { + const wg = WeightedGraph.empty(); + wg.graph.addNode(foo); + wg.graph.addNode(bar); + const contraction = {old: [foo.address, bar.address], replacement: zod}; + const expected = WeightedGraph.empty(); + expected.graph.addNode(zod); + const combined = _combineGraphs([wg], [contraction], Weights.empty()); + expect(combined).toEqual(expected); + }); + it("uses the weights as overrides", () => { + const wg = WeightedGraph.empty(); + wg.weights.nodeWeights.set(foo.address, 3); + wg.weights.nodeWeights.set(bar.address, 3); + const weights = Weights.empty(); + weights.nodeWeights.set(foo.address, 5); + weights.nodeWeights.set(zod.address, 5); + const combined = _combineGraphs([wg], [], weights); + const expected = WeightedGraph.empty(); + expected.weights.nodeWeights.set(bar.address, 3); + expected.weights.nodeWeights.set(foo.address, 5); + expected.weights.nodeWeights.set(zod.address, 5); + expect(expected).toEqual(combined); + }); + }); +}); diff --git a/src/plugins/identity/contractIdentities.js b/src/plugins/identity/contractIdentities.js index 84908be..8960642 100644 --- a/src/plugins/identity/contractIdentities.js +++ b/src/plugins/identity/contractIdentities.js @@ -23,7 +23,7 @@ import {type IdentitySpec} from "./identity"; * * For more context on this decision, see discussion in #1591. */ -export function _contractWeightedGraph( +export function contractWeightedGraph( wg: WeightedGraphT, contractions: $ReadOnlyArray ): WeightedGraphT { @@ -62,5 +62,5 @@ export function contractIdentities( wg: WeightedGraphT, identitySpec: IdentitySpec ): WeightedGraphT { - return _contractWeightedGraph(wg, nodeContractions(identitySpec)); + return contractWeightedGraph(wg, nodeContractions(identitySpec)); } diff --git a/src/plugins/identity/contractIdentities.test.js b/src/plugins/identity/contractIdentities.test.js index 8c91b49..3e76cd7 100644 --- a/src/plugins/identity/contractIdentities.test.js +++ b/src/plugins/identity/contractIdentities.test.js @@ -3,7 +3,7 @@ import {node} from "../../core/graphTestUtil"; import {Graph, NodeAddress} from "../../core/graph"; import * as Weights from "../../core/weights"; -import {_contractWeightedGraph} from "./contractIdentities"; +import {contractWeightedGraph} from "./contractIdentities"; describe("plugins/identity/contractIdentities", () => { const a = node("a"); @@ -16,17 +16,17 @@ describe("plugins/identity/contractIdentities", () => { w.nodeWeights.set(NodeAddress.empty, 3); return w; }; - describe("_contractWeightedGraph", () => { + describe("contractWeightedGraph", () => { it("contracts the graph", () => { const wg = {graph: graph(), weights: weights()}; - const contracted = _contractWeightedGraph(wg, contractions()); + const contracted = contractWeightedGraph(wg, contractions()); const expected = graph().contractNodes(contractions()); expect(expected.equals(contracted.graph)).toBe(true); }); it("returns a copy of the weights", () => { const ws = weights(); const wg = {graph: graph(), weights: ws}; - const contracted = _contractWeightedGraph(wg, contractions()); + const contracted = contractWeightedGraph(wg, contractions()); expect(contracted.weights).not.toBe(ws); expect(ws).toEqual(contracted.weights); // check they can be modified independently @@ -37,7 +37,7 @@ describe("plugins/identity/contractIdentities", () => { const ws = weights(); ws.nodeWeights.set(a.address, 5); const wg = {graph: graph(), weights: ws}; - expect(() => _contractWeightedGraph(wg, contractions())).toThrow( + expect(() => contractWeightedGraph(wg, contractions())).toThrow( "Explicit weight 5 on contracted node" ); });