From 40426f353ce009913448f212190636596d9e7d36 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dandelion=20Man=C3=A9?= Date: Sat, 30 May 2020 15:26:01 -0700 Subject: [PATCH] Implement bare-bones sc2 merge and score (#1815) This adds the missing `merge` and `score` commands to the sc2 CLI. `merge` currently just merges the plugin graphs, and doesn't yet support identity resolution or weight overrides. `score` computes the new data output format (cf #1773) and writes it to disk. It doesn't yet support using custom parameters. Test plan: Follow the test plans for the previous commits, then run `sc2 merge`. It will create a combined graph at `output/graph.json`, which will contain data for both Discourse and GitHub. Then run `sc2 score` and the `output/cred.json` file will contain scores for the combined graph. --- src/cli2/merge.js | 53 +++++++++++++++++++++++++++++++++++++++ src/cli2/score.js | 56 ++++++++++++++++++++++++++++++++++++++++++ src/cli2/sourcecred.js | 6 +++++ 3 files changed, 115 insertions(+) create mode 100644 src/cli2/merge.js create mode 100644 src/cli2/score.js diff --git a/src/cli2/merge.js b/src/cli2/merge.js new file mode 100644 index 0000000..e35e5b0 --- /dev/null +++ b/src/cli2/merge.js @@ -0,0 +1,53 @@ +// @flow + +import fs from "fs-extra"; +import stringify from "json-stable-stringify"; +import {join as pathJoin} from "path"; + +import {LoggingTaskReporter} from "../util/taskReporter"; +import type {Command} from "./command"; +import {makePluginDir, loadInstanceConfig} from "./common"; +import { + toJSON as weightedGraphToJSON, + fromJSON as weightedGraphFromJSON, + type WeightedGraph, + merge, +} from "../core/weightedGraph"; + +function die(std, message) { + std.err("fatal: " + message); + return 1; +} + +const mergeCommand: Command = async (args, std) => { + if (args.length !== 0) { + return die(std, "usage: sourcecred merge"); + } + const taskReporter = new LoggingTaskReporter(); + taskReporter.start("merge"); + const baseDir = process.cwd(); + const config = await loadInstanceConfig(baseDir); + const graphOutputPrefix = ["output", "graphs"]; + + async function loadGraph(pluginName): Promise { + const outputDir = makePluginDir(baseDir, graphOutputPrefix, pluginName); + const outputPath = pathJoin(outputDir, "graph.json"); + const graphJSON = JSON.parse(await fs.readFile(outputPath)); + return weightedGraphFromJSON(graphJSON); + } + + const pluginNames = Array.from(config.bundledPlugins.keys()); + const graphs = await Promise.all(pluginNames.map(loadGraph)); + + // TODO: Support identity merging. + // TODO: Support weight overrides. + const combinedGraph = merge(graphs); + + const outputPath = pathJoin(baseDir, "output", "graph.json"); + const serializedGraph = stringify(weightedGraphToJSON(combinedGraph)); + await fs.writeFile(outputPath, serializedGraph); + taskReporter.finish("merge"); + return 0; +}; + +export default mergeCommand; diff --git a/src/cli2/score.js b/src/cli2/score.js new file mode 100644 index 0000000..7eb485b --- /dev/null +++ b/src/cli2/score.js @@ -0,0 +1,56 @@ +// @flow + +import fs from "fs-extra"; +import stringify from "json-stable-stringify"; +import {join as pathJoin} from "path"; + +import type {Command} from "./command"; +import {loadInstanceConfig} from "./common"; +import {fromJSON as weightedGraphFromJSON} from "../core/weightedGraph"; +import {defaultParams} from "../analysis/timeline/params"; +import {TimelineCred} from "../analysis/timeline/timelineCred"; +import {LoggingTaskReporter} from "../util/taskReporter"; +import { + fromTimelineCredAndPlugins, + COMPAT_INFO as OUTPUT_COMPAT_INFO, +} from "../analysis/output"; +import {toCompat} from "../util/compat"; + +function die(std, message) { + std.err("fatal: " + message); + return 1; +} + +const scoreCommand: Command = async (args, std) => { + if (args.length !== 0) { + return die(std, "usage: sourcecred score"); + } + const taskReporter = new LoggingTaskReporter(); + taskReporter.start("score"); + const baseDir = process.cwd(); + const config = await loadInstanceConfig(baseDir); + + const graphFilePath = pathJoin(baseDir, "output", "graph.json"); + const graphJSON = JSON.parse(await fs.readFile(graphFilePath)); + const graph = weightedGraphFromJSON(graphJSON); + + const plugins = Array.from(config.bundledPlugins.values()); + const declarations = plugins.map((x) => x.declaration()); + + // TODO: Support loading params from config. + const params = defaultParams(); + + const tc = await TimelineCred.compute({ + weightedGraph: graph, + params, + plugins: declarations, + }); + const output = fromTimelineCredAndPlugins(tc, declarations); + const outputJSON = stringify(toCompat(OUTPUT_COMPAT_INFO, output)); + const outputPath = pathJoin(baseDir, "output", "cred.json"); + await fs.writeFile(outputPath, outputJSON); + taskReporter.finish("score"); + return 0; +}; + +export default scoreCommand; diff --git a/src/cli2/sourcecred.js b/src/cli2/sourcecred.js index 43c0e8b..b5a5d3f 100644 --- a/src/cli2/sourcecred.js +++ b/src/cli2/sourcecred.js @@ -4,6 +4,8 @@ import type {Command} from "./command"; import load from "./load"; import graph from "./graph"; +import merge from "./merge"; +import score from "./score"; const sourcecred: Command = async (args, std) => { if (args.length === 0) { @@ -15,6 +17,10 @@ const sourcecred: Command = async (args, std) => { return load(args.slice(1), std); case "graph": return graph(args.slice(1), std); + case "merge": + return merge(args.slice(1), std); + case "score": + return score(args.slice(1), std); default: std.err("fatal: unknown command: " + JSON.stringify(args[0])); return 1;