Request reactions data from GitHub (#839) (#840)

This commit updates the GitHub graphql query to also fetch reactions.
We update the JSON typedefs to include this new information, add
continuations from comments, and update existing continuation and query
code. Also, I added a safety check when updating comments for issues
that was previously unnecessary but is now needed.

Test plan:
- `yarn test --full` passes.
- Setting the page limits to 1 and running on the example-github does
not error with unexhausted pages, and loads all the expected reactions.
- Running on a larger repository (go-ipfs) works as expected.
- I have written dependent code that consumes these reactions in the
RelationalView, and works as intended, which suggests that the type
signatures are correct.
This commit is contained in:
Dandelion Mané 2018-09-17 11:47:37 -07:00 committed by GitHub
parent 51461f4842
commit 1ad2cc0958
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 217 additions and 0 deletions

View File

@ -16,6 +16,8 @@ Object {
}
`;
exports[`plugins/github/relationalView Comment has reactions 1`] = `Array []`;
exports[`plugins/github/relationalView Comment has url 1`] = `"https://github.com/sourcecred/example-github/pull/5#discussion_r171460198"`;
exports[`plugins/github/relationalView Commit authors has expected number of authors 1`] = `1`;
@ -67,6 +69,8 @@ Object {
}
`;
exports[`plugins/github/relationalView Issue has reactions 1`] = `Array []`;
exports[`plugins/github/relationalView Issue has title 1`] = `"A referencing issue."`;
exports[`plugins/github/relationalView Issue has url 1`] = `"https://github.com/sourcecred/example-github/issues/2"`;
@ -114,6 +118,8 @@ Object {
}
`;
exports[`plugins/github/relationalView Pull has reactions 1`] = `Array []`;
exports[`plugins/github/relationalView Pull has title 1`] = `"This pull request will be more contentious. I can feel it..."`;
exports[`plugins/github/relationalView Pull has url 1`] = `"https://github.com/sourcecred/example-github/pull/5"`;
@ -285,6 +291,159 @@ exports[`plugins/github/relationalView Userlike has login 1`] = `"wchargin"`;
exports[`plugins/github/relationalView Userlike has url 1`] = `"https://github.com/wchargin"`;
exports[`plugins/github/relationalView reaction detection set of all reactions matches snapshot 1`] = `
Object {
"https://github.com/sourcecred/example-github/issues/1": Array [
Object {
"content": "LAUGH",
"user": Object {
"login": "decentralion",
"subtype": "USER",
"type": "USERLIKE",
},
},
Object {
"content": "HEART",
"user": Object {
"login": "decentralion",
"subtype": "USER",
"type": "USERLIKE",
},
},
],
"https://github.com/sourcecred/example-github/issues/11#issuecomment-420813206": Array [
Object {
"content": "THUMBS_UP",
"user": Object {
"login": "decentralion",
"subtype": "USER",
"type": "USERLIKE",
},
},
Object {
"content": "THUMBS_DOWN",
"user": Object {
"login": "decentralion",
"subtype": "USER",
"type": "USERLIKE",
},
},
Object {
"content": "LAUGH",
"user": Object {
"login": "decentralion",
"subtype": "USER",
"type": "USERLIKE",
},
},
Object {
"content": "HOORAY",
"user": Object {
"login": "decentralion",
"subtype": "USER",
"type": "USERLIKE",
},
},
],
"https://github.com/sourcecred/example-github/issues/12": Array [
Object {
"content": "THUMBS_UP",
"user": Object {
"login": "decentralion",
"subtype": "USER",
"type": "USERLIKE",
},
},
],
"https://github.com/sourcecred/example-github/issues/13": Array [
Object {
"content": "THUMBS_UP",
"user": Object {
"login": "decentralion",
"subtype": "USER",
"type": "USERLIKE",
},
},
Object {
"content": "THUMBS_DOWN",
"user": Object {
"login": "decentralion",
"subtype": "USER",
"type": "USERLIKE",
},
},
Object {
"content": "LAUGH",
"user": Object {
"login": "decentralion",
"subtype": "USER",
"type": "USERLIKE",
},
},
Object {
"content": "HOORAY",
"user": Object {
"login": "decentralion",
"subtype": "USER",
"type": "USERLIKE",
},
},
Object {
"content": "CONFUSED",
"user": Object {
"login": "decentralion",
"subtype": "USER",
"type": "USERLIKE",
},
},
Object {
"content": "HEART",
"user": Object {
"login": "decentralion",
"subtype": "USER",
"type": "USERLIKE",
},
},
],
"https://github.com/sourcecred/example-github/pull/5#issuecomment-396430464": Array [
Object {
"content": "HEART",
"user": Object {
"login": "decentralion",
"subtype": "USER",
"type": "USERLIKE",
},
},
Object {
"content": "CONFUSED",
"user": Object {
"login": "decentralion",
"subtype": "USER",
"type": "USERLIKE",
},
},
],
"https://github.com/sourcecred/example-github/pull/9": Array [
Object {
"content": "HEART",
"user": Object {
"login": "decentralion",
"subtype": "USER",
"type": "USERLIKE",
},
},
Object {
"content": "THUMBS_UP",
"user": Object {
"login": "decentralion",
"subtype": "USER",
"type": "USERLIKE",
},
},
],
}
`;
exports[`plugins/github/relationalView reference detection references match snapshot 1`] = `
Array [
Object {

View File

@ -25,6 +25,8 @@ import type {
CommitJSON,
NullableAuthorJSON,
ReviewState,
ReactionJSON,
ReactionContent,
} from "./graphql";
import * as GitNode from "../git/nodes";
import * as MapUtil from "../../util/map";
@ -255,6 +257,12 @@ export class RelationalView {
yield* this.userlikes();
}
*reactableEntities(): Iterator<ReactableEntity> {
yield* this.issues();
yield* this.pulls();
yield* this.comments();
}
toJSON(): RelationalViewJSON {
const rawJSON = {
repos: MapUtil.toObject(this._repos),
@ -332,6 +340,7 @@ export class RelationalView {
authors: this._addNullableAuthor(json.author),
body: json.body,
title: json.title,
reactions: json.reactions.nodes.map((x) => this._addReaction(x)),
};
this._issues.set(N.toRaw(address), entry);
return address;
@ -371,6 +380,7 @@ export class RelationalView {
mergedAs,
additions: json.additions,
deletions: json.deletions,
reactions: json.reactions.nodes.map((x) => this._addReaction(x)),
};
this._pulls.set(N.toRaw(address), entry);
return address;
@ -418,11 +428,22 @@ export class RelationalView {
url: json.url,
authors: this._addNullableAuthor(json.author),
body: json.body,
reactions: json.reactions.nodes.map((x) => this._addReaction(x)),
};
this._comments.set(N.toRaw(address), entry);
return address;
}
_addReaction(json: ReactionJSON): ReactionRecord {
const authorAddresses = this._addNullableAuthor(json.user);
if (authorAddresses.length !== 1) {
throw new Error(
`Invariant violation: Reaction with id ${json.id} did not have 1 author`
);
}
return {content: json.content, user: authorAddresses[0]};
}
_addNullableAuthor(json: NullableAuthorJSON): UserlikeAddress[] {
if (json == null) {
return [];
@ -617,6 +638,11 @@ export class RelationalView {
}
}
type ReactionRecord = {|
+content: ReactionContent,
+user: UserlikeAddress,
|};
type Entry =
| RepoEntry
| IssueEntry
@ -685,6 +711,7 @@ type IssueEntry = {|
+url: string,
+comments: CommentAddress[],
+authors: UserlikeAddress[],
+reactions: ReactionRecord[],
|};
export class Issue extends _Entity<IssueEntry> {
@ -720,6 +747,9 @@ export class Issue extends _Entity<IssueEntry> {
referencedBy(): Iterator<TextContentEntity> {
return this._view._referencedBy(this);
}
reactions(): $ReadOnlyArray<ReactionRecord> {
return this._entry.reactions;
}
}
type PullEntry = {|
@ -733,6 +763,7 @@ type PullEntry = {|
+additions: number,
+deletions: number,
+authors: UserlikeAddress[],
+reactions: ReactionRecord[],
|};
export class Pull extends _Entity<PullEntry> {
@ -783,6 +814,9 @@ export class Pull extends _Entity<PullEntry> {
referencedBy(): Iterator<TextContentEntity> {
return this._view._referencedBy(this);
}
reactions(): $ReadOnlyArray<ReactionRecord> {
return this._entry.reactions;
}
}
type ReviewEntry = {|
@ -831,6 +865,7 @@ type CommentEntry = {|
+body: string,
+url: string,
+authors: UserlikeAddress[],
+reactions: ReactionRecord[],
|};
export class Comment extends _Entity<CommentEntry> {
@ -867,6 +902,9 @@ export class Comment extends _Entity<CommentEntry> {
referencedBy(): Iterator<TextContentEntity> {
return this._view._referencedBy(this);
}
reactions(): $ReadOnlyArray<ReactionRecord> {
return this._entry.reactions;
}
}
type CommitEntry = {|
@ -977,6 +1015,7 @@ export type ReferentEntity =
| Comment
| Commit
| Userlike;
export type ReactableEntity = Issue | Pull | Comment;
export opaque type AddressEntryMapJSON<T> = {[N.RawAddress]: T};
export opaque type RelationalViewJSON = Compatible<{|

View File

@ -3,6 +3,7 @@
import * as R from "./relationalView";
import * as N from "./nodes";
import {exampleData, exampleRelationalView} from "./example/example";
import * as MapUtil from "../../util/map";
describe("plugins/github/relationalView", () => {
// Sharing this state is OK because it's just a view - no mutation allowed!
@ -96,6 +97,7 @@ describe("plugins/github/relationalView", () => {
has("parent", () => entity.parent());
hasEntities("comments", () => entity.comments());
hasEntities("authors", () => entity.authors());
has("reactions", () => entity.reactions());
});
const pull = Array.from(repo.pulls())[1];
@ -112,6 +114,7 @@ describe("plugins/github/relationalView", () => {
hasEntities("reviews", () => entity.reviews());
hasEntities("comments", () => entity.comments());
hasEntities("authors", () => entity.authors());
has("reactions", () => entity.reactions());
});
const review = Array.from(pull.reviews())[0];
@ -132,6 +135,7 @@ describe("plugins/github/relationalView", () => {
has("url", () => entity.url());
has("parent", () => entity.parent());
hasEntities("authors", () => entity.authors());
has("reactions", () => entity.reactions());
});
const commit = Array.from(view.commits())[0];
@ -270,6 +274,21 @@ describe("plugins/github/relationalView", () => {
});
});
describe("reaction detection", () => {
it("set of all reactions matches snapshot", () => {
const view = new R.RelationalView();
view.addData(exampleData());
const urlToReactions = new Map();
for (const reactable of view.reactableEntities()) {
const url = reactable.url();
for (const reactionRecord of reactable.reactions()) {
MapUtil.pushValue(urlToReactions, url, reactionRecord);
}
}
expect(MapUtil.toObject(urlToReactions)).toMatchSnapshot();
});
});
it("addData is idempotent", () => {
const rv1 = new R.RelationalView();
rv1.addData(exampleData());