Create github.Porcelain: whole-graph porcelain (#230)

Now that we have repository nodes (#171), it makes sense that the Github
porcelain should provide a way to wrap the entire graph, and provide
easy access for the various repositories. This adds a `Porcelain` class
to fulfill that need.

The `Porcelain` is very straightforward: it takes in the whole graph,
and gives a way to get all the Repositories, or to request a particular
Repository by owner/name. In the odd case wherein a graph contains
multiple repository nodes with the same owner and name, an error is
thrown. Per standard JS map semantics (bleh), it can return undefined if
there is no matching repository.

Test plan:
See that the unit tests now use the standard behavior, and a test
verifies behavior for non-existant repositories. I don't have a test
case where there are multiple repo nodes, but that itself would be an
error, so throwing an error in that case is just defensive programming.
This commit is contained in:
Dandelion Mané 2018-05-07 17:56:33 -07:00 committed by GitHub
parent f219636a56
commit 9b3019434d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 45 additions and 4 deletions

View File

@ -31,6 +31,7 @@ import {
PULL_REQUEST_NODE_TYPE,
PULL_REQUEST_REVIEW_COMMENT_NODE_TYPE,
PULL_REQUEST_REVIEW_NODE_TYPE,
REPOSITORY_NODE_TYPE,
REFERENCES_EDGE_TYPE,
} from "./types";
@ -53,6 +54,34 @@ function assertEntityType(e: Entity, t: NodeType) {
}
}
export class Porcelain {
graph: Graph<NodePayload, EdgePayload>;
constructor(graph: Graph<NodePayload, EdgePayload>) {
this.graph = graph;
}
/* Return all the repositories in the graph */
repositories(): Repository[] {
return this.graph
.nodes({type: REPOSITORY_NODE_TYPE})
.map((n) => new Repository(this.graph, n.address));
}
/* Return the repository with the given owner and name */
repository(owner: string, name: string): Repository {
const repo = this.repositories().filter(
(r) => r.owner() === owner && r.name() === name
);
if (repo.length > 1) {
throw new Error(
`Unexpectedly found multiple repositories named ${owner}/${name}`
);
}
return repo[0];
}
}
class GithubEntity<T: NodePayload> {
graph: Graph<NodePayload, EdgePayload>;
nodeAddress: Address;

View File

@ -2,7 +2,7 @@
import {parse} from "./parser";
import exampleRepoData from "./demoData/example-github.json";
import {Repository, Issue, PullRequest, Comment, Author} from "./api";
import {Porcelain, Issue, PullRequest, Comment, Author} from "./api";
import {
AUTHOR_NODE_TYPE,
COMMENT_NODE_TYPE,
@ -11,11 +11,11 @@ import {
PULL_REQUEST_REVIEW_NODE_TYPE,
PULL_REQUEST_REVIEW_COMMENT_NODE_TYPE,
} from "./types";
describe("GitHub porcelain API", () => {
const graph = parse(exampleRepoData);
// TODO: Create a higher level API that contains all the repositories
const repoNode = graph.nodes({type: "REPOSITORY"})[0];
const repo = new Repository(graph, repoNode.address);
const porcelain = new Porcelain(graph);
const repo = porcelain.repository("sourcecred", "example-github");
function issueOrPRByNumber(n: number): Issue | PullRequest {
const result = repo.issueOrPRByNumber(n);
if (result == null) {
@ -23,6 +23,18 @@ describe("GitHub porcelain API", () => {
}
return result;
}
describe("has repository finding", () => {
it("which works for an existing repository", () => {
expect(porcelain.repository("sourcecred", "example-github")).toEqual(
expect.anything()
);
});
it("which returns undefined when asking for a nonexistent repo", () => {
expect(porcelain.repository("sourcecred", "bad-repo")).toBe(undefined);
});
});
describe("has wrappers for", () => {
it("Repositories", () => {
expect(repo.url()).toBe("https://github.com/sourcecred/example-github");