Add GitHub reactions to the graph (#846)

* Define Reaction edges

This adds support to `github/edges` for creating edges representing
GitHub reactions. These edges are not actually added to the graph.

Test plan: Unit tests

* Add GitHub reactions to the graph

This commit adds functional support for reactions in SourceCred.
Only thumbs-up, heart, and hooray reactions are supported for now, as
they are all unambiguously positive; adding support for negative
reactions like thumbs-down will require some more thought.

The reactions are added to the graph, and new edge types have been added
to the UI.

Test plan:
The `graphView` class has been updated to do invariant checking for the
reaction edges, including that the unsupported reaction types like
"THUMBS_DOWN" aren't added to the graph.

I've tested this feature by downloading data for a large repository
(ipfs/go-ipfs). The reaction edges appear and transfer cred reasonably.
The edge types are displayed in the weight config appropriately.

Builds on #839, #840, and #845.
This commit is contained in:
Dandelion Mané 2018-09-17 13:44:11 -07:00 committed by GitHub
parent 488c98c3e1
commit 62d3c180ee
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 317 additions and 0 deletions

View File

@ -1,6 +1,7 @@
# Changelog
## [Unreleased]
- Add GitHub reactions to the graph (#846)
- Detect references to commits (#833)
- Detect references in commit messages (#829)
- Add commit authorship to the graph (#826)

View File

@ -1541,6 +1541,242 @@ Array [
"dstIndex": 2,
"srcIndex": 36,
},
Object {
"address": Array [
"sourcecred",
"github",
"REACTS",
"HEART",
"5",
"sourcecred",
"github",
"USERLIKE",
"USER",
"decentralion",
"6",
"sourcecred",
"github",
"ISSUE",
"sourcecred",
"example-github",
"1",
],
"dstIndex": 25,
"srcIndex": 42,
},
Object {
"address": Array [
"sourcecred",
"github",
"REACTS",
"HEART",
"5",
"sourcecred",
"github",
"USERLIKE",
"USER",
"decentralion",
"6",
"sourcecred",
"github",
"ISSUE",
"sourcecred",
"example-github",
"13",
],
"dstIndex": 29,
"srcIndex": 42,
},
Object {
"address": Array [
"sourcecred",
"github",
"REACTS",
"HEART",
"5",
"sourcecred",
"github",
"USERLIKE",
"USER",
"decentralion",
"6",
"sourcecred",
"github",
"PULL",
"sourcecred",
"example-github",
"9",
],
"dstIndex": 37,
"srcIndex": 42,
},
Object {
"address": Array [
"sourcecred",
"github",
"REACTS",
"HEART",
"5",
"sourcecred",
"github",
"USERLIKE",
"USER",
"decentralion",
"8",
"sourcecred",
"github",
"COMMENT",
"PULL",
"sourcecred",
"example-github",
"5",
"396430464",
],
"dstIndex": 23,
"srcIndex": 42,
},
Object {
"address": Array [
"sourcecred",
"github",
"REACTS",
"HOORAY",
"5",
"sourcecred",
"github",
"USERLIKE",
"USER",
"decentralion",
"6",
"sourcecred",
"github",
"ISSUE",
"sourcecred",
"example-github",
"13",
],
"dstIndex": 29,
"srcIndex": 42,
},
Object {
"address": Array [
"sourcecred",
"github",
"REACTS",
"HOORAY",
"5",
"sourcecred",
"github",
"USERLIKE",
"USER",
"decentralion",
"8",
"sourcecred",
"github",
"COMMENT",
"ISSUE",
"sourcecred",
"example-github",
"11",
"420813206",
],
"dstIndex": 8,
"srcIndex": 42,
},
Object {
"address": Array [
"sourcecred",
"github",
"REACTS",
"THUMBS_UP",
"5",
"sourcecred",
"github",
"USERLIKE",
"USER",
"decentralion",
"6",
"sourcecred",
"github",
"ISSUE",
"sourcecred",
"example-github",
"12",
],
"dstIndex": 28,
"srcIndex": 42,
},
Object {
"address": Array [
"sourcecred",
"github",
"REACTS",
"THUMBS_UP",
"5",
"sourcecred",
"github",
"USERLIKE",
"USER",
"decentralion",
"6",
"sourcecred",
"github",
"ISSUE",
"sourcecred",
"example-github",
"13",
],
"dstIndex": 29,
"srcIndex": 42,
},
Object {
"address": Array [
"sourcecred",
"github",
"REACTS",
"THUMBS_UP",
"5",
"sourcecred",
"github",
"USERLIKE",
"USER",
"decentralion",
"6",
"sourcecred",
"github",
"PULL",
"sourcecred",
"example-github",
"9",
],
"dstIndex": 37,
"srcIndex": 42,
},
Object {
"address": Array [
"sourcecred",
"github",
"REACTS",
"THUMBS_UP",
"5",
"sourcecred",
"github",
"USERLIKE",
"USER",
"decentralion",
"8",
"sourcecred",
"github",
"COMMENT",
"ISSUE",
"sourcecred",
"example-github",
"11",
"420813206",
],
"dstIndex": 8,
"srcIndex": 42,
},
Object {
"address": Array [
"sourcecred",

View File

@ -6,6 +6,8 @@ import * as N from "./nodes";
import * as R from "./relationalView";
import {createEdge} from "./edges";
import {findMentionsAuthorReferences} from "./heuristics/mentionsAuthorReference";
// TODO(@decentralion): Opportunity to reduce bundle size
import {Reactions} from "./graphql";
export function createGraph(view: R.RelationalView): Graph {
const creator = new GraphCreator();
@ -49,6 +51,21 @@ class GraphCreator {
}
}
for (const reactable of view.reactableEntities()) {
for (const {content, user} of reactable.reactions()) {
// We only support unambiguously positive reactions for now
if (
content === Reactions.THUMBS_UP ||
content === Reactions.HEART ||
content === Reactions.HOORAY
) {
this.graph.addEdge(
createEdge.reacts(content, user, reactable.address())
);
}
}
}
for (const mentionsAuthorReference of findMentionsAuthorReferences(view)) {
this.graph.addEdge(createEdge.mentionsAuthor(mentionsAuthorReference));
}

View File

@ -7,6 +7,8 @@ import * as GN from "./nodes";
import * as GE from "./edges";
import * as GitNode from "../git/nodes";
// TODO(@decentralion): Opportunity to reduce bundle size
import {Reactions} from "./graphql";
import {
Graph,
@ -245,6 +247,14 @@ export class GraphView {
srcAccessor: (x) => GN.toRaw((x: any).reference.src),
dstAccessor: (x) => GN.toRaw((x: any).reference.dst),
},
[GE.REACTS_TYPE]: {
homs: homProduct(
[GN.Prefix.userlike],
[GN.Prefix.issue, GN.Prefix.pull, GN.Prefix.comment]
),
srcAccessor: (x) => GN.toRaw((x: any).user),
dstAccessor: (x) => GN.toRaw((x: any).reactable),
},
};
for (const edge of this._graph.edges({
@ -299,5 +309,26 @@ export class GraphView {
);
}
}
for (const reactionEdge of this._graph.edges({
addressPrefix: GE.Prefix.reacts,
srcPrefix: NodeAddress.empty,
dstPrefix: NodeAddress.empty,
})) {
const address: GE.RawAddress = (reactionEdge.address: any);
const reactsAddress: GE.ReactsAddress = (GE.fromRaw(address): any);
const {reactionType} = reactsAddress;
if (
reactionType !== Reactions.THUMBS_UP &&
reactionType !== Reactions.HEART &&
reactionType !== Reactions.HOORAY
) {
throw new Error(
`Invariant: Edge ${stringify(
reactsAddress
)} has unspported reactionType`
);
}
}
}
}

View File

@ -339,6 +339,14 @@ describe("plugins/github/graphView", () => {
expect(() => new GraphView(g)).toThrow("Invariant: Expected src");
});
});
describe("reactions edges", () => {
it("must have a supported type", () => {
const unsupported = ["THUMBS_DOWN", "LAUGH", "CONFUSED"];
for (const u of unsupported) {
failsForEdge(GE.createEdge.reacts(u, userlike, issue));
}
});
});
});
it("are properly re-entrant", () => {

View File

@ -108,6 +108,30 @@ export class StaticPluginAdapter implements IStaticPluginAdapter {
defaultBackwardWeight: 1 / 32,
prefix: E.Prefix.mentionsAuthor,
},
{
forwardName: "reacted ❤️ to",
backwardName: "got ❤️ from",
defaultForwardWeight: 2,
// TODO(#811): Probably change this to 0
defaultBackwardWeight: 1 / 32,
prefix: E.Prefix.reactsHeart,
},
{
forwardName: "reacted 👍 to",
backwardName: "got 👍 from",
defaultForwardWeight: 1,
// TODO(#811): Probably change this to 0
defaultBackwardWeight: 1 / 32,
prefix: E.Prefix.reactsThumbsUp,
},
{
forwardName: "reacted 🎉 to",
backwardName: "got 🎉 from",
defaultForwardWeight: 4,
// TODO(#811): Probably change this to 0
defaultBackwardWeight: 1 / 32,
prefix: E.Prefix.reactsHooray,
},
];
}
async load(assets: Assets, repo: Repo): Promise<IDynamicPluginAdapater> {