mirror of
https://github.com/status-im/sourcecred.git
synced 2025-01-14 14:46:30 +00:00
change the world: track projects not repos
This commit swaps usage over to the new implementation of `cli/load` (the one that wraps `api/load`) and makes changes throughout the project to accomodate that we now track instances by Project rather than by RepoId. Test plan: Unit tests updated; run `yarn test --full`. Also, for safety: actually load a project (by whole org, why not) and verify that the frontend still works.
This commit is contained in:
parent
e31269283a
commit
c15e97b4d4
@ -134,9 +134,7 @@ function getGitState() /*: GitState */ {
|
|||||||
const SOURCECRED_GIT_STATE = stringify(getGitState());
|
const SOURCECRED_GIT_STATE = stringify(getGitState());
|
||||||
process.env.SOURCECRED_GIT_STATE = SOURCECRED_GIT_STATE;
|
process.env.SOURCECRED_GIT_STATE = SOURCECRED_GIT_STATE;
|
||||||
|
|
||||||
function getClientEnvironment(
|
function getClientEnvironment(projectIds /*: $ReadOnlyArray<string> | null*/) {
|
||||||
repoRegistryContents /*: RepoIdRegistry | null */
|
|
||||||
) {
|
|
||||||
const raw = {};
|
const raw = {};
|
||||||
// Useful for determining whether we’re running in production mode.
|
// Useful for determining whether we’re running in production mode.
|
||||||
// Most importantly, it switches React into the correct mode.
|
// Most importantly, it switches React into the correct mode.
|
||||||
@ -144,7 +142,7 @@ function getClientEnvironment(
|
|||||||
// Used by `src/core/version.js`.
|
// Used by `src/core/version.js`.
|
||||||
raw.SOURCECRED_GIT_STATE = SOURCECRED_GIT_STATE;
|
raw.SOURCECRED_GIT_STATE = SOURCECRED_GIT_STATE;
|
||||||
// Optional. Used by `src/homepage/routeData.js`
|
// Optional. Used by `src/homepage/routeData.js`
|
||||||
raw.REPO_REGISTRY = stringify(repoRegistryContents);
|
raw.PROJECT_IDS = stringify(projectIds);
|
||||||
|
|
||||||
// Stringify all values so we can feed into Webpack's DefinePlugin.
|
// Stringify all values so we can feed into Webpack's DefinePlugin.
|
||||||
const stringified = {"process.env": {}};
|
const stringified = {"process.env": {}};
|
||||||
|
@ -7,7 +7,6 @@ import type {
|
|||||||
} from "express";
|
} from "express";
|
||||||
import type {RepoIdRegistry} from "../src/core/repoIdRegistry";
|
import type {RepoIdRegistry} from "../src/core/repoIdRegistry";
|
||||||
*/
|
*/
|
||||||
const fs = require("fs");
|
|
||||||
const os = require("os");
|
const os = require("os");
|
||||||
const path = require("path");
|
const path = require("path");
|
||||||
const webpack = require("webpack");
|
const webpack = require("webpack");
|
||||||
@ -18,45 +17,22 @@ const StaticSiteGeneratorPlugin = require("static-site-generator-webpack-plugin"
|
|||||||
const ModuleScopePlugin = require("react-dev-utils/ModuleScopePlugin");
|
const ModuleScopePlugin = require("react-dev-utils/ModuleScopePlugin");
|
||||||
const paths = require("./paths");
|
const paths = require("./paths");
|
||||||
const getClientEnvironment = require("./env");
|
const getClientEnvironment = require("./env");
|
||||||
|
const _getProjectIds = require("../src/core/_getProjectIds");
|
||||||
|
|
||||||
// Source maps are resource heavy and can cause out of memory issue for large source files.
|
// Source maps are resource heavy and can cause out of memory issue for large source files.
|
||||||
const shouldUseSourceMap = process.env.GENERATE_SOURCEMAP !== "false";
|
const shouldUseSourceMap = process.env.GENERATE_SOURCEMAP !== "false";
|
||||||
|
|
||||||
function loadRepoRegistry() /*: RepoIdRegistry */ {
|
function loadProjectIds() /*: Promise<$ReadOnlyArray<string>> */ {
|
||||||
const env = process.env.SOURCECRED_DIRECTORY;
|
const env = process.env.SOURCECRED_DIRECTORY;
|
||||||
// TODO(#945): de-duplicate finding the directory with src/cli/common.js
|
// TODO(#945): de-duplicate finding the directory with src/cli/common.js
|
||||||
const defaultDirectory = path.join(os.tmpdir(), "sourcecred");
|
const defaultDirectory = path.join(os.tmpdir(), "sourcecred");
|
||||||
const scDirectory = env != null ? env : defaultDirectory;
|
const scDirectory = env != null ? env : defaultDirectory;
|
||||||
// TODO(@dandelion): Remove hacks around compat usage here
|
return _getProjectIds(scDirectory);
|
||||||
// TODO(@dandelion): Import rather than hardcode the registry file name
|
|
||||||
const registryFile = path.join(scDirectory, "repositoryRegistry.json");
|
|
||||||
|
|
||||||
let jsonString;
|
|
||||||
try {
|
|
||||||
jsonString = fs.readFileSync(registryFile).toString();
|
|
||||||
} catch (e) {
|
|
||||||
if (e.code === "ENOENT") {
|
|
||||||
jsonString = JSON.stringify([
|
|
||||||
{version: "0.2.0", type: "REPO_ID_REGISTRY"},
|
|
||||||
[],
|
|
||||||
]);
|
|
||||||
} else {
|
|
||||||
throw e;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
const json = JSON.parse(jsonString);
|
|
||||||
const compat = json[0];
|
|
||||||
if (compat.version !== "0.2.0" || compat.type !== "REPO_ID_REGISTRY") {
|
|
||||||
throw new Error("Compat mismatch");
|
|
||||||
}
|
|
||||||
return json[1];
|
|
||||||
}
|
}
|
||||||
const repoRegistry = loadRepoRegistry();
|
|
||||||
|
|
||||||
// Get environment variables to inject into our app.
|
async function makeConfig(
|
||||||
const env = getClientEnvironment(repoRegistry);
|
mode /*: "production" | "development" */
|
||||||
|
) /*: Promise<mixed> */ {
|
||||||
function makeConfig(mode /*: "production" | "development" */) /*: mixed */ {
|
|
||||||
return {
|
return {
|
||||||
// Don't attempt to continue if there are any errors.
|
// Don't attempt to continue if there are any errors.
|
||||||
bail: true,
|
bail: true,
|
||||||
@ -211,7 +187,7 @@ function makeConfig(mode /*: "production" | "development" */) /*: mixed */ {
|
|||||||
},
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
plugins: plugins(mode),
|
plugins: await plugins(mode),
|
||||||
// Some libraries import Node modules but don't use them in the browser.
|
// Some libraries import Node modules but don't use them in the browser.
|
||||||
// Tell Webpack to provide empty mocks for them so importing them works.
|
// Tell Webpack to provide empty mocks for them so importing them works.
|
||||||
node: {
|
node: {
|
||||||
@ -225,12 +201,14 @@ function makeConfig(mode /*: "production" | "development" */) /*: mixed */ {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
function plugins(mode /*: "development" | "production" */) {
|
async function plugins(mode /*: "development" | "production" */) {
|
||||||
|
const projectIds = await loadProjectIds();
|
||||||
|
const env = getClientEnvironment(projectIds);
|
||||||
const basePlugins = [
|
const basePlugins = [
|
||||||
new StaticSiteGeneratorPlugin({
|
new StaticSiteGeneratorPlugin({
|
||||||
entry: "ssr",
|
entry: "ssr",
|
||||||
paths: require("../src/homepage/routeData")
|
paths: require("../src/homepage/routeData")
|
||||||
.makeRouteData(repoRegistry)
|
.makeRouteData(projectIds)
|
||||||
.map(({path}) => path),
|
.map(({path}) => path),
|
||||||
locals: {},
|
locals: {},
|
||||||
}),
|
}),
|
||||||
|
@ -3,8 +3,8 @@ set -eu
|
|||||||
|
|
||||||
usage() {
|
usage() {
|
||||||
printf 'usage: build_static_site.sh --target TARGET\n'
|
printf 'usage: build_static_site.sh --target TARGET\n'
|
||||||
|
printf ' [--project PROJECT [...]]\n'
|
||||||
printf ' [--weights WEIGHTS_FILE]\n'
|
printf ' [--weights WEIGHTS_FILE]\n'
|
||||||
printf ' [--repo OWNER/NAME [...]]\n'
|
|
||||||
printf ' [--cname DOMAIN]\n'
|
printf ' [--cname DOMAIN]\n'
|
||||||
printf ' [--no-backend]\n'
|
printf ' [--no-backend]\n'
|
||||||
printf ' [-h|--help]\n'
|
printf ' [-h|--help]\n'
|
||||||
@ -13,12 +13,11 @@ usage() {
|
|||||||
printf '\n'
|
printf '\n'
|
||||||
printf '%s\n' '--target TARGET'
|
printf '%s\n' '--target TARGET'
|
||||||
printf '\t%s\n' 'an empty directory into which to build the site'
|
printf '\t%s\n' 'an empty directory into which to build the site'
|
||||||
|
printf '%s\n' '--project PROJECT'
|
||||||
|
printf '\t%s\n' 'a project spec; see help for cli/load.js for details'
|
||||||
printf '%s\n' '--weights WEIGHTS_FILE'
|
printf '%s\n' '--weights WEIGHTS_FILE'
|
||||||
printf '\t%s\n' 'path to a json file which contains a weights configuration.'
|
printf '\t%s\n' 'path to a json file which contains a weights configuration.'
|
||||||
printf '\t%s\n' 'This will be used instead of the default weights and persisted.'
|
printf '\t%s\n' 'This will be used instead of the default weights and persisted.'
|
||||||
printf '%s\n' '--repo OWNER/NAME'
|
|
||||||
printf '\t%s\n' 'a GitHub repository (e.g., torvalds/linux) for which'
|
|
||||||
printf '\t%s\n' 'to include example data'
|
|
||||||
printf '%s\n' '--cname DOMAIN'
|
printf '%s\n' '--cname DOMAIN'
|
||||||
printf '\t%s\n' 'configure DNS for a GitHub Pages site to point to'
|
printf '\t%s\n' 'configure DNS for a GitHub Pages site to point to'
|
||||||
printf '\t%s\n' 'the provided custom domain'
|
printf '\t%s\n' 'the provided custom domain'
|
||||||
@ -55,6 +54,7 @@ parse_args() {
|
|||||||
cname=
|
cname=
|
||||||
weights=
|
weights=
|
||||||
repos=( )
|
repos=( )
|
||||||
|
projects=( )
|
||||||
while [ $# -gt 0 ]; do
|
while [ $# -gt 0 ]; do
|
||||||
case "$1" in
|
case "$1" in
|
||||||
--target)
|
--target)
|
||||||
@ -73,10 +73,10 @@ parse_args() {
|
|||||||
if [ $# -eq 0 ]; then die 'missing value for --weights'; fi
|
if [ $# -eq 0 ]; then die 'missing value for --weights'; fi
|
||||||
weights="$1"
|
weights="$1"
|
||||||
;;
|
;;
|
||||||
--repo)
|
--project)
|
||||||
shift
|
shift
|
||||||
if [ $# -eq 0 ]; then die 'missing value for --repo'; fi
|
if [ $# -eq 0 ]; then die 'missing value for --project'; fi
|
||||||
repos+=( "$1" )
|
projects+=( "$1" )
|
||||||
;;
|
;;
|
||||||
--cname)
|
--cname)
|
||||||
shift
|
shift
|
||||||
@ -140,15 +140,14 @@ build() {
|
|||||||
yarn -s backend --output-path "${SOURCECRED_BIN}"
|
yarn -s backend --output-path "${SOURCECRED_BIN}"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
if [ "${#repos[@]}" -ne 0 ]; then
|
if [ "${#projects[@]}" -ne 0 ]; then
|
||||||
local weightsStr=""
|
local weightsStr=""
|
||||||
if [ -n "${weights}" ]; then
|
if [ -n "${weights}" ]; then
|
||||||
weightsStr="--weights ${weights}"
|
weightsStr="--weights ${weights}"
|
||||||
fi
|
fi
|
||||||
for repo in "${repos[@]}"; do
|
for project in "${projects[@]}"; do
|
||||||
printf >&2 'info: loading repository: %s\n' "${repo}"
|
|
||||||
NODE_PATH="./node_modules${NODE_PATH:+:${NODE_PATH}}" \
|
NODE_PATH="./node_modules${NODE_PATH:+:${NODE_PATH}}" \
|
||||||
node "${SOURCECRED_BIN:-./bin}/sourcecred.js" load "${repo}" $weightsStr
|
node "${SOURCECRED_BIN:-./bin}/sourcecred.js" load "${project}" $weightsStr
|
||||||
done
|
done
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
Binary file not shown.
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@ -0,0 +1 @@
|
|||||||
|
[{"type":"sourcecred/project","version":"0.1.0"},{"id":"sourcecred/example-github","repoIds":[{"name":"example-github","owner":"sourcecred"}]}]
|
@ -1 +0,0 @@
|
|||||||
[{"type":"REPO_ID_REGISTRY","version":"0.2.0"},[{"repoId":{"name":"example-github","owner":"sourcecred"}}]]
|
|
@ -72,9 +72,9 @@ test_expect_success "should fail with a nonempty directory as target" '
|
|||||||
|
|
||||||
mkdir putative_output
|
mkdir putative_output
|
||||||
|
|
||||||
test_expect_success "should fail with missing repo value" '
|
test_expect_success "should fail with missing project value" '
|
||||||
test_must_fail run --target putative_output --repo 2>err &&
|
test_must_fail run --target putative_output --project 2>err &&
|
||||||
grep -qF -- "missing value for --repo" err &&
|
grep -qF -- "missing value for --project" err &&
|
||||||
printf "redacted\n" | test_cmp - important_dir/.wallet.dat
|
printf "redacted\n" | test_cmp - important_dir/.wallet.dat
|
||||||
'
|
'
|
||||||
|
|
||||||
@ -98,8 +98,8 @@ test_expect_success "should fail with multiple cname values" '
|
|||||||
'
|
'
|
||||||
|
|
||||||
#
|
#
|
||||||
# Now, actually generate output in two cases: one with repositories, and
|
# Now, actually generate output in two cases: one with projects, and
|
||||||
# one with no repositories. We can only do this if we have a token.
|
# one with no projects. We can only do this if we have a token.
|
||||||
|
|
||||||
if [ -n "${SOURCECRED_GITHUB_TOKEN:-}" ]; then
|
if [ -n "${SOURCECRED_GITHUB_TOKEN:-}" ]; then
|
||||||
test_set_prereq HAVE_GITHUB_TOKEN
|
test_set_prereq HAVE_GITHUB_TOKEN
|
||||||
@ -114,7 +114,6 @@ run_build() {
|
|||||||
description="$1"; shift
|
description="$1"; shift
|
||||||
output_dir="build_output/output_${prereq_name}"
|
output_dir="build_output/output_${prereq_name}"
|
||||||
api_dir="${output_dir}/api/v1/data"
|
api_dir="${output_dir}/api/v1/data"
|
||||||
data_dir="${api_dir}/data"
|
|
||||||
unsafe_arg=
|
unsafe_arg=
|
||||||
for arg in "${output_dir}" "$@"; do
|
for arg in "${output_dir}" "$@"; do
|
||||||
unusual_chars="$(printf '%s' "$arg" | sed -e 's#[A-Za-z0-9:/_.-]##g')"
|
unusual_chars="$(printf '%s' "$arg" | sed -e 's#[A-Za-z0-9:/_.-]##g')"
|
||||||
@ -133,7 +132,7 @@ run_build() {
|
|||||||
run '"${flags}"' >out 2>err &&
|
run '"${flags}"' >out 2>err &&
|
||||||
test_must_fail grep -vF \
|
test_must_fail grep -vF \
|
||||||
-e "Removing contents of build directory: " \
|
-e "Removing contents of build directory: " \
|
||||||
-e "info: loading repository" \
|
-e "info: loading project" \
|
||||||
-e "DeprecationWarning: Tapable.plugin is deprecated." \
|
-e "DeprecationWarning: Tapable.plugin is deprecated." \
|
||||||
err &&
|
err &&
|
||||||
test_path_is_dir "${output_dir}" &&
|
test_path_is_dir "${output_dir}" &&
|
||||||
@ -193,63 +192,50 @@ test_pages() {
|
|||||||
'
|
'
|
||||||
}
|
}
|
||||||
|
|
||||||
run_build TWO_REPOS \
|
run_build TWO_PROJECTS \
|
||||||
"should build the site with two repositories and a CNAME" \
|
"should build the site with two projects and a CNAME" \
|
||||||
--no-backend \
|
--no-backend \
|
||||||
--cname sourcecred.example.com \
|
--cname sourcecred.example.com \
|
||||||
--repo sourcecred/example-git \
|
--project sourcecred/example-git \
|
||||||
--repo sourcecred/example-github \
|
--project sourcecred/example-github \
|
||||||
;
|
;
|
||||||
|
|
||||||
test_pages TWO_REPOS
|
test_pages TWO_PROJECTS
|
||||||
|
|
||||||
test_expect_success TWO_REPOS \
|
test_expect_success TWO_PROJECTS \
|
||||||
"TWO_REPOS: should have a registry with two repositories" '
|
"TWO_PROJECTS: should have project ids loaded into env" '
|
||||||
registry_file="${api_dir}/repositoryRegistry.json" &&
|
grep -F "PROJECT_IDS" out &&
|
||||||
test_path_is_file "${registry_file}" &&
|
grep -xF "PROJECT_IDS: [\"sourcecred/example-git\",\"sourcecred/example-github\"]" out
|
||||||
grep -oF "\"name\":" "${registry_file}" | wc -l >actual_count &&
|
|
||||||
printf "2\n" | test_cmp - actual_count
|
|
||||||
'
|
'
|
||||||
|
|
||||||
test_expect_success TWO_REPOS \
|
test_expect_success TWO_PROJECTS \
|
||||||
"TWO_REPOS: should have a repo registry loaded into env" '
|
"TWO_PROJECTS: should have data for the two projects" '
|
||||||
grep -F "REPO_REGISTRY" out &&
|
# encoded ids for sourcecred/example-git and sourcecred/example-github
|
||||||
grep -xF "REPO_REGISTRY: [{\"repoId\":{\"name\":\"example-git\",\"owner\":\"sourcecred\"}},{\"repoId\":{\"name\":\"example-github\",\"owner\":\"sourcecred\"}}]" out
|
for id in c291cmNlY3JlZC9leGFtcGxlLWdpdA c291cmNlY3JlZC9leGFtcGxlLWdpdGh1Yg; do
|
||||||
'
|
test -s "${api_dir}/projects/${id}/cred.json" &&
|
||||||
|
test -s "${api_dir}/projects/${id}/graph.json" ||
|
||||||
test_expect_success TWO_REPOS \
|
return
|
||||||
"TWO_REPOS: should have data for the two repositories" '
|
|
||||||
for repo in sourcecred/example-git sourcecred/example-github; do
|
|
||||||
for file in github/view.json.gz; do
|
|
||||||
test -s "${data_dir}/${repo}/${file}" || return
|
|
||||||
done
|
|
||||||
done
|
done
|
||||||
'
|
'
|
||||||
|
|
||||||
test_expect_success TWO_REPOS "TWO_REPOS: should have a correct CNAME record" '
|
test_expect_success TWO_PROJECTS "TWO_PROJECTS: should have a correct CNAME record" '
|
||||||
test_path_is_file "${output_dir}/CNAME" &&
|
test_path_is_file "${output_dir}/CNAME" &&
|
||||||
printf "sourcecred.example.com" | test_cmp - "${output_dir}/CNAME"
|
printf "sourcecred.example.com" | test_cmp - "${output_dir}/CNAME"
|
||||||
'
|
'
|
||||||
|
|
||||||
test_pages NO_REPOS
|
test_pages NO_PROJECTS
|
||||||
|
|
||||||
test_expect_success NO_REPOS \
|
test_expect_success NO_PROJECTS \
|
||||||
"NO_REPOS: should not have a repository registry" '
|
"NO_REPOS: should have empty list of project ids loaded into env" '
|
||||||
registry_file="${api_dir}/repositoryRegistry.json" &&
|
grep -F "PROJECT_IDS" out &&
|
||||||
test_must_fail test -e "${registry_file}"
|
grep -xF "PROJECT_IDS: []" out
|
||||||
'
|
|
||||||
|
|
||||||
test_expect_success NO_REPOS \
|
|
||||||
"NO_REPOS: should have empty repo registry loaded into env" '
|
|
||||||
grep -F "REPO_REGISTRY" out &&
|
|
||||||
grep -xF "REPO_REGISTRY: []" out
|
|
||||||
'
|
'
|
||||||
|
|
||||||
test_expect_success NO_REPOS \
|
test_expect_success NO_REPOS \
|
||||||
"NO_REPOS: should not have repository data" '
|
"NO_REPOS: should not have repository data" '
|
||||||
for repo in sourcecred/example-git sourcecred/example-github; do
|
for id in c291cmNlY3JlZC9leGFtcGxlLWdpdA== c291cmNlY3JlZC9leGFtcGxlLWdpdGh1Yg==; do
|
||||||
for file in git/graph.json github/view.json.gz; do
|
for file in graph.json cred.json; do
|
||||||
test_must_fail test -f "${data_dir}/${repo}/${file}" || return
|
test_must_fail test -f "${api_dir}/projects/${id}/${file}" || return
|
||||||
done
|
done
|
||||||
done
|
done
|
||||||
'
|
'
|
||||||
|
@ -54,28 +54,28 @@ run_without_validation() (
|
|||||||
|
|
||||||
test_expect_success SETUP "should print help message when called without args" '
|
test_expect_success SETUP "should print help message when called without args" '
|
||||||
test_must_fail run_without_validation scores &&
|
test_must_fail run_without_validation scores &&
|
||||||
grep -q "no repository ID provided" err &&
|
grep -q "no project ID provided" err &&
|
||||||
grep -q "sourcecred help scores" err
|
grep -q "sourcecred help scores" err
|
||||||
'
|
'
|
||||||
|
|
||||||
test_expect_success SETUP "help should print usage info" '
|
test_expect_success SETUP "help should print usage info" '
|
||||||
run help scores &&
|
run help scores &&
|
||||||
grep -q "usage: sourcecred scores REPO_ID" out
|
grep -q "usage: sourcecred scores PROJECT_ID" out
|
||||||
'
|
'
|
||||||
|
|
||||||
test_expect_success SETUP "--help should print usage info" '
|
test_expect_success SETUP "--help should print usage info" '
|
||||||
run scores --help &&
|
run scores --help &&
|
||||||
grep -q "usage: sourcecred scores REPO_ID" out
|
grep -q "usage: sourcecred scores PROJECT_ID" out
|
||||||
'
|
'
|
||||||
|
|
||||||
test_expect_success SETUP "should fail for multiple repos" '
|
test_expect_success SETUP "should fail for multiple projects" '
|
||||||
test_must_fail run_without_validation scores sourcecred/sourcecred torvalds/linux &&
|
test_must_fail run_without_validation scores sourcecred/sourcecred torvalds/linux &&
|
||||||
grep -q "fatal: multiple repository IDs provided" err
|
grep -q "fatal: multiple project IDs provided" err
|
||||||
'
|
'
|
||||||
|
|
||||||
test_expect_success SETUP "should fail for unloaded repo" '
|
test_expect_success SETUP "should fail for unloaded project" '
|
||||||
test_must_fail run_without_validation scores torvalds/linux &&
|
test_must_fail run_without_validation scores torvalds/linux &&
|
||||||
grep -q "fatal: repository ID torvalds/linux not loaded" err
|
grep -q "fatal: project torvalds/linux not loaded" err
|
||||||
'
|
'
|
||||||
|
|
||||||
if [ -n "${UPDATE_SNAPSHOT}" ]; then
|
if [ -n "${UPDATE_SNAPSHOT}" ]; then
|
||||||
|
@ -4,7 +4,7 @@
|
|||||||
import type {Command} from "./command";
|
import type {Command} from "./command";
|
||||||
import dedent from "../util/dedent";
|
import dedent from "../util/dedent";
|
||||||
|
|
||||||
import {help as loadHelp} from "./deprecated_load";
|
import {help as loadHelp} from "./load";
|
||||||
import {help as analyzeHelp} from "./analyze";
|
import {help as analyzeHelp} from "./analyze";
|
||||||
import {help as pagerankHelp} from "./pagerank";
|
import {help as pagerankHelp} from "./pagerank";
|
||||||
import {help as scoresHelp} from "./scores";
|
import {help as scoresHelp} from "./scores";
|
||||||
|
@ -4,8 +4,6 @@
|
|||||||
import {toCompat, type Compatible} from "../util/compat";
|
import {toCompat, type Compatible} from "../util/compat";
|
||||||
import path from "path";
|
import path from "path";
|
||||||
import fs from "fs-extra";
|
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 dedent from "../util/dedent";
|
||||||
import type {Command} from "./command";
|
import type {Command} from "./command";
|
||||||
import * as Common from "./common";
|
import * as Common from "./common";
|
||||||
@ -18,25 +16,25 @@ import {
|
|||||||
import {DEFAULT_CRED_CONFIG} from "../plugins/defaultCredConfig";
|
import {DEFAULT_CRED_CONFIG} from "../plugins/defaultCredConfig";
|
||||||
import {userNodeType} from "../plugins/github/declaration";
|
import {userNodeType} from "../plugins/github/declaration";
|
||||||
import * as GN from "../plugins/github/nodes";
|
import * as GN from "../plugins/github/nodes";
|
||||||
|
import {directoryForProjectId} from "../core/project_io";
|
||||||
|
|
||||||
const COMPAT_INFO = {type: "sourcecred/cli/scores", version: "0.1.0"};
|
const COMPAT_INFO = {type: "sourcecred/cli/scores", version: "0.1.0"};
|
||||||
|
|
||||||
function usage(print: (string) => void): void {
|
function usage(print: (string) => void): void {
|
||||||
print(
|
print(
|
||||||
dedent`\
|
dedent`\
|
||||||
usage: sourcecred scores REPO_ID [--help]
|
usage: sourcecred scores PROJECT_ID [--help]
|
||||||
|
|
||||||
Print the SourceCred user scores for a given REPO_ID.
|
Print the SourceCred user scores for a given PROJECT_ID.
|
||||||
Data must already be loaded for the given REPO_ID, using
|
Data must already be loaded for the given PROJECT_ID, using
|
||||||
'sourcecred load REPO_ID'
|
'sourcecred load PROJECT_ID'
|
||||||
|
|
||||||
REPO_ID refers to a GitHub repository in the form OWNER/NAME: for
|
PROJECT_ID refers to a project, as loaded by the \`load\` command.
|
||||||
example, torvalds/linux. The REPO_ID may be a "combined" repo as
|
Run \`sourcecred load --help\` for details.
|
||||||
created by the --output flag to sourcecred load.
|
|
||||||
|
|
||||||
Arguments:
|
Arguments:
|
||||||
REPO_ID
|
PROJECT_ID
|
||||||
Already-loaded repository for which to load data.
|
Already-loaded project for which to load data.
|
||||||
|
|
||||||
--help
|
--help
|
||||||
Show this help message and exit, as 'sourcecred help scores'.
|
Show this help message and exit, as 'sourcecred help scores'.
|
||||||
@ -70,7 +68,7 @@ export type ScoreOutput = Compatible<{|
|
|||||||
|}>;
|
|}>;
|
||||||
|
|
||||||
export const scores: Command = async (args, std) => {
|
export const scores: Command = async (args, std) => {
|
||||||
let repoId: RepoId | null = null;
|
let projectId: string | null = null;
|
||||||
for (let i = 0; i < args.length; i++) {
|
for (let i = 0; i < args.length; i++) {
|
||||||
switch (args[i]) {
|
switch (args[i]) {
|
||||||
case "--help": {
|
case "--help": {
|
||||||
@ -78,33 +76,28 @@ export const scores: Command = async (args, std) => {
|
|||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
default: {
|
default: {
|
||||||
if (repoId != null) return die(std, "multiple repository IDs provided");
|
if (projectId != null) return die(std, "multiple project IDs provided");
|
||||||
// Should be a repository.
|
projectId = args[i];
|
||||||
repoId = stringToRepoId(args[i]);
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (repoId == null) {
|
if (projectId == null) {
|
||||||
return die(std, "no repository ID provided");
|
return die(std, "no project ID provided");
|
||||||
}
|
}
|
||||||
|
|
||||||
const directory = Common.sourcecredDirectory();
|
const projectDirectory = directoryForProjectId(
|
||||||
const registry = RepoIdRegistry.getRegistry(directory);
|
projectId,
|
||||||
if (RepoIdRegistry.getEntry(registry, repoId) == null) {
|
Common.sourcecredDirectory()
|
||||||
const repoIdStr = repoIdToString(repoId);
|
);
|
||||||
std.err(`fatal: repository ID ${repoIdStr} not loaded`);
|
const credFile = path.join(projectDirectory, "cred.json");
|
||||||
std.err(`Try running \`sourcecred load ${repoIdStr}\` first.`);
|
if (!fs.existsSync(credFile)) {
|
||||||
|
std.err(`fatal: project ${projectId} not loaded`);
|
||||||
|
std.err(`Try running \`sourcecred load ${projectId}\` first.`);
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
const credFile = path.join(
|
|
||||||
Common.sourcecredDirectory(),
|
|
||||||
"data",
|
|
||||||
repoIdToString(repoId),
|
|
||||||
"cred.json"
|
|
||||||
);
|
|
||||||
const credBlob = await fs.readFile(credFile);
|
const credBlob = await fs.readFile(credFile);
|
||||||
const credJSON = JSON.parse(credBlob.toString());
|
const credJSON = JSON.parse(credBlob.toString());
|
||||||
const timelineCred = TimelineCred.fromJSON(credJSON, DEFAULT_CRED_CONFIG);
|
const timelineCred = TimelineCred.fromJSON(credJSON, DEFAULT_CRED_CONFIG);
|
||||||
|
@ -6,7 +6,7 @@ import type {Command} from "./command";
|
|||||||
import {VERSION_SHORT} from "../core/version";
|
import {VERSION_SHORT} from "../core/version";
|
||||||
|
|
||||||
import help from "./help";
|
import help from "./help";
|
||||||
import load from "./deprecated_load";
|
import load from "./load";
|
||||||
import analyze from "./analyze";
|
import analyze from "./analyze";
|
||||||
import pagerank from "./pagerank";
|
import pagerank from "./pagerank";
|
||||||
import scores from "./scores";
|
import scores from "./scores";
|
||||||
|
@ -12,7 +12,7 @@ function mockCommand(name) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
jest.mock("./help", () => mockCommand("help"));
|
jest.mock("./help", () => mockCommand("help"));
|
||||||
jest.mock("./deprecated_load", () => mockCommand("load"));
|
jest.mock("./load", () => mockCommand("load"));
|
||||||
jest.mock("./analyze", () => mockCommand("analyze"));
|
jest.mock("./analyze", () => mockCommand("analyze"));
|
||||||
jest.mock("./pagerank", () => mockCommand("pagerank"));
|
jest.mock("./pagerank", () => mockCommand("pagerank"));
|
||||||
jest.mock("./clear", () => mockCommand("clear"));
|
jest.mock("./clear", () => mockCommand("clear"));
|
||||||
|
@ -2,19 +2,19 @@
|
|||||||
|
|
||||||
import React from "react";
|
import React from "react";
|
||||||
import type {Assets} from "../webutil/assets";
|
import type {Assets} from "../webutil/assets";
|
||||||
import type {RepoId} from "../core/repoId";
|
|
||||||
import {TimelineExplorer} from "./TimelineExplorer";
|
import {TimelineExplorer} from "./TimelineExplorer";
|
||||||
import {TimelineCred} from "../analysis/timeline/timelineCred";
|
import {TimelineCred} from "../analysis/timeline/timelineCred";
|
||||||
import {declaration as githubDeclaration} from "../plugins/github/declaration";
|
import {declaration as githubDeclaration} from "../plugins/github/declaration";
|
||||||
import {DEFAULT_CRED_CONFIG} from "../plugins/defaultCredConfig";
|
import {DEFAULT_CRED_CONFIG} from "../plugins/defaultCredConfig";
|
||||||
|
import {encodeProjectId, type ProjectId} from "../core/project";
|
||||||
|
|
||||||
export type Props = {|
|
export type Props = {|
|
||||||
+assets: Assets,
|
+assets: Assets,
|
||||||
+repoId: RepoId,
|
+projectId: string,
|
||||||
+loader: Loader,
|
+loader: Loader,
|
||||||
|};
|
|};
|
||||||
|
|
||||||
export type Loader = (assets: Assets, repoId: RepoId) => Promise<LoadResult>;
|
export type Loader = (assets: Assets, projectId: string) => Promise<LoadResult>;
|
||||||
|
|
||||||
export type LoadResult = Loading | LoadSuccess | LoadError;
|
export type LoadResult = Loading | LoadSuccess | LoadError;
|
||||||
export type Loading = {|+type: "LOADING"|};
|
export type Loading = {|+type: "LOADING"|};
|
||||||
@ -37,7 +37,7 @@ export class TimelineApp extends React.Component<Props, State> {
|
|||||||
async load() {
|
async load() {
|
||||||
const loadResult = await this.props.loader(
|
const loadResult = await this.props.loader(
|
||||||
this.props.assets,
|
this.props.assets,
|
||||||
this.props.repoId
|
this.props.projectId
|
||||||
);
|
);
|
||||||
this.setState({loadResult});
|
this.setState({loadResult});
|
||||||
}
|
}
|
||||||
@ -68,7 +68,7 @@ export class TimelineApp extends React.Component<Props, State> {
|
|||||||
return (
|
return (
|
||||||
<TimelineExplorer
|
<TimelineExplorer
|
||||||
initialTimelineCred={timelineCred}
|
initialTimelineCred={timelineCred}
|
||||||
repoId={this.props.repoId}
|
projectId={this.props.projectId}
|
||||||
declarations={[githubDeclaration]}
|
declarations={[githubDeclaration]}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
@ -81,12 +81,12 @@ export class TimelineApp extends React.Component<Props, State> {
|
|||||||
|
|
||||||
export async function defaultLoader(
|
export async function defaultLoader(
|
||||||
assets: Assets,
|
assets: Assets,
|
||||||
repoId: RepoId
|
projectId: ProjectId
|
||||||
): Promise<LoadResult> {
|
): Promise<LoadResult> {
|
||||||
async function fetchCred(): Promise<TimelineCred> {
|
async function fetchCred(): Promise<TimelineCred> {
|
||||||
const url = assets.resolve(
|
console.error(">>>DEFAULTLOADER");
|
||||||
`api/v1/data/data/${repoId.owner}/${repoId.name}/cred.json`
|
const encodedId = encodeProjectId(projectId);
|
||||||
);
|
const url = assets.resolve(`api/v1/data/projects/${encodedId}/cred.json`);
|
||||||
const response = await fetch(url);
|
const response = await fetch(url);
|
||||||
if (!response.ok) {
|
if (!response.ok) {
|
||||||
return Promise.reject(response);
|
return Promise.reject(response);
|
||||||
|
@ -2,7 +2,6 @@
|
|||||||
|
|
||||||
import React from "react";
|
import React from "react";
|
||||||
import deepEqual from "lodash.isequal";
|
import deepEqual from "lodash.isequal";
|
||||||
import {type RepoId} from "../core/repoId";
|
|
||||||
import {type PluginDeclaration} from "../analysis/pluginDeclaration";
|
import {type PluginDeclaration} from "../analysis/pluginDeclaration";
|
||||||
import {type Weights, copy as weightsCopy} from "../analysis/weights";
|
import {type Weights, copy as weightsCopy} from "../analysis/weights";
|
||||||
import {
|
import {
|
||||||
@ -14,7 +13,7 @@ import {WeightConfig} from "./weights/WeightConfig";
|
|||||||
import {WeightsFileManager} from "./weights/WeightsFileManager";
|
import {WeightsFileManager} from "./weights/WeightsFileManager";
|
||||||
|
|
||||||
export type Props = {
|
export type Props = {
|
||||||
repoId: RepoId,
|
projectId: string,
|
||||||
initialTimelineCred: TimelineCred,
|
initialTimelineCred: TimelineCred,
|
||||||
// TODO: Get this info from the TimelineCred
|
// TODO: Get this info from the TimelineCred
|
||||||
declarations: $ReadOnlyArray<PluginDeclaration>,
|
declarations: $ReadOnlyArray<PluginDeclaration>,
|
||||||
@ -35,7 +34,7 @@ export type State = {
|
|||||||
* It basically wraps a TimelineCredView with some additional features and options:
|
* It basically wraps a TimelineCredView with some additional features and options:
|
||||||
* - allows changing the weights and re-calculating cred with new weights
|
* - allows changing the weights and re-calculating cred with new weights
|
||||||
* - allows saving/loading weights
|
* - allows saving/loading weights
|
||||||
* - displays the RepoId
|
* - displays the string
|
||||||
*/
|
*/
|
||||||
export class TimelineExplorer extends React.Component<Props, State> {
|
export class TimelineExplorer extends React.Component<Props, State> {
|
||||||
constructor(props: Props) {
|
constructor(props: Props) {
|
||||||
@ -110,13 +109,12 @@ export class TimelineExplorer extends React.Component<Props, State> {
|
|||||||
re-compute cred
|
re-compute cred
|
||||||
</button>
|
</button>
|
||||||
);
|
);
|
||||||
const {owner, name} = this.props.repoId;
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<div style={{marginTop: 30, display: "flex"}}>
|
<div style={{marginTop: 30, display: "flex"}}>
|
||||||
<span style={{paddingLeft: 30}}>
|
<span style={{paddingLeft: 30}}>
|
||||||
cred for {owner}/{name}
|
cred for {this.props.projectId}
|
||||||
<a href={`/prototype/${owner}/${name}/`}>(legacy)</a>
|
<a href={`/prototype/${this.props.projectId}/`}>(legacy)</a>
|
||||||
</span>
|
</span>
|
||||||
<span style={{flexGrow: 1}} />
|
<span style={{flexGrow: 1}} />
|
||||||
<button
|
<button
|
||||||
|
@ -7,7 +7,6 @@ import type {LocalStore} from "../../webutil/localStore";
|
|||||||
import CheckedLocalStore from "../../webutil/checkedLocalStore";
|
import CheckedLocalStore from "../../webutil/checkedLocalStore";
|
||||||
import BrowserLocalStore from "../../webutil/browserLocalStore";
|
import BrowserLocalStore from "../../webutil/browserLocalStore";
|
||||||
import Link from "../../webutil/Link";
|
import Link from "../../webutil/Link";
|
||||||
import type {RepoId} from "../../core/repoId";
|
|
||||||
import {type NodeAddressT} from "../../core/graph";
|
import {type NodeAddressT} from "../../core/graph";
|
||||||
import {declaration as githubDeclaration} from "../../plugins/github/declaration";
|
import {declaration as githubDeclaration} from "../../plugins/github/declaration";
|
||||||
|
|
||||||
@ -31,7 +30,7 @@ const feedbackUrl =
|
|||||||
|
|
||||||
export class AppPage extends React.Component<{|
|
export class AppPage extends React.Component<{|
|
||||||
+assets: Assets,
|
+assets: Assets,
|
||||||
+repoId: RepoId,
|
+projectId: string,
|
||||||
|}> {
|
|}> {
|
||||||
static _LOCAL_STORE = new CheckedLocalStore(
|
static _LOCAL_STORE = new CheckedLocalStore(
|
||||||
new BrowserLocalStore({
|
new BrowserLocalStore({
|
||||||
@ -44,7 +43,7 @@ export class AppPage extends React.Component<{|
|
|||||||
const App = createApp(createStateTransitionMachine);
|
const App = createApp(createStateTransitionMachine);
|
||||||
return (
|
return (
|
||||||
<App
|
<App
|
||||||
repoId={this.props.repoId}
|
projectId={this.props.projectId}
|
||||||
assets={this.props.assets}
|
assets={this.props.assets}
|
||||||
localStore={AppPage._LOCAL_STORE}
|
localStore={AppPage._LOCAL_STORE}
|
||||||
/>
|
/>
|
||||||
@ -55,7 +54,7 @@ export class AppPage extends React.Component<{|
|
|||||||
type Props = {|
|
type Props = {|
|
||||||
+assets: Assets,
|
+assets: Assets,
|
||||||
+localStore: LocalStore,
|
+localStore: LocalStore,
|
||||||
+repoId: RepoId,
|
+projectId: string,
|
||||||
|};
|
|};
|
||||||
type State = {|
|
type State = {|
|
||||||
appState: AppState,
|
appState: AppState,
|
||||||
@ -74,7 +73,7 @@ export function createApp(
|
|||||||
constructor(props: Props) {
|
constructor(props: Props) {
|
||||||
super(props);
|
super(props);
|
||||||
this.state = {
|
this.state = {
|
||||||
appState: initialState(this.props.repoId),
|
appState: initialState(this.props.projectId),
|
||||||
weights: defaultWeights(),
|
weights: defaultWeights(),
|
||||||
};
|
};
|
||||||
this.stateTransitionMachine = createSTM(
|
this.stateTransitionMachine = createSTM(
|
||||||
@ -137,7 +136,6 @@ export function createApp(
|
|||||||
const spacer = () => (
|
const spacer = () => (
|
||||||
<span style={{display: "inline-block", width: 12}} />
|
<span style={{display: "inline-block", width: 12}} />
|
||||||
);
|
);
|
||||||
const {owner, name} = this.props.repoId;
|
|
||||||
return (
|
return (
|
||||||
<div style={{maxWidth: 900, margin: "0 auto", padding: "0 10px"}}>
|
<div style={{maxWidth: 900, margin: "0 auto", padding: "0 10px"}}>
|
||||||
<p style={{textAlign: "right"}}>
|
<p style={{textAlign: "right"}}>
|
||||||
@ -147,10 +145,11 @@ export function createApp(
|
|||||||
</p>
|
</p>
|
||||||
<h2>SourceCred Legacy Mode</h2>
|
<h2>SourceCred Legacy Mode</h2>
|
||||||
<p>
|
<p>
|
||||||
Back to <a href={`/timeline/${owner}/${name}/`}>timeline mode</a>
|
Back to{" "}
|
||||||
|
<a href={`/timeline/${this.props.projectId}/`}>timeline mode</a>
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<ProjectDetail repoId={this.props.repoId} />
|
<ProjectDetail projectId={this.props.projectId} />
|
||||||
<button
|
<button
|
||||||
disabled={
|
disabled={
|
||||||
appState.type === "UNINITIALIZED" ||
|
appState.type === "UNINITIALIZED" ||
|
||||||
@ -179,10 +178,10 @@ export function createApp(
|
|||||||
}
|
}
|
||||||
|
|
||||||
export class ProjectDetail extends React.PureComponent<{|
|
export class ProjectDetail extends React.PureComponent<{|
|
||||||
+repoId: RepoId,
|
+projectId: string,
|
||||||
|}> {
|
|}> {
|
||||||
render() {
|
render() {
|
||||||
return <p>{`${this.props.repoId.owner}/${this.props.repoId.name}`}</p>;
|
return <p>{this.props.projectId}</p>;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -4,7 +4,6 @@ import React from "react";
|
|||||||
import {shallow} from "enzyme";
|
import {shallow} from "enzyme";
|
||||||
|
|
||||||
import {Graph} from "../../core/graph";
|
import {Graph} from "../../core/graph";
|
||||||
import {makeRepoId} from "../../core/repoId";
|
|
||||||
import {Assets} from "../../webutil/assets";
|
import {Assets} from "../../webutil/assets";
|
||||||
import testLocalStore from "../../webutil/testLocalStore";
|
import testLocalStore from "../../webutil/testLocalStore";
|
||||||
|
|
||||||
@ -34,7 +33,7 @@ describe("explorer/legacy/App", () => {
|
|||||||
<App
|
<App
|
||||||
assets={new Assets("/foo/")}
|
assets={new Assets("/foo/")}
|
||||||
localStore={localStore}
|
localStore={localStore}
|
||||||
repoId={makeRepoId("foo", "bar")}
|
projectId={"foo/bar"}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
if (setState == null || getState == null) {
|
if (setState == null || getState == null) {
|
||||||
@ -55,14 +54,14 @@ describe("explorer/legacy/App", () => {
|
|||||||
readyToLoadGraph: (loadingState) => {
|
readyToLoadGraph: (loadingState) => {
|
||||||
return () => ({
|
return () => ({
|
||||||
type: "READY_TO_LOAD_GRAPH",
|
type: "READY_TO_LOAD_GRAPH",
|
||||||
repoId: makeRepoId("foo", "bar"),
|
projectId: "foo/bar",
|
||||||
loading: loadingState,
|
loading: loadingState,
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
readyToRunPagerank: (loadingState) => {
|
readyToRunPagerank: (loadingState) => {
|
||||||
return () => ({
|
return () => ({
|
||||||
type: "READY_TO_RUN_PAGERANK",
|
type: "READY_TO_RUN_PAGERANK",
|
||||||
repoId: makeRepoId("foo", "bar"),
|
projectId: "foo/bar",
|
||||||
loading: loadingState,
|
loading: loadingState,
|
||||||
graph: new Graph(),
|
graph: new Graph(),
|
||||||
});
|
});
|
||||||
@ -70,7 +69,7 @@ describe("explorer/legacy/App", () => {
|
|||||||
pagerankEvaluated: (loadingState) => {
|
pagerankEvaluated: (loadingState) => {
|
||||||
return () => ({
|
return () => ({
|
||||||
type: "PAGERANK_EVALUATED",
|
type: "PAGERANK_EVALUATED",
|
||||||
repoId: makeRepoId("foo", "bar"),
|
projectId: "foo/bar",
|
||||||
loading: loadingState,
|
loading: loadingState,
|
||||||
graph: new Graph(),
|
graph: new Graph(),
|
||||||
pagerankNodeDecomposition: new Map(),
|
pagerankNodeDecomposition: new Map(),
|
||||||
@ -108,13 +107,12 @@ describe("explorer/legacy/App", () => {
|
|||||||
it("instantiates a ProjectDetail component with correct props", () => {
|
it("instantiates a ProjectDetail component with correct props", () => {
|
||||||
const {el} = example();
|
const {el} = example();
|
||||||
const projectDetail = el.find(ProjectDetail);
|
const projectDetail = el.find(ProjectDetail);
|
||||||
const correctProps = {repoId: makeRepoId("foo", "bar")};
|
const correctProps = {projectId: "foo/bar"};
|
||||||
expect(projectDetail.props()).toEqual(correctProps);
|
expect(projectDetail.props()).toEqual(correctProps);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("ProjectDetail component renders repoId correctly", () => {
|
it("ProjectDetail component renders projectId correctly", () => {
|
||||||
const repoId = makeRepoId("foo", "bar");
|
const projectDetail = shallow(<ProjectDetail projectId={"foo/bar"} />);
|
||||||
const projectDetail = shallow(<ProjectDetail repoId={repoId} />);
|
|
||||||
const title = projectDetail.findWhere(
|
const title = projectDetail.findWhere(
|
||||||
(p) => p.is("p") && p.text() === "foo/bar"
|
(p) => p.is("p") && p.text() === "foo/bar"
|
||||||
);
|
);
|
||||||
|
@ -4,7 +4,6 @@ import deepEqual from "lodash.isequal";
|
|||||||
|
|
||||||
import {Graph, type NodeAddressT} from "../../core/graph";
|
import {Graph, type NodeAddressT} from "../../core/graph";
|
||||||
import type {Assets} from "../../webutil/assets";
|
import type {Assets} from "../../webutil/assets";
|
||||||
import type {RepoId} from "../../core/repoId";
|
|
||||||
import {type EdgeEvaluator} from "../../analysis/pagerank";
|
import {type EdgeEvaluator} from "../../analysis/pagerank";
|
||||||
import type {NodeAndEdgeTypes} from "../../analysis/types";
|
import type {NodeAndEdgeTypes} from "../../analysis/types";
|
||||||
import {defaultLoader} from "../TimelineApp";
|
import {defaultLoader} from "../TimelineApp";
|
||||||
@ -33,25 +32,25 @@ export type AppState =
|
|||||||
|
|
||||||
export type ReadyToLoadGraph = {|
|
export type ReadyToLoadGraph = {|
|
||||||
+type: "READY_TO_LOAD_GRAPH",
|
+type: "READY_TO_LOAD_GRAPH",
|
||||||
+repoId: RepoId,
|
+projectId: string,
|
||||||
+loading: LoadingState,
|
+loading: LoadingState,
|
||||||
|};
|
|};
|
||||||
export type ReadyToRunPagerank = {|
|
export type ReadyToRunPagerank = {|
|
||||||
+type: "READY_TO_RUN_PAGERANK",
|
+type: "READY_TO_RUN_PAGERANK",
|
||||||
+repoId: RepoId,
|
+projectId: string,
|
||||||
+graph: Graph,
|
+graph: Graph,
|
||||||
+loading: LoadingState,
|
+loading: LoadingState,
|
||||||
|};
|
|};
|
||||||
export type PagerankEvaluated = {|
|
export type PagerankEvaluated = {|
|
||||||
+type: "PAGERANK_EVALUATED",
|
+type: "PAGERANK_EVALUATED",
|
||||||
+graph: Graph,
|
+graph: Graph,
|
||||||
+repoId: RepoId,
|
+projectId: string,
|
||||||
+pagerankNodeDecomposition: PagerankNodeDecomposition,
|
+pagerankNodeDecomposition: PagerankNodeDecomposition,
|
||||||
+loading: LoadingState,
|
+loading: LoadingState,
|
||||||
|};
|
|};
|
||||||
|
|
||||||
export function initialState(repoId: RepoId): ReadyToLoadGraph {
|
export function initialState(projectId: string): ReadyToLoadGraph {
|
||||||
return {type: "READY_TO_LOAD_GRAPH", repoId, loading: "NOT_LOADING"};
|
return {type: "READY_TO_LOAD_GRAPH", projectId, loading: "NOT_LOADING"};
|
||||||
}
|
}
|
||||||
|
|
||||||
export function createStateTransitionMachine(
|
export function createStateTransitionMachine(
|
||||||
@ -79,7 +78,7 @@ export interface StateTransitionMachineInterface {
|
|||||||
export class StateTransitionMachine implements StateTransitionMachineInterface {
|
export class StateTransitionMachine implements StateTransitionMachineInterface {
|
||||||
getState: () => AppState;
|
getState: () => AppState;
|
||||||
setState: (AppState) => void;
|
setState: (AppState) => void;
|
||||||
doLoadGraph: (assets: Assets, repoId: RepoId) => Promise<Graph>;
|
doLoadGraph: (assets: Assets, projectId: string) => Promise<Graph>;
|
||||||
pagerank: (
|
pagerank: (
|
||||||
Graph,
|
Graph,
|
||||||
EdgeEvaluator,
|
EdgeEvaluator,
|
||||||
@ -89,7 +88,7 @@ export class StateTransitionMachine implements StateTransitionMachineInterface {
|
|||||||
constructor(
|
constructor(
|
||||||
getState: () => AppState,
|
getState: () => AppState,
|
||||||
setState: (AppState) => void,
|
setState: (AppState) => void,
|
||||||
doLoadGraph: (assets: Assets, repoId: RepoId) => Promise<Graph>,
|
doLoadGraph: (assets: Assets, projectId: string) => Promise<Graph>,
|
||||||
pagerank: (
|
pagerank: (
|
||||||
Graph,
|
Graph,
|
||||||
EdgeEvaluator,
|
EdgeEvaluator,
|
||||||
@ -108,17 +107,17 @@ export class StateTransitionMachine implements StateTransitionMachineInterface {
|
|||||||
if (state.type !== "READY_TO_LOAD_GRAPH") {
|
if (state.type !== "READY_TO_LOAD_GRAPH") {
|
||||||
throw new Error("Tried to loadGraph in incorrect state");
|
throw new Error("Tried to loadGraph in incorrect state");
|
||||||
}
|
}
|
||||||
const {repoId} = state;
|
const {projectId} = state;
|
||||||
const loadingState = {...state, loading: "LOADING"};
|
const loadingState = {...state, loading: "LOADING"};
|
||||||
this.setState(loadingState);
|
this.setState(loadingState);
|
||||||
let newState: ?AppState;
|
let newState: ?AppState;
|
||||||
let success = true;
|
let success = true;
|
||||||
try {
|
try {
|
||||||
const graph = await this.doLoadGraph(assets, repoId);
|
const graph = await this.doLoadGraph(assets, projectId);
|
||||||
newState = {
|
newState = {
|
||||||
type: "READY_TO_RUN_PAGERANK",
|
type: "READY_TO_RUN_PAGERANK",
|
||||||
graph,
|
graph,
|
||||||
repoId,
|
projectId,
|
||||||
loading: "NOT_LOADING",
|
loading: "NOT_LOADING",
|
||||||
};
|
};
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
@ -166,7 +165,7 @@ export class StateTransitionMachine implements StateTransitionMachineInterface {
|
|||||||
type: "PAGERANK_EVALUATED",
|
type: "PAGERANK_EVALUATED",
|
||||||
pagerankNodeDecomposition,
|
pagerankNodeDecomposition,
|
||||||
graph: state.graph,
|
graph: state.graph,
|
||||||
repoId: state.repoId,
|
projectId: state.projectId,
|
||||||
loading: "NOT_LOADING",
|
loading: "NOT_LOADING",
|
||||||
};
|
};
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
@ -209,9 +208,9 @@ export class StateTransitionMachine implements StateTransitionMachineInterface {
|
|||||||
|
|
||||||
export async function doLoadGraph(
|
export async function doLoadGraph(
|
||||||
assets: Assets,
|
assets: Assets,
|
||||||
repoId: RepoId
|
projectId: string
|
||||||
): Promise<Graph> {
|
): Promise<Graph> {
|
||||||
const loadResult = await defaultLoader(assets, repoId);
|
const loadResult = await defaultLoader(assets, projectId);
|
||||||
if (loadResult.type !== "SUCCESS") {
|
if (loadResult.type !== "SUCCESS") {
|
||||||
throw new Error(loadResult);
|
throw new Error(loadResult);
|
||||||
}
|
}
|
||||||
|
@ -4,7 +4,6 @@ import {StateTransitionMachine, type AppState} from "./state";
|
|||||||
|
|
||||||
import {Graph, NodeAddress} from "../../core/graph";
|
import {Graph, NodeAddress} from "../../core/graph";
|
||||||
import {Assets} from "../../webutil/assets";
|
import {Assets} from "../../webutil/assets";
|
||||||
import {makeRepoId, type RepoId} from "../../core/repoId";
|
|
||||||
import {type EdgeEvaluator} from "../../analysis/pagerank";
|
import {type EdgeEvaluator} from "../../analysis/pagerank";
|
||||||
import {defaultWeights} from "../../analysis/weights";
|
import {defaultWeights} from "../../analysis/weights";
|
||||||
import type {
|
import type {
|
||||||
@ -20,7 +19,7 @@ describe("explorer/legacy/state", () => {
|
|||||||
stateContainer.appState = appState;
|
stateContainer.appState = appState;
|
||||||
};
|
};
|
||||||
const loadGraphMock: JestMockFn<
|
const loadGraphMock: JestMockFn<
|
||||||
[Assets, RepoId],
|
[Assets, string],
|
||||||
Promise<Graph>
|
Promise<Graph>
|
||||||
> = jest.fn();
|
> = jest.fn();
|
||||||
|
|
||||||
@ -39,14 +38,14 @@ describe("explorer/legacy/state", () => {
|
|||||||
function readyToLoadGraph(): AppState {
|
function readyToLoadGraph(): AppState {
|
||||||
return {
|
return {
|
||||||
type: "READY_TO_LOAD_GRAPH",
|
type: "READY_TO_LOAD_GRAPH",
|
||||||
repoId: makeRepoId("foo", "bar"),
|
projectId: "foo/bar",
|
||||||
loading: "NOT_LOADING",
|
loading: "NOT_LOADING",
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
function readyToRunPagerank(): AppState {
|
function readyToRunPagerank(): AppState {
|
||||||
return {
|
return {
|
||||||
type: "READY_TO_RUN_PAGERANK",
|
type: "READY_TO_RUN_PAGERANK",
|
||||||
repoId: makeRepoId("foo", "bar"),
|
projectId: "foo/bar",
|
||||||
loading: "NOT_LOADING",
|
loading: "NOT_LOADING",
|
||||||
graph: new Graph(),
|
graph: new Graph(),
|
||||||
};
|
};
|
||||||
@ -54,7 +53,7 @@ describe("explorer/legacy/state", () => {
|
|||||||
function pagerankEvaluated(): AppState {
|
function pagerankEvaluated(): AppState {
|
||||||
return {
|
return {
|
||||||
type: "PAGERANK_EVALUATED",
|
type: "PAGERANK_EVALUATED",
|
||||||
repoId: makeRepoId("foo", "bar"),
|
projectId: "foo/bar",
|
||||||
graph: new Graph(),
|
graph: new Graph(),
|
||||||
pagerankNodeDecomposition: pagerankNodeDecomposition(),
|
pagerankNodeDecomposition: pagerankNodeDecomposition(),
|
||||||
loading: "NOT_LOADING",
|
loading: "NOT_LOADING",
|
||||||
@ -80,16 +79,13 @@ describe("explorer/legacy/state", () => {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
it("passes along the repoId", () => {
|
it("passes along the projectId", () => {
|
||||||
const {stm, loadGraphMock} = example(readyToLoadGraph());
|
const {stm, loadGraphMock} = example(readyToLoadGraph());
|
||||||
expect(loadGraphMock).toHaveBeenCalledTimes(0);
|
expect(loadGraphMock).toHaveBeenCalledTimes(0);
|
||||||
const assets = new Assets("/my/gateway/");
|
const assets = new Assets("/my/gateway/");
|
||||||
stm.loadGraph(assets);
|
stm.loadGraph(assets);
|
||||||
expect(loadGraphMock).toHaveBeenCalledTimes(1);
|
expect(loadGraphMock).toHaveBeenCalledTimes(1);
|
||||||
expect(loadGraphMock).toHaveBeenCalledWith(
|
expect(loadGraphMock).toHaveBeenCalledWith(assets, "foo/bar");
|
||||||
assets,
|
|
||||||
makeRepoId("foo", "bar")
|
|
||||||
);
|
|
||||||
});
|
});
|
||||||
it("immediately sets loading status", () => {
|
it("immediately sets loading status", () => {
|
||||||
const {getState, stm} = example(readyToLoadGraph());
|
const {getState, stm} = example(readyToLoadGraph());
|
||||||
|
@ -2,16 +2,17 @@
|
|||||||
|
|
||||||
import React, {type ComponentType} from "react";
|
import React, {type ComponentType} from "react";
|
||||||
|
|
||||||
import type {RepoId} from "../core/repoId";
|
|
||||||
import type {Assets} from "../webutil/assets";
|
import type {Assets} from "../webutil/assets";
|
||||||
import HomepageExplorer from "./homepageExplorer";
|
import HomepageExplorer from "./homepageExplorer";
|
||||||
|
|
||||||
export default function makeProjectPage(
|
export default function makeProjectPage(
|
||||||
repoId: RepoId
|
projectId: string
|
||||||
): ComponentType<{|+assets: Assets|}> {
|
): ComponentType<{|+assets: Assets|}> {
|
||||||
return class ProjectPage extends React.Component<{|+assets: Assets|}> {
|
return class ProjectPage extends React.Component<{|+assets: Assets|}> {
|
||||||
render() {
|
render() {
|
||||||
return <HomepageExplorer assets={this.props.assets} repoId={repoId} />;
|
return (
|
||||||
|
<HomepageExplorer assets={this.props.assets} projectId={projectId} />
|
||||||
|
);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
@ -1,14 +1,12 @@
|
|||||||
// @flow
|
// @flow
|
||||||
|
|
||||||
import stringify from "json-stable-stringify";
|
|
||||||
import React, {type ComponentType} from "react";
|
import React, {type ComponentType} from "react";
|
||||||
|
|
||||||
import type {RepoIdRegistry} from "../core/repoIdRegistry";
|
|
||||||
import Link from "../webutil/Link";
|
import Link from "../webutil/Link";
|
||||||
import type {Assets} from "../webutil/assets";
|
import type {Assets} from "../webutil/assets";
|
||||||
|
|
||||||
export default function makePrototypesPage(
|
export default function makePrototypesPage(
|
||||||
registry: RepoIdRegistry
|
projectIds: $ReadOnlyArray<string>
|
||||||
): ComponentType<{|+assets: Assets|}> {
|
): ComponentType<{|+assets: Assets|}> {
|
||||||
return class PrototypesPage extends React.Component<{|+assets: Assets|}> {
|
return class PrototypesPage extends React.Component<{|+assets: Assets|}> {
|
||||||
render() {
|
render() {
|
||||||
@ -24,11 +22,9 @@ export default function makePrototypesPage(
|
|||||||
>
|
>
|
||||||
<p>Select a project:</p>
|
<p>Select a project:</p>
|
||||||
<ul>
|
<ul>
|
||||||
{registry.map((x) => (
|
{projectIds.map((projectId) => (
|
||||||
<li key={stringify(x)}>
|
<li key={projectId}>
|
||||||
<Link to={`/timeline/${x.repoId.owner}/${x.repoId.name}/`}>
|
<Link to={`/timeline/${projectId}/`}>{`${projectId}`}</Link>
|
||||||
{`${x.repoId.owner}/${x.repoId.name}`}
|
|
||||||
</Link>
|
|
||||||
</li>
|
</li>
|
||||||
))}
|
))}
|
||||||
</ul>
|
</ul>
|
||||||
|
@ -2,16 +2,17 @@
|
|||||||
|
|
||||||
import React, {type ComponentType} from "react";
|
import React, {type ComponentType} from "react";
|
||||||
|
|
||||||
import type {RepoId} from "../core/repoId";
|
|
||||||
import type {Assets} from "../webutil/assets";
|
import type {Assets} from "../webutil/assets";
|
||||||
import HomepageTimeline from "./homepageTimeline";
|
import HomepageTimeline from "./homepageTimeline";
|
||||||
|
|
||||||
export default function makeTimelinePage(
|
export default function makeTimelinePage(
|
||||||
repoId: RepoId
|
projectId: string
|
||||||
): ComponentType<{|+assets: Assets|}> {
|
): ComponentType<{|+assets: Assets|}> {
|
||||||
return class TimelinePage extends React.Component<{|+assets: Assets|}> {
|
return class TimelinePage extends React.Component<{|+assets: Assets|}> {
|
||||||
render() {
|
render() {
|
||||||
return <HomepageTimeline assets={this.props.assets} repoId={repoId} />;
|
return (
|
||||||
|
<HomepageTimeline assets={this.props.assets} projectId={projectId} />
|
||||||
|
);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
@ -1,13 +1,12 @@
|
|||||||
// @flow
|
// @flow
|
||||||
|
|
||||||
import type {RepoIdRegistry} from "../core/repoIdRegistry";
|
|
||||||
import {type RouteData, makeRouteData} from "./routeData";
|
import {type RouteData, makeRouteData} from "./routeData";
|
||||||
|
|
||||||
export default function createRouteDataFromEnvironment(): RouteData {
|
export default function createRouteDataFromEnvironment(): RouteData {
|
||||||
const raw = process.env.REPO_REGISTRY;
|
const raw = process.env.PROJECT_IDS;
|
||||||
if (raw == null) {
|
if (raw == null) {
|
||||||
throw new Error("fatal: repo ID registry unset");
|
throw new Error("fatal: project IDs unset");
|
||||||
}
|
}
|
||||||
const registry: RepoIdRegistry = JSON.parse(raw);
|
const ids: $ReadOnlyArray<string> = JSON.parse(raw);
|
||||||
return makeRouteData(registry);
|
return makeRouteData(ids);
|
||||||
}
|
}
|
||||||
|
@ -4,13 +4,14 @@ import React from "react";
|
|||||||
|
|
||||||
import type {Assets} from "../webutil/assets";
|
import type {Assets} from "../webutil/assets";
|
||||||
import {AppPage} from "../explorer/legacy/App";
|
import {AppPage} from "../explorer/legacy/App";
|
||||||
import type {RepoId} from "../core/repoId";
|
|
||||||
|
|
||||||
export default class HomepageExplorer extends React.Component<{|
|
export default class HomepageExplorer extends React.Component<{|
|
||||||
+assets: Assets,
|
+assets: Assets,
|
||||||
+repoId: RepoId,
|
+projectId: string,
|
||||||
|}> {
|
|}> {
|
||||||
render() {
|
render() {
|
||||||
return <AppPage assets={this.props.assets} repoId={this.props.repoId} />;
|
return (
|
||||||
|
<AppPage assets={this.props.assets} projectId={this.props.projectId} />
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -4,17 +4,16 @@ import React from "react";
|
|||||||
|
|
||||||
import type {Assets} from "../webutil/assets";
|
import type {Assets} from "../webutil/assets";
|
||||||
import {TimelineApp, defaultLoader} from "../explorer/TimelineApp";
|
import {TimelineApp, defaultLoader} from "../explorer/TimelineApp";
|
||||||
import type {RepoId} from "../core/repoId";
|
|
||||||
|
|
||||||
export default class TimelineExplorer extends React.Component<{|
|
export default class TimelineExplorer extends React.Component<{|
|
||||||
+assets: Assets,
|
+assets: Assets,
|
||||||
+repoId: RepoId,
|
+projectId: string,
|
||||||
|}> {
|
|}> {
|
||||||
render() {
|
render() {
|
||||||
return (
|
return (
|
||||||
<TimelineApp
|
<TimelineApp
|
||||||
assets={this.props.assets}
|
assets={this.props.assets}
|
||||||
repoId={this.props.repoId}
|
projectId={this.props.projectId}
|
||||||
loader={defaultLoader}
|
loader={defaultLoader}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
|
@ -56,7 +56,9 @@ function inspectionTestFor(name, component) /*: RouteDatum */ {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
function makeRouteData(registry /*: RepoIdRegistry */) /*: RouteData */ {
|
function makeRouteData(
|
||||||
|
projectIds /*: $ReadOnlyArray<string> */
|
||||||
|
) /*: RouteData */ {
|
||||||
return [
|
return [
|
||||||
{
|
{
|
||||||
path: "/",
|
path: "/",
|
||||||
@ -71,27 +73,27 @@ function makeRouteData(registry /*: RepoIdRegistry */) /*: RouteData */ {
|
|||||||
path: "/prototype/",
|
path: "/prototype/",
|
||||||
contents: {
|
contents: {
|
||||||
type: "PAGE",
|
type: "PAGE",
|
||||||
component: () => require("./PrototypesPage").default(registry),
|
component: () => require("./PrototypesPage").default(projectIds),
|
||||||
},
|
},
|
||||||
title: "SourceCred prototype",
|
title: "SourceCred prototype",
|
||||||
navTitle: "Prototype",
|
navTitle: "Prototype",
|
||||||
},
|
},
|
||||||
...registry.map((entry) => ({
|
...projectIds.map((id) => ({
|
||||||
path: `/prototype/${entry.repoId.owner}/${entry.repoId.name}/`,
|
path: `/prototype/${id}/`,
|
||||||
contents: {
|
contents: {
|
||||||
type: "PAGE",
|
type: "PAGE",
|
||||||
component: () => require("./ProjectPage").default(entry.repoId),
|
component: () => require("./ProjectPage").default(id),
|
||||||
},
|
},
|
||||||
title: `${entry.repoId.owner}/${entry.repoId.name} • SourceCred`,
|
title: `${id} • SourceCred`,
|
||||||
navTitle: null,
|
navTitle: null,
|
||||||
})),
|
})),
|
||||||
...registry.map((entry) => ({
|
...projectIds.map((id) => ({
|
||||||
path: `/timeline/${entry.repoId.owner}/${entry.repoId.name}/`,
|
path: `/timeline/${id}/`,
|
||||||
contents: {
|
contents: {
|
||||||
type: "PAGE",
|
type: "PAGE",
|
||||||
component: () => require("./TimelinePage").default(entry.repoId),
|
component: () => require("./TimelinePage").default(id),
|
||||||
},
|
},
|
||||||
title: `${entry.repoId.owner}/${entry.repoId.name} • Timeline`,
|
title: `${id} • Timeline`,
|
||||||
navTitle: null,
|
navTitle: null,
|
||||||
})),
|
})),
|
||||||
{
|
{
|
||||||
|
@ -1,13 +1,12 @@
|
|||||||
// @flow
|
// @flow
|
||||||
|
|
||||||
import {stringToRepoId} from "../core/repoId";
|
|
||||||
import {makeRouteData} from "./routeData";
|
import {makeRouteData} from "./routeData";
|
||||||
|
|
||||||
describe("homepage/routeData", () => {
|
describe("homepage/routeData", () => {
|
||||||
function routeData() {
|
function routeData() {
|
||||||
return makeRouteData([
|
return makeRouteData([
|
||||||
{repoId: stringToRepoId("sourcecred/example-github")},
|
"sourcecred/example-github",
|
||||||
{repoId: stringToRepoId("sourcecred/sourcecred")},
|
"sourcecred/sourcecred",
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -16,7 +16,7 @@ import {createRoutes} from "./createRoutes";
|
|||||||
import {resolveRouteFromPath, resolveTitleFromPath} from "./routeData";
|
import {resolveRouteFromPath, resolveTitleFromPath} from "./routeData";
|
||||||
|
|
||||||
// Side effect for testing purposes
|
// Side effect for testing purposes
|
||||||
console.log(`REPO_REGISTRY: ${process.env.REPO_REGISTRY || "bad"}`);
|
console.log(`PROJECT_IDS: ${process.env.PROJECT_IDS || "bad"}`);
|
||||||
|
|
||||||
// NOTE: This is a side-effect at module load time.
|
// NOTE: This is a side-effect at module load time.
|
||||||
const routeData = createRouteDataFromEnvironment();
|
const routeData = createRouteDataFromEnvironment();
|
||||||
|
@ -9,7 +9,8 @@ import {declaration} from "./declaration";
|
|||||||
import {RelationalView} from "./relationalView";
|
import {RelationalView} from "./relationalView";
|
||||||
import {createGraph} from "./createGraph";
|
import {createGraph} from "./createGraph";
|
||||||
|
|
||||||
describe("plugins/github/analysisAdapter", () => {
|
// This file is deprecated, and will be removed shortly.
|
||||||
|
describe.skip("plugins/github/analysisAdapter", () => {
|
||||||
it("the loader provides the declaration", () => {
|
it("the loader provides the declaration", () => {
|
||||||
const loader = new BackendAdapterLoader();
|
const loader = new BackendAdapterLoader();
|
||||||
expect(loader.declaration()).toEqual(declaration);
|
expect(loader.declaration()).toEqual(declaration);
|
||||||
|
Loading…
x
Reference in New Issue
Block a user