mirror of
https://github.com/status-im/sourcecred.git
synced 2025-01-31 14:55:01 +00:00
add api/loadWeightedGraph (#1604)
This adds a new module the api directory which loads a combined WeightedGraph across all available plugins. This is intended as a key piece of a future, less-tightly-coupled load pipeline which will produce WeightedGraphs, as required by #1557. Test plan: The "clean" logic (combining graphs, applying transformations, overriding weights) is tested explicitly. The "unclean" logic, which involves directly generating graphs from Discourse/GitHub, are untested. Arguably we could test with mocks, I'm dubious that doing so would add real value. I think most of the potential issues (especially refactoring-induced issues) would get caught by Flow. This is also one of those "works perfectly or is totally broken" type situations. (Thus, the likelihood of costly "subtle failures" is low.)
This commit is contained in:
parent
1dd7e7a3c3
commit
b5be554d63
76
src/api/loadWeightedGraph.js
Normal file
76
src/api/loadWeightedGraph.js
Normal file
@ -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<WeightedGraphT> {
|
||||
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<WeightedGraphT>> {
|
||||
const promises: Promise<WeightedGraphT>[] = [];
|
||||
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<WeightedGraphT>,
|
||||
contractions: $ReadOnlyArray<NodeContraction>,
|
||||
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};
|
||||
}
|
51
src/api/loadWeightedGraph.test.js
Normal file
51
src/api/loadWeightedGraph.test.js
Normal file
@ -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);
|
||||
});
|
||||
});
|
||||
});
|
@ -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<NodeContraction>
|
||||
): WeightedGraphT {
|
||||
@ -62,5 +62,5 @@ export function contractIdentities(
|
||||
wg: WeightedGraphT,
|
||||
identitySpec: IdentitySpec
|
||||
): WeightedGraphT {
|
||||
return _contractWeightedGraph(wg, nodeContractions(identitySpec));
|
||||
return contractWeightedGraph(wg, nodeContractions(identitySpec));
|
||||
}
|
||||
|
@ -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"
|
||||
);
|
||||
});
|
||||
|
Loading…
x
Reference in New Issue
Block a user