airbyte-source-github 1.5.7__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (88) hide show
  1. airbyte_source_github-1.5.7.dist-info/METADATA +144 -0
  2. airbyte_source_github-1.5.7.dist-info/RECORD +88 -0
  3. airbyte_source_github-1.5.7.dist-info/WHEEL +5 -0
  4. airbyte_source_github-1.5.7.dist-info/entry_points.txt +2 -0
  5. airbyte_source_github-1.5.7.dist-info/top_level.txt +3 -0
  6. integration_tests/__init__.py +0 -0
  7. integration_tests/abnormal_state.json +237 -0
  8. integration_tests/acceptance.py +16 -0
  9. integration_tests/configured_catalog.json +435 -0
  10. integration_tests/configured_catalog_full_refresh_test.json +415 -0
  11. integration_tests/invalid_config.json +5 -0
  12. integration_tests/sample_config.json +5 -0
  13. integration_tests/sample_state.json +137 -0
  14. source_github/__init__.py +27 -0
  15. source_github/config_migrations.py +106 -0
  16. source_github/constants.py +9 -0
  17. source_github/github_schema.py +41034 -0
  18. source_github/graphql.py +327 -0
  19. source_github/run.py +17 -0
  20. source_github/schemas/assignees.json +63 -0
  21. source_github/schemas/branches.json +48 -0
  22. source_github/schemas/collaborators.json +80 -0
  23. source_github/schemas/comments.json +104 -0
  24. source_github/schemas/commit_comment_reactions.json +4 -0
  25. source_github/schemas/commit_comments.json +53 -0
  26. source_github/schemas/commits.json +126 -0
  27. source_github/schemas/contributor_activity.json +109 -0
  28. source_github/schemas/deployments.json +77 -0
  29. source_github/schemas/events.json +63 -0
  30. source_github/schemas/issue_comment_reactions.json +4 -0
  31. source_github/schemas/issue_events.json +335 -0
  32. source_github/schemas/issue_labels.json +30 -0
  33. source_github/schemas/issue_milestones.json +61 -0
  34. source_github/schemas/issue_reactions.json +28 -0
  35. source_github/schemas/issue_timeline_events.json +1056 -0
  36. source_github/schemas/issues.json +281 -0
  37. source_github/schemas/organizations.json +197 -0
  38. source_github/schemas/project_cards.json +50 -0
  39. source_github/schemas/project_columns.json +38 -0
  40. source_github/schemas/projects.json +50 -0
  41. source_github/schemas/projects_v2.json +80 -0
  42. source_github/schemas/pull_request_comment_reactions.json +28 -0
  43. source_github/schemas/pull_request_commits.json +122 -0
  44. source_github/schemas/pull_request_stats.json +84 -0
  45. source_github/schemas/pull_requests.json +363 -0
  46. source_github/schemas/releases.json +126 -0
  47. source_github/schemas/repositories.json +313 -0
  48. source_github/schemas/review_comments.json +118 -0
  49. source_github/schemas/reviews.json +69 -0
  50. source_github/schemas/shared/events/comment.json +188 -0
  51. source_github/schemas/shared/events/commented.json +118 -0
  52. source_github/schemas/shared/events/committed.json +56 -0
  53. source_github/schemas/shared/events/cross_referenced.json +784 -0
  54. source_github/schemas/shared/events/reviewed.json +139 -0
  55. source_github/schemas/shared/reaction.json +27 -0
  56. source_github/schemas/shared/reactions.json +35 -0
  57. source_github/schemas/shared/user.json +59 -0
  58. source_github/schemas/shared/user_graphql.json +26 -0
  59. source_github/schemas/stargazers.json +19 -0
  60. source_github/schemas/tags.json +32 -0
  61. source_github/schemas/team_members.json +66 -0
  62. source_github/schemas/team_memberships.json +24 -0
  63. source_github/schemas/teams.json +50 -0
  64. source_github/schemas/users.json +63 -0
  65. source_github/schemas/workflow_jobs.json +109 -0
  66. source_github/schemas/workflow_runs.json +449 -0
  67. source_github/schemas/workflows.json +41 -0
  68. source_github/source.py +339 -0
  69. source_github/spec.json +179 -0
  70. source_github/streams.py +1678 -0
  71. source_github/utils.py +152 -0
  72. unit_tests/__init__.py +3 -0
  73. unit_tests/conftest.py +29 -0
  74. unit_tests/projects_v2_pull_requests_query.json +3 -0
  75. unit_tests/pull_request_stats_query.json +3 -0
  76. unit_tests/responses/contributor_activity_response.json +33 -0
  77. unit_tests/responses/graphql_reviews_responses.json +405 -0
  78. unit_tests/responses/issue_timeline_events.json +166 -0
  79. unit_tests/responses/issue_timeline_events_response.json +170 -0
  80. unit_tests/responses/projects_v2_response.json +45 -0
  81. unit_tests/responses/pull_request_comment_reactions.json +744 -0
  82. unit_tests/responses/pull_request_stats_response.json +317 -0
  83. unit_tests/test_migrations/test_config.json +8 -0
  84. unit_tests/test_migrations/test_new_config.json +8 -0
  85. unit_tests/test_multiple_token_authenticator.py +160 -0
  86. unit_tests/test_source.py +326 -0
  87. unit_tests/test_stream.py +1471 -0
  88. unit_tests/utils.py +78 -0
source_github/utils.py ADDED
@@ -0,0 +1,152 @@
1
+ #
2
+ # Copyright (c) 2023 Airbyte, Inc., all rights reserved.
3
+ #
4
+
5
+ import time
6
+ from dataclasses import dataclass
7
+ from itertools import cycle
8
+ from typing import Any, List, Mapping
9
+
10
+ import pendulum
11
+ import requests
12
+ from airbyte_cdk.models import SyncMode
13
+ from airbyte_cdk.sources.streams import Stream
14
+ from airbyte_cdk.sources.streams.http.requests_native_auth import TokenAuthenticator
15
+ from airbyte_cdk.sources.streams.http.requests_native_auth.abstract_token import AbstractHeaderAuthenticator
16
+
17
+
18
+ def getter(D: dict, key_or_keys, strict=True):
19
+ if not isinstance(key_or_keys, list):
20
+ key_or_keys = [key_or_keys]
21
+ for k in key_or_keys:
22
+ if strict:
23
+ D = D[k]
24
+ else:
25
+ D = D.get(k, {})
26
+ return D
27
+
28
+
29
+ def read_full_refresh(stream_instance: Stream):
30
+ slices = stream_instance.stream_slices(sync_mode=SyncMode.full_refresh)
31
+ for _slice in slices:
32
+ records = stream_instance.read_records(stream_slice=_slice, sync_mode=SyncMode.full_refresh)
33
+ for record in records:
34
+ yield record
35
+
36
+
37
+ class GitHubAPILimitException(Exception):
38
+ """General class for Rate Limits errors"""
39
+
40
+
41
+ @dataclass
42
+ class Token:
43
+ count_rest: int = 5000
44
+ count_graphql: int = 5000
45
+ reset_at_rest: pendulum.DateTime = pendulum.now()
46
+ reset_at_graphql: pendulum.DateTime = pendulum.now()
47
+
48
+
49
+ class MultipleTokenAuthenticatorWithRateLimiter(AbstractHeaderAuthenticator):
50
+ """
51
+ Each token in the cycle is checked against the rate limiter.
52
+ If a token exceeds the capacity limit, the system switches to another token.
53
+ If all tokens are exhausted, the system will enter a sleep state until
54
+ the first token becomes available again.
55
+ """
56
+
57
+ DURATION = pendulum.duration(seconds=3600) # Duration at which the current rate limit window resets
58
+
59
+ def __init__(self, tokens: List[str], auth_method: str = "token", auth_header: str = "Authorization"):
60
+ self._auth_method = auth_method
61
+ self._auth_header = auth_header
62
+ self._tokens = {t: Token() for t in tokens}
63
+ self.check_all_tokens()
64
+ self._tokens_iter = cycle(self._tokens)
65
+ self._active_token = next(self._tokens_iter)
66
+ self._max_time = 60 * 10 # 10 minutes as default
67
+
68
+ @property
69
+ def auth_header(self) -> str:
70
+ return self._auth_header
71
+
72
+ def get_auth_header(self) -> Mapping[str, Any]:
73
+ """The header to set on outgoing HTTP requests"""
74
+ if self.auth_header:
75
+ return {self.auth_header: self.token}
76
+ return {}
77
+
78
+ def __call__(self, request):
79
+ """Attach the HTTP headers required to authenticate on the HTTP request"""
80
+ while True:
81
+ current_token = self._tokens[self.current_active_token]
82
+ if "graphql" in request.path_url:
83
+ if self.process_token(current_token, "count_graphql", "reset_at_graphql"):
84
+ break
85
+ else:
86
+ if self.process_token(current_token, "count_rest", "reset_at_rest"):
87
+ break
88
+
89
+ request.headers.update(self.get_auth_header())
90
+
91
+ return request
92
+
93
+ @property
94
+ def current_active_token(self) -> str:
95
+ return self._active_token
96
+
97
+ def update_token(self) -> None:
98
+ self._active_token = next(self._tokens_iter)
99
+
100
+ @property
101
+ def token(self) -> str:
102
+
103
+ token = self.current_active_token
104
+ return f"{self._auth_method} {token}"
105
+
106
+ @property
107
+ def max_time(self) -> int:
108
+ return self._max_time
109
+
110
+ @max_time.setter
111
+ def max_time(self, value: int) -> None:
112
+ self._max_time = value
113
+
114
+ def _check_token_limits(self, token: str):
115
+ """check that token is not limited"""
116
+ headers = {"Accept": "application/vnd.github+json", "X-GitHub-Api-Version": "2022-11-28"}
117
+ rate_limit_info = (
118
+ requests.get(
119
+ "https://api.github.com/rate_limit", headers=headers, auth=TokenAuthenticator(token, auth_method=self._auth_method)
120
+ )
121
+ .json()
122
+ .get("resources")
123
+ )
124
+ token_info = self._tokens[token]
125
+ remaining_info_core = rate_limit_info.get("core")
126
+ token_info.count_rest, token_info.reset_at_rest = remaining_info_core.get("remaining"), pendulum.from_timestamp(
127
+ remaining_info_core.get("reset")
128
+ )
129
+
130
+ remaining_info_graphql = rate_limit_info.get("graphql")
131
+ token_info.count_graphql, token_info.reset_at_graphql = remaining_info_graphql.get("remaining"), pendulum.from_timestamp(
132
+ remaining_info_graphql.get("reset")
133
+ )
134
+
135
+ def check_all_tokens(self):
136
+ for token in self._tokens:
137
+ self._check_token_limits(token)
138
+
139
+ def process_token(self, current_token, count_attr, reset_attr):
140
+ if getattr(current_token, count_attr) > 0:
141
+ setattr(current_token, count_attr, getattr(current_token, count_attr) - 1)
142
+ return True
143
+ elif all(getattr(x, count_attr) == 0 for x in self._tokens.values()):
144
+ min_time_to_wait = min((getattr(x, reset_attr) - pendulum.now()).in_seconds() for x in self._tokens.values())
145
+ if min_time_to_wait < self.max_time:
146
+ time.sleep(min_time_to_wait if min_time_to_wait > 0 else 0)
147
+ self.check_all_tokens()
148
+ else:
149
+ raise GitHubAPILimitException(f"Rate limits for all tokens ({count_attr}) were reached")
150
+ else:
151
+ self.update_token()
152
+ return False
unit_tests/__init__.py ADDED
@@ -0,0 +1,3 @@
1
+ #
2
+ # Copyright (c) 2021 Airbyte, Inc., all rights reserved.
3
+ #
unit_tests/conftest.py ADDED
@@ -0,0 +1,29 @@
1
+ # Copyright (c) 2023 Airbyte, Inc., all rights reserved.
2
+
3
+ import os
4
+
5
+ import pytest
6
+ import responses
7
+
8
+ os.environ["REQUEST_CACHE_PATH"] = "REQUEST_CACHE_PATH"
9
+
10
+
11
+ @pytest.fixture(name="rate_limit_mock_response")
12
+ def rate_limit_mock_response():
13
+ rate_limit_response = {
14
+ "resources": {
15
+ "core": {
16
+ "limit": 5000,
17
+ "used": 0,
18
+ "remaining": 5000,
19
+ "reset": 4070908800
20
+ },
21
+ "graphql": {
22
+ "limit": 5000,
23
+ "used": 0,
24
+ "remaining": 5000,
25
+ "reset": 4070908800
26
+ }
27
+ }
28
+ }
29
+ responses.add(responses.GET, "https://api.github.com/rate_limit", json=rate_limit_response)
@@ -0,0 +1,3 @@
1
+ {
2
+ "query": "query {\n repository(owner: \"airbytehq\", name: \"airbyte\") {\n name\n owner {\n login\n }\n projectsV2(first: 100, orderBy: {field: UPDATED_AT, direction: ASC}) {\n nodes {\n closed\n created_at: createdAt\n closed_at: closedAt\n updated_at: updatedAt\n creator: creator {\n avatarUrl\n login\n resourcePath\n url\n }\n node_id: id\n id: databaseId\n number\n public\n readme: readme\n short_description: shortDescription\n template\n title: title\n url: url\n viewerCanClose\n viewerCanReopen\n viewerCanUpdate\n owner {\n id: id\n }\n }\n pageInfo {\n hasNextPage\n endCursor\n }\n }\n }\n}"
3
+ }
@@ -0,0 +1,3 @@
1
+ {
2
+ "query": "query {\n repository(owner: \"airbytehq\", name: \"airbyte\") {\n name\n owner {\n login\n }\n pullRequests(first: 10, orderBy: {field: UPDATED_AT, direction: ASC}) {\n nodes {\n node_id: id\n id: databaseId\n number\n updated_at: updatedAt\n changed_files: changedFiles\n deletions\n additions\n merged\n mergeable\n can_be_rebased: canBeRebased\n maintainer_can_modify: maintainerCanModify\n merge_state_status: mergeStateStatus\n comments {\n totalCount\n }\n commits {\n totalCount\n }\n review_comments: reviews(first: 100) {\n totalCount\n nodes {\n comments {\n totalCount\n }\n }\n }\n merged_by: mergedBy {\n __typename\n ... on User {\n node_id: id\n id: databaseId\n login\n avatar_url: avatarUrl\n html_url: url\n site_admin: isSiteAdmin\n }\n }\n }\n pageInfo {\n hasNextPage\n endCursor\n }\n }\n }\n}"
3
+ }
@@ -0,0 +1,33 @@
1
+ [
2
+ {
3
+ "author": {
4
+ "login": "octocat",
5
+ "id": 1,
6
+ "node_id": "MDQ6VXNlcjE=",
7
+ "avatar_url": "https://github.com/images/error/octocat_happy.gif",
8
+ "gravatar_id": "",
9
+ "url": "https://api.github.com/users/octocat",
10
+ "html_url": "https://github.com/octocat",
11
+ "followers_url": "https://api.github.com/users/octocat/followers",
12
+ "following_url": "https://api.github.com/users/octocat/following{/other_user}",
13
+ "gists_url": "https://api.github.com/users/octocat/gists{/gist_id}",
14
+ "starred_url": "https://api.github.com/users/octocat/starred{/owner}{/repo}",
15
+ "subscriptions_url": "https://api.github.com/users/octocat/subscriptions",
16
+ "organizations_url": "https://api.github.com/users/octocat/orgs",
17
+ "repos_url": "https://api.github.com/users/octocat/repos",
18
+ "events_url": "https://api.github.com/users/octocat/events{/privacy}",
19
+ "received_events_url": "https://api.github.com/users/octocat/received_events",
20
+ "type": "User",
21
+ "site_admin": false
22
+ },
23
+ "total": 135,
24
+ "weeks": [
25
+ {
26
+ "w": 1367712000,
27
+ "a": 6898,
28
+ "d": 77,
29
+ "c": 10
30
+ }
31
+ ]
32
+ }
33
+ ]
@@ -0,0 +1,405 @@
1
+ [
2
+ {
3
+ "data": {
4
+ "repository": {
5
+ "owner": {
6
+ "login": "airbytehq"
7
+ },
8
+ "name": "airbyte",
9
+ "pullRequests": {
10
+ "nodes": [
11
+ {
12
+ "number": 1,
13
+ "url": "https://github.com/airbytehq/airbyte/pull/1",
14
+ "reviews": {
15
+ "nodes": [
16
+ {
17
+ "id": 1000,
18
+ "updated_at": "2000-01-01T00:00:01Z",
19
+ "html_url": "https://github.com/airbytehq/airbyte/pull/1#pullrequestreview-1000",
20
+ "commit": null,
21
+ "user": {
22
+ "__typename": "User"
23
+ }
24
+ },
25
+ {
26
+ "id": 1001,
27
+ "updated_at": "2000-01-01T00:00:01Z",
28
+ "html_url": "https://github.com/airbytehq/airbyte/pull/1#pullrequestreview-1001",
29
+ "commit": null,
30
+ "user": {
31
+ "__typename": "User"
32
+ }
33
+ }
34
+ ],
35
+ "pageInfo": {
36
+ "endCursor": "cursor",
37
+ "hasNextPage": true
38
+ }
39
+ }
40
+ },
41
+ {
42
+ "number": 2,
43
+ "url": "https://github.com/airbytehq/airbyte/pull/2",
44
+ "reviews": {
45
+ "nodes": [
46
+ {
47
+ "id": 1002,
48
+ "updated_at": "2000-01-01T00:00:01Z",
49
+ "html_url": "https://github.com/airbytehq/airbyte/pull/2#pullrequestreview-1002",
50
+ "commit": null,
51
+ "user": {
52
+ "__typename": "User"
53
+ }
54
+ },
55
+ {
56
+ "id": 1003,
57
+ "updated_at": "2000-01-01T00:00:01Z",
58
+ "html_url": "https://github.com/airbytehq/airbyte/pull/2#pullrequestreview-1003",
59
+ "commit": null,
60
+ "user": {
61
+ "__typename": "User"
62
+ }
63
+ }
64
+ ],
65
+ "pageInfo": {
66
+ "endCursor": "cursor",
67
+ "hasNextPage": true
68
+ }
69
+ }
70
+ }
71
+ ],
72
+ "pageInfo": {
73
+ "endCursor": "cursor",
74
+ "hasNextPage": true
75
+ }
76
+ }
77
+ }
78
+ }
79
+ },
80
+ {
81
+ "data": {
82
+ "repository": {
83
+ "owner": {
84
+ "login": "airbytehq"
85
+ },
86
+ "name": "airbyte",
87
+ "pullRequest": {
88
+ "number": 1,
89
+ "url": "https://github.com/airbytehq/airbyte/pull/1",
90
+ "reviews": {
91
+ "nodes": [
92
+ {
93
+ "id": 1004,
94
+ "updated_at": "2000-01-01T00:00:01Z",
95
+ "html_url": "https://github.com/airbytehq/airbyte/pull/1#pullrequestreview-1004",
96
+ "commit": null,
97
+ "user": {
98
+ "__typename": "User"
99
+ }
100
+ },
101
+ {
102
+ "id": 1005,
103
+ "updated_at": "2000-01-01T00:00:01Z",
104
+ "html_url": "https://github.com/airbytehq/airbyte/pull/1#pullrequestreview-1005",
105
+ "commit": null,
106
+ "user": {
107
+ "__typename": "User"
108
+ }
109
+ }
110
+ ],
111
+ "pageInfo": {
112
+ "endCursor": null,
113
+ "hasNextPage": false
114
+ }
115
+ }
116
+ }
117
+ }
118
+ }
119
+ },
120
+ {
121
+ "data": {
122
+ "repository": {
123
+ "owner": {
124
+ "login": "airbytehq"
125
+ },
126
+ "name": "airbyte",
127
+ "pullRequest": {
128
+ "number": 2,
129
+ "url": "https://github.com/airbytehq/airbyte/pull/2",
130
+ "reviews": {
131
+ "nodes": [
132
+ {
133
+ "id": 1006,
134
+ "updated_at": "2000-01-01T00:00:01Z",
135
+ "html_url": "https://github.com/airbytehq/airbyte/pull/2#pullrequestreview-1006",
136
+ "commit": null,
137
+ "user": {
138
+ "__typename": "User"
139
+ }
140
+ },
141
+ {
142
+ "id": 1007,
143
+ "updated_at": "2000-01-01T00:00:01Z",
144
+ "html_url": "https://github.com/airbytehq/airbyte/pull/2#pullrequestreview-1007",
145
+ "commit": null,
146
+ "user": {
147
+ "__typename": "User"
148
+ }
149
+ }
150
+ ],
151
+ "pageInfo": {
152
+ "endCursor": null,
153
+ "hasNextPage": false
154
+ }
155
+ }
156
+ }
157
+ }
158
+ }
159
+ },
160
+ {
161
+ "data": {
162
+ "repository": {
163
+ "owner": {
164
+ "login": "airbytehq"
165
+ },
166
+ "name": "airbyte",
167
+ "pullRequests": {
168
+ "nodes": [
169
+ {
170
+ "number": 3,
171
+ "url": "https://github.com/airbytehq/airbyte/pull/3",
172
+ "reviews": {
173
+ "nodes": [
174
+ {
175
+ "id": 1008,
176
+ "updated_at": "2000-01-01T00:00:01Z",
177
+ "html_url": "https://github.com/airbytehq/airbyte/pull/3#pullrequestreview-1008",
178
+ "commit": null,
179
+ "user": {
180
+ "__typename": "User"
181
+ }
182
+ }
183
+ ],
184
+ "pageInfo": {
185
+ "endCursor": null,
186
+ "hasNextPage": false
187
+ }
188
+ }
189
+ }
190
+ ],
191
+ "pageInfo": {
192
+ "endCursor": null,
193
+ "hasNextPage": false
194
+ }
195
+ }
196
+ }
197
+ }
198
+ },
199
+ {
200
+ "data": {
201
+ "repository": {
202
+ "owner": {
203
+ "login": "airbytehq"
204
+ },
205
+ "name": "airbyte",
206
+ "pullRequests": {
207
+ "nodes": [
208
+ {
209
+ "number": 1,
210
+ "url": "https://github.com/airbytehq/airbyte/pull/1",
211
+ "reviews": {
212
+ "nodes": [
213
+ {
214
+ "id": 1000,
215
+ "updated_at": "2000-01-01T00:00:02Z",
216
+ "html_url": "https://github.com/airbytehq/airbyte/pull/1#pullrequestreview-1000",
217
+ "commit": null,
218
+ "user": {
219
+ "__typename": "User"
220
+ }
221
+ },
222
+ {
223
+ "id": 1001,
224
+ "updated_at": "2000-01-01T00:00:01Z",
225
+ "html_url": "https://github.com/airbytehq/airbyte/pull/1#pullrequestreview-1001",
226
+ "commit": null,
227
+ "user": {
228
+ "__typename": "User"
229
+ }
230
+ }
231
+ ],
232
+ "pageInfo": {
233
+ "endCursor": "cursor",
234
+ "hasNextPage": true
235
+ }
236
+ }
237
+ },
238
+ {
239
+ "number": 2,
240
+ "url": "https://github.com/airbytehq/airbyte/pull/2",
241
+ "reviews": {
242
+ "nodes": [
243
+ {
244
+ "id": 1002,
245
+ "updated_at": "2000-01-01T00:00:01Z",
246
+ "html_url": "https://github.com/airbytehq/airbyte/pull/2#pullrequestreview-1002",
247
+ "commit": null,
248
+ "user": {
249
+ "__typename": "User"
250
+ }
251
+ },
252
+ {
253
+ "id": 1003,
254
+ "updated_at": "2000-01-01T00:00:01Z",
255
+ "html_url": "https://github.com/airbytehq/airbyte/pull/2#pullrequestreview-1003",
256
+ "commit": null,
257
+ "user": {
258
+ "__typename": "User"
259
+ }
260
+ }
261
+ ],
262
+ "pageInfo": {
263
+ "endCursor": "cursor",
264
+ "hasNextPage": true
265
+ }
266
+ }
267
+ }
268
+ ],
269
+ "pageInfo": {
270
+ "endCursor": "cursor",
271
+ "hasNextPage": true
272
+ }
273
+ }
274
+ }
275
+ }
276
+ },
277
+ {
278
+ "data": {
279
+ "repository": {
280
+ "owner": {
281
+ "login": "airbytehq"
282
+ },
283
+ "name": "airbyte",
284
+ "pullRequest": {
285
+ "number": 1,
286
+ "url": "https://github.com/airbytehq/airbyte/pull/1",
287
+ "reviews": {
288
+ "nodes": [
289
+ {
290
+ "id": 1004,
291
+ "updated_at": "2000-01-01T00:00:01Z",
292
+ "html_url": "https://github.com/airbytehq/airbyte/pull/1#pullrequestreview-1004",
293
+ "commit": null,
294
+ "user": {
295
+ "__typename": "User"
296
+ }
297
+ },
298
+ {
299
+ "id": 1005,
300
+ "updated_at": "2000-01-01T00:00:01Z",
301
+ "html_url": "https://github.com/airbytehq/airbyte/pull/1#pullrequestreview-1005",
302
+ "commit": null,
303
+ "user": {
304
+ "__typename": "User"
305
+ }
306
+ }
307
+ ],
308
+ "pageInfo": {
309
+ "endCursor": null,
310
+ "hasNextPage": false
311
+ }
312
+ }
313
+ }
314
+ }
315
+ }
316
+ },
317
+ {
318
+ "data": {
319
+ "repository": {
320
+ "owner": {
321
+ "login": "airbytehq"
322
+ },
323
+ "name": "airbyte",
324
+ "pullRequest": {
325
+ "number": 2,
326
+ "url": "https://github.com/airbytehq/airbyte/pull/2",
327
+ "reviews": {
328
+ "nodes": [
329
+ {
330
+ "id": 1006,
331
+ "updated_at": "2000-01-01T00:00:01Z",
332
+ "html_url": "https://github.com/airbytehq/airbyte/pull/2#pullrequestreview-1006",
333
+ "commit": null,
334
+ "user": {
335
+ "__typename": "User"
336
+ }
337
+ },
338
+ {
339
+ "id": 1007,
340
+ "updated_at": "2000-01-01T00:00:02Z",
341
+ "html_url": "https://github.com/airbytehq/airbyte/pull/2#pullrequestreview-1007",
342
+ "commit": null,
343
+ "user": {
344
+ "__typename": "User"
345
+ }
346
+ }
347
+ ],
348
+ "pageInfo": {
349
+ "endCursor": null,
350
+ "hasNextPage": false
351
+ }
352
+ }
353
+ }
354
+ }
355
+ }
356
+ },
357
+ {
358
+ "data": {
359
+ "repository": {
360
+ "owner": {
361
+ "login": "airbytehq"
362
+ },
363
+ "name": "airbyte",
364
+ "pullRequests": {
365
+ "nodes": [
366
+ {
367
+ "number": 3,
368
+ "url": "https://github.com/airbytehq/airbyte/pull/3",
369
+ "reviews": {
370
+ "nodes": [
371
+ {
372
+ "id": 1008,
373
+ "updated_at": "2000-01-01T00:00:01Z",
374
+ "html_url": "https://github.com/airbytehq/airbyte/pull/3#pullrequestreview-1008",
375
+ "commit": null,
376
+ "user": {
377
+ "__typename": "User"
378
+ }
379
+ },
380
+ {
381
+ "id": 1009,
382
+ "updated_at": "2000-01-01T00:00:02Z",
383
+ "html_url": "https://github.com/airbytehq/airbyte/pull/3#pullrequestreview-1009",
384
+ "commit": null,
385
+ "user": {
386
+ "__typename": "User"
387
+ }
388
+ }
389
+ ],
390
+ "pageInfo": {
391
+ "endCursor": null,
392
+ "hasNextPage": false
393
+ }
394
+ }
395
+ }
396
+ ],
397
+ "pageInfo": {
398
+ "endCursor": null,
399
+ "hasNextPage": false
400
+ }
401
+ }
402
+ }
403
+ }
404
+ }
405
+ ]