From bd10cf4b2ad19b7ff111e1ea6effc070dbceb08b Mon Sep 17 00:00:00 2001 From: Ryan Barrett Date: Thu, 5 Jul 2018 21:27:45 -0700 Subject: [PATCH] github: improve GraphQL support in comment and user handling --- README.md | 1 + granary/github.py | 44 +++++++++++++++++++++++++------------ granary/test/test_github.py | 13 ++++++++++- 3 files changed, 43 insertions(+), 15 deletions(-) diff --git a/README.md b/README.md index 99474466..48c5a83e 100644 --- a/README.md +++ b/README.md @@ -317,6 +317,7 @@ Changelog * `get_activities()` and `get_comment()` now return `ValueError` instead of `AssertionError` on malformed `activity_id` and `comment_id` args, respectively. * `get_activities()` bug fix for issues/PRs with no body text. * Switch from GraphQL to REST API for creating comments and reactions, since GraphQL hits authorization errors on many org repos. ([snarfed/bridgy#824](https://github.com/snarfed/bridgy/issues/824)) + * Improve GraphQL support for comments and users. * Atom: * Shorten and ellipsize feed title when necessary ([#144](https://github.com/snarfed/granary/issues/144)). diff --git a/granary/github.py b/granary/github.py index 4ed6e238..140acd5c 100644 --- a/granary/github.py +++ b/granary/github.py @@ -35,7 +35,9 @@ REST_API_MARKDOWN = REST_API_BASE + '/markdown' REST_API_NOTIFICATIONS = REST_API_BASE + '/notifications?all=true&participating=true' GRAPHQL_BASE = 'https://api.github.com/graphql' -GRAPHQL_USER_FIELDS = 'id avatarUrl bio company createdAt location login name websiteUrl' # not email, it requires an additional oauth scope +GRAPHQL_BOT_FIELDS = 'id avatarUrl createdAt login url' +GRAPHQL_ORG_FIELDS = 'id avatarUrl description location login name url websiteUrl' +GRAPHQL_USER_FIELDS = 'id avatarUrl bio company createdAt location login name url websiteUrl' # not email, it requires an additional oauth scope GRAPHQL_USER = """ query { user(login: "%(login)s") { @@ -113,7 +115,14 @@ GRAPHQL_COMMENT = """ query { node(id:"%(id)s") { - ... on IssueComment {body} + ... on IssueComment { + id url body createdAt + author { + ... on Bot {""" + GRAPHQL_BOT_FIELDS + """} + ... on Organization {""" + GRAPHQL_ORG_FIELDS + """} + ... on User {""" + GRAPHQL_USER_FIELDS + """} + } + } } } """ @@ -399,8 +408,8 @@ def get_comment(self, comment_id, **kwargs): """Fetches and returns a comment. Args: - comment_id: string comment id, of the form REPO-OWNER_REPO-NAME_ID, - e.g. snarfed:bridgy:456789 + comment_id: string comment id (either REST or GraphQL), of the form + REPO-OWNER_REPO-NAME_ID, e.g. snarfed:bridgy:456789 Returns: dict, an ActivityStreams comment object """ @@ -408,7 +417,11 @@ def get_comment(self, comment_id, **kwargs): if len(parts) != 3: raise ValueError('GitHub comment ids must be of the form USER:REPO:COMMENT_ID') - comment = self.rest(REST_API_COMMENT % parts).json() + if util.is_int(parts[2]): # REST API id + comment = self.rest(REST_API_COMMENT % parts).json() + else: # GraphQL node id + comment = self.graphql(GRAPHQL_COMMENT, {'id': parts[2]})['node'] + return self.comment_to_object(comment) def render_markdown(self, markdown, owner, repo): @@ -808,7 +821,7 @@ def user_to_actor(cls, user): return actor username = user.get('login') - bio = user.get('bio') + desc = user.get('bio') or user.get('description') actor.update({ # TODO: orgs, bots @@ -816,18 +829,21 @@ def user_to_actor(cls, user): 'displayName': user.get('name') or username, 'username': username, 'email': user.get('email'), - 'description': bio, - 'summary': bio, - 'image': {'url': user.get('avatarUrl') or user.get('avatar_url')}, + 'description': desc, + 'summary': desc, + 'image': {'url': user.get('avatarUrl') or user.get('avatar_url') or user.get('url')}, 'location': {'displayName': user.get('location')}, }) # extract web site links. extract_links uniquifies and preserves order - urls = sum((util.extract_links(user.get(field)) for field in - ('websiteUrl', # GraphQL - 'blog', # REST - 'bio', # both - )), []) + urls = sum((util.extract_links(user.get(field)) for field in ( + 'websiteUrl', # GraphQL + 'blog', # REST + 'bio', # both + 'html_url', # REST + 'url', # both + )), []) + urls = [u for u in urls if util.domain_from_link(u) != 'api.github.com'] if urls: actor['url'] = urls[0] if len(urls) > 1: diff --git a/granary/test/test_github.py b/granary/test/test_github.py index 40e19661..bfb231b4 100644 --- a/granary/test/test_github.py +++ b/granary/test/test_github.py @@ -13,6 +13,7 @@ from granary import github from granary.github import ( GRAPHQL_BASE, + GRAPHQL_COMMENT, REACTIONS_REST_CHARS, REST_API_COMMENT, REST_API_COMMENT_REACTIONS, @@ -87,6 +88,7 @@ def tag_uri(name): 'urls': [ {'value': 'https://snarfed.org/'}, {'value': 'https://brid.gy/'}, + {'value': 'https://github.com/snarfed'}, ], 'username': 'snarfed', 'email': 'github@ryanb.org', @@ -613,11 +615,20 @@ def test_issue_to_object_minimal(self): def test_issue_to_object_empty(self): self.assert_equals({}, self.gh.issue_to_object({})) - def test_get_comment(self): + def test_get_comment_rest(self): self.expect_rest(REST_API_COMMENT % ('foo', 'bar', 123), COMMENT_REST) self.mox.ReplayAll() self.assert_equals(COMMENT_OBJ, self.gh.get_comment('foo:bar:123')) + def test_get_comment_graphql(self): + self.expect_graphql(json={'query': GRAPHQL_COMMENT % {'id': 'abc'}}, + response={'node': COMMENT_GRAPHQL}) + self.mox.ReplayAll() + + obj = copy.deepcopy(COMMENT_OBJ) + obj['id'] = 'tag:github.com:foo:bar:MDEwOlNQ==' + self.assert_equals(obj, self.gh.get_comment('foo:bar:abc')) + def test_get_activities_bad_comment_id(self): for bad in 'no_colons', 'one:colon', 'fo:ur:col:ons': with self.assertRaises(ValueError):