Update GitHub to use NodeReference & NodePorcelain (#287)
See #286 for context. There are a few miscellaneous changes in src/app/credExplorer to change clients to use the new API. Test plan: New unit test were added, and existing behavior is preserved. Most of the functionality of the GitHub porcelain was already well tested. Paired with @wchargin
This commit is contained in:
parent
7ccef98c87
commit
bb77c36626
|
@ -6,6 +6,7 @@ import stringify from "json-stable-stringify";
|
|||
import {Graph} from "../../core/graph";
|
||||
import type {Address} from "../../core/address";
|
||||
import {AddressMap} from "../../core/address";
|
||||
import {NodeReference} from "../../core/porcelain";
|
||||
import {PLUGIN_NAME as GITHUB_PLUGIN_NAME} from "../../plugins/github/pluginName";
|
||||
import {GIT_PLUGIN_NAME} from "../../plugins/git/types";
|
||||
import {nodeDescription as githubNodeDescription} from "../../plugins/github/render";
|
||||
|
@ -24,16 +25,16 @@ type State = {
|
|||
|},
|
||||
};
|
||||
|
||||
function nodeDescription(graph, address) {
|
||||
switch (address.pluginName) {
|
||||
function nodeDescription(ref) {
|
||||
switch (ref.address().pluginName) {
|
||||
case GITHUB_PLUGIN_NAME: {
|
||||
return githubNodeDescription(graph, address);
|
||||
return githubNodeDescription(ref);
|
||||
}
|
||||
case GIT_PLUGIN_NAME: {
|
||||
return gitNodeDescription(graph, address);
|
||||
return gitNodeDescription(ref.graph(), ref.address());
|
||||
}
|
||||
default: {
|
||||
return stringify(address);
|
||||
return stringify(ref.address());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -184,7 +185,7 @@ class RecursiveTable extends React.Component<RTProps, RTState> {
|
|||
>
|
||||
{expanded ? "\u2212" : "+"}
|
||||
</button>
|
||||
{nodeDescription(graph, address)}
|
||||
{nodeDescription(new NodeReference(graph, address))}
|
||||
</td>
|
||||
<td>{(score * 100).toPrecision(3)}</td>
|
||||
<td>{Math.log(score).toPrecision(3)}</td>
|
||||
|
|
|
@ -15,9 +15,9 @@
|
|||
*/
|
||||
import stringify from "json-stable-stringify";
|
||||
|
||||
import {Graph} from "../../core/graph";
|
||||
import type {Node} from "../../core/graph";
|
||||
import type {Address} from "../../core/address";
|
||||
import {Graph} from "../../core/graph";
|
||||
import {NodeReference, NodePorcelain} from "../../core/porcelain";
|
||||
import type {
|
||||
AuthorNodePayload,
|
||||
AuthorSubtype,
|
||||
|
@ -44,36 +44,26 @@ import {
|
|||
PULL_REQUEST_NODE_TYPE,
|
||||
PULL_REQUEST_REVIEW_COMMENT_NODE_TYPE,
|
||||
PULL_REQUEST_REVIEW_NODE_TYPE,
|
||||
REPOSITORY_NODE_TYPE,
|
||||
REFERENCES_EDGE_TYPE,
|
||||
REPOSITORY_NODE_TYPE,
|
||||
} from "./types";
|
||||
|
||||
import {PLUGIN_NAME} from "./pluginName";
|
||||
|
||||
import {COMMIT_NODE_TYPE} from "../git/types";
|
||||
|
||||
export type Entity =
|
||||
| Repository
|
||||
| Issue
|
||||
| PullRequest
|
||||
| Comment
|
||||
| Author
|
||||
| PullRequestReview
|
||||
| PullRequestReviewComment;
|
||||
|
||||
function assertEntityType(e: Entity, t: NodeType) {
|
||||
if (e.type() !== t) {
|
||||
function assertAddressType(address: Address, t: NodeType) {
|
||||
if (address.type !== t) {
|
||||
throw new Error(
|
||||
`Expected entity at ${stringify(e.address())} to have type ${t}`
|
||||
`Expected entity at ${stringify(address)} to have type ${t}`
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export function asEntity(
|
||||
g: Graph<NodePayload, EdgePayload>,
|
||||
addr: Address
|
||||
): Entity {
|
||||
const type: NodeType = (addr.type: any);
|
||||
function asGithubReference(
|
||||
ref: NodeReference<any>
|
||||
): GithubReference<NodePayload> {
|
||||
const addr = ref.address();
|
||||
if (addr.pluginName !== PLUGIN_NAME) {
|
||||
throw new Error(
|
||||
`Tried to make GitHub porcelain, but got the wrong plugin name: ${stringify(
|
||||
|
@ -81,21 +71,22 @@ export function asEntity(
|
|||
)}`
|
||||
);
|
||||
}
|
||||
const type: NodeType = (addr.type: any);
|
||||
switch (type) {
|
||||
case "ISSUE":
|
||||
return new Issue(g, addr);
|
||||
return new IssueReference(ref);
|
||||
case "PULL_REQUEST":
|
||||
return new PullRequest(g, addr);
|
||||
return new PullRequestReference(ref);
|
||||
case "COMMENT":
|
||||
return new Comment(g, addr);
|
||||
return new CommentReference(ref);
|
||||
case "AUTHOR":
|
||||
return new Author(g, addr);
|
||||
return new AuthorReference(ref);
|
||||
case "PULL_REQUEST_REVIEW":
|
||||
return new PullRequestReview(g, addr);
|
||||
return new PullRequestReviewReference(ref);
|
||||
case "PULL_REQUEST_REVIEW_COMMENT":
|
||||
return new PullRequestReviewComment(g, addr);
|
||||
return new PullRequestReviewCommentReference(ref);
|
||||
case "REPOSITORY":
|
||||
return new Repository(g, addr);
|
||||
return new RepositoryReference(ref);
|
||||
default:
|
||||
// eslint-disable-next-line no-unused-expressions
|
||||
(type: empty);
|
||||
|
@ -107,7 +98,7 @@ export function asEntity(
|
|||
}
|
||||
}
|
||||
|
||||
export class Porcelain {
|
||||
export class GraphPorcelain {
|
||||
graph: Graph<NodePayload, EdgePayload>;
|
||||
|
||||
constructor(graph: Graph<NodePayload, EdgePayload>) {
|
||||
|
@ -115,216 +106,261 @@ export class Porcelain {
|
|||
}
|
||||
|
||||
/* Return all the repositories in the graph */
|
||||
repositories(): Repository[] {
|
||||
repositories(): RepositoryReference[] {
|
||||
return this.graph
|
||||
.nodes({type: REPOSITORY_NODE_TYPE})
|
||||
.map((n) => new Repository(this.graph, n.address));
|
||||
.map(
|
||||
(n) => new RepositoryReference(new NodeReference(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}`
|
||||
);
|
||||
repository(owner: string, name: string): ?RepositoryReference {
|
||||
for (const repo of this.repositories()) {
|
||||
const repoNode = repo.get();
|
||||
if (
|
||||
repoNode != null &&
|
||||
repoNode.owner() === owner &&
|
||||
repoNode.name() === name
|
||||
) {
|
||||
return repo;
|
||||
}
|
||||
}
|
||||
return repo[0];
|
||||
}
|
||||
|
||||
authors(): Author[] {
|
||||
return this.graph
|
||||
.nodes({type: AUTHOR_NODE_TYPE})
|
||||
.map((n) => new Author(this.graph, n.address));
|
||||
}
|
||||
}
|
||||
|
||||
class GithubEntity<T: NodePayload> {
|
||||
graph: Graph<NodePayload, EdgePayload>;
|
||||
nodeAddress: Address;
|
||||
|
||||
constructor(graph: Graph<NodePayload, EdgePayload>, nodeAddress: Address) {
|
||||
this.graph = graph;
|
||||
this.nodeAddress = nodeAddress;
|
||||
}
|
||||
|
||||
node(): Node<T> {
|
||||
return (this.graph.node(this.nodeAddress): Node<any>);
|
||||
}
|
||||
|
||||
url(): string {
|
||||
return this.node().payload.url;
|
||||
export class GithubReference<+T: NodePayload> extends NodeReference<T> {
|
||||
constructor(ref: NodeReference<any>) {
|
||||
const addr = ref.address();
|
||||
if (addr.pluginName !== PLUGIN_NAME) {
|
||||
throw new Error(
|
||||
`Wrong plugin name ${addr.pluginName} for GitHub plugin!`
|
||||
);
|
||||
}
|
||||
super(ref.graph(), addr);
|
||||
}
|
||||
|
||||
type(): NodeType {
|
||||
return (this.nodeAddress.type: any);
|
||||
return ((super.type(): string): any);
|
||||
}
|
||||
|
||||
address(): Address {
|
||||
return this.nodeAddress;
|
||||
get(): ?GithubPorcelain<T> {
|
||||
const nodePorcelain = super.get();
|
||||
if (nodePorcelain != null) {
|
||||
return new GithubPorcelain(nodePorcelain);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export class Repository extends GithubEntity<RepositoryNodePayload> {
|
||||
static from(e: Entity): Repository {
|
||||
assertEntityType(e, REPOSITORY_NODE_TYPE);
|
||||
return (e: any);
|
||||
export class GithubPorcelain<+T: NodePayload> extends NodePorcelain<T> {
|
||||
constructor(nodePorcelain: NodePorcelain<any>) {
|
||||
if (nodePorcelain.ref().address().pluginName !== PLUGIN_NAME) {
|
||||
throw new Error(
|
||||
`Wrong plugin name ${
|
||||
nodePorcelain.ref().address().pluginName
|
||||
} for GitHub plugin!`
|
||||
);
|
||||
}
|
||||
super(nodePorcelain.ref(), nodePorcelain.node());
|
||||
}
|
||||
|
||||
issueByNumber(number: number): ?Issue {
|
||||
for (const {neighbor} of this.graph.neighborhood(this.nodeAddress, {
|
||||
url(): string {
|
||||
return this.payload().url;
|
||||
}
|
||||
}
|
||||
|
||||
export class RepositoryReference extends GithubReference<
|
||||
RepositoryNodePayload
|
||||
> {
|
||||
constructor(ref: NodeReference<any>) {
|
||||
super(ref);
|
||||
assertAddressType(ref.address(), REPOSITORY_NODE_TYPE);
|
||||
}
|
||||
|
||||
issueByNumber(number: number): ?IssueReference {
|
||||
const neighbors = this.neighbors({
|
||||
edgeType: CONTAINS_EDGE_TYPE,
|
||||
direction: "OUT",
|
||||
nodeType: ISSUE_NODE_TYPE,
|
||||
})) {
|
||||
const node = this.graph.node(neighbor);
|
||||
if (node.payload.number === number) {
|
||||
return new Issue(this.graph, neighbor);
|
||||
});
|
||||
for (const {ref} of neighbors) {
|
||||
const issueRef = new IssueReference(ref);
|
||||
const node = issueRef.get();
|
||||
if (node != null && node.number() === number) {
|
||||
return issueRef;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pullRequestByNumber(number: number): ?PullRequest {
|
||||
for (const {neighbor} of this.graph.neighborhood(this.nodeAddress, {
|
||||
pullRequestByNumber(number: number): ?PullRequestReference {
|
||||
const neighbors = this.neighbors({
|
||||
edgeType: CONTAINS_EDGE_TYPE,
|
||||
direction: "OUT",
|
||||
nodeType: PULL_REQUEST_NODE_TYPE,
|
||||
})) {
|
||||
const node = this.graph.node(neighbor);
|
||||
if (node.payload.number === number) {
|
||||
return new PullRequest(this.graph, neighbor);
|
||||
});
|
||||
for (const {ref} of neighbors) {
|
||||
const pullRequest = new PullRequestReference(ref);
|
||||
const node = pullRequest.get();
|
||||
if (node != null && node.number() === number) {
|
||||
return pullRequest;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
owner(): string {
|
||||
return this.node().payload.owner;
|
||||
issues(): IssueReference[] {
|
||||
return this.neighbors({
|
||||
edgeType: CONTAINS_EDGE_TYPE,
|
||||
direction: "OUT",
|
||||
nodeType: ISSUE_NODE_TYPE,
|
||||
}).map(({ref}) => new IssueReference(ref));
|
||||
}
|
||||
|
||||
name(): string {
|
||||
return this.node().payload.name;
|
||||
pullRequests(): PullRequestReference[] {
|
||||
return this.neighbors({
|
||||
edgeType: CONTAINS_EDGE_TYPE,
|
||||
direction: "OUT",
|
||||
nodeType: PULL_REQUEST_NODE_TYPE,
|
||||
}).map(({ref}) => new PullRequestReference(ref));
|
||||
}
|
||||
|
||||
issues(): Issue[] {
|
||||
return this.graph
|
||||
.neighborhood(this.nodeAddress, {
|
||||
direction: "OUT",
|
||||
edgeType: CONTAINS_EDGE_TYPE,
|
||||
nodeType: ISSUE_NODE_TYPE,
|
||||
})
|
||||
.map(({neighbor}) => new Issue(this.graph, neighbor));
|
||||
}
|
||||
|
||||
pullRequests(): PullRequest[] {
|
||||
return this.graph
|
||||
.neighborhood(this.nodeAddress, {
|
||||
direction: "OUT",
|
||||
edgeType: CONTAINS_EDGE_TYPE,
|
||||
nodeType: PULL_REQUEST_NODE_TYPE,
|
||||
})
|
||||
.map(({neighbor}) => new PullRequest(this.graph, neighbor));
|
||||
get(): ?RepositoryPorcelain {
|
||||
const nodePorcelain = super.get();
|
||||
if (nodePorcelain != null) {
|
||||
return new RepositoryPorcelain(nodePorcelain);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class Post<
|
||||
export class RepositoryPorcelain extends GithubPorcelain<
|
||||
RepositoryNodePayload
|
||||
> {
|
||||
constructor(nodePorcelain: NodePorcelain<any>) {
|
||||
assertAddressType(nodePorcelain.ref().address(), REPOSITORY_NODE_TYPE);
|
||||
super(nodePorcelain);
|
||||
}
|
||||
|
||||
owner(): string {
|
||||
return this.payload().owner;
|
||||
}
|
||||
|
||||
name(): string {
|
||||
return this.payload().name;
|
||||
}
|
||||
|
||||
ref(): RepositoryReference {
|
||||
return new RepositoryReference(super.ref());
|
||||
}
|
||||
}
|
||||
|
||||
class PostReference<
|
||||
T:
|
||||
| IssueNodePayload
|
||||
| PullRequestNodePayload
|
||||
| CommentNodePayload
|
||||
| PullRequestReviewNodePayload
|
||||
| PullRequestReviewCommentNodePayload
|
||||
> extends GithubEntity<T> {
|
||||
authors(): Author[] {
|
||||
return this.graph
|
||||
.neighborhood(this.nodeAddress, {
|
||||
edgeType: AUTHORS_EDGE_TYPE,
|
||||
nodeType: AUTHOR_NODE_TYPE,
|
||||
})
|
||||
.map(({neighbor}) => new Author(this.graph, neighbor));
|
||||
> extends GithubReference<T> {
|
||||
authors(): AuthorReference[] {
|
||||
return this.neighbors({
|
||||
edgeType: AUTHORS_EDGE_TYPE,
|
||||
nodeType: AUTHOR_NODE_TYPE,
|
||||
}).map(({ref}) => new AuthorReference(ref));
|
||||
}
|
||||
|
||||
references(): GithubReference<NodePayload>[] {
|
||||
return this.neighbors({
|
||||
edgeType: REFERENCES_EDGE_TYPE,
|
||||
direction: "OUT",
|
||||
}).map(({ref}) => asGithubReference(ref));
|
||||
}
|
||||
}
|
||||
|
||||
class PostPorcelain<
|
||||
T:
|
||||
| IssueNodePayload
|
||||
| PullRequestNodePayload
|
||||
| CommentNodePayload
|
||||
| PullRequestReviewNodePayload
|
||||
| PullRequestReviewCommentNodePayload
|
||||
> extends GithubPorcelain<T> {
|
||||
body(): string {
|
||||
return this.node().payload.body;
|
||||
}
|
||||
|
||||
references(): Entity[] {
|
||||
return this.graph
|
||||
.neighborhood(this.nodeAddress, {
|
||||
edgeType: REFERENCES_EDGE_TYPE,
|
||||
direction: "OUT",
|
||||
})
|
||||
.map(({neighbor}) => asEntity(this.graph, neighbor));
|
||||
return this.payload().body;
|
||||
}
|
||||
}
|
||||
|
||||
class Commentable<T: IssueNodePayload | PullRequestNodePayload> extends Post<
|
||||
T
|
||||
> {
|
||||
comments(): Comment[] {
|
||||
return this.graph
|
||||
.neighborhood(this.nodeAddress, {
|
||||
edgeType: CONTAINS_EDGE_TYPE,
|
||||
nodeType: COMMENT_NODE_TYPE,
|
||||
})
|
||||
.map(({neighbor}) => new Comment(this.graph, neighbor));
|
||||
class CommentableReference<
|
||||
T: IssueNodePayload | PullRequestNodePayload
|
||||
> extends PostReference<T> {
|
||||
comments(): CommentReference[] {
|
||||
return this.neighbors({
|
||||
edgeType: CONTAINS_EDGE_TYPE,
|
||||
nodeType: COMMENT_NODE_TYPE,
|
||||
direction: "OUT",
|
||||
}).map(({ref}) => new CommentReference(ref));
|
||||
}
|
||||
}
|
||||
|
||||
export class Author extends GithubEntity<AuthorNodePayload> {
|
||||
static from(e: Entity): Author {
|
||||
assertEntityType(e, AUTHOR_NODE_TYPE);
|
||||
return (e: any);
|
||||
export class AuthorReference extends GithubReference<AuthorNodePayload> {
|
||||
constructor(ref: NodeReference<any>) {
|
||||
super(ref);
|
||||
assertAddressType(ref.address(), AUTHOR_NODE_TYPE);
|
||||
}
|
||||
|
||||
get(): ?AuthorPorcelain {
|
||||
const nodePorcelain = super.get();
|
||||
if (nodePorcelain != null) {
|
||||
return new AuthorPorcelain(nodePorcelain);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export class AuthorPorcelain extends GithubPorcelain<AuthorNodePayload> {
|
||||
constructor(nodePorcelain: NodePorcelain<any>) {
|
||||
assertAddressType(nodePorcelain.ref().address(), AUTHOR_NODE_TYPE);
|
||||
super(nodePorcelain);
|
||||
}
|
||||
login(): string {
|
||||
return this.node().payload.login;
|
||||
return this.payload().login;
|
||||
}
|
||||
|
||||
subtype(): AuthorSubtype {
|
||||
return this.node().payload.subtype;
|
||||
return this.payload().subtype;
|
||||
}
|
||||
|
||||
ref(): AuthorReference {
|
||||
return new AuthorReference(super.ref());
|
||||
}
|
||||
}
|
||||
|
||||
export class PullRequest extends Commentable<PullRequestNodePayload> {
|
||||
static from(e: Entity): PullRequest {
|
||||
assertEntityType(e, PULL_REQUEST_NODE_TYPE);
|
||||
return (e: any);
|
||||
export class PullRequestReference extends CommentableReference<
|
||||
PullRequestNodePayload
|
||||
> {
|
||||
constructor(ref: NodeReference<any>) {
|
||||
super(ref);
|
||||
assertAddressType(ref.address(), PULL_REQUEST_NODE_TYPE);
|
||||
}
|
||||
|
||||
number(): number {
|
||||
return this.node().payload.number;
|
||||
}
|
||||
|
||||
title(): string {
|
||||
return this.node().payload.title;
|
||||
}
|
||||
|
||||
reviews(): PullRequestReview[] {
|
||||
return this.graph
|
||||
.neighborhood(this.nodeAddress, {
|
||||
edgeType: CONTAINS_EDGE_TYPE,
|
||||
nodeType: PULL_REQUEST_REVIEW_NODE_TYPE,
|
||||
})
|
||||
.map(({neighbor}) => new PullRequestReview(this.graph, neighbor));
|
||||
}
|
||||
|
||||
parent(): Repository {
|
||||
parent(): RepositoryReference {
|
||||
return (_parent(this): any);
|
||||
}
|
||||
|
||||
reviews(): PullRequestReviewReference[] {
|
||||
return this.neighbors({
|
||||
edgeType: CONTAINS_EDGE_TYPE,
|
||||
nodeType: PULL_REQUEST_REVIEW_NODE_TYPE,
|
||||
direction: "OUT",
|
||||
}).map(({ref}) => new PullRequestReviewReference(ref));
|
||||
}
|
||||
|
||||
mergeCommitHash(): ?string {
|
||||
const mergeEdge = this.graph
|
||||
.neighborhood(this.nodeAddress, {
|
||||
edgeType: MERGED_AS_EDGE_TYPE,
|
||||
nodeType: COMMIT_NODE_TYPE,
|
||||
direction: "OUT",
|
||||
})
|
||||
.map(({edge}) => edge);
|
||||
const mergeEdge = this.neighbors({
|
||||
edgeType: MERGED_AS_EDGE_TYPE,
|
||||
nodeType: COMMIT_NODE_TYPE,
|
||||
direction: "OUT",
|
||||
}).map(({edge}) => edge);
|
||||
if (mergeEdge.length > 1) {
|
||||
throw new Error(
|
||||
`Node at ${this.nodeAddress.id} has too many MERGED_AS edges`
|
||||
`Node at ${stringify(this.address())} has too many MERGED_AS edges`
|
||||
);
|
||||
}
|
||||
if (mergeEdge.length === 0) {
|
||||
|
@ -333,81 +369,188 @@ export class PullRequest extends Commentable<PullRequestNodePayload> {
|
|||
const payload: MergedAsEdgePayload = (mergeEdge[0].payload: any);
|
||||
return payload.hash;
|
||||
}
|
||||
|
||||
get(): ?PullRequestPorcelain {
|
||||
const nodePorcelain = super.get();
|
||||
if (nodePorcelain != null) {
|
||||
return new PullRequestPorcelain(nodePorcelain);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export class Issue extends Commentable<IssueNodePayload> {
|
||||
static from(e: Entity): Issue {
|
||||
assertEntityType(e, ISSUE_NODE_TYPE);
|
||||
return (e: any);
|
||||
export class PullRequestPorcelain extends PostPorcelain<
|
||||
PullRequestNodePayload
|
||||
> {
|
||||
constructor(nodePorcelain: NodePorcelain<any>) {
|
||||
assertAddressType(nodePorcelain.ref().address(), PULL_REQUEST_NODE_TYPE);
|
||||
super(nodePorcelain);
|
||||
}
|
||||
|
||||
number(): number {
|
||||
return this.node().payload.number;
|
||||
return this.payload().number;
|
||||
}
|
||||
|
||||
title(): string {
|
||||
return this.node().payload.title;
|
||||
return this.payload().title;
|
||||
}
|
||||
|
||||
parent(): Repository {
|
||||
return (_parent(this): any);
|
||||
ref(): PullRequestReference {
|
||||
return new PullRequestReference(super.ref());
|
||||
}
|
||||
}
|
||||
|
||||
export class Comment extends Post<CommentNodePayload> {
|
||||
static from(e: Entity): Comment {
|
||||
assertEntityType(e, COMMENT_NODE_TYPE);
|
||||
return (e: any);
|
||||
export class IssueReference extends CommentableReference<IssueNodePayload> {
|
||||
constructor(ref: NodeReference<any>) {
|
||||
super(ref);
|
||||
assertAddressType(ref.address(), ISSUE_NODE_TYPE);
|
||||
}
|
||||
|
||||
parent(): Issue | PullRequest {
|
||||
parent(): RepositoryReference {
|
||||
return (_parent(this): any);
|
||||
}
|
||||
|
||||
get(): ?IssuePorcelain {
|
||||
const nodePorcelain = super.get();
|
||||
if (nodePorcelain != null) {
|
||||
return new IssuePorcelain(nodePorcelain);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export class PullRequestReview extends Post<PullRequestReviewNodePayload> {
|
||||
static from(e: Entity): PullRequestReview {
|
||||
assertEntityType(e, PULL_REQUEST_REVIEW_NODE_TYPE);
|
||||
return (e: any);
|
||||
export class IssuePorcelain extends PostPorcelain<IssueNodePayload> {
|
||||
constructor(nodePorcelain: NodePorcelain<any>) {
|
||||
assertAddressType(nodePorcelain.ref().address(), ISSUE_NODE_TYPE);
|
||||
super(nodePorcelain);
|
||||
}
|
||||
number(): number {
|
||||
return this.payload().number;
|
||||
}
|
||||
|
||||
title(): string {
|
||||
return this.payload().title;
|
||||
}
|
||||
|
||||
ref(): IssueReference {
|
||||
return new IssueReference(super.ref());
|
||||
}
|
||||
}
|
||||
|
||||
export class CommentReference extends PostReference<CommentNodePayload> {
|
||||
constructor(ref: NodeReference<any>) {
|
||||
super(ref);
|
||||
assertAddressType(ref.address(), COMMENT_NODE_TYPE);
|
||||
}
|
||||
|
||||
parent(): IssueReference | PullRequestReference {
|
||||
return (_parent(this): any);
|
||||
}
|
||||
|
||||
get(): ?CommentPorcelain {
|
||||
const nodePorcelain = super.get();
|
||||
if (nodePorcelain != null) {
|
||||
return new CommentPorcelain(nodePorcelain);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export class CommentPorcelain extends PostPorcelain<CommentNodePayload> {
|
||||
constructor(nodePorcelain: NodePorcelain<any>) {
|
||||
assertAddressType(nodePorcelain.ref().address(), COMMENT_NODE_TYPE);
|
||||
super(nodePorcelain);
|
||||
}
|
||||
ref(): CommentReference {
|
||||
return new CommentReference(super.ref());
|
||||
}
|
||||
}
|
||||
|
||||
export class PullRequestReviewReference extends PostReference<
|
||||
PullRequestReviewNodePayload
|
||||
> {
|
||||
constructor(ref: NodeReference<any>) {
|
||||
super(ref);
|
||||
assertAddressType(ref.address(), PULL_REQUEST_REVIEW_NODE_TYPE);
|
||||
}
|
||||
|
||||
parent(): PullRequestReference {
|
||||
return (_parent(this): any);
|
||||
}
|
||||
|
||||
comments(): PullRequestReviewCommentReference[] {
|
||||
return this.neighbors({
|
||||
edgeType: CONTAINS_EDGE_TYPE,
|
||||
nodeType: PULL_REQUEST_REVIEW_COMMENT_NODE_TYPE,
|
||||
direction: "OUT",
|
||||
}).map(({ref}) => new PullRequestReviewCommentReference(ref));
|
||||
}
|
||||
|
||||
get(): ?PullRequestReviewPorcelain {
|
||||
const nodePorcelain = super.get();
|
||||
if (nodePorcelain != null) {
|
||||
return new PullRequestReviewPorcelain(nodePorcelain);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export class PullRequestReviewPorcelain extends PostPorcelain<
|
||||
PullRequestReviewNodePayload
|
||||
> {
|
||||
constructor(nodePorcelain: NodePorcelain<any>) {
|
||||
assertAddressType(
|
||||
nodePorcelain.ref().address(),
|
||||
PULL_REQUEST_REVIEW_NODE_TYPE
|
||||
);
|
||||
super(nodePorcelain);
|
||||
}
|
||||
|
||||
state(): PullRequestReviewState {
|
||||
return this.node().payload.state;
|
||||
return this.payload().state;
|
||||
}
|
||||
|
||||
parent(): PullRequest {
|
||||
return (_parent(this): any);
|
||||
}
|
||||
|
||||
comments(): PullRequestReviewComment[] {
|
||||
return this.graph
|
||||
.neighborhood(this.nodeAddress, {
|
||||
edgeType: CONTAINS_EDGE_TYPE,
|
||||
nodeType: PULL_REQUEST_REVIEW_COMMENT_NODE_TYPE,
|
||||
})
|
||||
.map(({neighbor}) => new PullRequestReviewComment(this.graph, neighbor));
|
||||
ref(): PullRequestReviewReference {
|
||||
return new PullRequestReviewReference(super.ref());
|
||||
}
|
||||
}
|
||||
|
||||
export class PullRequestReviewComment extends Post<
|
||||
export class PullRequestReviewCommentReference extends PostReference<
|
||||
PullRequestReviewCommentNodePayload
|
||||
> {
|
||||
static from(e: Entity): PullRequestReviewComment {
|
||||
assertEntityType(e, PULL_REQUEST_REVIEW_COMMENT_NODE_TYPE);
|
||||
return (e: any);
|
||||
constructor(ref: NodeReference<any>) {
|
||||
super(ref);
|
||||
assertAddressType(ref.address(), PULL_REQUEST_REVIEW_COMMENT_NODE_TYPE);
|
||||
}
|
||||
parent(): PullRequestReview {
|
||||
|
||||
parent(): PullRequestReviewReference {
|
||||
return (_parent(this): any);
|
||||
}
|
||||
|
||||
get(): ?PullRequestReviewCommentPorcelain {
|
||||
const nodePorcelain = super.get();
|
||||
if (nodePorcelain != null) {
|
||||
return new PullRequestReviewCommentPorcelain(nodePorcelain);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function _parent(x: Entity): Entity {
|
||||
const parents = x.graph.neighborhood(x.address(), {
|
||||
edgeType: "CONTAINS",
|
||||
direction: "IN",
|
||||
});
|
||||
export class PullRequestReviewCommentPorcelain extends PostPorcelain<
|
||||
PullRequestReviewCommentNodePayload
|
||||
> {
|
||||
constructor(nodePorcelain: NodePorcelain<any>) {
|
||||
assertAddressType(
|
||||
nodePorcelain.ref().address(),
|
||||
PULL_REQUEST_REVIEW_COMMENT_NODE_TYPE
|
||||
);
|
||||
super(nodePorcelain);
|
||||
}
|
||||
ref(): PullRequestReviewCommentReference {
|
||||
return new PullRequestReviewCommentReference(super.ref());
|
||||
}
|
||||
}
|
||||
|
||||
function _parent(
|
||||
x: GithubReference<NodePayload>
|
||||
): GithubReference<NodePayload> {
|
||||
const parents = x.neighbors({edgeType: CONTAINS_EDGE_TYPE, direction: "IN"});
|
||||
if (parents.length !== 1) {
|
||||
throw new Error(`Bad parent relationships for ${stringify(x.address())}`);
|
||||
}
|
||||
return asEntity(x.graph, parents[0].neighbor);
|
||||
return asGithubReference(parents[0].ref);
|
||||
}
|
||||
|
|
|
@ -1,20 +1,26 @@
|
|||
// @flow
|
||||
|
||||
import type {Address} from "../../core/address";
|
||||
import {parse} from "./parser";
|
||||
import exampleRepoData from "./demoData/example-github.json";
|
||||
import type {Entity} from "./porcelain";
|
||||
import {
|
||||
asEntity,
|
||||
Porcelain,
|
||||
Repository,
|
||||
Issue,
|
||||
PullRequest,
|
||||
PullRequestReview,
|
||||
PullRequestReviewComment,
|
||||
Comment,
|
||||
Author,
|
||||
AuthorReference,
|
||||
AuthorPorcelain,
|
||||
CommentReference,
|
||||
CommentPorcelain,
|
||||
GithubReference,
|
||||
GraphPorcelain,
|
||||
IssueReference,
|
||||
IssuePorcelain,
|
||||
PullRequestReference,
|
||||
PullRequestPorcelain,
|
||||
PullRequestReviewReference,
|
||||
PullRequestReviewPorcelain,
|
||||
PullRequestReviewCommentReference,
|
||||
PullRequestReviewCommentPorcelain,
|
||||
RepositoryReference,
|
||||
RepositoryPorcelain,
|
||||
} from "./porcelain";
|
||||
import type {NodePayload} from "./types";
|
||||
import {
|
||||
AUTHOR_NODE_TYPE,
|
||||
COMMENT_NODE_TYPE,
|
||||
|
@ -26,14 +32,19 @@ import {
|
|||
|
||||
import {nodeDescription} from "./render";
|
||||
|
||||
import {PLUGIN_NAME} from "./pluginName";
|
||||
|
||||
describe("GitHub porcelain", () => {
|
||||
const graph = parse(exampleRepoData);
|
||||
const porcelain = new Porcelain(graph);
|
||||
const repo = porcelain.repository("sourcecred", "example-github");
|
||||
const porcelain = new GraphPorcelain(graph);
|
||||
const repoRef = porcelain.repository("sourcecred", "example-github");
|
||||
if (repoRef == null) {
|
||||
throw new Error("Where did the repository go?");
|
||||
}
|
||||
const repo = repoRef.get();
|
||||
if (repo == null) {
|
||||
throw new Error("Where did the repository go?");
|
||||
}
|
||||
|
||||
function expectPropertiesToMatchSnapshot<T: Entity>(
|
||||
function expectPropertiesToMatchSnapshot<T: {+url: () => string}>(
|
||||
entities: $ReadOnlyArray<T>,
|
||||
extractor: (T) => mixed
|
||||
) {
|
||||
|
@ -47,36 +58,75 @@ describe("GitHub porcelain", () => {
|
|||
expect(urlToProperty).toMatchSnapshot();
|
||||
}
|
||||
|
||||
function issueByNumber(n: number): Issue {
|
||||
const result = repo.issueByNumber(n);
|
||||
function issueByNumber(n: number): IssuePorcelain {
|
||||
const ref = repo.ref().issueByNumber(n);
|
||||
if (ref == null) {
|
||||
throw new Error(`Expected issue #${n} to exist`);
|
||||
}
|
||||
const result = ref.get();
|
||||
if (result == null) {
|
||||
throw new Error(`Expected Issue #${n} to exist`);
|
||||
throw new Error(`Expected issue #${n} to exist`);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
function prByNumber(n: number): PullRequest {
|
||||
const result = repo.pullRequestByNumber(n);
|
||||
function prByNumber(n: number): PullRequestPorcelain {
|
||||
const ref = repo.ref().pullRequestByNumber(n);
|
||||
if (ref == null) {
|
||||
throw new Error(`Expected pull request #${n} to exist`);
|
||||
}
|
||||
const result = ref.get();
|
||||
if (result == null) {
|
||||
throw new Error(`Expected PR #${n} to exist`);
|
||||
throw new Error(`Expected pull request #${n} to exist`);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
function issueOrPrByNumber(n: number): Issue | PullRequest {
|
||||
const result = repo.issueByNumber(n) || repo.pullRequestByNumber(n);
|
||||
function issueOrPrByNumber(n: number): IssuePorcelain | PullRequestPorcelain {
|
||||
const ref =
|
||||
repo.ref().issueByNumber(n) || repo.ref().pullRequestByNumber(n);
|
||||
if (ref == null) {
|
||||
throw new Error(`Expected Issue/PR #${n} to exist`);
|
||||
}
|
||||
const result = ref.get();
|
||||
if (result == null) {
|
||||
throw new Error(`Expected Issue/PR #${n} to exist`);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
const issue = issueByNumber(2);
|
||||
const comment = issue.comments()[0];
|
||||
const pullRequest = prByNumber(5);
|
||||
const pullRequestReview = pullRequest.reviews()[0];
|
||||
const pullRequestReviewComment = pullRequestReview.comments()[0];
|
||||
const author = issue.authors()[0];
|
||||
function really<T>(x: ?T): T {
|
||||
if (x == null) {
|
||||
throw new Error(String(x));
|
||||
}
|
||||
return x;
|
||||
}
|
||||
const issue = really(issueByNumber(2));
|
||||
const comment = really(
|
||||
issue
|
||||
.ref()
|
||||
.comments()[0]
|
||||
.get()
|
||||
);
|
||||
const pullRequest = really(prByNumber(5));
|
||||
const pullRequestReview = really(
|
||||
pullRequest
|
||||
.ref()
|
||||
.reviews()[0]
|
||||
.get()
|
||||
);
|
||||
const pullRequestReviewComment = really(
|
||||
pullRequestReview
|
||||
.ref()
|
||||
.comments()[0]
|
||||
.get()
|
||||
);
|
||||
const author = really(
|
||||
issue
|
||||
.ref()
|
||||
.authors()[0]
|
||||
.get()
|
||||
);
|
||||
const allWrappers = [
|
||||
issue,
|
||||
pullRequest,
|
||||
|
@ -87,62 +137,43 @@ describe("GitHub porcelain", () => {
|
|||
];
|
||||
|
||||
it("all wrappers provide a type() method", () => {
|
||||
expectPropertiesToMatchSnapshot(allWrappers, (e) => e.type());
|
||||
expectPropertiesToMatchSnapshot(allWrappers, (e) => e.ref().type());
|
||||
});
|
||||
|
||||
it("all wrappers provide a url() method", () => {
|
||||
expectPropertiesToMatchSnapshot(allWrappers, (e) => e.url());
|
||||
});
|
||||
|
||||
it("all wrappers provide an address() method", () => {
|
||||
allWrappers.forEach((w) => {
|
||||
const addr = w.address();
|
||||
const url = w.url();
|
||||
const type = w.type();
|
||||
expect(addr.id).toBe(url);
|
||||
expect(addr.type).toBe(type);
|
||||
expect(addr.pluginName).toBe(PLUGIN_NAME);
|
||||
});
|
||||
test("reference constructors throw errors when used incorrectly", () => {
|
||||
expect(() => new RepositoryReference(issue.ref())).toThrowError(
|
||||
"to have type"
|
||||
);
|
||||
expect(() => new IssueReference(repo.ref())).toThrowError("to have type");
|
||||
expect(() => new CommentReference(repo.ref())).toThrowError("to have type");
|
||||
expect(() => new PullRequestReference(repo.ref())).toThrowError(
|
||||
"to have type"
|
||||
);
|
||||
expect(() => new PullRequestReviewReference(repo.ref())).toThrowError(
|
||||
"to have type"
|
||||
);
|
||||
expect(
|
||||
() => new PullRequestReviewCommentReference(repo.ref())
|
||||
).toThrowError("to have type");
|
||||
expect(() => new AuthorReference(repo.ref())).toThrowError("to have type");
|
||||
});
|
||||
|
||||
it("all wrappers provide a node() method", () => {
|
||||
allWrappers.forEach((w) => {
|
||||
const node = w.node();
|
||||
const addr = w.address();
|
||||
expect(node.address).toEqual(addr);
|
||||
});
|
||||
});
|
||||
|
||||
describe("type verifiers", () => {
|
||||
it("are provided by all wrappers", () => {
|
||||
// Check each one individually to verify the flowtypes
|
||||
const _unused_repo: Repository = Repository.from(repo);
|
||||
const _unused_issue: Issue = Issue.from(issue);
|
||||
const _unused_pullRequest: PullRequest = PullRequest.from(pullRequest);
|
||||
const _unused_comment: Comment = Comment.from(comment);
|
||||
const _unused_pullRequestReview: PullRequestReview = PullRequestReview.from(
|
||||
pullRequestReview
|
||||
);
|
||||
const _unused_pullRequestReviewComment: PullRequestReviewComment = PullRequestReviewComment.from(
|
||||
pullRequestReviewComment
|
||||
);
|
||||
const _unused_author: Author = Author.from(author);
|
||||
// Check them programatically so that if we add another wrapper, we can't forget to update.
|
||||
allWrappers.forEach((e) => {
|
||||
expect(e.constructor.from(e)).toEqual(e);
|
||||
});
|
||||
});
|
||||
it("and errors are thrown when used incorrectly", () => {
|
||||
expect(() => Repository.from(issue)).toThrowError("to have type");
|
||||
expect(() => Issue.from(repo)).toThrowError("to have type");
|
||||
expect(() => Comment.from(repo)).toThrowError("to have type");
|
||||
expect(() => PullRequest.from(repo)).toThrowError("to have type");
|
||||
expect(() => PullRequestReview.from(repo)).toThrowError("to have type");
|
||||
expect(() => PullRequestReviewComment.from(repo)).toThrowError(
|
||||
"to have type"
|
||||
);
|
||||
expect(() => Author.from(repo)).toThrowError("to have type");
|
||||
});
|
||||
test("porcelain constructors throw errors when used incorrectly", () => {
|
||||
expect(() => new RepositoryPorcelain(issue)).toThrowError("to have type");
|
||||
expect(() => new IssuePorcelain(repo)).toThrowError("to have type");
|
||||
expect(() => new CommentPorcelain(repo)).toThrowError("to have type");
|
||||
expect(() => new PullRequestPorcelain(repo)).toThrowError("to have type");
|
||||
expect(() => new PullRequestReviewPorcelain(repo)).toThrowError(
|
||||
"to have type"
|
||||
);
|
||||
expect(() => new PullRequestReviewCommentPorcelain(repo)).toThrowError(
|
||||
"to have type"
|
||||
);
|
||||
expect(() => new AuthorPorcelain(repo)).toThrowError("to have type");
|
||||
});
|
||||
|
||||
describe("posts", () => {
|
||||
|
@ -154,19 +185,32 @@ describe("GitHub porcelain", () => {
|
|||
comment,
|
||||
];
|
||||
it("have parents", () => {
|
||||
expectPropertiesToMatchSnapshot(allPosts, (e) => e.parent().url());
|
||||
expectPropertiesToMatchSnapshot(allPosts, (e) =>
|
||||
really(
|
||||
e
|
||||
.ref()
|
||||
.parent()
|
||||
.get()
|
||||
).url()
|
||||
);
|
||||
});
|
||||
it("have bodies", () => {
|
||||
expectPropertiesToMatchSnapshot(allPosts, (e) => e.body());
|
||||
});
|
||||
it("have authors", () => {
|
||||
expectPropertiesToMatchSnapshot(allPosts, (e) =>
|
||||
e.authors().map((a) => a.login())
|
||||
e
|
||||
.ref()
|
||||
.authors()
|
||||
.map((a) => really(a.get()).login())
|
||||
);
|
||||
});
|
||||
it("have references", () => {
|
||||
expectPropertiesToMatchSnapshot(allPosts, (e) =>
|
||||
e.references().map((r) => r.url())
|
||||
e
|
||||
.ref()
|
||||
.references()
|
||||
.map((r: GithubReference<NodePayload>) => really(r.get()).url())
|
||||
);
|
||||
});
|
||||
});
|
||||
|
@ -183,7 +227,10 @@ describe("GitHub porcelain", () => {
|
|||
});
|
||||
it("have comments", () => {
|
||||
expectPropertiesToMatchSnapshot(issuesAndPRs, (e) =>
|
||||
e.comments().map((c) => c.url())
|
||||
e
|
||||
.ref()
|
||||
.comments()
|
||||
.map((c) => really(c.get()).url())
|
||||
);
|
||||
});
|
||||
});
|
||||
|
@ -191,49 +238,36 @@ describe("GitHub porcelain", () => {
|
|||
describe("pull requests", () => {
|
||||
const prs = [prByNumber(3), prByNumber(5), prByNumber(9)];
|
||||
it("have mergeCommitHashes", () => {
|
||||
expectPropertiesToMatchSnapshot(prs, (e) => e.mergeCommitHash());
|
||||
expectPropertiesToMatchSnapshot(prs, (e) => e.ref().mergeCommitHash());
|
||||
});
|
||||
|
||||
it("have reviews", () => {
|
||||
expectPropertiesToMatchSnapshot(prs, (e) =>
|
||||
e.reviews().map((r) => r.url())
|
||||
e
|
||||
.ref()
|
||||
.reviews()
|
||||
.map((r) => really(r.get()).url())
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe("pull request reviews", () => {
|
||||
const reviews = pullRequest.reviews();
|
||||
const reviews = pullRequest.ref().reviews();
|
||||
it("have review comments", () => {
|
||||
expectPropertiesToMatchSnapshot(reviews, (e) =>
|
||||
e.comments().map((e) => e.url())
|
||||
expectPropertiesToMatchSnapshot(
|
||||
reviews.map((r) => really(r.get())),
|
||||
(e) =>
|
||||
e
|
||||
.ref()
|
||||
.comments()
|
||||
.map((e) => really(e.get()).url())
|
||||
);
|
||||
});
|
||||
it("have states", () => {
|
||||
expectPropertiesToMatchSnapshot(reviews, (e) => e.state());
|
||||
});
|
||||
});
|
||||
|
||||
describe("asEntity", () => {
|
||||
it("works for each wrapper", () => {
|
||||
allWrappers.forEach((w) => {
|
||||
expect(asEntity(w.graph, w.address())).toEqual(w);
|
||||
});
|
||||
});
|
||||
it("errors when given an address with the wrong plugin name", () => {
|
||||
const addr: Address = {
|
||||
pluginName: "the magnificent foo plugin",
|
||||
id: "who are you to ask an id of the magnificent foo plugin?",
|
||||
type: "ISSUE",
|
||||
};
|
||||
expect(() => asEntity(graph, addr)).toThrow("wrong plugin name");
|
||||
});
|
||||
it("errors when given an address with a bad node type", () => {
|
||||
const addr: Address = {
|
||||
pluginName: PLUGIN_NAME,
|
||||
id: "if you keep asking for my id you will make me angry",
|
||||
type: "the foo plugin's magnificence extends to many plugins",
|
||||
};
|
||||
expect(() => asEntity(graph, addr)).toThrow("invalid type");
|
||||
expectPropertiesToMatchSnapshot(
|
||||
reviews.map((r) => really(r.get())),
|
||||
(e) => e.state()
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
|
@ -252,22 +286,24 @@ describe("GitHub porcelain", () => {
|
|||
describe("References", () => {
|
||||
it("via #-number", () => {
|
||||
const srcIssue = issueByNumber(2);
|
||||
const references = srcIssue.references();
|
||||
const references = srcIssue.ref().references();
|
||||
expect(references).toHaveLength(1);
|
||||
// Note: this verifies that we are not counting in-references, as
|
||||
// https://github.com/sourcecred/example-github/issues/6#issuecomment-385223316
|
||||
// references #2.
|
||||
|
||||
const referenced = Issue.from(references[0]);
|
||||
const referenced = new IssuePorcelain(really(references[0].get()));
|
||||
expect(referenced.number()).toBe(1);
|
||||
});
|
||||
|
||||
describe("by exact url", () => {
|
||||
function expectCommentToHaveSingleReference({commentNumber, type, url}) {
|
||||
const comments = issueByNumber(2).comments();
|
||||
const comments = issueByNumber(2)
|
||||
.ref()
|
||||
.comments();
|
||||
const references = comments[commentNumber].references();
|
||||
expect(references).toHaveLength(1);
|
||||
expect(references[0].url()).toBe(url);
|
||||
expect(really(references[0].get()).url()).toBe(url);
|
||||
expect(references[0].type()).toBe(type);
|
||||
}
|
||||
|
||||
|
@ -324,6 +360,7 @@ describe("GitHub porcelain", () => {
|
|||
|
||||
it("to multiple entities", () => {
|
||||
const references = issueByNumber(2)
|
||||
.ref()
|
||||
.comments()[6]
|
||||
.references();
|
||||
expect(references).toHaveLength(5);
|
||||
|
@ -331,6 +368,7 @@ describe("GitHub porcelain", () => {
|
|||
|
||||
it("to no entities", () => {
|
||||
const references = issueByNumber(2)
|
||||
.ref()
|
||||
.comments()[7]
|
||||
.references();
|
||||
expect(references).toHaveLength(0);
|
||||
|
@ -339,10 +377,11 @@ describe("GitHub porcelain", () => {
|
|||
|
||||
it("References by @-author", () => {
|
||||
const pr = prByNumber(5);
|
||||
const references = pr.references();
|
||||
const references = pr.ref().references();
|
||||
expect(references).toHaveLength(1);
|
||||
const referenced = Author.from(references[0]);
|
||||
expect(referenced.login()).toBe("wchargin");
|
||||
const referenced = new AuthorReference(references[0]);
|
||||
const login = really(referenced.get()).login();
|
||||
expect(login).toBe("wchargin");
|
||||
});
|
||||
});
|
||||
|
||||
|
@ -352,7 +391,7 @@ describe("GitHub porcelain", () => {
|
|||
// file, and move this test to render.test.js (assuming we don't move the
|
||||
// description method into the porcelain anyway...)
|
||||
expectPropertiesToMatchSnapshot(allWrappers, (e) =>
|
||||
nodeDescription(e.graph, e.address())
|
||||
nodeDescription(e.ref())
|
||||
);
|
||||
});
|
||||
});
|
||||
|
|
|
@ -4,64 +4,82 @@
|
|||
* Methods for rendering and displaying GitHub nodes.
|
||||
*/
|
||||
import stringify from "json-stable-stringify";
|
||||
import {Graph} from "../../core/graph";
|
||||
import type {Address} from "../../core/address";
|
||||
import type {NodeReference} from "../../core/porcelain";
|
||||
import type {NodePayload} from "./types";
|
||||
import {
|
||||
asEntity,
|
||||
Issue,
|
||||
PullRequest,
|
||||
Comment,
|
||||
PullRequestReview,
|
||||
PullRequestReviewComment,
|
||||
Author,
|
||||
Repository,
|
||||
GithubReference,
|
||||
AuthorReference,
|
||||
AuthorPorcelain,
|
||||
CommentReference,
|
||||
IssueReference,
|
||||
IssuePorcelain,
|
||||
PullRequestReference,
|
||||
PullRequestPorcelain,
|
||||
PullRequestReviewReference,
|
||||
PullRequestReviewCommentReference,
|
||||
RepositoryPorcelain,
|
||||
} from "./porcelain";
|
||||
|
||||
/* Give a short description for the GitHub node at given address.
|
||||
* Useful for e.g. displaying a title.
|
||||
*/
|
||||
export function nodeDescription(graph: Graph<any, any>, addr: Address) {
|
||||
const entity = asEntity(graph, addr);
|
||||
const type = entity.type();
|
||||
export function nodeDescription(ref: NodeReference<NodePayload>) {
|
||||
const porcelain = ref.get();
|
||||
if (porcelain == null) {
|
||||
return `[unknown ${ref.type()}]`;
|
||||
}
|
||||
const type = new GithubReference(ref).type();
|
||||
switch (type) {
|
||||
case "REPOSITORY": {
|
||||
const repo = Repository.from(entity);
|
||||
const repo = new RepositoryPorcelain(porcelain);
|
||||
return `${repo.owner()}/${repo.name()}`;
|
||||
}
|
||||
case "ISSUE": {
|
||||
const issue = Issue.from(entity);
|
||||
const issue = new IssuePorcelain(porcelain);
|
||||
return `#${issue.number()}: ${issue.title()}`;
|
||||
}
|
||||
case "PULL_REQUEST": {
|
||||
const pr = PullRequest.from(entity);
|
||||
const pr = new PullRequestPorcelain(porcelain);
|
||||
return `#${pr.number()}: ${pr.title()}`;
|
||||
}
|
||||
case "COMMENT": {
|
||||
const comment = Comment.from(entity);
|
||||
const author = comment.authors()[0];
|
||||
return `comment by @${author.login()} on #${comment.parent().number()}`;
|
||||
const comment = new CommentReference(ref);
|
||||
const issue = comment.parent();
|
||||
return `comment by @${authors(comment)} on #${num(issue)}`;
|
||||
}
|
||||
case "PULL_REQUEST_REVIEW": {
|
||||
const review = PullRequestReview.from(entity);
|
||||
const author = review.authors()[0];
|
||||
return `review by @${author.login()} on #${review.parent().number()}`;
|
||||
const review = new PullRequestReviewReference(ref);
|
||||
const pr = review.parent();
|
||||
return `review by @${authors(review)} on #${num(pr)}`;
|
||||
}
|
||||
case "PULL_REQUEST_REVIEW_COMMENT": {
|
||||
const comment = PullRequestReviewComment.from(entity);
|
||||
const author = comment.authors()[0];
|
||||
const comment = new PullRequestReviewCommentReference(ref);
|
||||
const pr = comment.parent().parent();
|
||||
return `review comment by @${author.login()} on #${pr.number()}`;
|
||||
return `review comment by @${authors(comment)} on #${num(pr)}`;
|
||||
}
|
||||
case "AUTHOR": {
|
||||
const author = Author.from(entity);
|
||||
return `@${author.login()}`;
|
||||
return `@${new AuthorPorcelain(porcelain).login()}`;
|
||||
}
|
||||
default: {
|
||||
// eslint-disable-next-line no-unused-expressions
|
||||
(type: empty);
|
||||
throw new Error(
|
||||
`Tried to write description for invalid type ${stringify(addr)}`
|
||||
`Tried to write description for invalid type ${stringify(
|
||||
ref.address()
|
||||
)}`
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function num(x: IssueReference | PullRequestReference) {
|
||||
const np = x.get();
|
||||
return np == null ? "[unknown]" : np.number();
|
||||
}
|
||||
|
||||
function authors(authorable: {+authors: () => AuthorReference[]}) {
|
||||
// TODO: modify to accomodate multi-authorship
|
||||
const authorRefs = authorable.authors();
|
||||
const firstAuthor = authorRefs[0].get();
|
||||
return firstAuthor != null ? firstAuthor.login() : "[unknown]";
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue