{"action":"created","comment":{"url":"https://api.github.com/repos/hypothesis/h/pulls/comments/75338986","id":75338986,"diff_hunk":"@@ -1,158 +1,199 @@\n # -*- coding: utf-8 -*-\n \n+import mock\n import pytest\n+from webob.multidict import NestedMultiDict\n \n+from h.paginator import paginate\n from h.paginator import paginate_query\n \n \n-class FakeQuery(object):\n+class TestPaginate(object):\n \n-    \"\"\"\n-    A helper class to fake out the fluent API of a SQLAlchemy Query object.\n-    \"\"\"\n+    def test_current_page_defaults_to_1(self, pyramid_request):\n+        \"\"\"If there's no 'page' request param it defaults to 1.\"\"\"\n+        pyramid_request.params = {}\n \n-    def __init__(self, total):\n-        self.total = total\n-        self.offset_param = None\n-        self.limit_param = None\n-        self.all_called = False\n+        page = paginate(pyramid_request, 600, 10)\n \n-    def count(self):\n-        return self.total\n+        assert page['cur'] == 1\n \n-    def offset(self, n):\n-        self.offset_param = n\n-        return self\n+    @pytest.mark.parametrize('page_param,expected', [\n+        # Normally the current page just comes directly from the request's\n+        # 'page' param.\n+        ('32', 32),\n \n-    def limit(self, n):\n-        self.limit_param = n\n-        return self\n+        # If the 'page' param is less than 1 the current page is clipped to 1.\n+        ('-3', 1),\n+        ('0', 1),\n \n-    def all(self):\n-        self.all_called = True\n-        return self\n+        # If the 'page' param isn't a number the current page defaults to 1.\n+        ('foo', 1),\n \n+        # If the 'page' param is greater than the number of pages the current\n+        # page is clipped to the number of pages.\n+        ('100', 60),\n \n-def fake_view(total):\n-    \"\"\"\n-    Make a fake view function and a mock query object for testing.\n-    \"\"\"\n-    query = FakeQuery(total)\n+    ])\n+    def test_current_page(self, pyramid_request, page_param, expected):\n+        pyramid_request.params = {'page': page_param}\n \n-    def view(context, request):\n-        return query\n+        page = paginate(\n+            pyramid_request,\n+            # With 600 items in total and 10 items per page there are 60 pages.\n+            600,\n+            10)\n \n-    return query, view\n+        assert page['cur'] == expected\n \n+    @pytest.mark.parametrize('total,page_size,expected', [\n+        # Normally 'max' is just total / page_size.\n+        (600, 10, 60),\n \n-def test_paginate_returns_resultset(pyramid_request):\n-    query, view = fake_view(total=10)\n+        # Total doesn't divide evenly into page_size.\n+        (605, 10, 61),\n \n-    wrapped = paginate_query(view)\n-    result = wrapped(context=None, request=pyramid_request)\n+        # If total is less than page size there should be one page.\n+        (6, 10, 1),\n+    ])\n+    def test_max(self, pyramid_request, total, page_size, expected):\n+        assert paginate(pyramid_request, total, page_size)['max'] == expected\n \n-    assert result['results'] == query\n-    # Assert that .all() has been called to turn the query into a list.\n-    assert query.all_called\n-\n-\n-@pytest.mark.parametrize('total', [10, 20, 40, 87])\n-def test_paginate_includes_total(pyramid_request, total):\n-    query, view = fake_view(total=total)\n-\n-    wrapped = paginate_query(view)\n-    result = wrapped(context=None, request=pyramid_request)\n-\n-    assert result['total'] == total\n-\n-\n-@pytest.mark.parametrize('total,param,offset', [\n-    # When there are no items:\n-    (0, '1', 0),\n-    (0, '2', 0),\n-\n-    # When there are N * PAGE_SIZE items:\n-    (20, '1', 0),\n-    (20, '2', 0),\n-    (40, '1', 0),\n-    (40, '2', 20),\n-    (40, '3', 20),\n-\n-    # When the number of items doesn't fit perfectly into N pages:\n-    (25, '1', 0),\n-    (25, '2', 20),\n-    (25, '3', 20),\n-\n-    # Some extreme cases:\n-    (400, '20', 380),\n-\n-    # Junk data should be discarded:\n-    (100, '0', 0),\n-    (100, '999e9', 0),\n-    (100, 'fish', 0),\n-    (400, '-19000', 0),\n-    (400, '?????', 0),\n-])\n-def test_paginate_returns_offsets_and_limits_resultset(pyramid_request, total, param, offset):\n-    pyramid_request.params = {'page': param}\n-    query, view = fake_view(total)\n-\n-    wrapped = paginate_query(view)\n-    result = wrapped(context=None, request=pyramid_request)\n-\n-    assert result['results'].offset_param == offset\n-    assert result['results'].limit_param == 20\n-\n-\n-@pytest.mark.parametrize('total,param,meta', [\n-    # When there are no items:\n-    (0, '1', (1, 1, None, None)),\n-    (0, '2', (1, 1, None, None)),\n-\n-    # When there are N * PAGE_SIZE items:\n-    (20, '1', (1, 1, None, None)),\n-    (20, '2', (1, 1, None, None)),\n-    (40, '1', (1, 2, 2, None)),\n-    (40, '2', (2, 2, None, 1)),\n-    (40, '3', (2, 2, None, 1)),\n-\n-    # When the number of items doesn't fit perfectly into N pages:\n-    (25, '1', (1, 2, 2, None)),\n-    (25, '2', (2, 2, None, 1)),\n-    (25, '3', (2, 2, None, 1)),\n-\n-    # Some extreme cases:\n-    (400, '19', (19, 20, 20, 18)),\n-\n-    # Junk data should be discarded:\n-    (100, '999e9', (1, 5, 2, None)),\n-    (100, 'fish',  (1, 5, 2, None)),\n-    (400, '-19000', (1, 20, 2, None)),\n-    (400, '?????', (1, 20, 2, None)),\n-])\n-def test_paginate_returns_page_metadata(pyramid_request, total, param, meta):\n-    pyramid_request.params = {'page': param}\n-    query, view = fake_view(total)\n-    ecur, emax, enext, eprev = meta\n-\n-    wrapped = paginate_query(view)\n-    result = wrapped(context=None, request=pyramid_request)\n-\n-    assert result['page'] == {\n-        'cur': ecur,\n-        'max': emax,\n-        'next': enext,\n-        'prev': eprev,\n-    }\n-\n-\n-def test_paginate_params(pyramid_request):\n-    pyramid_request.params = {'page': 2}\n-    query, view = fake_view(20)\n-\n-    decorator = paginate_query(page_size=5)\n-    wrapped = decorator(view)\n-    result = wrapped(context=None, request=pyramid_request)\n-\n-    assert result['results'].offset_param == 5\n-    assert result['results'].limit_param == 5\n+    @pytest.mark.parametrize('page_param,expected', [\n+        # Normally 'next' is simply the current page + 1.\n+        ('32', 33),\n+\n+        # If the current page is the last page then 'next' is None.\n+        ('60', None),\n+    ])\n+    def test_next(self, pyramid_request, page_param, expected):\n+        pyramid_request.params = {'page': page_param}\n+        assert paginate(pyramid_request, 600, 10)['next'] == expected\n+\n+    @pytest.mark.parametrize('page_param,expected', [\n+        # Normally 'prev' is simply the current page - 1.\n+        ('32', 31),\n+\n+        # If the current page is the first page then 'prev' is None.\n+        ('1', None),\n+    ])\n+    def test_prev(self, pyramid_request, page_param, expected):\n+        pyramid_request.params = {'page': page_param}\n+        assert paginate(pyramid_request, 600, 10)['prev'] == expected\n+\n+\n+@pytest.mark.usefixtures('paginate')\n+class TestPaginateQuery(object):\n+\n+    CURRENT_PAGE = 3\n+    \"The current page that will be returned by paginate().\"","path":"tests/h/paginator_test.py","position":277,"original_position":226,"commit_id":"380fe2d885b97da2e9ef397dd0d2de703d610f5c","original_commit_id":"84d0cac95055928b553db5ba90ce5b0488cc8a37","user":{"login":"nickstenning","id":3602,"avatar_url":"https://avatars.githubusercontent.com/u/3602?v=3","gravatar_id":"","url":"https://api.github.com/users/nickstenning","html_url":"https://github.com/nickstenning","followers_url":"https://api.github.com/users/nickstenning/followers","following_url":"https://api.github.com/users/nickstenning/following{/other_user}","gists_url":"https://api.github.com/users/nickstenning/gists{/gist_id}","starred_url":"https://api.github.com/users/nickstenning/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/nickstenning/subscriptions","organizations_url":"https://api.github.com/users/nickstenning/orgs","repos_url":"https://api.github.com/users/nickstenning/repos","events_url":"https://api.github.com/users/nickstenning/events{/privacy}","received_events_url":"https://api.github.com/users/nickstenning/received_events","type":"User","site_admin":false},"body":"I think a normal comment would do fine here, given this is test code...","created_at":"2016-08-18T16:13:24Z","updated_at":"2016-08-18T16:13:24Z","html_url":"https://github.com/hypothesis/h/pull/3719#discussion_r75338986","pull_request_url":"https://api.github.com/repos/hypothesis/h/pulls/3719","_links":{"self":{"href":"https://api.github.com/repos/hypothesis/h/pulls/comments/75338986"},"html":{"href":"https://github.com/hypothesis/h/pull/3719#discussion_r75338986"},"pull_request":{"href":"https://api.github.com/repos/hypothesis/h/pulls/3719"}}},"pull_request":{"url":"https://api.github.com/repos/hypothesis/h/pulls/3719","id":81825451,"html_url":"https://github.com/hypothesis/h/pull/3719","diff_url":"https://github.com/hypothesis/h/pull/3719.diff","patch_url":"https://github.com/hypothesis/h/pull/3719.patch","issue_url":"https://api.github.com/repos/hypothesis/h/issues/3719","number":3719,"state":"open","locked":false,"title":"Backend support for search paginator","user":{"login":"seanh","id":22498,"avatar_url":"https://avatars.githubusercontent.com/u/22498?v=3","gravatar_id":"","url":"https://api.github.com/users/seanh","html_url":"https://github.com/seanh","followers_url":"https://api.github.com/users/seanh/followers","following_url":"https://api.github.com/users/seanh/following{/other_user}","gists_url":"https://api.github.com/users/seanh/gists{/gist_id}","starred_url":"https://api.github.com/users/seanh/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/seanh/subscriptions","organizations_url":"https://api.github.com/users/seanh/orgs","repos_url":"https://api.github.com/users/seanh/repos","events_url":"https://api.github.com/users/seanh/events{/privacy}","received_events_url":"https://api.github.com/users/seanh/received_events","type":"User","site_admin":false},"body":"This pull request refactors `h.paginate` to work with both SQLAlchemy query objects and Elasticsearch search results.\r\n\r\n`h.paginate.paginate()` is renamed to `paginate_query()`, but otherwise has the same interface. The only change that needs to be made to the `/admin/groups` and `/admin/features/cohorts` pages that were already using `paginate()` is that they now call `paginate_query()` instead.\r\n\r\nA new `paginate()` function containing all the logic that is _not_ specific to handling SQLAlchemy query objects is then factored out of `paginate_query()`. `paginate_query()` now calls `paginate()` for the pagination logic, and only does the SQLAlchemy query handling itself.\r\n\r\nA new `paginate_es()` is then added that calls `paginate()` and handles Elasticsearch results.\r\n\r\n`paginate_es()` is then used to add a (completely unstyled) paginator control to the `/search` page.\r\n\r\nThis pull request does not do the HTML and CSS work to style the paginator control on the search page.\r\n\r\nThis pr does not add the \"eliding\" feature to the paginator. It does not produce a paginator control that looks like this: `1 ... 149 [150] 151 ... 300`. It will list all 300 pages instead.\r\n\r\nThis pr does not actually paginate the results on the `/search` page. Pagination does continue to work on the `/admin/groups` and `/admin/features/cohorts` pages, but on the `/search` page you will see the first page's result on every page.\r\n\r\nThis pr _does_ add one new feature to the paginator (other than handling Es results as input): the page links that it generates preserve any other URL params (such as search queries) that were already in the URL.","created_at":"2016-08-18T14:27:51Z","updated_at":"2016-08-18T16:13:24Z","closed_at":null,"merged_at":null,"merge_commit_sha":"72ddd67f15138e0074cfb5af65e1b1a8ebf09bf1","assignee":null,"assignees":[],"milestone":null,"commits_url":"https://api.github.com/repos/hypothesis/h/pulls/3719/commits","review_comments_url":"https://api.github.com/repos/hypothesis/h/pulls/3719/comments","review_comment_url":"https://api.github.com/repos/hypothesis/h/pulls/comments{/number}","comments_url":"https://api.github.com/repos/hypothesis/h/issues/3719/comments","statuses_url":"https://api.github.com/repos/hypothesis/h/statuses/380fe2d885b97da2e9ef397dd0d2de703d610f5c","head":{"label":"hypothesis:backend-support-for-search-paginator","ref":"backend-support-for-search-paginator","sha":"380fe2d885b97da2e9ef397dd0d2de703d610f5c","user":{"login":"hypothesis","id":1243215,"avatar_url":"https://avatars.githubusercontent.com/u/1243215?v=3","gravatar_id":"","url":"https://api.github.com/users/hypothesis","html_url":"https://github.com/hypothesis","followers_url":"https://api.github.com/users/hypothesis/followers","following_url":"https://api.github.com/users/hypothesis/following{/other_user}","gists_url":"https://api.github.com/users/hypothesis/gists{/gist_id}","starred_url":"https://api.github.com/users/hypothesis/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/hypothesis/subscriptions","organizations_url":"https://api.github.com/users/hypothesis/orgs","repos_url":"https://api.github.com/users/hypothesis/repos","events_url":"https://api.github.com/users/hypothesis/events{/privacy}","received_events_url":"https://api.github.com/users/hypothesis/received_events","type":"Organization","site_admin":false},"repo":{"id":3910945,"name":"h","full_name":"hypothesis/h","owner":{"login":"hypothesis","id":1243215,"avatar_url":"https://avatars.githubusercontent.com/u/1243215?v=3","gravatar_id":"","url":"https://api.github.com/users/hypothesis","html_url":"https://github.com/hypothesis","followers_url":"https://api.github.com/users/hypothesis/followers","following_url":"https://api.github.com/users/hypothesis/following{/other_user}","gists_url":"https://api.github.com/users/hypothesis/gists{/gist_id}","starred_url":"https://api.github.com/users/hypothesis/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/hypothesis/subscriptions","organizations_url":"https://api.github.com/users/hypothesis/orgs","repos_url":"https://api.github.com/users/hypothesis/repos","events_url":"https://api.github.com/users/hypothesis/events{/privacy}","received_events_url":"https://api.github.com/users/hypothesis/received_events","type":"Organization","site_admin":false},"private":false,"html_url":"https://github.com/hypothesis/h","description":"Annotate with anyone, anywhere.","fork":false,"url":"https://api.github.com/repos/hypothesis/h","forks_url":"https://api.github.com/repos/hypothesis/h/forks","keys_url":"https://api.github.com/repos/hypothesis/h/keys{/key_id}","collaborators_url":"https://api.github.com/repos/hypothesis/h/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/hypothesis/h/teams","hooks_url":"https://api.github.com/repos/hypothesis/h/hooks","issue_events_url":"https://api.github.com/repos/hypothesis/h/issues/events{/number}","events_url":"https://api.github.com/repos/hypothesis/h/events","assignees_url":"https://api.github.com/repos/hypothesis/h/assignees{/user}","branches_url":"https://api.github.com/repos/hypothesis/h/branches{/branch}","tags_url":"https://api.github.com/repos/hypothesis/h/tags","blobs_url":"https://api.github.com/repos/hypothesis/h/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/hypothesis/h/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/hypothesis/h/git/refs{/sha}","trees_url":"https://api.github.com/repos/hypothesis/h/git/trees{/sha}","statuses_url":"https://api.github.com/repos/hypothesis/h/statuses/{sha}","languages_url":"https://api.github.com/repos/hypothesis/h/languages","stargazers_url":"https://api.github.com/repos/hypothesis/h/stargazers","contributors_url":"https://api.github.com/repos/hypothesis/h/contributors","subscribers_url":"https://api.github.com/repos/hypothesis/h/subscribers","subscription_url":"https://api.github.com/repos/hypothesis/h/subscription","commits_url":"https://api.github.com/repos/hypothesis/h/commits{/sha}","git_commits_url":"https://api.github.com/repos/hypothesis/h/git/commits{/sha}","comments_url":"https://api.github.com/repos/hypothesis/h/comments{/number}","issue_comment_url":"https://api.github.com/repos/hypothesis/h/issues/comments{/number}","contents_url":"https://api.github.com/repos/hypothesis/h/contents/{+path}","compare_url":"https://api.github.com/repos/hypothesis/h/compare/{base}...{head}","merges_url":"https://api.github.com/repos/hypothesis/h/merges","archive_url":"https://api.github.com/repos/hypothesis/h/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/hypothesis/h/downloads","issues_url":"https://api.github.com/repos/hypothesis/h/issues{/number}","pulls_url":"https://api.github.com/repos/hypothesis/h/pulls{/number}","milestones_url":"https://api.github.com/repos/hypothesis/h/milestones{/number}","notifications_url":"https://api.github.com/repos/hypothesis/h/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/hypothesis/h/labels{/name}","releases_url":"https://api.github.com/repos/hypothesis/h/releases{/id}","deployments_url":"https://api.github.com/repos/hypothesis/h/deployments","created_at":"2012-04-02T19:56:59Z","updated_at":"2016-08-17T07:39:46Z","pushed_at":"2016-08-18T16:05:00Z","git_url":"git://github.com/hypothesis/h.git","ssh_url":"git@github.com:hypothesis/h.git","clone_url":"https://github.com/hypothesis/h.git","svn_url":"https://github.com/hypothesis/h","homepage":"https://hypothes.is/","size":31013,"stargazers_count":693,"watchers_count":693,"language":"Python","has_issues":true,"has_downloads":true,"has_wiki":true,"has_pages":false,"forks_count":153,"mirror_url":null,"open_issues_count":60,"forks":153,"open_issues":60,"watchers":693,"default_branch":"master"}},"base":{"label":"hypothesis:master","ref":"master","sha":"bc6018c9a867535bf14a5da3615e5f8b58a1deb6","user":{"login":"hypothesis","id":1243215,"avatar_url":"https://avatars.githubusercontent.com/u/1243215?v=3","gravatar_id":"","url":"https://api.github.com/users/hypothesis","html_url":"https://github.com/hypothesis","followers_url":"https://api.github.com/users/hypothesis/followers","following_url":"https://api.github.com/users/hypothesis/following{/other_user}","gists_url":"https://api.github.com/users/hypothesis/gists{/gist_id}","starred_url":"https://api.github.com/users/hypothesis/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/hypothesis/subscriptions","organizations_url":"https://api.github.com/users/hypothesis/orgs","repos_url":"https://api.github.com/users/hypothesis/repos","events_url":"https://api.github.com/users/hypothesis/events{/privacy}","received_events_url":"https://api.github.com/users/hypothesis/received_events","type":"Organization","site_admin":false},"repo":{"id":3910945,"name":"h","full_name":"hypothesis/h","owner":{"login":"hypothesis","id":1243215,"avatar_url":"https://avatars.githubusercontent.com/u/1243215?v=3","gravatar_id":"","url":"https://api.github.com/users/hypothesis","html_url":"https://github.com/hypothesis","followers_url":"https://api.github.com/users/hypothesis/followers","following_url":"https://api.github.com/users/hypothesis/following{/other_user}","gists_url":"https://api.github.com/users/hypothesis/gists{/gist_id}","starred_url":"https://api.github.com/users/hypothesis/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/hypothesis/subscriptions","organizations_url":"https://api.github.com/users/hypothesis/orgs","repos_url":"https://api.github.com/users/hypothesis/repos","events_url":"https://api.github.com/users/hypothesis/events{/privacy}","received_events_url":"https://api.github.com/users/hypothesis/received_events","type":"Organization","site_admin":false},"private":false,"html_url":"https://github.com/hypothesis/h","description":"Annotate with anyone, anywhere.","fork":false,"url":"https://api.github.com/repos/hypothesis/h","forks_url":"https://api.github.com/repos/hypothesis/h/forks","keys_url":"https://api.github.com/repos/hypothesis/h/keys{/key_id}","collaborators_url":"https://api.github.com/repos/hypothesis/h/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/hypothesis/h/teams","hooks_url":"https://api.github.com/repos/hypothesis/h/hooks","issue_events_url":"https://api.github.com/repos/hypothesis/h/issues/events{/number}","events_url":"https://api.github.com/repos/hypothesis/h/events","assignees_url":"https://api.github.com/repos/hypothesis/h/assignees{/user}","branches_url":"https://api.github.com/repos/hypothesis/h/branches{/branch}","tags_url":"https://api.github.com/repos/hypothesis/h/tags","blobs_url":"https://api.github.com/repos/hypothesis/h/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/hypothesis/h/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/hypothesis/h/git/refs{/sha}","trees_url":"https://api.github.com/repos/hypothesis/h/git/trees{/sha}","statuses_url":"https://api.github.com/repos/hypothesis/h/statuses/{sha}","languages_url":"https://api.github.com/repos/hypothesis/h/languages","stargazers_url":"https://api.github.com/repos/hypothesis/h/stargazers","contributors_url":"https://api.github.com/repos/hypothesis/h/contributors","subscribers_url":"https://api.github.com/repos/hypothesis/h/subscribers","subscription_url":"https://api.github.com/repos/hypothesis/h/subscription","commits_url":"https://api.github.com/repos/hypothesis/h/commits{/sha}","git_commits_url":"https://api.github.com/repos/hypothesis/h/git/commits{/sha}","comments_url":"https://api.github.com/repos/hypothesis/h/comments{/number}","issue_comment_url":"https://api.github.com/repos/hypothesis/h/issues/comments{/number}","contents_url":"https://api.github.com/repos/hypothesis/h/contents/{+path}","compare_url":"https://api.github.com/repos/hypothesis/h/compare/{base}...{head}","merges_url":"https://api.github.com/repos/hypothesis/h/merges","archive_url":"https://api.github.com/repos/hypothesis/h/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/hypothesis/h/downloads","issues_url":"https://api.github.com/repos/hypothesis/h/issues{/number}","pulls_url":"https://api.github.com/repos/hypothesis/h/pulls{/number}","milestones_url":"https://api.github.com/repos/hypothesis/h/milestones{/number}","notifications_url":"https://api.github.com/repos/hypothesis/h/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/hypothesis/h/labels{/name}","releases_url":"https://api.github.com/repos/hypothesis/h/releases{/id}","deployments_url":"https://api.github.com/repos/hypothesis/h/deployments","created_at":"2012-04-02T19:56:59Z","updated_at":"2016-08-17T07:39:46Z","pushed_at":"2016-08-18T16:05:00Z","git_url":"git://github.com/hypothesis/h.git","ssh_url":"git@github.com:hypothesis/h.git","clone_url":"https://github.com/hypothesis/h.git","svn_url":"https://github.com/hypothesis/h","homepage":"https://hypothes.is/","size":31013,"stargazers_count":693,"watchers_count":693,"language":"Python","has_issues":true,"has_downloads":true,"has_wiki":true,"has_pages":false,"forks_count":153,"mirror_url":null,"open_issues_count":60,"forks":153,"open_issues":60,"watchers":693,"default_branch":"master"}},"_links":{"self":{"href":"https://api.github.com/repos/hypothesis/h/pulls/3719"},"html":{"href":"https://github.com/hypothesis/h/pull/3719"},"issue":{"href":"https://api.github.com/repos/hypothesis/h/issues/3719"},"comments":{"href":"https://api.github.com/repos/hypothesis/h/issues/3719/comments"},"review_comments":{"href":"https://api.github.com/repos/hypothesis/h/pulls/3719/comments"},"review_comment":{"href":"https://api.github.com/repos/hypothesis/h/pulls/comments{/number}"},"commits":{"href":"https://api.github.com/repos/hypothesis/h/pulls/3719/commits"},"statuses":{"href":"https://api.github.com/repos/hypothesis/h/statuses/380fe2d885b97da2e9ef397dd0d2de703d610f5c"}}}}
{"actor":{"display_login":"nickstenning"}}