Add GitPluginAdapter and Git render module (#462)

Test plan:
Run the following commands:

```
node bin/sourcecredV3.js load-plugin-v3 sourcecred example-github --plugin=git
node bin/sourcecredV3.js load-plugin-v3 sourcecred example-github --plugin=github
yarn start
```

Then, navigate in-browser to the v3 cred explorer and load data for
`sourcecred/example-github`. The following messages are printed to
console:

```
GitHub: Loaded graph: 31 nodes, 73 edges.
Git: Loaded graph: 15 nodes, 19 edges.
Combined: Loaded graph: 44 nodes, 92 edges.
```

Paired with @wchargin
This commit is contained in:
Dandelion Mané 2018-06-29 18:38:28 -07:00 committed by GitHub
parent 4767dce749
commit dd063f5203
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 146 additions and 3 deletions

View File

@ -5,6 +5,8 @@ import {StyleSheet, css} from "aphrodite/no-important";
import LocalStore from "./LocalStore"; import LocalStore from "./LocalStore";
import {createPluginAdapter as createGithubAdapter} from "../../plugins/github/pluginAdapter"; import {createPluginAdapter as createGithubAdapter} from "../../plugins/github/pluginAdapter";
import {createPluginAdapter as createGitAdapter} from "../../plugins/git/pluginAdapter";
import {Graph} from "../../core/graph";
type Props = {}; type Props = {};
type State = { type State = {
@ -82,11 +84,38 @@ export default class App extends React.Component<Props, State> {
console.error(`Invalid repository name: ${JSON.stringify(repoName)}`); console.error(`Invalid repository name: ${JSON.stringify(repoName)}`);
return; return;
} }
createGithubAdapter(repoOwner, repoName).then((githubAdapter) => {
const graph = githubAdapter.graph(); const githubGraphPromise = createGithubAdapter(repoOwner, repoName).then(
(githubAdapter) => {
const graph = githubAdapter.graph();
const nodeCount = Array.from(graph.nodes()).length;
const edgeCount = Array.from(graph.edges()).length;
console.log(
`GitHub: Loaded graph: ${nodeCount} nodes, ${edgeCount} edges.`
);
return graph;
}
);
const gitGraphPromise = createGitAdapter(repoOwner, repoName).then(
(gitAdapter) => {
const graph = gitAdapter.graph();
const nodeCount = Array.from(graph.nodes()).length;
const edgeCount = Array.from(graph.edges()).length;
console.log(
`Git: Loaded graph: ${nodeCount} nodes, ${edgeCount} edges.`
);
return graph;
}
);
Promise.all([gitGraphPromise, githubGraphPromise]).then((graphs) => {
const graph = Graph.merge(graphs);
const nodeCount = Array.from(graph.nodes()).length; const nodeCount = Array.from(graph.nodes()).length;
const edgeCount = Array.from(graph.edges()).length; const edgeCount = Array.from(graph.edges()).length;
console.log(`Loaded graph: ${nodeCount} nodes, ${edgeCount} edges.`); console.log(
`Combined: Loaded graph: ${nodeCount} nodes, ${edgeCount} edges.`
);
}); });
} }
} }

View File

@ -0,0 +1,9 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`plugins/git/render blob snapshots as expected 1`] = `"blob f1f2514ca6d7a6a1a0511957021b1995bf9ace1c"`;
exports[`plugins/git/render commit snapshots as expected 1`] = `"commit 3715ddfb8d4c4fd2a6f6af75488c82f84c92ec2f"`;
exports[`plugins/git/render tree snapshots as expected 1`] = `"tree 7be3ecfee5314ffa9b2d93fc4377792b2d6d70ed"`;
exports[`plugins/git/render treeEntry snapshots as expected 1`] = `"entry \\"science.txt\\" in tree 7be3ecfee5314ffa9b2d93fc4377792b2d6d70ed"`;

View File

@ -0,0 +1,47 @@
// @flow
import type {
PluginAdapter as IPluginAdapter,
Renderer as IRenderer,
} from "../../app/pluginAdapter";
import {Graph} from "../../core/graph";
import * as N from "./nodes";
import {description} from "./render";
export async function createPluginAdapter(
repoOwner: string,
repoName: string
): Promise<IPluginAdapter> {
const url = `/api/v1/data/data/${repoOwner}/${repoName}/git/graph.json`;
const response = await fetch(url);
if (!response.ok) {
return Promise.reject(response);
}
const json = await response.json();
const graph = Graph.fromJSON(json);
return new PluginAdapter(graph);
}
class PluginAdapter implements IPluginAdapter {
+_graph: Graph;
constructor(graph: Graph) {
this._graph = graph;
}
graph() {
return this._graph;
}
renderer() {
return new Renderer();
}
nodePrefix() {
return N._Prefix.base;
}
}
class Renderer implements IRenderer {
nodeDescription(node) {
// This cast is unsound, and might throw at runtime, but won't have
// silent failures or cause problems down the road.
const address = N.fromRaw((node: any));
return description(address);
}
}

View File

@ -0,0 +1,20 @@
// @flow
import * as N from "./nodes";
export function description(address: N.StructuredAddress) {
switch (address.type) {
case "COMMIT":
return `commit ${address.hash}`;
case "TREE":
return `tree ${address.hash}`;
case "BLOB":
return `blob ${address.hash}`;
case "TREE_ENTRY":
return `entry ${JSON.stringify(address.name)} in tree ${
address.treeHash
}`;
default:
throw new Error(`unknown type: ${(address.type: empty)}`);
}
}

View File

@ -0,0 +1,38 @@
// @flow
import * as GN from "./nodes";
import {description} from "./render";
describe("plugins/git/render", () => {
const examples = {
blob: (): GN.BlobAddress => ({
type: GN.BLOB_TYPE,
hash: "f1f2514ca6d7a6a1a0511957021b1995bf9ace1c",
}),
commit: (): GN.CommitAddress => ({
type: GN.COMMIT_TYPE,
hash: "3715ddfb8d4c4fd2a6f6af75488c82f84c92ec2f",
}),
tree: (): GN.TreeAddress => ({
type: GN.TREE_TYPE,
hash: "7be3ecfee5314ffa9b2d93fc4377792b2d6d70ed",
}),
treeEntry: (): GN.TreeEntryAddress => ({
type: GN.TREE_ENTRY_TYPE,
treeHash: "7be3ecfee5314ffa9b2d93fc4377792b2d6d70ed",
name: "science.txt",
}),
};
it("blob snapshots as expected", () => {
expect(description(examples.blob())).toMatchSnapshot();
});
it("commit snapshots as expected", () => {
expect(description(examples.commit())).toMatchSnapshot();
});
it("tree snapshots as expected", () => {
expect(description(examples.tree())).toMatchSnapshot();
});
it("treeEntry snapshots as expected", () => {
expect(description(examples.treeEntry())).toMatchSnapshot();
});
});