github: add GraphQL schema and Flow types (#928)

Summary:
The included schema is forked from the one in `graphql/demo.js`.
Primitive types have been added, and the `parents` connection has been
added to commit objects per #920. (We do not include this in the demo
script because without prefetching it would take a long time to load.)

Test Plan:
Unit tests added; run `yarn unit`. Then run `yarn backend` and verify
that `node ./bin/generateGithubGraphqlFlowTypes.js` generates exactly
the same output as in the types file:

```
$ node ./bin/generateGithubGraphqlFlowTypes.js |
> diff -u - ./src/plugins/github/graphqlTypes.js
$ echo $?
0
```

Change the `graphqlTypes.js` file and verify that `yarn unit` fails.

As the build config has been changed, a `yarn test --full` is warranted.
It passes.

Finally, I have manually verified that the schema is consistent with the
documentation at <https://developer.github.com/v4/object/repository/>
and related pages.

wchargin-branch: github-schema-flow-types
This commit is contained in:
William Chargin 2018-10-19 09:04:54 -07:00 committed by GitHub
parent 04f7e9ea8c
commit 889febb7f6
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 364 additions and 0 deletions

View File

@ -29,6 +29,9 @@ module.exports = {
backendEntryPoints: { backendEntryPoints: {
sourcecred: resolveApp("src/cli/main.js"), sourcecred: resolveApp("src/cli/main.js"),
// //
generateGithubGraphqlFlowTypes: resolveApp(
"src/plugins/github/bin/generateGraphqlFlowTypes.js"
),
fetchAndPrintGithubRepo: resolveApp( fetchAndPrintGithubRepo: resolveApp(
"src/plugins/github/bin/fetchAndPrintGithubRepo.js" "src/plugins/github/bin/fetchAndPrintGithubRepo.js"
), ),

View File

@ -21,6 +21,12 @@ module.exports = {
bail: true, bail: true,
// Target Node instead of the browser. // Target Node instead of the browser.
target: "node", target: "node",
node: {
// Don't munge `__dirname` and `__filename`.
// https://github.com/webpack/webpack/issues/1599#issuecomment-186841345
__dirname: false,
__filename: false,
},
entry: paths.backendEntryPoints, entry: paths.backendEntryPoints,
externals: [nodeExternals()], externals: [nodeExternals()],
output: { output: {

View File

@ -0,0 +1,4 @@
// @flow
import generateGraphqlFlowTypes from "../generateGraphqlFlowTypes";
process.stdout.write(generateGraphqlFlowTypes());

View File

@ -0,0 +1,14 @@
// @flow
import prettier from "prettier";
import generateFlowTypes from "../../graphql/generateFlowTypes";
import schema from "./schema";
export default function generateGraphqlFlowTypes() {
const prettierOptions = {
...{parser: "babylon"},
...(prettier.resolveConfig.sync(__filename) || {}),
};
return generateFlowTypes(schema(), prettierOptions);
}

View File

@ -0,0 +1,168 @@
// @flow
// Autogenerated file. Do not edit.
export type Actor = Bot | Organization | User;
export type Blob = {|
+__typename: "Blob",
+id: string,
+oid: GitObjectID,
|};
export type Bot = {|
+__typename: "Bot",
+id: string,
+login: String,
+url: URI,
|};
export type Commit = {|
+__typename: "Commit",
+author: null | {|
+date: null | GitTimestamp,
+user: null | User,
|},
+id: string,
+message: String,
+oid: GitObjectID,
+parents: $ReadOnlyArray<null | Commit>,
+url: URI,
|};
export type DateTime = string;
export type GitObject = Blob | Commit | Tag | Tree;
export type GitObjectID = string;
export type GitTimestamp = string;
export type Int = number;
export type Issue = {|
+__typename: "Issue",
+author: null | Actor,
+body: String,
+comments: $ReadOnlyArray<null | IssueComment>,
+id: string,
+number: Int,
+reactions: $ReadOnlyArray<null | Reaction>,
+title: String,
+url: URI,
|};
export type IssueComment = {|
+__typename: "IssueComment",
+author: null | Actor,
+body: String,
+id: string,
+reactions: $ReadOnlyArray<null | Reaction>,
+url: URI,
|};
export type Organization = {|
+__typename: "Organization",
+id: string,
+login: String,
+url: URI,
|};
export type PullRequest = {|
+__typename: "PullRequest",
+additions: Int,
+author: null | Actor,
+body: String,
+comments: $ReadOnlyArray<null | IssueComment>,
+deletions: Int,
+id: string,
+mergeCommit: null | Commit,
+number: Int,
+reactions: $ReadOnlyArray<null | Reaction>,
+reviews: $ReadOnlyArray<null | PullRequestReview>,
+title: String,
+url: URI,
|};
export type PullRequestReview = {|
+__typename: "PullRequestReview",
+author: null | Actor,
+body: String,
+comments: $ReadOnlyArray<null | PullRequestReviewComment>,
+id: string,
+state: PullRequestReviewState,
+url: URI,
|};
export type PullRequestReviewComment = {|
+__typename: "PullRequestReviewComment",
+author: null | Actor,
+body: String,
+id: string,
+reactions: $ReadOnlyArray<null | Reaction>,
+url: URI,
|};
export type PullRequestReviewState =
| "APPROVED"
| "CHANGES_REQUESTED"
| "COMMENTED"
| "DISMISSED"
| "PENDING";
export type Reaction = {|
+__typename: "Reaction",
+content: ReactionContent,
+id: string,
+user: null | User,
|};
export type ReactionContent =
| "CONFUSED"
| "HEART"
| "HOORAY"
| "LAUGH"
| "THUMBS_DOWN"
| "THUMBS_UP";
export type Ref = {|
+__typename: "Ref",
+id: string,
+target: null | GitObject,
|};
export type Repository = {|
+__typename: "Repository",
+defaultBranchRef: null | Ref,
+id: string,
+issues: $ReadOnlyArray<null | Issue>,
+name: String,
+owner: null | RepositoryOwner,
+pullRequests: $ReadOnlyArray<null | PullRequest>,
+url: URI,
|};
export type RepositoryOwner = Organization | User;
export type String = string;
export type Tag = {|
+__typename: "Tag",
+id: string,
+oid: GitObjectID,
|};
export type Tree = {|
+__typename: "Tree",
+id: string,
+oid: GitObjectID,
|};
export type URI = string;
export type User = {|
+__typename: "User",
+id: string,
+login: String,
+url: URI,
|};

View File

@ -0,0 +1,18 @@
// @flow
import fs from "fs-extra";
import path from "path";
import generateGithubGraphqlFlowTypes from "./generateGraphqlFlowTypes";
describe("plugins/github/graphqlTypes", () => {
it("is up to date", async () => {
const typesFilename = path.join(__dirname, "graphqlTypes.js");
const actual = (await fs.readFile(typesFilename)).toString();
const expected = generateGithubGraphqlFlowTypes();
// If this fails, run `yarn backend` and then invoke
// node ./bin/generateGithubGraphqlFlowTypes.js
// saving the output to the types file listed above.
expect(actual).toEqual(expected);
});
});

View File

@ -0,0 +1,140 @@
// @flow
import * as Schema from "../../graphql/schema";
export default function schema(): Schema.Schema {
const s = Schema;
const types = {
DateTime: s.scalar("string"),
GitObjectID: s.scalar("string"),
GitTimestamp: s.scalar("string"),
Int: s.scalar("number"),
String: s.scalar("string"),
URI: s.scalar("string"),
PullRequestReviewState: s.enum([
"PENDING",
"COMMENTED",
"APPROVED",
"CHANGES_REQUESTED",
"DISMISSED",
]),
ReactionContent: s.enum([
"THUMBS_UP",
"THUMBS_DOWN",
"LAUGH",
"HOORAY",
"CONFUSED",
"HEART",
]),
Repository: s.object({
id: s.id(),
url: s.primitive(s.nonNull("URI")),
name: s.primitive(s.nonNull("String")),
owner: s.node("RepositoryOwner"),
issues: s.connection("Issue"),
pullRequests: s.connection("PullRequest"),
defaultBranchRef: s.node("Ref"),
}),
Issue: s.object({
id: s.id(),
url: s.primitive(s.nonNull("URI")),
title: s.primitive(s.nonNull("String")),
body: s.primitive(s.nonNull("String")),
number: s.primitive(s.nonNull("Int")),
author: s.node("Actor"),
comments: s.connection("IssueComment"),
reactions: s.connection("Reaction"),
}),
PullRequest: s.object({
id: s.id(),
url: s.primitive(s.nonNull("URI")),
title: s.primitive(s.nonNull("String")),
body: s.primitive(s.nonNull("String")),
number: s.primitive(s.nonNull("Int")),
mergeCommit: s.node("Commit"),
additions: s.primitive(s.nonNull("Int")),
deletions: s.primitive(s.nonNull("Int")),
author: s.node("Actor"),
comments: s.connection("IssueComment"), // yes, PRs have IssueComments
reviews: s.connection("PullRequestReview"),
reactions: s.connection("Reaction"),
}),
IssueComment: s.object({
id: s.id(),
url: s.primitive(s.nonNull("URI")),
body: s.primitive(s.nonNull("String")),
author: s.node("Actor"),
reactions: s.connection("Reaction"),
}),
PullRequestReview: s.object({
id: s.id(),
url: s.primitive(s.nonNull("URI")),
body: s.primitive(s.nonNull("String")),
author: s.node("Actor"),
state: s.primitive(s.nonNull("PullRequestReviewState")),
comments: s.connection("PullRequestReviewComment"),
}),
PullRequestReviewComment: s.object({
id: s.id(),
url: s.primitive(s.nonNull("URI")),
body: s.primitive(s.nonNull("String")),
author: s.node("Actor"),
reactions: s.connection("Reaction"),
}),
Reaction: s.object({
id: s.id(),
content: s.primitive(s.nonNull("ReactionContent")),
user: s.node("User"),
}),
Ref: s.object({
id: s.id(),
// Unlike most node references, this is guaranteed non-null (but
// we have no way to express that).
target: s.node("GitObject"),
}),
GitObject: s.union(["Blob", "Commit", "Tag", "Tree"]),
Blob: s.object({
id: s.id(),
oid: s.primitive(s.nonNull("GitObjectID")),
}),
Commit: s.object({
id: s.id(),
url: s.primitive(s.nonNull("URI")),
oid: s.primitive(s.nonNull("GitObjectID")),
message: s.primitive(s.nonNull("String")),
author: /* GitActor */ s.nested({
// The GitHub schema indicates that `date` can be null, but does
// not indicate when this might be the case.
date: s.primitive(s.nullable("GitTimestamp")),
user: s.node("User"),
}),
parents: s.connection("Commit"),
}),
Tag: s.object({
id: s.id(),
oid: s.primitive(s.nonNull("GitObjectID")),
}),
Tree: s.object({
id: s.id(),
oid: s.primitive(s.nonNull("GitObjectID")),
}),
Actor: s.union(["User", "Bot", "Organization"]), // actually an interface
RepositoryOwner: s.union(["User", "Organization"]), // actually an interface
User: s.object({
id: s.id(),
url: s.primitive(s.nonNull("URI")),
login: s.primitive(s.nonNull("String")),
}),
Bot: s.object({
id: s.id(),
url: s.primitive(s.nonNull("URI")),
login: s.primitive(s.nonNull("String")),
}),
Organization: s.object({
id: s.id(),
url: s.primitive(s.nonNull("URI")),
login: s.primitive(s.nonNull("String")),
}),
};
return s.schema(types);
}

View File

@ -0,0 +1,11 @@
// @flow
import schema from "./schema";
describe("plugins/github/schema", () => {
describe("schema", () => {
it("creates a valid schema", () => {
expect(schema()).toEqual(expect.anything());
});
});
});