Create a weights module with types and utils (#787)

This commit creates a central `weights` module that defines all of the
weight-related types, and provides some utilities for dealing with them.

This way users of weight-concepts do not need to depend on a lot of
random modules just to get the relevant types. The utility methods are
implicitly defined a few places in the codebase: now we can avoid
re-writing them, and test them more thoroughly.

Test plan: Unit tests pass.
This commit is contained in:
Dandelion Mané 2018-09-05 17:34:29 -07:00 committed by GitHub
parent d77c76082d
commit b40c1d078d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 205 additions and 92 deletions

View File

@ -1,22 +1,18 @@
// @flow
import React from "react";
import * as NullUtil from "../../util/null";
import {type EdgeEvaluator} from "../../core/attribution/pagerank";
import {weightsToEdgeEvaluator} from "./weights/weightsToEdgeEvaluator";
import type {StaticPluginAdapter} from "../adapters/pluginAdapter";
import type {StaticAdapterSet} from "../adapters/adapterSet";
import {
type WeightedTypes,
PluginWeightConfig,
} from "./weights/PluginWeightConfig";
import {
type WeightedNodeType,
defaultWeightedNodeType,
} from "./weights/NodeTypeConfig";
import {
type WeightedEdgeType,
defaultWeightedEdgeType,
} from "./weights/EdgeTypeConfig";
defaultWeightsForAdapter,
combineWeights,
} from "./weights/weights";
import {PluginWeightConfig} from "./weights/PluginWeightConfig";
import {FALLBACK_NAME} from "../adapters/fallbackAdapter";
type Props = {|
@ -88,23 +84,16 @@ export class WeightConfig extends React.Component<Props, State> {
}
fire() {
let nodeWeights: WeightedNodeType[] = [];
let edgeWeights: WeightedEdgeType[] = [];
for (const adapter of this.props.adapters.adapters()) {
const weights = this.state.pluginNameToWeights.get(adapter.name());
const newNodeWeights =
weights == null
? adapter.nodeTypes().map(defaultWeightedNodeType)
: weights.nodes;
const newEdgeWeights =
weights == null
? adapter.edgeTypes().map(defaultWeightedEdgeType)
: weights.edges;
nodeWeights = nodeWeights.concat(newNodeWeights);
edgeWeights = edgeWeights.concat(newEdgeWeights);
}
const weights = {nodes: nodeWeights, edges: edgeWeights};
const weights = combineWeights(
this.props.adapters
.adapters()
.map((adapter: StaticPluginAdapter) =>
NullUtil.orElse(
this.state.pluginNameToWeights.get(adapter.name()),
defaultWeightsForAdapter(adapter)
)
)
);
const edgeEvaluator = weightsToEdgeEvaluator(weights);
this.props.onChange(edgeEvaluator);
}

View File

@ -2,21 +2,8 @@
import React from "react";
import {WeightSlider, type Props as WeightSliderProps} from "./WeightSlider";
import {type EdgeType} from "../../adapters/pluginAdapter";
export type WeightedEdgeType = {|
+type: EdgeType,
+forwardWeight: number,
+backwardWeight: number,
|};
export function defaultWeightedEdgeType(type: EdgeType): WeightedEdgeType {
return {
type,
forwardWeight: type.defaultForwardWeight,
backwardWeight: type.defaultBackwardWeight,
};
}
import type {WeightedEdgeType} from "./weights";
export class EdgeTypeConfig extends React.Component<{
+weightedType: WeightedEdgeType,

View File

@ -4,23 +4,12 @@ import React from "react";
import {shallow} from "enzyme";
import {WeightSlider} from "./WeightSlider";
import {
defaultWeightedEdgeType,
EdgeTypeConfig,
EdgeWeightSlider,
} from "./EdgeTypeConfig";
import {EdgeTypeConfig, EdgeWeightSlider} from "./EdgeTypeConfig";
import {assemblesEdgeType} from "../../adapters/demoAdapters";
require("../../testUtil").configureEnzyme();
describe("app/credExplorer/weights/EdgeTypeConfig", () => {
describe("defaultWeightedEdgeType", () => {
it("sets default weights as specified in the type", () => {
const wet = defaultWeightedEdgeType(assemblesEdgeType);
expect(wet.forwardWeight).toEqual(wet.type.defaultForwardWeight);
expect(wet.backwardWeight).toEqual(wet.type.defaultBackwardWeight);
});
});
describe("EdgeTypeConfig", () => {
function example() {
const onChange = jest.fn();

View File

@ -2,16 +2,7 @@
import React from "react";
import {WeightSlider} from "./WeightSlider";
import {type NodeType} from "../../adapters/pluginAdapter";
export type WeightedNodeType = {|+type: NodeType, +weight: number|};
export function defaultWeightedNodeType(type: NodeType): WeightedNodeType {
return {
type,
weight: type.defaultWeight,
};
}
import type {WeightedNodeType} from "./weights";
export class NodeTypeConfig extends React.Component<{
+weightedType: WeightedNodeType,

View File

@ -4,18 +4,12 @@ import React from "react";
import {shallow} from "enzyme";
import {WeightSlider} from "./WeightSlider";
import {defaultWeightedNodeType, NodeTypeConfig} from "./NodeTypeConfig";
import {NodeTypeConfig} from "./NodeTypeConfig";
import {inserterNodeType} from "../../adapters/demoAdapters";
require("../../testUtil").configureEnzyme();
describe("app/credExplorer/weights/NodeTypeConfig", () => {
describe("defaultWeightedNodeType", () => {
it("sets default weight as specified in type", () => {
const wnt = defaultWeightedNodeType(inserterNodeType);
expect(wnt.weight).toEqual(wnt.type.defaultWeight);
});
});
describe("NodeTypeConfig", () => {
function example() {
const onChange = jest.fn();

View File

@ -2,23 +2,17 @@
import React from "react";
import deepEqual from "lodash.isequal";
import {
NodeTypeConfig,
defaultWeightedNodeType,
type WeightedNodeType,
} from "./NodeTypeConfig";
import {
EdgeTypeConfig,
defaultWeightedEdgeType,
type WeightedEdgeType,
} from "./EdgeTypeConfig";
import {NodeTypeConfig} from "./NodeTypeConfig";
import {EdgeTypeConfig} from "./EdgeTypeConfig";
import {StaticPluginAdapter} from "../../adapters/pluginAdapter";
import {styledVariable} from "./EdgeTypeConfig";
export type WeightedTypes = {|
+nodes: $ReadOnlyArray<WeightedNodeType>,
+edges: $ReadOnlyArray<WeightedEdgeType>,
|};
import {
type WeightedTypes,
type WeightedEdgeType,
type WeightedNodeType,
defaultWeightedNodeType,
defaultWeightedEdgeType,
} from "./weights";
export type Props = {|
+adapter: StaticPluginAdapter,

View File

@ -4,8 +4,13 @@ import React from "react";
import {shallow} from "enzyme";
import {PluginWeightConfig} from "./PluginWeightConfig";
import {FactorioStaticAdapter} from "../../adapters/demoAdapters";
import {NodeTypeConfig, defaultWeightedNodeType} from "./NodeTypeConfig";
import {EdgeTypeConfig, defaultWeightedEdgeType} from "./EdgeTypeConfig";
import {NodeTypeConfig} from "./NodeTypeConfig";
import {EdgeTypeConfig} from "./EdgeTypeConfig";
import {
defaultWeightsForAdapter,
defaultWeightedNodeType,
defaultWeightedEdgeType,
} from "./weights";
require("../../testUtil").configureEnzyme();
@ -21,10 +26,7 @@ describe("src/app/credExplorer/weights/PluginWeightConfig", () => {
}
it("fires plugin's default weights on mount", () => {
const {onChange, adapter} = example();
const expected = {
nodes: adapter.nodeTypes().map(defaultWeightedNodeType),
edges: adapter.edgeTypes().map(defaultWeightedEdgeType),
};
const expected = defaultWeightsForAdapter(adapter);
expect(onChange).toHaveBeenCalledWith(expected);
});
it("renders a NodeTypeConfig for each node type", () => {

View File

@ -0,0 +1,74 @@
// @flow
import {NodeAddress, EdgeAddress} from "../../../core/graph";
import type {NodeType, EdgeType} from "../../adapters/pluginAdapter";
import type {StaticPluginAdapter} from "../../adapters/pluginAdapter";
import type {StaticAdapterSet} from "../../adapters/adapterSet";
export type WeightedNodeType = {|+type: NodeType, +weight: number|};
export type WeightedEdgeType = {|
+type: EdgeType,
+forwardWeight: number,
+backwardWeight: number,
|};
export type WeightedTypes = {|
+nodes: $ReadOnlyArray<WeightedNodeType>,
+edges: $ReadOnlyArray<WeightedEdgeType>,
|};
export function defaultWeightedNodeType(type: NodeType): WeightedNodeType {
return {
type,
weight: type.defaultWeight,
};
}
export function defaultWeightedEdgeType(type: EdgeType): WeightedEdgeType {
return {
type,
forwardWeight: type.defaultForwardWeight,
backwardWeight: type.defaultBackwardWeight,
};
}
export function defaultWeightsForAdapter(
adapter: StaticPluginAdapter
): WeightedTypes {
return {
nodes: adapter.nodeTypes().map(defaultWeightedNodeType),
edges: adapter.edgeTypes().map(defaultWeightedEdgeType),
};
}
export function combineWeights(
ws: $ReadOnlyArray<WeightedTypes>
): WeightedTypes {
const seenPrefixes = new Set();
const nodes = [].concat(...ws.map((x) => x.nodes));
for (const {
type: {prefix},
} of nodes) {
if (seenPrefixes.has(prefix)) {
throw new Error(`Duplicate prefix: ${NodeAddress.toString(prefix)}`);
}
seenPrefixes.add(prefix);
}
const edges = [].concat(...ws.map((x) => x.edges));
for (const {
type: {prefix},
} of edges) {
if (seenPrefixes.has(prefix)) {
throw new Error(`Duplicate prefix: ${EdgeAddress.toString(prefix)}`);
}
seenPrefixes.add(prefix);
}
return {nodes, edges};
}
export function defaultWeightsForAdapterSet(
adapters: StaticAdapterSet
): WeightedTypes {
return combineWeights(adapters.adapters().map(defaultWeightsForAdapter));
}

View File

@ -0,0 +1,93 @@
// @flow
import {
defaultWeightedNodeType,
defaultWeightedEdgeType,
defaultWeightsForAdapter,
combineWeights,
defaultWeightsForAdapterSet,
} from "./weights";
import {
inserterNodeType,
machineNodeType,
assemblesEdgeType,
transportsEdgeType,
FactorioStaticAdapter,
staticAdapterSet,
} from "../../adapters/demoAdapters";
describe("app/credExplorer/weights/weights", () => {
describe("defaultWeightedNodeType", () => {
it("sets default weight as specified in type", () => {
const wnt = defaultWeightedNodeType(inserterNodeType);
expect(wnt.weight).toEqual(wnt.type.defaultWeight);
});
});
describe("defaultWeightedEdgeType", () => {
it("sets default weights as specified in the type", () => {
const wet = defaultWeightedEdgeType(assemblesEdgeType);
expect(wet.forwardWeight).toEqual(wet.type.defaultForwardWeight);
expect(wet.backwardWeight).toEqual(wet.type.defaultBackwardWeight);
});
});
describe("defaultWeightsForAdapter", () => {
it("works on the demo adapter", () => {
const adapter = new FactorioStaticAdapter();
const expected = {
nodes: adapter.nodeTypes().map(defaultWeightedNodeType),
edges: adapter.edgeTypes().map(defaultWeightedEdgeType),
};
expect(defaultWeightsForAdapter(adapter)).toEqual(expected);
});
});
describe("combineWeights", () => {
const defaultWeights = () =>
defaultWeightsForAdapter(new FactorioStaticAdapter());
const emptyWeights = () => ({nodes: [], edges: []});
it("successfully combines WeightedTypes", () => {
const weights1 = {
nodes: [defaultWeightedNodeType(inserterNodeType)],
edges: [defaultWeightedEdgeType(assemblesEdgeType)],
};
const weights2 = {
nodes: [defaultWeightedNodeType(machineNodeType)],
edges: [defaultWeightedEdgeType(transportsEdgeType)],
};
expect(combineWeights([weights1, weights2])).toEqual(defaultWeights());
});
it("treats empty weights as an identity", () => {
expect(
combineWeights([emptyWeights(), defaultWeights(), emptyWeights()])
).toEqual(defaultWeights());
});
it("errors on duplicate edge prefix", () => {
const weights = {
nodes: [],
edges: [defaultWeightedEdgeType(assemblesEdgeType)],
};
expect(() => combineWeights([weights, weights])).toThrowError(
"Duplicate prefix"
);
});
it("errors on duplicate node prefix", () => {
const weights = {
nodes: [defaultWeightedNodeType(inserterNodeType)],
edges: [],
};
expect(() => combineWeights([weights, weights])).toThrowError(
"Duplicate prefix"
);
});
});
describe("defaultWeightsForAdapterSet", () => {
it("works on a demo adapter set", () => {
expect(defaultWeightsForAdapterSet(staticAdapterSet())).toEqual(
combineWeights(
staticAdapterSet()
.adapters()
.map(defaultWeightsForAdapter)
)
);
});
});
});

View File

@ -1,7 +1,7 @@
// @flow
import type {Edge} from "../../../core/graph";
import type {WeightedTypes} from "./PluginWeightConfig";
import type {WeightedTypes} from "./weights";
import type {EdgeEvaluator} from "../../../core/attribution/pagerank";
import {NodeTrie, EdgeTrie} from "../../../core/trie";