Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Share default TimelineCredParameters #1376

Merged
merged 1 commit into from Sep 12, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
44 changes: 44 additions & 0 deletions src/analysis/timeline/params.js
Expand Up @@ -5,6 +5,7 @@ import {
type WeightsJSON,
toJSON as weightsToJSON,
fromJSON as weightsFromJSON,
defaultWeights,
} from "../weights";

/**
Expand All @@ -31,6 +32,20 @@ export type TimelineCredParameters = {|
+weights: Weights,
|};

export const DEFAULT_ALPHA = 0.05;
export const DEFAULT_INTERVAL_DECAY = 0.5;

/**
* The PartialTimelineCredParameters are a version of TimelineCredParameters
* where every field has been marked optional, to make it convenient for API
* clients to override just the parameters they want to.
*/
export type PartialTimelineCredParameters = {|
+alpha?: number,
+intervalDecay?: number,
+weights?: Weights,
|};

export type TimelineCredParametersJSON = {|
+alpha: number,
+intervalDecay: number,
Expand All @@ -56,3 +71,32 @@ export function paramsFromJSON(
weights: weightsFromJSON(p.weights),
};
}

/**
* Exports the default TimelineCredParameters.
*
* End consumers of SourceCred will not need to depend on this; it's
* provided for implementation of SourceCred's APIs.
*/
export function defaultParams(): TimelineCredParameters {
return {
alpha: DEFAULT_ALPHA,
intervalDecay: DEFAULT_INTERVAL_DECAY,
weights: defaultWeights(),
};
}

/**
* Promote PartialTimelineCredParameters to TimelineCredParameters.
*
* This takes PartialTimelineCredParameters and mixes them with the
* default parameters to provide a full TimelineCredParameters.
*
* End consumers of SourceCred will not need to depend on this; it's
* provided for implementation of SourceCred's APIs.
*/
export function partialParams(
partial: PartialTimelineCredParameters
): TimelineCredParameters {
return {...defaultParams(), ...partial};
}
54 changes: 51 additions & 3 deletions src/analysis/timeline/params.test.js
@@ -1,19 +1,67 @@
// @flow

import {paramsToJSON, paramsFromJSON} from "./params";
import {
paramsToJSON,
paramsFromJSON,
defaultParams,
partialParams,
type TimelineCredParameters,
DEFAULT_ALPHA,
DEFAULT_INTERVAL_DECAY,
} from "./params";
import {defaultWeights} from "../weights";
import {NodeAddress} from "../../core/graph";

describe("analysis/timeline/params", () => {
it("JSON round trip", () => {
const customWeights = () => {
const weights = defaultWeights();
// Ensure it works with non-default weights
weights.nodeManualWeights.set(NodeAddress.empty, 33);
const p = {alpha: 0.5, intervalDecay: 0.5, weights};
return weights;
};
it("JSON round trip", () => {
const p: TimelineCredParameters = {
alpha: 0.1337,
intervalDecay: 0.31337,
weights: customWeights(),
};
const j = paramsToJSON(p);
const p_ = paramsFromJSON(j);
const j_ = paramsToJSON(p_);
expect(j).toEqual(j_);
expect(p).toEqual(p_);
});
it("defaultParams", () => {
const expected: TimelineCredParameters = {
alpha: DEFAULT_ALPHA,
intervalDecay: DEFAULT_INTERVAL_DECAY,
weights: defaultWeights(),
};
expect(defaultParams()).toEqual(expected);
});
describe("partialParams", () => {
it("uses default values if no overrides provided", () => {
const params = partialParams({});
expect(params).toEqual(defaultParams());
});
it("accepts an alpha override", () => {
const params = partialParams({alpha: 0.99});
expect(params.weights).toEqual(defaultWeights());
expect(params.alpha).toEqual(0.99);
expect(params.intervalDecay).toEqual(DEFAULT_INTERVAL_DECAY);
});
it("accepts weights override", () => {
const weights = customWeights();
const params = partialParams({weights});
expect(params.weights).toEqual(weights);
expect(params.alpha).toEqual(DEFAULT_ALPHA);
expect(params.intervalDecay).toEqual(DEFAULT_INTERVAL_DECAY);
});
it("accepts intervalDecay override", () => {
const params = partialParams({intervalDecay: 0.1});
expect(params.weights).toEqual(defaultWeights());
expect(params.alpha).toEqual(DEFAULT_ALPHA);
expect(params.intervalDecay).toEqual(0.1);
});
});
});
32 changes: 21 additions & 11 deletions src/analysis/timeline/timelineCred.js
Expand Up @@ -21,10 +21,12 @@ import {
paramsToJSON,
paramsFromJSON,
type TimelineCredParametersJSON,
type PartialTimelineCredParameters,
partialParams,
defaultParams,
} from "./params";

export type {Interval} from "./interval";
export type {TimelineCredParameters} from "./params";

/**
* A Graph Node wrapped with cred information.
Expand Down Expand Up @@ -85,8 +87,14 @@ 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._plugins);
async reanalyze(
newParams: PartialTimelineCredParameters
): Promise<TimelineCred> {
return await TimelineCred.compute({
graph: this._graph,
params: newParams,
plugins: this._plugins,
});
}

/**
Expand Down Expand Up @@ -217,21 +225,23 @@ export class TimelineCred {
return new TimelineCred(graph, intervalsJSON, cred, params, pluginsJSON);
}

static async compute(
static async compute(opts: {|
graph: Graph,
params: TimelineCredParameters,
plugins: $ReadOnlyArray<PluginDeclaration>
): Promise<TimelineCred> {
params?: PartialTimelineCredParameters,
plugins: $ReadOnlyArray<PluginDeclaration>,
|}): Promise<TimelineCred> {
const {graph, params, plugins} = opts;
const fullParams = params == null ? defaultParams() : partialParams(params);
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,
types,
params.weights,
params.intervalDecay,
params.alpha
fullParams.weights,
fullParams.intervalDecay,
fullParams.alpha
);
const cred = distributionToCred(
distribution,
Expand All @@ -249,7 +259,7 @@ export class TimelineCred {
graph,
intervals,
addressToCred,
params,
fullParams,
plugins
);
return preliminaryCred.reduceSize({
Expand Down
7 changes: 4 additions & 3 deletions src/analysis/timeline/timelineCred.test.js
Expand Up @@ -11,8 +11,8 @@ import {
EdgeAddress,
} from "../../core/graph";
import {TimelineCred} from "./timelineCred";
import {defaultParams} from "./params";
import {type PluginDeclaration} from "../pluginDeclaration";
import {defaultWeights} from "../weights";
import {type NodeType} from "../types";

describe("src/analysis/timeline/timelineCred", () => {
Expand Down Expand Up @@ -84,8 +84,9 @@ describe("src/analysis/timeline/timelineCred", () => {
const scores = intervals.map((_) => i);
addressToCred.set(address, scores);
}
const params = {alpha: 0.05, intervalDecay: 0.5, weights: defaultWeights()};
return new TimelineCred(graph, intervals, addressToCred, params, [plugin]);
return new TimelineCred(graph, intervals, addressToCred, defaultParams(), [
plugin,
]);
}

it("JSON serialization works", () => {
Expand Down
12 changes: 6 additions & 6 deletions src/api/load.js
Expand Up @@ -6,10 +6,9 @@ import path from "path";
import {TaskReporter} from "../util/taskReporter";
import {Graph} from "../core/graph";
import {loadGraph} from "../plugins/github/loadGraph";
import {
type TimelineCredParameters,
TimelineCred,
} from "../analysis/timeline/timelineCred";
import {TimelineCred} from "../analysis/timeline/timelineCred";
import {defaultParams, partialParams} from "../analysis/timeline/params";
import {type PartialTimelineCredParameters} from "../analysis/timeline/params";

import {type Project} from "../core/project";
import {setupProjectDirectory} from "../core/project_io";
Expand All @@ -19,7 +18,7 @@ import * as NullUtil from "../util/null";

export type LoadOptions = {|
+project: Project,
+params: TimelineCredParameters,
+params: ?PartialTimelineCredParameters,
+plugins: $ReadOnlyArray<PluginDeclaration>,
+sourcecredDirectory: string,
+githubToken: string | null,
Expand All @@ -45,6 +44,7 @@ export async function load(
taskReporter: TaskReporter
): Promise<void> {
const {project, params, plugins, sourcecredDirectory, githubToken} = options;
const fullParams = params == null ? defaultParams() : partialParams(params);
const loadTask = `load-${options.project.id}`;
taskReporter.start(loadTask);
const cacheDirectory = path.join(sourcecredDirectory, "cache");
Expand Down Expand Up @@ -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, plugins);
const cred = await TimelineCred.compute({graph, params: fullParams, plugins});
const credJSON = cred.toJSON();
const credFile = path.join(projectDirectory, "cred.json");
await fs.writeFile(credFile, JSON.stringify(credJSON));
Expand Down
18 changes: 9 additions & 9 deletions src/api/load.test.js
Expand Up @@ -19,6 +19,10 @@ import {NodeAddress, Graph} from "../core/graph";
import {node} from "../core/graphTestUtil";
import {TestTaskReporter} from "../util/taskReporter";
import {load, type LoadOptions} from "./load";
import {
type PartialTimelineCredParameters,
partialParams,
} from "../analysis/timeline/params";

type JestMockFn = $Call<typeof jest.fn>;
jest.mock("../plugins/github/loadGraph", () => ({
Expand Down Expand Up @@ -70,7 +74,7 @@ describe("api/load", () => {
// Tweaks the weights so that we can ensure we aren't overriding with default weights
weights.nodeManualWeights.set(NodeAddress.empty, 33);
// Deep freeze will freeze the weights, too
const params = deepFreeze({alpha: 0.05, intervalDecay: 0.5, weights});
const params: PartialTimelineCredParameters = {weights};
const plugins = deepFreeze([]);
const example = () => {
const sourcecredDirectory = tmp.dirSync().name;
Expand Down Expand Up @@ -139,14 +143,10 @@ describe("api/load", () => {
it("calls TimelineCred.compute with the right graph and options", async () => {
const {options, taskReporter} = example();
await load(options, taskReporter);
expect(timelineCredCompute).toHaveBeenCalledWith(
expect.anything(),
params,
plugins
);
expect(timelineCredCompute.mock.calls[0][0].equals(combinedGraph())).toBe(
true
);
const args = timelineCredCompute.mock.calls[0][0];
expect(args.graph.equals(combinedGraph())).toBe(true);
expect(args.params).toEqual(partialParams(params));
expect(args.plugins).toEqual(plugins);
});

it("saves the resultant cred.json to disk", async () => {
Expand Down
3 changes: 2 additions & 1 deletion src/cli/load.js
Expand Up @@ -9,6 +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 {partialParams} from "../analysis/timeline/params";
import {DEFAULT_PLUGINS} from "./defaultPlugins";

function usage(print: (string) => void): void {
Expand Down Expand Up @@ -105,7 +106,7 @@ const loadCommand: Command = async (args, std) => {
const projects = await Promise.all(
projectSpecs.map((s) => specToProject(s, githubToken))
);
const params = {alpha: 0.05, intervalDecay: 0.5, weights};
const params = partialParams({weights});
const plugins = DEFAULT_PLUGINS;
const optionses = projects.map((project) => ({
project,
Expand Down
7 changes: 4 additions & 3 deletions src/cli/load.test.js
Expand Up @@ -11,6 +11,7 @@ import type {LoadOptions} from "../api/load";
import {defaultWeights, toJSON as weightsToJSON} from "../analysis/weights";
import * as Common from "./common";
import {DEFAULT_PLUGINS} from "./defaultPlugins";
import {defaultParams, partialParams} from "../analysis/timeline/params";

import {makeRepoId, stringToRepoId} from "../core/repoId";

Expand Down Expand Up @@ -78,7 +79,7 @@ describe("cli/load", () => {
discourseServer: null,
},
plugins: DEFAULT_PLUGINS,
params: {alpha: 0.05, intervalDecay: 0.5, weights: defaultWeights()},
params: defaultParams(),
sourcecredDirectory: Common.sourcecredDirectory(),
githubToken: fakeGithubToken,
discourseKey: fakeDiscourseKey,
Expand All @@ -102,7 +103,7 @@ describe("cli/load", () => {
repoIds: [stringToRepoId(projectId)],
discourseServer: null,
},
params: {alpha: 0.05, intervalDecay: 0.5, weights: defaultWeights()},
params: defaultParams(),
plugins: DEFAULT_PLUGINS,
sourcecredDirectory: Common.sourcecredDirectory(),
githubToken: fakeGithubToken,
Expand Down Expand Up @@ -140,7 +141,7 @@ describe("cli/load", () => {
repoIds: [makeRepoId("foo", "bar")],
discourseServer: null,
},
params: {alpha: 0.05, intervalDecay: 0.5, weights},
params: partialParams({weights}),
plugins: DEFAULT_PLUGINS,
sourcecredDirectory: Common.sourcecredDirectory(),
githubToken: fakeGithubToken,
Expand Down
4 changes: 2 additions & 2 deletions src/explorer/TimelineCredViewInspectionTest.js
Expand Up @@ -6,7 +6,7 @@ import type {Assets} from "../webutil/assets";
import {TimelineCredView} from "./TimelineCredView";
import {Graph, NodeAddress} from "../core/graph";
import {type Interval, TimelineCred} from "../analysis/timeline/timelineCred";
import {defaultWeights} from "../analysis/weights";
import {defaultParams} from "../analysis/timeline/params";

export default class TimelineCredViewInspectiontest extends React.Component<{|
+assets: Assets,
Expand Down Expand Up @@ -46,7 +46,7 @@ export default class TimelineCredViewInspectiontest extends React.Component<{|
const scores = intervals.map((_unuesd, i) => generator(i));
addressToCred.set(address, scores);
}
const params = {alpha: 0.05, intervalDecay: 0.5, weights: defaultWeights()};
const params = defaultParams();
return new TimelineCred(graph, intervals, addressToCred, params, []);
}

Expand Down
6 changes: 2 additions & 4 deletions src/explorer/TimelineExplorer.js
Expand Up @@ -4,10 +4,8 @@ import React from "react";
import deepEqual from "lodash.isequal";
import {type Weights, copy as weightsCopy} from "../analysis/weights";
import {type NodeAddressT} from "../core/graph";
import {
TimelineCred,
type TimelineCredParameters,
} from "../analysis/timeline/timelineCred";
import {TimelineCred} from "../analysis/timeline/timelineCred";
import {type TimelineCredParameters} from "../analysis/timeline/params";
import {TimelineCredView} from "./TimelineCredView";
import Link from "../webutil/Link";
import {WeightConfig} from "./weights/WeightConfig";
Expand Down