mirror of
https://github.com/status-im/sourcecred.git
synced 2025-01-24 11:29:44 +00:00
Replace TimelineCredConfig with array of plugins (#1367)
The PluginDeclaration has all of the information we need to configure TimelineCred: it knows all the node and edge types, as well as which node types are user (or scoring) node types. Therefore, we can replace the ad-hoc config object with a simple array of plugin declarations. Since the plugins will be saved as part of the TimelineCred, it means the UI can configure to only show information for plugins that are actually in scope. Test plan: `yarn test` passes, and the prototype still works. Snapshots updated.
This commit is contained in:
parent
dcf4010ff0
commit
65f22a0a74
File diff suppressed because one or more lines are too long
@ -8,6 +8,7 @@ import {toCompat, fromCompat, type Compatible} from "../../util/compat";
|
||||
import {type Interval} from "./interval";
|
||||
import {timelinePagerank} from "./timelinePagerank";
|
||||
import {distributionToCred} from "./distributionToCred";
|
||||
import {type PluginDeclaration, combineTypes} from "../pluginDeclaration";
|
||||
import {
|
||||
Graph,
|
||||
type GraphJSON,
|
||||
@ -21,7 +22,6 @@ import {
|
||||
toJSON as weightsToJSON,
|
||||
fromJSON as weightsFromJSON,
|
||||
} from "../weights";
|
||||
import {type NodeAndEdgeTypes} from "../types";
|
||||
|
||||
export type {Interval} from "./interval";
|
||||
|
||||
@ -61,26 +61,6 @@ export type TimelineCredParameters = {|
|
||||
+weights: Weights,
|
||||
|};
|
||||
|
||||
/**
|
||||
* Configuration for computing TimelineCred
|
||||
*
|
||||
* Unlike the parameters, the config is expected to be static.
|
||||
* It's code-level config that isolates the TimelineCred algorithms from
|
||||
* specific plugin-level details about which nodes addresses are used for scoring,
|
||||
* etc.
|
||||
*
|
||||
* A default config is available in `src/plugins/defaultCredConfig`
|
||||
*/
|
||||
export type TimelineCredConfig = {|
|
||||
// Cred is normalized so that for a given interval, the total score of all
|
||||
// nodes matching this prefix will be equal to the total weight of nodes in
|
||||
// the interval.
|
||||
+scoreNodePrefixes: $ReadOnlyArray<NodeAddressT>,
|
||||
// The types are used to assign base cred to nodes based on their type. Node
|
||||
// that the weight for each type may be overriden in the params.
|
||||
+types: NodeAndEdgeTypes,
|
||||
|};
|
||||
|
||||
/**
|
||||
* Represents the timeline cred of a graph. This class wraps all the data
|
||||
* needed to analyze and interpet cred (ie. it has the Graph and the cred
|
||||
@ -94,20 +74,20 @@ export class TimelineCred {
|
||||
_intervals: $ReadOnlyArray<Interval>;
|
||||
_addressToCred: Map<NodeAddressT, $ReadOnlyArray<number>>;
|
||||
_params: TimelineCredParameters;
|
||||
_config: TimelineCredConfig;
|
||||
_plugins: $ReadOnlyArray<PluginDeclaration>;
|
||||
|
||||
constructor(
|
||||
graph: Graph,
|
||||
intervals: $ReadOnlyArray<Interval>,
|
||||
addressToCred: Map<NodeAddressT, $ReadOnlyArray<number>>,
|
||||
params: TimelineCredParameters,
|
||||
config: TimelineCredConfig
|
||||
plugins: $ReadOnlyArray<PluginDeclaration>
|
||||
) {
|
||||
this._graph = graph;
|
||||
this._intervals = intervals;
|
||||
this._addressToCred = addressToCred;
|
||||
this._params = params;
|
||||
this._config = config;
|
||||
this._plugins = plugins;
|
||||
}
|
||||
|
||||
graph(): Graph {
|
||||
@ -118,8 +98,8 @@ export class TimelineCred {
|
||||
return this._params;
|
||||
}
|
||||
|
||||
config(): TimelineCredConfig {
|
||||
return this._config;
|
||||
plugins(): $ReadOnlyArray<PluginDeclaration> {
|
||||
return this._plugins;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -129,7 +109,7 @@ export class TimelineCred {
|
||||
* This returns a new TimelineCred; it does not modify the existing one.
|
||||
*/
|
||||
async reanalyze(newParams: TimelineCredParameters): Promise<TimelineCred> {
|
||||
return await TimelineCred.compute(this._graph, newParams, this._config);
|
||||
return await TimelineCred.compute(this._graph, newParams, this._plugins);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -216,7 +196,7 @@ export class TimelineCred {
|
||||
this._intervals,
|
||||
filteredAddressToCred,
|
||||
this._params,
|
||||
this._config
|
||||
this._plugins
|
||||
);
|
||||
}
|
||||
|
||||
@ -226,29 +206,32 @@ export class TimelineCred {
|
||||
intervalsJSON: this._intervals,
|
||||
credJSON: MapUtil.toObject(this._addressToCred),
|
||||
paramsJSON: paramsToJSON(this._params),
|
||||
configJSON: this._config,
|
||||
pluginsJSON: this._plugins,
|
||||
};
|
||||
return toCompat(COMPAT_INFO, rawJSON);
|
||||
}
|
||||
|
||||
static fromJSON(j: TimelineCredJSON): TimelineCred {
|
||||
const json = fromCompat(COMPAT_INFO, j);
|
||||
const {graphJSON, intervalsJSON, credJSON, paramsJSON, configJSON} = json;
|
||||
const {graphJSON, intervalsJSON, credJSON, paramsJSON, pluginsJSON} = json;
|
||||
const cred = MapUtil.fromObject(credJSON);
|
||||
const graph = Graph.fromJSON(graphJSON);
|
||||
const params = paramsFromJSON(paramsJSON);
|
||||
return new TimelineCred(graph, intervalsJSON, cred, params, configJSON);
|
||||
return new TimelineCred(graph, intervalsJSON, cred, params, pluginsJSON);
|
||||
}
|
||||
|
||||
static async compute(
|
||||
graph: Graph,
|
||||
params: TimelineCredParameters,
|
||||
config: TimelineCredConfig
|
||||
plugins: $ReadOnlyArray<PluginDeclaration>
|
||||
): Promise<TimelineCred> {
|
||||
const nodeOrder = Array.from(graph.nodes()).map((x) => x.address);
|
||||
const types = combineTypes(plugins);
|
||||
const userTypes = [].concat(...plugins.map((x) => x.userTypes));
|
||||
const scorePrefixes = userTypes.map((x) => x.prefix);
|
||||
const distribution = await timelinePagerank(
|
||||
graph,
|
||||
config.types,
|
||||
types,
|
||||
params.weights,
|
||||
params.intervalDecay,
|
||||
params.alpha
|
||||
@ -256,7 +239,7 @@ export class TimelineCred {
|
||||
const cred = distributionToCred(
|
||||
distribution,
|
||||
nodeOrder,
|
||||
config.scoreNodePrefixes
|
||||
userTypes.map((x) => x.prefix)
|
||||
);
|
||||
const addressToCred = new Map();
|
||||
for (let i = 0; i < nodeOrder.length; i++) {
|
||||
@ -270,22 +253,22 @@ export class TimelineCred {
|
||||
intervals,
|
||||
addressToCred,
|
||||
params,
|
||||
config
|
||||
plugins
|
||||
);
|
||||
return preliminaryCred.reduceSize({
|
||||
typePrefixes: config.types.nodeTypes.map((x) => x.prefix),
|
||||
typePrefixes: types.nodeTypes.map((x) => x.prefix),
|
||||
nodesPerType: 100,
|
||||
fullInclusionPrefixes: config.scoreNodePrefixes,
|
||||
fullInclusionPrefixes: scorePrefixes,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
const COMPAT_INFO = {type: "sourcecred/timelineCred", version: "0.4.0"};
|
||||
const COMPAT_INFO = {type: "sourcecred/timelineCred", version: "0.5.0"};
|
||||
|
||||
export opaque type TimelineCredJSON = Compatible<{|
|
||||
+graphJSON: GraphJSON,
|
||||
+paramsJSON: ParamsJSON,
|
||||
+configJSON: TimelineCredConfig,
|
||||
+pluginsJSON: $ReadOnlyArray<PluginDeclaration>,
|
||||
+credJSON: {[string]: $ReadOnlyArray<number>},
|
||||
+intervalsJSON: $ReadOnlyArray<Interval>,
|
||||
|}>;
|
||||
|
@ -4,8 +4,14 @@ import deepFreeze from "deep-freeze";
|
||||
import {sum} from "d3-array";
|
||||
import sortBy from "lodash.sortby";
|
||||
import {utcWeek} from "d3-time";
|
||||
import {NodeAddress, Graph, type NodeAddressT} from "../../core/graph";
|
||||
import {TimelineCred, type TimelineCredConfig} from "./timelineCred";
|
||||
import {
|
||||
NodeAddress,
|
||||
Graph,
|
||||
type NodeAddressT,
|
||||
EdgeAddress,
|
||||
} from "../../core/graph";
|
||||
import {TimelineCred} from "./timelineCred";
|
||||
import {type PluginDeclaration} from "../pluginDeclaration";
|
||||
import {defaultWeights} from "../weights";
|
||||
import {type NodeType} from "../types";
|
||||
|
||||
@ -26,9 +32,13 @@ describe("src/analysis/timeline/timelineCred", () => {
|
||||
description: "a foo",
|
||||
};
|
||||
const fooPrefix = fooType.prefix;
|
||||
const credConfig: () => TimelineCredConfig = () => ({
|
||||
scoreNodePrefixes: [userPrefix],
|
||||
types: {nodeTypes: [userType, fooType], edgeTypes: []},
|
||||
const plugin: PluginDeclaration = deepFreeze({
|
||||
name: "foo",
|
||||
nodePrefix: NodeAddress.empty,
|
||||
edgePrefix: EdgeAddress.empty,
|
||||
nodeTypes: [userType, fooType],
|
||||
edgeTypes: [],
|
||||
userTypes: [userType],
|
||||
});
|
||||
const users = [
|
||||
["starter", (x) => Math.max(0, 20 - x)],
|
||||
@ -75,13 +85,7 @@ describe("src/analysis/timeline/timelineCred", () => {
|
||||
addressToCred.set(address, scores);
|
||||
}
|
||||
const params = {alpha: 0.05, intervalDecay: 0.5, weights: defaultWeights()};
|
||||
return new TimelineCred(
|
||||
graph,
|
||||
intervals,
|
||||
addressToCred,
|
||||
params,
|
||||
credConfig()
|
||||
);
|
||||
return new TimelineCred(graph, intervals, addressToCred, params, [plugin]);
|
||||
}
|
||||
|
||||
it("JSON serialization works", () => {
|
||||
@ -90,7 +94,7 @@ describe("src/analysis/timeline/timelineCred", () => {
|
||||
const tc_ = TimelineCred.fromJSON(json);
|
||||
expect(tc.graph()).toEqual(tc_.graph());
|
||||
expect(tc.params()).toEqual(tc_.params());
|
||||
expect(tc.config()).toEqual(tc_.config());
|
||||
expect(tc.plugins()).toEqual(tc_.plugins());
|
||||
expect(tc.credSortedNodes(NodeAddress.empty)).toEqual(
|
||||
tc.credSortedNodes(NodeAddress.empty)
|
||||
);
|
||||
|
@ -8,19 +8,19 @@ import {Graph} from "../core/graph";
|
||||
import {loadGraph} from "../plugins/github/loadGraph";
|
||||
import {
|
||||
type TimelineCredParameters,
|
||||
type TimelineCredConfig,
|
||||
TimelineCred,
|
||||
} from "../analysis/timeline/timelineCred";
|
||||
|
||||
import {type Project} from "../core/project";
|
||||
import {setupProjectDirectory} from "../core/project_io";
|
||||
import {loadDiscourse} from "../plugins/discourse/loadDiscourse";
|
||||
import {type PluginDeclaration} from "../analysis/pluginDeclaration";
|
||||
import * as NullUtil from "../util/null";
|
||||
|
||||
export type LoadOptions = {|
|
||||
+project: Project,
|
||||
+params: TimelineCredParameters,
|
||||
+config: TimelineCredConfig,
|
||||
+plugins: $ReadOnlyArray<PluginDeclaration>,
|
||||
+sourcecredDirectory: string,
|
||||
+githubToken: string | null,
|
||||
+discourseKey: string | null,
|
||||
@ -44,7 +44,7 @@ export async function load(
|
||||
options: LoadOptions,
|
||||
taskReporter: TaskReporter
|
||||
): Promise<void> {
|
||||
const {project, params, config, sourcecredDirectory, githubToken} = options;
|
||||
const {project, params, plugins, sourcecredDirectory, githubToken} = options;
|
||||
const loadTask = `load-${options.project.id}`;
|
||||
taskReporter.start(loadTask);
|
||||
const cacheDirectory = path.join(sourcecredDirectory, "cache");
|
||||
@ -101,7 +101,7 @@ export async function load(
|
||||
await fs.writeFile(graphFile, JSON.stringify(graph.toJSON()));
|
||||
|
||||
taskReporter.start("compute-cred");
|
||||
const cred = await TimelineCred.compute(graph, params, config);
|
||||
const cred = await TimelineCred.compute(graph, params, plugins);
|
||||
const credJSON = cred.toJSON();
|
||||
const credFile = path.join(projectDirectory, "cred.json");
|
||||
await fs.writeFile(credFile, JSON.stringify(credJSON));
|
||||
|
@ -71,10 +71,7 @@ describe("api/load", () => {
|
||||
weights.nodeManualWeights.set(NodeAddress.empty, 33);
|
||||
// Deep freeze will freeze the weights, too
|
||||
const params = deepFreeze({alpha: 0.05, intervalDecay: 0.5, weights});
|
||||
const config = deepFreeze({
|
||||
scoreNodePrefixes: [NodeAddress.empty],
|
||||
types: {nodeTypes: [], edgeTypes: []},
|
||||
});
|
||||
const plugins = deepFreeze([]);
|
||||
const example = () => {
|
||||
const sourcecredDirectory = tmp.dirSync().name;
|
||||
const taskReporter = new TestTaskReporter();
|
||||
@ -82,7 +79,7 @@ describe("api/load", () => {
|
||||
sourcecredDirectory,
|
||||
githubToken,
|
||||
params,
|
||||
config,
|
||||
plugins,
|
||||
project,
|
||||
discourseKey,
|
||||
};
|
||||
@ -145,7 +142,7 @@ describe("api/load", () => {
|
||||
expect(timelineCredCompute).toHaveBeenCalledWith(
|
||||
expect.anything(),
|
||||
params,
|
||||
config
|
||||
plugins
|
||||
);
|
||||
expect(timelineCredCompute.mock.calls[0][0].equals(combinedGraph())).toBe(
|
||||
true
|
||||
|
@ -1,13 +0,0 @@
|
||||
// @flow
|
||||
|
||||
import deepFreeze from "deep-freeze";
|
||||
import * as Github from "../plugins/github/declaration";
|
||||
import type {TimelineCredConfig} from "../analysis/timeline/timelineCred";
|
||||
|
||||
export const DEFAULT_CRED_CONFIG: TimelineCredConfig = deepFreeze({
|
||||
scoreNodePrefixes: [Github.userNodeType.prefix],
|
||||
types: {
|
||||
nodeTypes: Github.declaration.nodeTypes.slice(),
|
||||
edgeTypes: Github.declaration.edgeTypes.slice(),
|
||||
},
|
||||
});
|
9
src/cli/defaultPlugins.js
Normal file
9
src/cli/defaultPlugins.js
Normal file
@ -0,0 +1,9 @@
|
||||
// @flow
|
||||
|
||||
import deepFreeze from "deep-freeze";
|
||||
import {type PluginDeclaration} from "../analysis/pluginDeclaration";
|
||||
import {declaration as githubDeclaration} from "../plugins/github/declaration";
|
||||
|
||||
export const DEFAULT_PLUGINS: $ReadOnlyArray<PluginDeclaration> = deepFreeze([
|
||||
githubDeclaration,
|
||||
]);
|
@ -9,7 +9,7 @@ import {defaultWeights, fromJSON as weightsFromJSON} from "../analysis/weights";
|
||||
import {load} from "../api/load";
|
||||
import {specToProject} from "../plugins/github/specToProject";
|
||||
import fs from "fs-extra";
|
||||
import {DEFAULT_CRED_CONFIG} from "./defaultCredConfig";
|
||||
import {DEFAULT_PLUGINS} from "./defaultPlugins";
|
||||
|
||||
function usage(print: (string) => void): void {
|
||||
print(
|
||||
@ -106,11 +106,11 @@ const loadCommand: Command = async (args, std) => {
|
||||
projectSpecs.map((s) => specToProject(s, githubToken))
|
||||
);
|
||||
const params = {alpha: 0.05, intervalDecay: 0.5, weights};
|
||||
const config = DEFAULT_CRED_CONFIG;
|
||||
const plugins = DEFAULT_PLUGINS;
|
||||
const optionses = projects.map((project) => ({
|
||||
project,
|
||||
params,
|
||||
config,
|
||||
plugins,
|
||||
sourcecredDirectory: Common.sourcecredDirectory(),
|
||||
githubToken,
|
||||
discourseKey: Common.discourseKey(),
|
||||
|
@ -10,7 +10,7 @@ import loadCommand, {help} from "./load";
|
||||
import type {LoadOptions} from "../api/load";
|
||||
import {defaultWeights, toJSON as weightsToJSON} from "../analysis/weights";
|
||||
import * as Common from "./common";
|
||||
import {DEFAULT_CRED_CONFIG} from "./defaultCredConfig";
|
||||
import {DEFAULT_PLUGINS} from "./defaultPlugins";
|
||||
|
||||
import {makeRepoId, stringToRepoId} from "../core/repoId";
|
||||
|
||||
@ -77,7 +77,7 @@ describe("cli/load", () => {
|
||||
repoIds: [makeRepoId("foo", "bar")],
|
||||
discourseServer: null,
|
||||
},
|
||||
config: DEFAULT_CRED_CONFIG,
|
||||
plugins: DEFAULT_PLUGINS,
|
||||
params: {alpha: 0.05, intervalDecay: 0.5, weights: defaultWeights()},
|
||||
sourcecredDirectory: Common.sourcecredDirectory(),
|
||||
githubToken: fakeGithubToken,
|
||||
@ -103,7 +103,7 @@ describe("cli/load", () => {
|
||||
discourseServer: null,
|
||||
},
|
||||
params: {alpha: 0.05, intervalDecay: 0.5, weights: defaultWeights()},
|
||||
config: DEFAULT_CRED_CONFIG,
|
||||
plugins: DEFAULT_PLUGINS,
|
||||
sourcecredDirectory: Common.sourcecredDirectory(),
|
||||
githubToken: fakeGithubToken,
|
||||
discourseKey: fakeDiscourseKey,
|
||||
@ -141,7 +141,7 @@ describe("cli/load", () => {
|
||||
discourseServer: null,
|
||||
},
|
||||
params: {alpha: 0.05, intervalDecay: 0.5, weights},
|
||||
config: DEFAULT_CRED_CONFIG,
|
||||
plugins: DEFAULT_PLUGINS,
|
||||
sourcecredDirectory: Common.sourcecredDirectory(),
|
||||
githubToken: fakeGithubToken,
|
||||
discourseKey: fakeDiscourseKey,
|
||||
|
@ -5,11 +5,7 @@ import {timeWeek} from "d3-time";
|
||||
import type {Assets} from "../webutil/assets";
|
||||
import {TimelineCredView} from "./TimelineCredView";
|
||||
import {Graph, NodeAddress} from "../core/graph";
|
||||
import {
|
||||
type Interval,
|
||||
TimelineCred,
|
||||
type TimelineCredConfig,
|
||||
} from "../analysis/timeline/timelineCred";
|
||||
import {type Interval, TimelineCred} from "../analysis/timeline/timelineCred";
|
||||
import {defaultWeights} from "../analysis/weights";
|
||||
|
||||
export default class TimelineCredViewInspectiontest extends React.Component<{|
|
||||
@ -51,11 +47,7 @@ export default class TimelineCredViewInspectiontest extends React.Component<{|
|
||||
addressToCred.set(address, scores);
|
||||
}
|
||||
const params = {alpha: 0.05, intervalDecay: 0.5, weights: defaultWeights()};
|
||||
const config: TimelineCredConfig = {
|
||||
scoreNodePrefixes: [NodeAddress.empty],
|
||||
types: {nodeTypes: [], edgeTypes: []},
|
||||
};
|
||||
return new TimelineCred(graph, intervals, addressToCred, params, config);
|
||||
return new TimelineCred(graph, intervals, addressToCred, params, []);
|
||||
}
|
||||
|
||||
render() {
|
||||
|
@ -2,7 +2,10 @@
|
||||
|
||||
import React from "react";
|
||||
import deepEqual from "lodash.isequal";
|
||||
import {type PluginDeclaration} from "../analysis/pluginDeclaration";
|
||||
import {
|
||||
type PluginDeclaration,
|
||||
combineTypes,
|
||||
} from "../analysis/pluginDeclaration";
|
||||
import {type Weights, copy as weightsCopy} from "../analysis/weights";
|
||||
import {type NodeAddressT} from "../core/graph";
|
||||
import {
|
||||
@ -151,6 +154,7 @@ export class TimelineExplorer extends React.Component<Props, State> {
|
||||
}
|
||||
|
||||
renderFilterSelect() {
|
||||
const {nodeTypes} = combineTypes(this.state.timelineCred.plugins());
|
||||
return (
|
||||
<label>
|
||||
<span style={{marginLeft: "5px"}}>Showing: </span>
|
||||
@ -160,13 +164,11 @@ export class TimelineExplorer extends React.Component<Props, State> {
|
||||
this.setState({selectedNodeTypePrefix: e.target.value})
|
||||
}
|
||||
>
|
||||
{this.state.timelineCred
|
||||
.config()
|
||||
.types.nodeTypes.map(({prefix, pluralName}) => (
|
||||
<option key={prefix} value={prefix}>
|
||||
{pluralName}
|
||||
</option>
|
||||
))}
|
||||
{nodeTypes.map(({prefix, pluralName}) => (
|
||||
<option key={prefix} value={prefix}>
|
||||
{pluralName}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
</label>
|
||||
);
|
||||
|
Loading…
x
Reference in New Issue
Block a user