Refactor GH parser to expose a functional api (#145)

Our GitHub parser is implemented via a `GithubParser` class which builds
the GitHub graph. This is a convenient implementation, but an awkward
API. This commit refactors the module so that it exposes a clean `parse`
function, which ingests the GitHub JSON data and returns as completed
graph.

Test plan:
The unit tests have been re-written to use the new public API. All the
snapshots are unchanged, and flow passes. Additionally, I ran `yarn
start` and verified that the GithubGraphFetcher for the Artifact plugin
is still working.
This commit is contained in:
Dandelion Mané 2018-04-25 19:39:28 -07:00 committed by GitHub
parent 5e4b7b1fcc
commit 18ce9982d2
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 71 additions and 34 deletions

View File

@ -9,7 +9,7 @@ import type {
NodePayload as GithubNodePayload, NodePayload as GithubNodePayload,
EdgePayload as GithubEdgePayload, EdgePayload as GithubEdgePayload,
} from "../../github/types"; } from "../../github/types";
import {GithubParser} from "../../github/parser"; import {parse} from "../../github/parser";
type Props = { type Props = {
settings: Settings, settings: Settings,
@ -32,9 +32,7 @@ export class GithubGraphFetcher extends React.Component<Props> {
const {repoOwner, repoName, githubApiToken} = this.props.settings; const {repoOwner, repoName, githubApiToken} = this.props.settings;
fetchGithubRepo(repoOwner, repoName, githubApiToken) fetchGithubRepo(repoOwner, repoName, githubApiToken)
.then((json) => { .then((json) => {
const parser = new GithubParser(`${repoOwner}/${repoName}`); return Promise.resolve(parse(`${repoOwner}/${repoName}`, json));
parser.addData(json);
return Promise.resolve(parser.graph);
}) })
.then((graph) => { .then((graph) => {
this.props.onCreateGraph(graph); this.props.onCreateGraph(graph);

View File

@ -5,7 +5,7 @@ import {shallow} from "enzyme";
import enzymeToJSON from "enzyme-to-json"; import enzymeToJSON from "enzyme-to-json";
import stringify from "json-stable-stringify"; import stringify from "json-stable-stringify";
import {GithubParser} from "../../../github/parser"; import {parse} from "../../../github/parser";
import exampleRepoData from "../../../github/demoData/example-repo.json"; import exampleRepoData from "../../../github/demoData/example-repo.json";
import adapter from "./githubPluginAdapter"; import adapter from "./githubPluginAdapter";
@ -13,9 +13,7 @@ require("../testUtil").configureEnzyme();
describe("githubPluginAdapter", () => { describe("githubPluginAdapter", () => {
it("operates on the example repo", () => { it("operates on the example repo", () => {
const parser = new GithubParser("sourcecred/example-repo"); const graph = parse("sourcecred/example-repo", exampleRepoData);
parser.addData(exampleRepoData);
const graph = parser.graph;
const result = graph const result = graph
.getNodes() .getNodes()

View File

@ -30,7 +30,16 @@ import {PLUGIN_NAME} from "./pluginName";
import {Graph, edgeID} from "../../core/graph"; import {Graph, edgeID} from "../../core/graph";
const stringify = require("json-stable-stringify"); const stringify = require("json-stable-stringify");
export class GithubParser { export function parse(
repositoryName: string,
repositoryJSON: RepositoryJSON
): Graph<NodePayload, EdgePayload> {
const parser = new GithubParser(repositoryName);
parser.addData(repositoryJSON);
return parser.graph;
}
class GithubParser {
repositoryName: string; repositoryName: string;
graph: Graph<NodePayload, EdgePayload>; graph: Graph<NodePayload, EdgePayload>;

View File

@ -1,15 +1,15 @@
// @flow // @flow
import {AUTHORS_EDGE_TYPE, CONTAINS_EDGE_TYPE} from "./types"; import {AUTHORS_EDGE_TYPE, CONTAINS_EDGE_TYPE} from "./types";
import type {NodePayload, EdgePayload} from "./types";
import {GithubParser} from "./parser"; import {parse} from "./parser";
import type {RepositoryJSON, PullRequestJSON, IssueJSON} from "./graphql";
import {Graph} from "../../core/graph";
import exampleRepoData from "./demoData/example-repo.json"; import exampleRepoData from "./demoData/example-repo.json";
describe("GithubParser", () => { describe("GithubParser", () => {
describe("whole repo parsing", () => { describe("whole repo parsing", () => {
const parser = new GithubParser("sourcecred/example-repo"); const graph = parse("sourcecred/example-repo", exampleRepoData);
parser.addData(exampleRepoData);
const graph = parser.graph;
it("parses the entire example-repo as expected", () => { it("parses the entire example-repo as expected", () => {
expect(graph).toMatchSnapshot(); expect(graph).toMatchSnapshot();
@ -66,38 +66,70 @@ describe("GithubParser", () => {
}); });
}); });
function getIssue(n: number): IssueJSON {
const issues = exampleRepoData.repository.issues.nodes;
const selected = issues.filter((x) => x.number === n);
if (selected.length !== 1) {
throw new Error(`Failure finding issue #${n}`);
}
return selected[0];
}
function getPR(n: number): PullRequestJSON {
const pulls = exampleRepoData.repository.pullRequests.nodes;
const selected = pulls.filter((x) => x.number === n);
if (selected.length !== 1) {
throw new Error(`Failure finding PR #${n}`);
}
return selected[0];
}
type ExampleInput = {
issues?: number[],
prs?: number[],
};
function parseExample({
issues: issueNums = [],
prs: prNums = [],
}: ExampleInput): Graph<NodePayload, EdgePayload> {
const issues = issueNums.map(getIssue);
const pullRequests = prNums.map(getPR);
const exampleData: RepositoryJSON = {
repository: {
id: exampleRepoData.repository.id,
issues: {
nodes: issues,
pageInfo: {
hasNextPage: false,
endCursor: null,
},
},
pullRequests: {
nodes: pullRequests,
pageInfo: {
hasNextPage: false,
endCursor: null,
},
},
},
};
return parse("sourcecred/example-repo", exampleData);
}
describe("issue parsing", () => { describe("issue parsing", () => {
it("parses a simple issue (https://github.com/sourcecred/example-repo/issues/1)", () => { it("parses a simple issue (https://github.com/sourcecred/example-repo/issues/1)", () => {
const issue1 = exampleRepoData.repository.issues.nodes[0]; expect(parseExample({issues: [1]})).toMatchSnapshot();
expect(issue1.number).toBe(1);
const parser = new GithubParser("sourcecred/example-repo");
parser.addIssue(issue1);
expect(parser.graph).toMatchSnapshot();
}); });
it("parses an issue with comments (https://github.com/sourcecred/example-repo/issues/6)", () => { it("parses an issue with comments (https://github.com/sourcecred/example-repo/issues/6)", () => {
const issue6 = exampleRepoData.repository.issues.nodes[3]; expect(parseExample({issues: [6]})).toMatchSnapshot();
expect(issue6.number).toBe(6);
const parser = new GithubParser("sourcecred/example-repo");
parser.addIssue(issue6);
expect(parser.graph).toMatchSnapshot();
}); });
}); });
describe("pull request parsing", () => { describe("pull request parsing", () => {
it("parses a simple pull request (https://github.com/sourcecred/example-repo/pull/3)", () => { it("parses a simple pull request (https://github.com/sourcecred/example-repo/pull/3)", () => {
const pr3 = exampleRepoData.repository.pullRequests.nodes[0]; expect(parseExample({prs: [3]})).toMatchSnapshot();
expect(pr3.number).toBe(3);
const parser = new GithubParser("sourcecred/example-repo");
parser.addPullRequest(pr3);
expect(parser.graph).toMatchSnapshot();
}); });
it("parses a pr with review comments (https://github.com/sourcecred/example-repo/pull/3)", () => { it("parses a pr with review comments (https://github.com/sourcecred/example-repo/pull/3)", () => {
const pr5 = exampleRepoData.repository.pullRequests.nodes[1]; expect(parseExample({prs: [5]})).toMatchSnapshot();
expect(pr5.number).toBe(5);
const parser = new GithubParser("sourcecred/example-repo");
parser.addPullRequest(pr5);
expect(parser.graph).toMatchSnapshot();
}); });
}); });
}); });