add `sourcecred/scores` (#1223)
The scores are lightly processed from their internal representation. Example usage: ``` $ yarn backend; $ node bin/sourcecred.js load sourcecred/sourcecred $ node bin/sourcecred.js scores sourcecred/sourcecred > scores.json ``` The data structure is as follows: ```js export type NodeOutput = {| +id: string, +totalCred: number, +intervalCred: $ReadOnlyArray<number>, |}; export type ScoreOutput = Compatible<{| +users: $ReadOnlyArray<NodeOutput>, +intervals: $ReadOnlyArray<Interval>, |}>; ``` Test plan: I added sharness tests at `sharness/test_cli_scores.t`. In the past, we've used javascript tests for CLI commands. However, those are pretty time-consuming to write, and are less robust than simply running the command from bash. Check the snapshot for a sense of what the new data format looks like. Also, the snapshot updater now updates this snapshot too. Relevant for #1047. Thanks to @Beanow for feedback on the output format and design. Thanks to @wchargin for help in code review.
This commit is contained in:
parent
8e0bbcf597
commit
88f736d180
|
@ -25,6 +25,9 @@ node "${SOURCECRED_BIN}/generateGithubGraphqlFlowTypes.js" \
|
|||
echo "Updating sharness/test_load_example_github.t"
|
||||
(cd sharness; UPDATE_SNAPSHOT=1 ./test_load_example_github.t -l)
|
||||
|
||||
echo "Updating sharness/test_cli_scores.t"
|
||||
(cd sharness; UPDATE_SNAPSHOT=1 ./test_cli_scores.t -l)
|
||||
|
||||
echo "Updating git/loadRepositoryTest.sh"
|
||||
./src/plugins/git/loadRepositoryTest.sh -u --no-build
|
||||
|
||||
|
|
|
@ -0,0 +1,312 @@
|
|||
[
|
||||
{
|
||||
"type": "sourcecred/cli/scores",
|
||||
"version": "0.1.0"
|
||||
},
|
||||
{
|
||||
"intervals": [
|
||||
{
|
||||
"endTimeMs": 1520121600000,
|
||||
"startTimeMs": 1519516800000
|
||||
},
|
||||
{
|
||||
"endTimeMs": 1520726400000,
|
||||
"startTimeMs": 1520121600000
|
||||
},
|
||||
{
|
||||
"endTimeMs": 1521331200000,
|
||||
"startTimeMs": 1520726400000
|
||||
},
|
||||
{
|
||||
"endTimeMs": 1521936000000,
|
||||
"startTimeMs": 1521331200000
|
||||
},
|
||||
{
|
||||
"endTimeMs": 1522540800000,
|
||||
"startTimeMs": 1521936000000
|
||||
},
|
||||
{
|
||||
"endTimeMs": 1523145600000,
|
||||
"startTimeMs": 1522540800000
|
||||
},
|
||||
{
|
||||
"endTimeMs": 1523750400000,
|
||||
"startTimeMs": 1523145600000
|
||||
},
|
||||
{
|
||||
"endTimeMs": 1524355200000,
|
||||
"startTimeMs": 1523750400000
|
||||
},
|
||||
{
|
||||
"endTimeMs": 1524960000000,
|
||||
"startTimeMs": 1524355200000
|
||||
},
|
||||
{
|
||||
"endTimeMs": 1525564800000,
|
||||
"startTimeMs": 1524960000000
|
||||
},
|
||||
{
|
||||
"endTimeMs": 1526169600000,
|
||||
"startTimeMs": 1525564800000
|
||||
},
|
||||
{
|
||||
"endTimeMs": 1526774400000,
|
||||
"startTimeMs": 1526169600000
|
||||
},
|
||||
{
|
||||
"endTimeMs": 1527379200000,
|
||||
"startTimeMs": 1526774400000
|
||||
},
|
||||
{
|
||||
"endTimeMs": 1527984000000,
|
||||
"startTimeMs": 1527379200000
|
||||
},
|
||||
{
|
||||
"endTimeMs": 1528588800000,
|
||||
"startTimeMs": 1527984000000
|
||||
},
|
||||
{
|
||||
"endTimeMs": 1529193600000,
|
||||
"startTimeMs": 1528588800000
|
||||
},
|
||||
{
|
||||
"endTimeMs": 1529798400000,
|
||||
"startTimeMs": 1529193600000
|
||||
},
|
||||
{
|
||||
"endTimeMs": 1530403200000,
|
||||
"startTimeMs": 1529798400000
|
||||
},
|
||||
{
|
||||
"endTimeMs": 1531008000000,
|
||||
"startTimeMs": 1530403200000
|
||||
},
|
||||
{
|
||||
"endTimeMs": 1531612800000,
|
||||
"startTimeMs": 1531008000000
|
||||
},
|
||||
{
|
||||
"endTimeMs": 1532217600000,
|
||||
"startTimeMs": 1531612800000
|
||||
},
|
||||
{
|
||||
"endTimeMs": 1532822400000,
|
||||
"startTimeMs": 1532217600000
|
||||
},
|
||||
{
|
||||
"endTimeMs": 1533427200000,
|
||||
"startTimeMs": 1532822400000
|
||||
},
|
||||
{
|
||||
"endTimeMs": 1534032000000,
|
||||
"startTimeMs": 1533427200000
|
||||
},
|
||||
{
|
||||
"endTimeMs": 1534636800000,
|
||||
"startTimeMs": 1534032000000
|
||||
},
|
||||
{
|
||||
"endTimeMs": 1535241600000,
|
||||
"startTimeMs": 1534636800000
|
||||
},
|
||||
{
|
||||
"endTimeMs": 1535846400000,
|
||||
"startTimeMs": 1535241600000
|
||||
},
|
||||
{
|
||||
"endTimeMs": 1536451200000,
|
||||
"startTimeMs": 1535846400000
|
||||
},
|
||||
{
|
||||
"endTimeMs": 1537056000000,
|
||||
"startTimeMs": 1536451200000
|
||||
},
|
||||
{
|
||||
"endTimeMs": 1537660800000,
|
||||
"startTimeMs": 1537056000000
|
||||
},
|
||||
{
|
||||
"endTimeMs": 1538265600000,
|
||||
"startTimeMs": 1537660800000
|
||||
},
|
||||
{
|
||||
"endTimeMs": 1538870400000,
|
||||
"startTimeMs": 1538265600000
|
||||
},
|
||||
{
|
||||
"endTimeMs": 1539475200000,
|
||||
"startTimeMs": 1538870400000
|
||||
},
|
||||
{
|
||||
"endTimeMs": 1540080000000,
|
||||
"startTimeMs": 1539475200000
|
||||
},
|
||||
{
|
||||
"endTimeMs": 1540684800000,
|
||||
"startTimeMs": 1540080000000
|
||||
},
|
||||
{
|
||||
"endTimeMs": 1541289600000,
|
||||
"startTimeMs": 1540684800000
|
||||
},
|
||||
{
|
||||
"endTimeMs": 1541894400000,
|
||||
"startTimeMs": 1541289600000
|
||||
},
|
||||
{
|
||||
"endTimeMs": 1542499200000,
|
||||
"startTimeMs": 1541894400000
|
||||
},
|
||||
{
|
||||
"endTimeMs": 1543104000000,
|
||||
"startTimeMs": 1542499200000
|
||||
},
|
||||
{
|
||||
"endTimeMs": 1543708800000,
|
||||
"startTimeMs": 1543104000000
|
||||
},
|
||||
{
|
||||
"endTimeMs": 1544313600000,
|
||||
"startTimeMs": 1543708800000
|
||||
},
|
||||
{
|
||||
"endTimeMs": 1544918400000,
|
||||
"startTimeMs": 1544313600000
|
||||
},
|
||||
{
|
||||
"endTimeMs": 1545523200000,
|
||||
"startTimeMs": 1544918400000
|
||||
},
|
||||
{
|
||||
"endTimeMs": 1546128000000,
|
||||
"startTimeMs": 1545523200000
|
||||
},
|
||||
{
|
||||
"endTimeMs": 1546732800000,
|
||||
"startTimeMs": 1546128000000
|
||||
},
|
||||
{
|
||||
"endTimeMs": 1547337600000,
|
||||
"startTimeMs": 1546732800000
|
||||
},
|
||||
{
|
||||
"endTimeMs": 1547942400000,
|
||||
"startTimeMs": 1547337600000
|
||||
},
|
||||
{
|
||||
"endTimeMs": 1548547200000,
|
||||
"startTimeMs": 1547942400000
|
||||
}
|
||||
],
|
||||
"users": [
|
||||
{
|
||||
"id": "decentralion",
|
||||
"intervalCred": [
|
||||
9.383869639663432,
|
||||
4.691404861944296,
|
||||
5.407033783069464,
|
||||
4.710039491158759,
|
||||
2.3542990795642016,
|
||||
1.1764257521110257,
|
||||
0.5874968415385614,
|
||||
0.2930472936432958,
|
||||
0.14585208714512637,
|
||||
3.367558953101356,
|
||||
1.6854511612955203,
|
||||
0.8443081404654077,
|
||||
0.4235721757793936,
|
||||
0.21292350141151434,
|
||||
0.10718062099935051,
|
||||
0.024900766153114956,
|
||||
0.01234421271106403,
|
||||
0.5639545904301708,
|
||||
0.2826874903289227,
|
||||
0.14116839002688855,
|
||||
0.07018698540293254,
|
||||
0.03469827124411613,
|
||||
0.017043428850844578,
|
||||
0.008340605522915224,
|
||||
0.004101657419219121,
|
||||
0.0020470494713788713,
|
||||
0.2330964724510903,
|
||||
0.11911297405802071,
|
||||
1.9437472461067256,
|
||||
1.2458984494463696,
|
||||
0.6241050193500056,
|
||||
0.3126272039780528,
|
||||
0.1565536991433818,
|
||||
0.07836083092825474,
|
||||
0.039204376624594785,
|
||||
0.019603716512520068,
|
||||
0.009792000539440526,
|
||||
0.004877883689977619,
|
||||
0.002415531283206432,
|
||||
0.0011833764411626655,
|
||||
0.0005707084414700211,
|
||||
0.00027081941634511306,
|
||||
0.00012783639288752084,
|
||||
0.00006135289874258643,
|
||||
0.0000305382491069489,
|
||||
0.000015865125198178594,
|
||||
0.00000853390543554515,
|
||||
8.18684979546365e-7
|
||||
],
|
||||
"totalCred": 41.343602084119254
|
||||
},
|
||||
{
|
||||
"id": "wchargin",
|
||||
"intervalCred": [
|
||||
3.6161303603365673,
|
||||
1.808595138055704,
|
||||
0.8429662169305362,
|
||||
0.41496050884124025,
|
||||
0.2082009204357985,
|
||||
0.10482424788897443,
|
||||
0.05312815846143868,
|
||||
0.02726520635670421,
|
||||
0.014304162854873638,
|
||||
2.2125191718986437,
|
||||
1.10458790120448,
|
||||
0.5507113907845924,
|
||||
0.2739375898456065,
|
||||
0.13583138140098566,
|
||||
0.0671968204068995,
|
||||
0.5622879545500101,
|
||||
0.2812501476404985,
|
||||
0.5828425897456103,
|
||||
0.2907110997589679,
|
||||
0.14553090501705676,
|
||||
0.07316266211904013,
|
||||
0.03697655251687019,
|
||||
0.018793983029648593,
|
||||
0.00957810041733136,
|
||||
0.004857695550904171,
|
||||
0.0024326270136827742,
|
||||
0.2691433657914406,
|
||||
0.1320069450632447,
|
||||
4.1818127134539065,
|
||||
1.8168815303339467,
|
||||
0.9072849705401523,
|
||||
0.45306779096702615,
|
||||
0.22629379832915772,
|
||||
0.11306291780801501,
|
||||
0.0565074977435401,
|
||||
0.02825222067154737,
|
||||
0.014135968052593194,
|
||||
0.00708610060603924,
|
||||
0.0035664608648019987,
|
||||
0.00180761963284155,
|
||||
0.0009247895955320864,
|
||||
0.00047692960215594074,
|
||||
0.0002460381163630061,
|
||||
0.000125584355882677,
|
||||
0.00006293037820568283,
|
||||
0.00003086918845813727,
|
||||
0.00001483325139261278,
|
||||
0.000010864893434532599
|
||||
],
|
||||
"totalCred": 21.65638623230234
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
|
@ -0,0 +1,96 @@
|
|||
#!/bin/sh
|
||||
|
||||
# Disable these lint rules globally:
|
||||
# 2034 = unused variable (used by sharness)
|
||||
# 2016 = parameter expansion in single quotes
|
||||
# 1004 = backslash-newline in single quotes
|
||||
# shellcheck disable=SC2034,SC2016,SC1004
|
||||
:
|
||||
|
||||
test_description='tests for cli/scores.js'
|
||||
|
||||
export GIT_CONFIG_NOSYSTEM=1
|
||||
export GIT_ATTR_NOSYSTEM=1
|
||||
|
||||
# shellcheck disable=SC1091
|
||||
. ./sharness.sh
|
||||
|
||||
test_expect_success "environment and Node linking setup" '
|
||||
toplevel="$(git -C "$(dirname "$0")" rev-parse --show-toplevel)" &&
|
||||
snapshot_directory="${toplevel}/sharness/__snapshots__/" &&
|
||||
SOURCECRED_DIRECTORY="${snapshot_directory}/example-github-load" &&
|
||||
export SOURCECRED_DIRECTORY &&
|
||||
snapshot_file="${snapshot_directory}/example-github-scores.json" &&
|
||||
if [ -z "${SOURCECRED_BIN}" ]; then
|
||||
printf >&2 "warn: missing environment variable SOURCECRED_BIN\n" &&
|
||||
printf >&2 "warn: using repository bin directory as fallback\n" &&
|
||||
export SOURCECRED_BIN="${toplevel}/bin"
|
||||
fi &&
|
||||
export NODE_PATH="${toplevel}/node_modules${NODE_PATH:+:${NODE_PATH}}" &&
|
||||
test_set_prereq SETUP
|
||||
'
|
||||
|
||||
run() (
|
||||
set -eu
|
||||
rm -f out err
|
||||
code=0
|
||||
node "${SOURCECRED_BIN}"/sourcecred.js "$@" >out 2>err || code=$?
|
||||
if [ "${code}" -ne 0 ]; then
|
||||
printf '%s failed with %d\n' "sourcecred $*"
|
||||
printf 'stdout:\n'
|
||||
cat out
|
||||
printf 'stderr:\n'
|
||||
cat err
|
||||
fi
|
||||
)
|
||||
|
||||
# Use this instead of `run` when we are expecting sourcecred to return a
|
||||
# non-zero exit code
|
||||
run_without_validation() (
|
||||
set -eu
|
||||
rm -f out err
|
||||
node "${SOURCECRED_BIN}"/sourcecred.js "$@" >out 2>err
|
||||
)
|
||||
|
||||
test_expect_success SETUP "should print help message when called without args" '
|
||||
test_must_fail run_without_validation scores &&
|
||||
grep -q "no repository ID provided" err &&
|
||||
grep -q "sourcecred help scores" err
|
||||
'
|
||||
|
||||
test_expect_success SETUP "help should print usage info" '
|
||||
run help scores &&
|
||||
grep -q "usage: sourcecred scores REPO_ID" out
|
||||
'
|
||||
|
||||
test_expect_success SETUP "--help should print usage info" '
|
||||
run scores --help &&
|
||||
grep -q "usage: sourcecred scores REPO_ID" out
|
||||
'
|
||||
|
||||
test_expect_success SETUP "should fail for multiple repos" '
|
||||
test_must_fail run_without_validation scores sourcecred/sourcecred torvalds/linux &&
|
||||
grep -q "fatal: multiple repository IDs provided" err
|
||||
'
|
||||
|
||||
test_expect_success SETUP "should fail for unloaded repo" '
|
||||
test_must_fail run_without_validation scores torvalds/linux &&
|
||||
grep -q "fatal: repository ID torvalds/linux not loaded" err
|
||||
'
|
||||
|
||||
if [ -n "${UPDATE_SNAPSHOT}" ]; then
|
||||
test_set_prereq UPDATE_SNAPSHOT
|
||||
fi
|
||||
|
||||
test_expect_success SETUP,UPDATE_SNAPSHOT "should update the snapshot" '
|
||||
run scores sourcecred/example-github &&
|
||||
mv out "${snapshot_file}"
|
||||
'
|
||||
|
||||
test_expect_success SETUP "should be identical to the snapshot" '
|
||||
run scores sourcecred/example-github &&
|
||||
diff -u out ${snapshot_file}
|
||||
'
|
||||
|
||||
test_done
|
||||
# vim: ft=sh
|
|
@ -7,6 +7,7 @@ import dedent from "../util/dedent";
|
|||
import {help as loadHelp} from "./load";
|
||||
import {help as analyzeHelp} from "./analyze";
|
||||
import {help as pagerankHelp} from "./pagerank";
|
||||
import {help as scoresHelp} from "./scores";
|
||||
import {help as clearHelp} from "./clear";
|
||||
|
||||
const help: Command = async (args, std) => {
|
||||
|
@ -19,6 +20,7 @@ const help: Command = async (args, std) => {
|
|||
help: metaHelp,
|
||||
load: loadHelp,
|
||||
analyze: analyzeHelp,
|
||||
scores: scoresHelp,
|
||||
pagerank: pagerankHelp,
|
||||
clear: clearHelp,
|
||||
};
|
||||
|
|
|
@ -0,0 +1,143 @@
|
|||
// @flow
|
||||
// Implementation of `sourcecred scores`.
|
||||
|
||||
import {toCompat, type Compatible} from "../util/compat";
|
||||
import path from "path";
|
||||
import fs from "fs-extra";
|
||||
import * as RepoIdRegistry from "../core/repoIdRegistry";
|
||||
import {repoIdToString, stringToRepoId, type RepoId} from "../core/repoId";
|
||||
import dedent from "../util/dedent";
|
||||
import type {Command} from "./command";
|
||||
import * as Common from "./common";
|
||||
import stringify from "json-stable-stringify";
|
||||
import {
|
||||
TimelineCred,
|
||||
type Interval,
|
||||
type CredNode,
|
||||
} from "../analysis/timeline/timelineCred";
|
||||
import {DEFAULT_CRED_CONFIG} from "../plugins/defaultCredConfig";
|
||||
import {userNodeType} from "../plugins/github/declaration";
|
||||
import * as GN from "../plugins/github/nodes";
|
||||
|
||||
const COMPAT_INFO = {type: "sourcecred/cli/scores", version: "0.1.0"};
|
||||
|
||||
function usage(print: (string) => void): void {
|
||||
print(
|
||||
dedent`\
|
||||
usage: sourcecred scores REPO_ID [--help]
|
||||
|
||||
Print the SourceCred user scores for a given REPO_ID.
|
||||
Data must already be loaded for the given REPO_ID, using
|
||||
'sourcecred load REPO_ID'
|
||||
|
||||
REPO_ID refers to a GitHub repository in the form OWNER/NAME: for
|
||||
example, torvalds/linux. The REPO_ID may be a "combined" repo as
|
||||
created by the --output flag to sourcecred load.
|
||||
|
||||
Arguments:
|
||||
REPO_ID
|
||||
Already-loaded repository for which to load data.
|
||||
|
||||
--help
|
||||
Show this help message and exit, as 'sourcecred help scores'.
|
||||
|
||||
Environment Variables:
|
||||
SOURCECRED_DIRECTORY
|
||||
Directory owned by SourceCred, in which data, caches,
|
||||
registries, etc. are stored. Optional: defaults to a
|
||||
directory 'sourcecred' under your OS's temporary directory;
|
||||
namely:
|
||||
${Common.defaultSourcecredDirectory()}
|
||||
`.trimRight()
|
||||
);
|
||||
}
|
||||
|
||||
function die(std, message) {
|
||||
std.err("fatal: " + message);
|
||||
std.err("fatal: run 'sourcecred help scores' for help");
|
||||
return 1;
|
||||
}
|
||||
|
||||
export type NodeOutput = {|
|
||||
+id: string,
|
||||
+totalCred: number,
|
||||
+intervalCred: $ReadOnlyArray<number>,
|
||||
|};
|
||||
|
||||
export type ScoreOutput = Compatible<{|
|
||||
+users: $ReadOnlyArray<NodeOutput>,
|
||||
+intervals: $ReadOnlyArray<Interval>,
|
||||
|}>;
|
||||
|
||||
export const scores: Command = async (args, std) => {
|
||||
let repoId: RepoId | null = null;
|
||||
for (let i = 0; i < args.length; i++) {
|
||||
switch (args[i]) {
|
||||
case "--help": {
|
||||
usage(std.out);
|
||||
return 0;
|
||||
}
|
||||
default: {
|
||||
if (repoId != null) return die(std, "multiple repository IDs provided");
|
||||
// Should be a repository.
|
||||
repoId = stringToRepoId(args[i]);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (repoId == null) {
|
||||
return die(std, "no repository ID provided");
|
||||
}
|
||||
|
||||
const directory = Common.sourcecredDirectory();
|
||||
const registry = RepoIdRegistry.getRegistry(directory);
|
||||
if (RepoIdRegistry.getEntry(registry, repoId) == null) {
|
||||
const repoIdStr = repoIdToString(repoId);
|
||||
std.err(`fatal: repository ID ${repoIdStr} not loaded`);
|
||||
std.err(`Try running \`sourcecred load ${repoIdStr}\` first.`);
|
||||
return 1;
|
||||
}
|
||||
|
||||
const credFile = path.join(
|
||||
Common.sourcecredDirectory(),
|
||||
"data",
|
||||
repoIdToString(repoId),
|
||||
"cred.json"
|
||||
);
|
||||
const credBlob = await fs.readFile(credFile);
|
||||
const credJSON = JSON.parse(credBlob.toString());
|
||||
const timelineCred = TimelineCred.fromJSON(credJSON, DEFAULT_CRED_CONFIG);
|
||||
const userOutput: NodeOutput[] = timelineCred
|
||||
.credSortedNodes(userNodeType.prefix)
|
||||
.map((n: CredNode) => {
|
||||
const address = n.node.address;
|
||||
const structuredAddress = GN.fromRaw((address: any));
|
||||
if (structuredAddress.type !== GN.USERLIKE_TYPE) {
|
||||
throw new Error("invariant violation");
|
||||
}
|
||||
return {
|
||||
id: structuredAddress.login,
|
||||
intervalCred: n.cred,
|
||||
totalCred: n.total,
|
||||
};
|
||||
});
|
||||
const output: ScoreOutput = toCompat(COMPAT_INFO, {
|
||||
users: userOutput,
|
||||
intervals: timelineCred.intervals(),
|
||||
});
|
||||
std.out(stringify(output, {space: 2}));
|
||||
return 0;
|
||||
};
|
||||
|
||||
export default scores;
|
||||
|
||||
export const help: Command = async (args, std) => {
|
||||
if (args.length === 0) {
|
||||
usage(std.out);
|
||||
return 0;
|
||||
} else {
|
||||
usage(std.err);
|
||||
return 1;
|
||||
}
|
||||
};
|
|
@ -9,6 +9,7 @@ import help from "./help";
|
|||
import load from "./load";
|
||||
import analyze from "./analyze";
|
||||
import pagerank from "./pagerank";
|
||||
import scores from "./scores";
|
||||
import clear from "./clear";
|
||||
|
||||
const sourcecred: Command = async (args, std) => {
|
||||
|
@ -31,6 +32,8 @@ const sourcecred: Command = async (args, std) => {
|
|||
return clear(args.slice(1), std);
|
||||
case "pagerank":
|
||||
return pagerank(args.slice(1), std);
|
||||
case "scores":
|
||||
return scores(args.slice(1), std);
|
||||
default:
|
||||
std.err("fatal: unknown command: " + JSON.stringify(args[0]));
|
||||
std.err("fatal: run 'sourcecred help' for commands and usage");
|
||||
|
|
Loading…
Reference in New Issue