Add flow types for GitHub graphql query response (#140)

This commit adds flow typing for the JSON result from hitting the GitHub
graphql api. We can't prove that the flow typing is correct, but since
the type definition is colocated with the corresponding fragment
definitions, we can hope that maintainers will maintain both together.

We update the parser to consume the new flow types. There are no flow
errors.

Test plan:
Inspect the flowtypes, verify that they correspond to the data in
example-repo.json, and that there are no flow errors.
This commit is contained in:
Dandelion Mané 2018-04-24 14:05:51 -07:00 committed by GitHub
parent 418b745d7c
commit 6a3e4d754c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 199 additions and 81 deletions

View File

@ -93,6 +93,22 @@ export type Continuation = {|
+destinationPath: $ReadOnlyArray<string | number>,
|};
export type ConnectionJSON<+T> = {|
+nodes: $ReadOnlyArray<T>,
+pageInfo: {|
+endCursor: ?string,
+hasNextPage: boolean,
|},
|};
export type RepositoryJSON = {|
+repository: {
+id: string,
+issues: ConnectionJSON<IssueJSON>,
+pullRequests: ConnectionJSON<PullRequestJSON>,
},
|};
/**
* The top-level GitHub query to request data about a repository.
* Callers will also be interested in `createVariables`.
@ -607,88 +623,179 @@ function mergeDirect<T>(destination: T, source: any): T {
}
}
export type AuthorJSON = {|
+__typename: "User" | "Bot" | "Organization",
+id: string,
+login: string,
+url: string,
|};
function makePageInfo() {
const b = build;
return b.field("pageInfo", {}, [
b.field("hasNextPage"),
b.field("endCursor"),
]);
}
function makeAuthor() {
const b = build;
return b.field("author", {}, [b.fragmentSpread("whoami")]);
}
function whoamiFragment(): FragmentDefinition {
const b = build;
return b.fragment("whoami", "Actor", [
b.field("__typename"),
b.field("login"),
b.field("url"),
b.inlineFragment("User", [b.field("id")]),
b.inlineFragment("Organization", [b.field("id")]),
b.inlineFragment("Bot", [b.field("id")]),
]);
}
export type IssueJSON = {|
+id: string,
+url: string,
+title: string,
+body: string,
+number: number,
+author: AuthorJSON,
+comments: ConnectionJSON<CommentJSON>,
|};
function issuesFragment(): FragmentDefinition {
const b = build;
return b.fragment("issues", "IssueConnection", [
makePageInfo(),
b.field("nodes", {}, [
b.field("id"),
b.field("url"),
b.field("title"),
b.field("body"),
b.field("number"),
makeAuthor(),
b.field("comments", {first: b.literal(PAGE_SIZE_COMMENTS)}, [
b.fragmentSpread("comments"),
]),
]),
]);
}
export type PullRequestJSON = {|
+id: string,
+url: string,
+title: string,
+body: string,
+number: number,
+author: AuthorJSON,
+comments: ConnectionJSON<CommentJSON>,
+reviews: ConnectionJSON<PullRequestReviewJSON>,
|};
function pullRequestsFragment(): FragmentDefinition {
const b = build;
return b.fragment("prs", "PullRequestConnection", [
makePageInfo(),
b.field("nodes", {}, [
b.field("id"),
b.field("url"),
b.field("title"),
b.field("body"),
b.field("number"),
makeAuthor(),
b.field("comments", {first: b.literal(PAGE_SIZE_COMMENTS)}, [
b.fragmentSpread("comments"),
]),
b.field("reviews", {first: b.literal(PAGE_SIZE_REVIEWS)}, [
b.fragmentSpread("reviews"),
]),
]),
]);
}
export type CommentJSON = {|
+id: string,
+url: string,
+body: string,
+author: AuthorJSON,
|};
function commentsFragment(): FragmentDefinition {
const b = build;
// (Note: issue comments and PR comments use the same connection type.)
return b.fragment("comments", "IssueCommentConnection", [
makePageInfo(),
b.field("nodes", {}, [
b.field("id"),
b.field("url"),
makeAuthor(),
b.field("body"),
]),
]);
}
export type PullRequestReviewState =
| "CHANGES_REQUESTED"
| "APPROVED"
| "COMMENTED"
| "DISMISSED"
| "PENDING";
export type PullRequestReviewJSON = {|
+id: string,
+url: string,
+body: string,
+author: AuthorJSON,
+state: PullRequestReviewState,
+comments: ConnectionJSON<PullRequestReviewCommentJSON>,
|};
function reviewsFragment(): FragmentDefinition {
const b = build;
return b.fragment("reviews", "PullRequestReviewConnection", [
makePageInfo(),
b.field("nodes", {}, [
b.field("id"),
b.field("url"),
b.field("body"),
makeAuthor(),
b.field("state"),
b.field("comments", {first: b.literal(PAGE_SIZE_REVIEW_COMMENTS)}, [
b.fragmentSpread("reviewComments"),
]),
]),
]);
}
export type PullRequestReviewCommentJSON = {|
+id: string,
+url: string,
+body: string,
+author: AuthorJSON,
|};
function reviewCommentsFragment(): FragmentDefinition {
const b = build;
return b.fragment("reviewComments", "PullRequestReviewCommentConnection", [
makePageInfo(),
b.field("nodes", {}, [
b.field("id"),
b.field("url"),
b.field("body"),
makeAuthor(),
]),
]);
}
/**
* These fragments are used to construct the root query, and also to
* fetch more pages of specific entity types.
*/
export function createFragments(): FragmentDefinition[] {
const b = build;
const makePageInfo = () =>
b.field("pageInfo", {}, [b.field("hasNextPage"), b.field("endCursor")]);
const makeAuthor = () => b.field("author", {}, [b.fragmentSpread("whoami")]);
return [
b.fragment("whoami", "Actor", [
b.field("__typename"),
b.field("login"),
b.field("url"),
b.inlineFragment("User", [b.field("id")]),
b.inlineFragment("Organization", [b.field("id")]),
b.inlineFragment("Bot", [b.field("id")]),
]),
b.fragment("issues", "IssueConnection", [
makePageInfo(),
b.field("nodes", {}, [
b.field("id"),
b.field("url"),
b.field("title"),
b.field("body"),
b.field("number"),
makeAuthor(),
b.field("comments", {first: b.literal(PAGE_SIZE_COMMENTS)}, [
b.fragmentSpread("comments"),
]),
]),
]),
b.fragment("prs", "PullRequestConnection", [
makePageInfo(),
b.field("nodes", {}, [
b.field("id"),
b.field("url"),
b.field("title"),
b.field("body"),
b.field("number"),
makeAuthor(),
b.field("comments", {first: b.literal(PAGE_SIZE_COMMENTS)}, [
b.fragmentSpread("comments"),
]),
b.field("reviews", {first: b.literal(PAGE_SIZE_REVIEWS)}, [
b.fragmentSpread("reviews"),
]),
]),
]),
// (Note: issue comments and PR comments use the same connection type.)
b.fragment("comments", "IssueCommentConnection", [
makePageInfo(),
b.field("nodes", {}, [
b.field("id"),
b.field("url"),
makeAuthor(),
b.field("body"),
b.field("url"),
]),
]),
b.fragment("reviews", "PullRequestReviewConnection", [
makePageInfo(),
b.field("nodes", {}, [
b.field("id"),
b.field("url"),
b.field("body"),
makeAuthor(),
b.field("state"),
b.field("comments", {first: b.literal(PAGE_SIZE_REVIEW_COMMENTS)}, [
b.fragmentSpread("reviewComments"),
]),
]),
]),
b.fragment("reviewComments", "PullRequestReviewCommentConnection", [
makePageInfo(),
b.field("nodes", {}, [
b.field("id"),
b.field("url"),
b.field("body"),
makeAuthor(),
]),
]),
whoamiFragment(),
issuesFragment(),
pullRequestsFragment(),
commentsFragment(),
reviewsFragment(),
reviewCommentsFragment(),
];
}

View File

@ -14,6 +14,17 @@ import type {
PullRequestNodePayload,
IssueNodePayload,
} from "./types";
import type {
RepositoryJSON,
PullRequestReviewJSON,
PullRequestJSON,
IssueJSON,
CommentJSON,
AuthorJSON,
PullRequestReviewCommentJSON,
} from "./graphql";
import type {Address} from "../../core/address";
import {PLUGIN_NAME} from "./pluginName";
import {Graph, edgeID} from "../../core/graph";
@ -54,7 +65,7 @@ export class GithubParser {
| PullRequestReviewCommentNodePayload
| PullRequestReviewNodePayload
>,
authorJson: *
authorJson: AuthorJSON
) {
let authorPayload: AuthorNodePayload = {
login: authorJson.login,
@ -102,7 +113,7 @@ export class GithubParser {
parentNode: Node<
IssueNodePayload | PullRequestNodePayload | PullRequestReviewNodePayload
>,
commentJson: *
commentJson: CommentJSON
) {
let commentType: NodeType;
switch (parentNode.address.type) {
@ -160,7 +171,7 @@ export class GithubParser {
this.graph.addEdge(containsEdge);
}
addIssue(issueJson: *) {
addIssue(issueJson: IssueJSON) {
const issuePayload: IssueNodePayload = {
url: issueJson.url,
number: issueJson.number,
@ -178,7 +189,7 @@ export class GithubParser {
issueJson.comments.nodes.forEach((c) => this.addComment(issueNode, c));
}
addPullRequest(prJson: *) {
addPullRequest(prJson: PullRequestJSON) {
const pullRequestPayload: PullRequestNodePayload = {
url: prJson.url,
number: prJson.number,
@ -201,7 +212,7 @@ export class GithubParser {
addPullRequestReview(
pullRequestNode: Node<PullRequestNodePayload>,
reviewJson: *
reviewJson: PullRequestReviewJSON
) {
const reviewPayload: PullRequestReviewNodePayload = {
url: reviewJson.url,
@ -218,7 +229,7 @@ export class GithubParser {
reviewJson.comments.nodes.forEach((c) => this.addComment(reviewNode, c));
}
addData(dataJson: *) {
addData(dataJson: RepositoryJSON) {
dataJson.repository.issues.nodes.forEach((i) => this.addIssue(i));
dataJson.repository.pullRequests.nodes.forEach((pr) =>
this.addPullRequest(pr)