mirror of
https://github.com/status-im/sourcecred.git
synced 2025-02-04 16:54:39 +00:00
Add GitHub commit entity (#819)
This adds a `Commit` entity to the GitHub relational view. It has all the standard methods: commits can be retrieved en masse or by particular address, they have a URL and authors, and (de)serialize appropriately. The code for adding pull requests has been modified so that the merge commits are added as commit entities. This does not have any effect on the ultimate graph being created; the same edge is added either way. Test plan: I've extended the standard RelationalView tests to cover the `Commit` entity. The case where the commit has 0 authors is not yet tested, but will be once I add support for getting all of the commits from the example-github (we have one example of a commit that doesn't map to a user). Progress on #815.
This commit is contained in:
parent
3e06c054db
commit
7d0d4fb2fa
@ -18,6 +18,16 @@ Object {
|
||||
|
||||
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`;
|
||||
|
||||
exports[`plugins/github/relationalView Commit authors have expected urls 1`] = `
|
||||
Array [
|
||||
"https://github.com/decentralion",
|
||||
]
|
||||
`;
|
||||
|
||||
exports[`plugins/github/relationalView Commit has url 1`] = `"https://github.com/sourcecred/example-github/commit/0a223346b4e6dec0127b1e6aa892c4ee0424b66a"`;
|
||||
|
||||
exports[`plugins/github/relationalView Issue authors has expected number of authors 1`] = `1`;
|
||||
|
||||
exports[`plugins/github/relationalView Issue authors have expected urls 1`] = `
|
||||
@ -137,6 +147,15 @@ Array [
|
||||
]
|
||||
`;
|
||||
|
||||
exports[`plugins/github/relationalView RelationalView entity: commits has expected number of them 1`] = `2`;
|
||||
|
||||
exports[`plugins/github/relationalView RelationalView entity: commits they have expected urls 1`] = `
|
||||
Array [
|
||||
"https://github.com/sourcecred/example-github/commit/0a223346b4e6dec0127b1e6aa892c4ee0424b66a",
|
||||
"https://github.com/sourcecred/example-github/commit/6d5b3aa31ebb68a06ceb46bbd6cf49b6ccd6f5e6",
|
||||
]
|
||||
`;
|
||||
|
||||
exports[`plugins/github/relationalView RelationalView entity: issues has expected number of them 1`] = `8`;
|
||||
|
||||
exports[`plugins/github/relationalView RelationalView entity: issues they have expected urls 1`] = `
|
||||
|
@ -3,6 +3,7 @@
|
||||
exports[`plugins/github/render descriptions are as expected 1`] = `
|
||||
Object {
|
||||
"comment": "comment by @wchargin on review by @wchargin of #5 (+1/−0): This pull request will be more contentious. I can feel it...",
|
||||
"commit": "commit 0a223346b4e6dec0127b1e6aa892c4ee0424b66a",
|
||||
"issue": "#2: A referencing issue.",
|
||||
"pull": "#5 (+1/−0): This pull request will be more contentious. I can feel it...",
|
||||
"repo": "sourcecred/example-github",
|
||||
|
@ -58,7 +58,7 @@ class GraphCreator {
|
||||
this.graph.addNode(N.toRaw(addr));
|
||||
}
|
||||
|
||||
addAuthors(entity: R.Issue | R.Pull | R.Comment | R.Review) {
|
||||
addAuthors(entity: R.AuthoredEntity) {
|
||||
for (const author of entity.authors()) {
|
||||
this.graph.addEdge(
|
||||
createEdge.authors(author.address(), entity.address())
|
||||
|
@ -27,6 +27,7 @@ export function exampleEntities() {
|
||||
const pull = Array.from(repo.pulls())[1];
|
||||
const review = Array.from(pull.reviews())[0];
|
||||
const comment = Array.from(review.comments())[0];
|
||||
const commit = Array.from(view.commits())[0];
|
||||
const userlike = Array.from(review.authors())[0];
|
||||
return {
|
||||
repo,
|
||||
@ -34,6 +35,7 @@ export function exampleEntities() {
|
||||
pull,
|
||||
review,
|
||||
comment,
|
||||
commit,
|
||||
userlike,
|
||||
};
|
||||
}
|
||||
|
@ -82,7 +82,8 @@ export type AuthorableAddress =
|
||||
| IssueAddress
|
||||
| PullAddress
|
||||
| ReviewAddress
|
||||
| CommentAddress;
|
||||
| CommentAddress
|
||||
| GitNode.CommitAddress;
|
||||
|
||||
// Each of these types has text content, which means
|
||||
// it may be the source of a reference to a ReferentAddress.
|
||||
|
@ -20,6 +20,7 @@ import type {
|
||||
PullJSON,
|
||||
ReviewJSON,
|
||||
CommentJSON,
|
||||
CommitJSON,
|
||||
NullableAuthorJSON,
|
||||
ReviewState,
|
||||
} from "./graphql";
|
||||
@ -36,7 +37,7 @@ import {
|
||||
|
||||
const COMPAT_INFO = {
|
||||
type: "sourcecred/github/relationalView",
|
||||
version: "0.1.0",
|
||||
version: "0.2.0",
|
||||
};
|
||||
|
||||
export class RelationalView {
|
||||
@ -44,6 +45,7 @@ export class RelationalView {
|
||||
_issues: Map<N.RawAddress, IssueEntry>;
|
||||
_pulls: Map<N.RawAddress, PullEntry>;
|
||||
_comments: Map<N.RawAddress, CommentEntry>;
|
||||
_commits: Map<N.RawAddress, CommitEntry>;
|
||||
_reviews: Map<N.RawAddress, ReviewEntry>;
|
||||
_userlikes: Map<N.RawAddress, UserlikeEntry>;
|
||||
_mapReferences: Map<N.RawAddress, N.ReferentAddress[]>;
|
||||
@ -54,6 +56,7 @@ export class RelationalView {
|
||||
this._issues = new Map();
|
||||
this._pulls = new Map();
|
||||
this._comments = new Map();
|
||||
this._commits = new Map();
|
||||
this._reviews = new Map();
|
||||
this._userlikes = new Map();
|
||||
this._mapReferences = new Map();
|
||||
@ -140,6 +143,17 @@ export class RelationalView {
|
||||
return entry == null ? entry : new Comment(this, entry);
|
||||
}
|
||||
|
||||
*commits(): Iterator<Commit> {
|
||||
for (const entry of this._commits.values()) {
|
||||
yield new Commit(this, entry);
|
||||
}
|
||||
}
|
||||
|
||||
commit(address: GitNode.CommitAddress): ?Commit {
|
||||
const entry = this._commits.get(N.toRaw(address));
|
||||
return entry == null ? entry : new Commit(this, entry);
|
||||
}
|
||||
|
||||
*reviews(): Iterator<Review> {
|
||||
for (const entry of this._reviews.values()) {
|
||||
yield new Review(this, entry);
|
||||
@ -177,7 +191,7 @@ export class RelationalView {
|
||||
case "USERLIKE":
|
||||
return this.userlike(address);
|
||||
case "COMMIT":
|
||||
return null;
|
||||
return this.commit(address);
|
||||
default:
|
||||
throw new Error(`Unexpected address type: ${(address.type: empty)}`);
|
||||
}
|
||||
@ -226,6 +240,7 @@ export class RelationalView {
|
||||
yield* this.pulls();
|
||||
yield* this.reviews();
|
||||
yield* this.comments();
|
||||
yield* this.commits();
|
||||
yield* this.userlikes();
|
||||
}
|
||||
|
||||
@ -236,6 +251,7 @@ export class RelationalView {
|
||||
pulls: MapUtil.toObject(this._pulls),
|
||||
reviews: MapUtil.toObject(this._reviews),
|
||||
comments: MapUtil.toObject(this._comments),
|
||||
commits: MapUtil.toObject(this._commits),
|
||||
userlikes: MapUtil.toObject(this._userlikes),
|
||||
references: MapUtil.toObject(this._mapReferences),
|
||||
referencedBy: MapUtil.toObject(this._mapReferencedBy),
|
||||
@ -251,6 +267,7 @@ export class RelationalView {
|
||||
rv._pulls = MapUtil.fromObject(json.pulls);
|
||||
rv._reviews = MapUtil.fromObject(json.reviews);
|
||||
rv._comments = MapUtil.fromObject(json.comments);
|
||||
rv._commits = MapUtil.fromObject(json.commits);
|
||||
rv._userlikes = MapUtil.fromObject(json.userlikes);
|
||||
rv._mapReferences = MapUtil.fromObject(json.references);
|
||||
rv._mapReferencedBy = MapUtil.fromObject(json.referencedBy);
|
||||
@ -291,19 +308,29 @@ export class RelationalView {
|
||||
return address;
|
||||
}
|
||||
|
||||
_addCommit(json: CommitJSON): GitNode.CommitAddress {
|
||||
const address = {type: GitNode.COMMIT_TYPE, hash: json.oid};
|
||||
const authors =
|
||||
json.author == null ? [] : this._addNullableAuthor(json.author.user);
|
||||
const entry: CommitEntry = {
|
||||
address,
|
||||
url: json.url,
|
||||
authors,
|
||||
};
|
||||
this._commits.set(N.toRaw(address), entry);
|
||||
return address;
|
||||
}
|
||||
|
||||
_addPull(repo: RepoAddress, json: PullJSON): PullAddress {
|
||||
const address: PullAddress = {
|
||||
type: N.PULL_TYPE,
|
||||
number: String(json.number),
|
||||
repo,
|
||||
};
|
||||
// TODO(@decentralion): Rewrite so that pulls actually have
|
||||
// the commit attached (not just oid)
|
||||
const mergedAs =
|
||||
json.mergeCommit == null
|
||||
? null
|
||||
: {
|
||||
type: GitNode.COMMIT_TYPE,
|
||||
hash: json.mergeCommit.oid,
|
||||
};
|
||||
json.mergeCommit == null ? null : this._addCommit(json.mergeCommit);
|
||||
|
||||
const entry: PullEntry = {
|
||||
address,
|
||||
@ -558,6 +585,7 @@ type Entry =
|
||||
| PullEntry
|
||||
| ReviewEntry
|
||||
| CommentEntry
|
||||
| CommitEntry
|
||||
| UserlikeEntry;
|
||||
|
||||
export class _Entity<+T: Entry> {
|
||||
@ -803,6 +831,21 @@ export class Comment extends _Entity<CommentEntry> {
|
||||
}
|
||||
}
|
||||
|
||||
type CommitEntry = {|
|
||||
+address: GitNode.CommitAddress,
|
||||
+url: string,
|
||||
+authors: UserlikeAddress[],
|
||||
|};
|
||||
|
||||
export class Commit extends _Entity<CommitEntry> {
|
||||
constructor(view: RelationalView, entry: CommitEntry) {
|
||||
super(view, entry);
|
||||
}
|
||||
authors(): Iterator<Userlike> {
|
||||
return getAuthors(this._view, this._entry);
|
||||
}
|
||||
}
|
||||
|
||||
type UserlikeEntry = {|
|
||||
+address: UserlikeAddress,
|
||||
+url: string,
|
||||
@ -831,7 +874,7 @@ function assertExists<T>(item: ?T, address: N.StructuredAddress): T {
|
||||
|
||||
function* getAuthors(
|
||||
view: RelationalView,
|
||||
entry: IssueEntry | PullEntry | ReviewEntry | CommentEntry
|
||||
entry: IssueEntry | PullEntry | ReviewEntry | CommentEntry | CommitEntry
|
||||
) {
|
||||
for (const address of entry.authors) {
|
||||
const author = view.userlike(address);
|
||||
@ -845,6 +888,7 @@ export type MatchHandlers<T> = {|
|
||||
+pull: (x: Pull) => T,
|
||||
+review: (x: Review) => T,
|
||||
+comment: (x: Comment) => T,
|
||||
+commit: (x: Commit) => T,
|
||||
+userlike: (x: Userlike) => T,
|
||||
|};
|
||||
export function match<T>(handlers: MatchHandlers<T>, x: Entity): T {
|
||||
@ -863,14 +907,17 @@ export function match<T>(handlers: MatchHandlers<T>, x: Entity): T {
|
||||
if (x instanceof Comment) {
|
||||
return handlers.comment(x);
|
||||
}
|
||||
if (x instanceof Commit) {
|
||||
return handlers.commit(x);
|
||||
}
|
||||
if (x instanceof Userlike) {
|
||||
return handlers.userlike(x);
|
||||
}
|
||||
throw new Error(`Unexpected entity ${x}`);
|
||||
}
|
||||
|
||||
export type Entity = Repo | Issue | Pull | Review | Comment | Userlike;
|
||||
export type AuthoredEntity = Issue | Pull | Review | Comment;
|
||||
export type Entity = Repo | Issue | Pull | Review | Comment | Commit | Userlike;
|
||||
export type AuthoredEntity = Issue | Pull | Review | Comment | Commit;
|
||||
export type TextContentEntity = Issue | Pull | Review | Comment;
|
||||
export type ParentEntity = Repo | Issue | Pull | Review;
|
||||
export type ChildEntity = Issue | Pull | Review | Comment;
|
||||
@ -883,6 +930,7 @@ export opaque type RelationalViewJSON = Compatible<{|
|
||||
+pulls: AddressEntryMapJSON<PullEntry>,
|
||||
+reviews: AddressEntryMapJSON<ReviewEntry>,
|
||||
+comments: AddressEntryMapJSON<CommentEntry>,
|
||||
+commits: AddressEntryMapJSON<CommitEntry>,
|
||||
+userlikes: AddressEntryMapJSON<UserlikeEntry>,
|
||||
+references: AddressEntryMapJSON<N.ReferentAddress[]>,
|
||||
+referencedBy: AddressEntryMapJSON<N.TextContentAddress[]>,
|
||||
|
@ -61,6 +61,7 @@ describe("plugins/github/relationalView", () => {
|
||||
hasEntityMethods("pulls", () => view.pulls(), (x) => view.pull(x));
|
||||
hasEntityMethods("reviews", () => view.reviews(), (x) => view.review(x));
|
||||
hasEntityMethods("comments", () => view.comments(), (x) => view.comment(x));
|
||||
hasEntityMethods("commits", () => view.commits(), (x) => view.commit(x));
|
||||
hasEntityMethods(
|
||||
"userlikes",
|
||||
() => view.userlikes(),
|
||||
@ -133,6 +134,13 @@ describe("plugins/github/relationalView", () => {
|
||||
hasEntities("authors", () => entity.authors());
|
||||
});
|
||||
|
||||
const commit = Array.from(view.commits())[0];
|
||||
describe("Commit", () => {
|
||||
const entity = commit;
|
||||
has("url", () => entity.url());
|
||||
hasEntities("authors", () => entity.authors());
|
||||
});
|
||||
|
||||
const userlike = Array.from(review.authors())[0];
|
||||
describe("Userlike", () => {
|
||||
const entity = userlike;
|
||||
@ -156,6 +164,9 @@ describe("plugins/github/relationalView", () => {
|
||||
it("works for comment", () => {
|
||||
expect(view.entity(comment.address())).toEqual(comment);
|
||||
});
|
||||
it("works for commit", () => {
|
||||
expect(view.entity(commit.address())).toEqual(commit);
|
||||
});
|
||||
it("works for userlike", () => {
|
||||
expect(view.entity(userlike.address())).toEqual(userlike);
|
||||
});
|
||||
@ -179,10 +190,11 @@ describe("plugins/github/relationalView", () => {
|
||||
pull: (x: R.Pull) => [x.address(), "PULL"],
|
||||
review: (x: R.Review) => [x.address(), "REVIEW"],
|
||||
comment: (x: R.Comment) => [x.address(), "COMMENT"],
|
||||
commit: (x: R.Commit) => [x.address(), "COMMIT"],
|
||||
userlike: (x: R.Userlike) => [x.address(), "USERLIKE"],
|
||||
};
|
||||
|
||||
const instances = [repo, issue, pull, review, comment, userlike];
|
||||
const instances = [repo, issue, pull, review, comment, commit, userlike];
|
||||
for (const instance of instances) {
|
||||
const [actualAddress, functionType] = R.match(handlers, instance);
|
||||
expect(actualAddress.type).toEqual(functionType);
|
||||
|
@ -20,6 +20,11 @@ export function description(e: R.Entity) {
|
||||
},
|
||||
review: (x) => `review ${withAuthors(x)}of ${description(x.parent())}`,
|
||||
comment: (x) => `comment ${withAuthors(x)}on ${description(x.parent())}`,
|
||||
// The commit type is included for completeness's sake and to
|
||||
// satisfy the typechecker, but won't ever be seen in the frontend
|
||||
// because the commit has a Git plugin prefix and will therefore by
|
||||
// handled by the git plugin adapter
|
||||
commit: (x) => `commit ${x.address().hash}`,
|
||||
userlike: (x) => `@${x.login()}`,
|
||||
};
|
||||
return R.match(handlers, e);
|
||||
|
Loading…
x
Reference in New Issue
Block a user